# Claimable Zyberdesk

Zyberdesk lets an agent provision a temporary team before a human creates an account. The agent can start building with scoped credentials, then a human can claim the team and continue from where the agent left off.

## Discover

Start from either of two entry points:

1. **Protected resource metadata** — fetch:

   `https://www.zyberdesk.com/api/.well-known/oauth-protected-resource`

   or read the `WWW-Authenticate` header on any `401` response from an authenticated endpoint (it carries a `resource_metadata="..."` parameter pointing at the same document).
2. **Authorization server metadata** — the resource metadata's `authorization_servers` entry points at:

   `https://www.zyberdesk.com/.well-known/oauth-authorization-server`

   This document describes the authorization server metadata and the Nexvio agent authentication capabilities:

   ```json
   {
     "agent_auth": {
       "skill": "https://www.zyberdesk.com/auth.md",
       "claim_endpoint": "https://www.zyberdesk.com/api/public/v1/claimable/claim",
       "register_uri": "https://www.zyberdesk.com/api/public/v1/claimable/register",
       "identity_types_supported": ["anonymous", "service_auth"],
       "anonymous": { "credential_types_supported": ["identity_assertion"] },
       "service_auth": { "credential_types_supported": ["api_key"], "claim_ttl_seconds": 604800 }
     }
   }
   ```

   `register_uri` for **service_auth** creates a **provisional team** + scoped `nex_` key that a human claims within **7 days** using the provided email (OTP on `/claim/{token}`). `claim_endpoint` completesthat transfer. The separate `anonymous` path creates a temporary restricted team without an email and returns an identity assertion; OAuth remains the signed-in authorization-code flow.

   If anything in this file conflicts with the live metadata, the metadata is authoritative.

## Choose a method

Zyberdesk supports three onboarding flows and two credentials for already-owned teams:

| Method | Best for | Credential |
| --- | --- | --- |
| Claimable team (`service_auth`) | Agents that know a human email but the person has not signed up yet | provisional `nex_` (7-day claim) |
| Anonymous claimable team | Agents with no user identity that need a restricted temporary team | identity assertion + short-lived bearer token |
| OAuth 2.1 + PKCE + dynamic client registration | MCP clients acting on behalf of a signed-in user | short-lived bearer access token, refreshable |
| Team API key | Server-to-server integrations owned by one team | long-lived `nex_` bearer token |
| External-agent key | Third-party agents calling the external-agent MCP endpoint | long-lived `nex_ext_` bearer token |

1. **Claimable team (`service_auth`)** — `POST` to `https://www.zyberdesk.com/api/public/v1/claimable/register` with `{ "email": "user@example.com", "title": "Optional" }`. Response includes `apiKey` (`nex_…`), `claimUrl`, and `claimExpiresAt` (7 days). Call public REST / MCP with that key. The human opens `claimUrl`, requests an OTP, and completes claim at `https://www.zyberdesk.com/api/public/v1/claimable/claim`. After claim the provisional key is revoked.
2. **Anonymous** — `POST` to `https://www.zyberdesk.com/api/public/v1/claimable/register` with `{ "identityType": "anonymous" }`. Exchange the returned `identityAssertion` at the token endpoint for a short-lived, pre-claim access token. Start a device-style claim with the claim token; a signed-in user confirms the displayed code, then exchange the assertion again for the post-claim access token. No email OTP is used.
3. **OAuth 2.1 + PKCE + dynamic client registration** — register a client at `https://www.zyberdesk.com/api/oauth/register`, send the user to authorize at `https://www.zyberdesk.com/api/oauth/authorize`, and exchange the resulting code at `https://www.zyberdesk.com/api/oauth/token`. This is the signed-in user-authorization flow and does not create a provisional team.
4. **Team API key** — self-serve in the dashboard under Settings → API Keys. Prefixed `nex_`. Send as `Authorization: Bearer nex_...`.
5. **External-agent key** — for calls to `https://www.zyberdesk.com/external-agent/mcp`. Prefixed `nex_ext_`. Send as `Authorization: Bearer nex_ext_...`.

## Register a team

### Claimable provisional team

```http
POST https://www.zyberdesk.com/api/public/v1/claimable/register
Content-Type: application/json

{
  "email": "owner@example.com",
  "title": "Agent workspace"
}
```

### OAuth dynamic client registration

Dynamic client registration (RFC 7591) at `https://www.zyberdesk.com/api/oauth/register`:

```http
POST https://www.zyberdesk.com/api/oauth/register
Content-Type: application/json

{
  "redirect_uris": ["https://your-agent.example/callback"],
  "client_name": "Your Agent",
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "none"
}
```

Response:

```json
{
  "client_id": "...",
  "client_secret": "...",
  "client_id_issued_at": 1700000000,
  "redirect_uris": ["https://your-agent.example/callback"]
}
```

Dashboard-issued team API keys and external-agent keys don't go through these endpoints — create them from Settings → API Keys instead.

### Anonymous claimable registration

Anonymous registration omits `email` and sets `identityType` to `anonymous`:

```bash
curl -X POST https://www.zyberdesk.com/api/public/v1/claimable/register
  -H "Content-Type: application/json"
  -d '{"identityType":"anonymous","title":"Anonymous workspace"}'
```

The response contains a `claimToken`, `claimUrl`, `identityAssertion`, and its expiry. Exchange the assertion:

```bash
curl -X POST https://www.zyberdesk.com/api/oauth/token
  -H "Content-Type: application/x-www-form-urlencoded"
  --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer"
  --data-urlencode "assertion=<identityAssertion>"
```

## Claim the team

For **service_auth** provisional teams:

1. Open `https://www.zyberdesk.com/claim/{claimToken}` (or use `claimUrl` from register).
2. `POST https://www.zyberdesk.com/api/public/v1/claimable/claim/otp` with `{ "claimToken": "clm_…" }` to email an OTP.
3. `POST https://www.zyberdesk.com/api/public/v1/claimable/claim` with `{ "claimToken": "clm_…", "otp": "…" }`.
4. On success the human owns the team; the provisional `nex_` key is revoked — create a new key in the dashboard.

For **anonymous** provisional teams, no email OTP is sent:
1. POST `{ "claimToken": "clm_…" }` to `https://www.zyberdesk.com/api/public/v1/claimable/anonymous/claim` to receive a `userCode` and `verificationUri`.
2. Show the code and verification URL to the user; the user signs in and confirms the code on the claim page.
3. The claim page POSTs `{ "claimToken": "clm_…", "userCode": "…" }` to `https://www.zyberdesk.com/api/public/v1/claimable/anonymous/claim/confirm`.
4. Exchange the same assertion again to receive the post-claim access token with upgraded scopes.

Unclaimed provisional teams expire after **7 days** and are soft-deleted.
Registrations may include an optional `clientIdentifier` (for example `codex` or `claude`) for abuse monitoring. The service records the source IP server-side; neither value is used as an identity or authorization credential.

For the OAuth flow, the registered client's authorization code is claimed via the standard authorization-code + PKCE exchange: send the user through `authorization_endpoint` with a `code_challenge`, then exchange the returned `code` and matching `code_verifier` at `token_endpoint` for tokens. Dashboard API keys require no claim step: they are already scoped to a team the moment they're created.

## Build with the team

Present whichever credential you obtained as a bearer token on every request:

```http
Authorization: Bearer <token>
```

### HTTP or CLI

Use `curl` for unauthenticated registration and claim requests:

```bash
curl -X POST https://www.zyberdesk.com/api/public/v1/claimable/register
  -H "Content-Type: application/json"
  -d '{"email":"owner@example.com","title":"Agent workspace"}'
```

Install the Nexvio CLI from the w3dev registry for authenticated team and resource operations:

```bash
npm install -g @nexvio/cli --registry=https://npm.w3api.dev/
nexvio auth login
nexvio agents list
nexvio agents create --body '{"name":"Support agent"}'
nexvio claimable register --anonymous --title "Anonymous workspace"
nexvio claimable exchange --assertion ia_...
nexvio claimable claim-start --token clm_...
nexvio claimable claim-confirm --token clm_... --code 123456
```

The CLI is a thin wrapper over the same HTTP APIs. Use `nexvio api request` for an endpoint that does not yet have a dedicated command; use `--json` for machine-readable output.

- **Streamable HTTP**: https://www.zyberdesk.com/api/chatgpt/mcp/mcp
- **SSE**: https://www.zyberdesk.com/api/mcp
- **External agent (A2A-style)**: https://www.zyberdesk.com/external-agent/mcp
- **ChatGPT connector**: https://www.zyberdesk.com/api/chatgpt/mcp/mcp

OAuth access tokens are short-lived; when one expires, use the paired `refresh_token` against `https://www.zyberdesk.com/api/oauth/token` (`grant_type=refresh_token`) to obtain a new one without re-prompting the user. Provisional and dashboard API keys don't expire on a short TTL — rotate or revoke them instead (provisional keys are revoked automatically on claim).

## Errors

Every authenticated endpoint that rejects a credential returns a `401` or `403` with a machine-readable `WWW-Authenticate` challenge header (RFC 6750) alongside a JSON body:

| `error` | HTTP status | Meaning | What to do |
| --- | --- | --- | --- |
| `invalid_request` | 401 | The `Authorization` header is missing or malformed | Resend with `Authorization: Bearer <token>` |
| `invalid_token` | 401 | The token is expired, malformed, or unknown | Refresh (OAuth) or re-issue (API key) the credential |
| `insufficient_scope` | 403 | The token is valid but lacks a required scope | Re-authorize with the missing scope, or use a credential that has it |
| `expired` | 410 | Provisional claim window elapsed | Register a new claimable team |
| `already_claimed` | 409 | Provisional team already owned by a human | Use that team's dashboard keys |

The `WWW-Authenticate` header on each of these carries `resource_metadata="..."` pointing back at `https://www.zyberdesk.com/api/.well-known/oauth-protected-resource`, so an agent that lands on an error can always re-run Discover from scratch.

## Revocation

- **OAuth tokens**: `POST https://www.zyberdesk.com/api/oauth/revoke` (RFC 7009) with the token to invalidate it immediately.
- **Provisional claimable keys**: revoked automatically when the team is claimed or expires.
- **API keys / external-agent keys**: delete the key from the dashboard (Settings → API Keys) — there is no revocation API for these; deletion is immediate and takes effect on the next request.

These are independent layers: revoking an OAuth token never touches a team's API keys, and vice versa.

## Deprecation policy

Every credential/endpoint above is versioned in its URL (`/api/v1`, `/api/public/v1`). No version documented here is currently deprecated. When a version is deprecated, its responses will carry a `Deprecation` header (RFC 8594) naming the date deprecation took effect, and — once a retirement date is set — a `Sunset` header naming it; the deprecated version will keep working for at least 90 days after `Deprecation` first appears. See https://www.zyberdesk.com/openapi.json's `info.description` for the same policy.

## Sandbox

The free Hobbyist team is the approved sandbox for testing against Zyberdesk: it is isolated from other teams' data, shares nothing with production teams, and only supports draft agent versions — safe to register OAuth clients and API keys against without any risk to real customer data.

## Further reading

- Authorization server metadata: https://www.zyberdesk.com/.well-known/oauth-authorization-server
- Protected resource metadata: https://www.zyberdesk.com/api/.well-known/oauth-protected-resource
- Dynamic client registration: https://www.zyberdesk.com/api/oauth/register
- Scopes: openid, profile, mcp:read, mcp:write, conversations:read, conversations:write, contacts:read, contacts:write, analytics:read, tickets:read, tickets:write, agents:read, agents:write, guardrails:read, guardrails:write, data-sources:read, data-sources:write
