> ## Documentation Index
> Fetch the complete documentation index at: https://sendmux.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent access

> Let an AI agent pass registration checks, create a constrained Sendmux mailbox, and invite its human owner.

Agent access lets an AI agent start with one constrained `@myagent.mx` mailbox before a person joins the team. The agent completes a short proof-of-work check, registers the mailbox, reads and receives mail, asks Sendmux to invite the human owner, and uses owner-approved sending after the owner joins and grants it.

<Info>
  Pre-claim agent tokens include `mailbox.read` and `email.receive`. They do
  not include `email.send`.
</Info>

If you already have Sendmux MCP, CLI, SDK, or an API key configured for the task, use that existing setup first. Register a new agent identity only when the agent has no usable Sendmux credential.

## Default: save the agent credential bundle

When registering a new agent identity, immediately save the agent credential bundle in the safest secret store your environment provides before token exchange, owner invite, reporting success, or losing context. Later MCP, CLI, SDK, mailbox, and sending work depends on this bundle.

Store `claim_token`, `registration_id`, `mailbox.email`, `claim_token_expires`, `identity_assertion`, `token_endpoint`, `app_resource`, and `sending_resource`. After token exchange, add the pre-claim `smx_agent_` access token or keep `identity_assertion` available until the owner invite returns `202`. If no secure store is available, ask the user where to store these credentials before continuing.

## Success checklist

* Request a registration challenge.
* Solve the proof-of-work challenge.
* Register with `proof_of_work`.
* Immediately save the returned `claim_token` and agent credential bundle in a secure store before token exchange.
* Exchange the `identity_assertion` for an `smx_agent_` access token.
* Keep the `identity_assertion` or pre-claim access token available until the owner invite returns `202`.
* Call `/api/v1/mailbox/me` with that token.
* Do not stop after pre-claim token success. Next required action: create an owner invite or ask the user for the owner email to invite.
* Do not poll the claim-token grant before the owner invite returns `202`; after `202`, poll with `claim_token` and handle `authorization_pending` until the owner approves sending.

## Optional: install Sendmux skills

If your agent supports Skills, install the Sendmux pack before continuing.

```bash theme={null}
npx skills add Sendmux/skills
```

Skills are optional. Agents that cannot install skills should continue with `auth.md` and this guide.

## Critical ordering

`claim_token` is for the post-approval token exchange. Save it, but do not use it to invite the owner.

Use the `identity_assertion` to get a pre-claim `smx_agent_` access token, then use that token for `/api/v1/mailbox/me` and `POST /agent-auth/agent/identity/invite`. Keep the `identity_assertion` or pre-claim token available until the invite returns `202`.

If you lose both the `identity_assertion` and pre-claim token before inviting the owner, register a fresh agent identity and invite immediately in the same flow.

## How it works

<Steps>
  <Step title="Discover the protected resource">
    Start from `https://app.sendmux.ai/auth.md`, follow the `auth.md` link on
    the human sign-up page, or call the Management API without credentials and
    follow the `WWW-Authenticate` metadata hint.
  </Step>

  <Step title="Request a registration challenge">
    Send the intended anonymous registration body to the challenge endpoint, then
    solve the returned proof-of-work challenge.
  </Step>

  <Step title="Create an anonymous agent identity">
    Register with the same body and the solved proof payload to get an identity
    assertion, a claim token, and a constrained agent mailbox.
  </Step>

  <Step title="Save the credential bundle">
    Store the agent credential bundle securely before any context is lost. The
    agent needs it later for MCP, CLI, SDK, mailbox, and sending work.
  </Step>

  <Step title="Exchange the assertion">
    Use the JWT bearer grant to exchange the identity assertion for an
    `smx_agent_` access token. Keep the assertion or token available until the
    owner invite succeeds.
  </Step>

  <Step title="Verify the mailbox">
    Call `/api/v1/mailbox/me` with the token before inviting the owner.
  </Step>

  <Step title="Invite the owner or ask for the email">
    If you know the owner email, ask Sendmux to send the invite. If you do not
    know it, ask: `What owner email should I invite for approval?`
  </Step>

  <Step title="Wait for owner approval">
    After the owner accepts, they can approve sending for the agent mailbox from
    the **Agent access** card on the team page.
  </Step>

  <Step title="Exchange the claim token">
    Use the claim grant to request an app-resource token for mailbox work or a
    Sending-resource token for the Sending API.
  </Step>
</Steps>

Sendmux sends the owner invite email through the invite system. The agent does not need Sending API access to invite its owner.

## Discovery

Agents can read the human-readable service document first.

```bash theme={null}
curl https://app.sendmux.ai/auth.md
```

Agents that land on the human sign-up page can use the `auth.md` link under
the Terms and Privacy line instead of filling in the form.

Agents can also start from a cold API request. Sendmux returns the protected-resource metadata URL in `WWW-Authenticate`:

```http theme={null}
GET /api/v1 HTTP/1.1
Host: app.sendmux.ai

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://app.sendmux.ai/.well-known/oauth-protected-resource/api/v1"
```

The same discovery challenge is returned from `/api/v1/mailbox` and mailbox routes such as `/api/v1/mailbox/me` when the Bearer token is missing or invalid.

Fetch the protected-resource metadata, then follow its `authorization_servers` entry.

```text theme={null}
https://app.sendmux.ai/.well-known/oauth-protected-resource/api/v1
```

The authorisation-server metadata advertises `token_endpoint` and `revocation_endpoint` at the top level. Its `agent_auth` block includes `identity_endpoint`, `x_sendmux_proof_of_work_challenge_endpoint`, `x_sendmux_identity_invite_endpoint`, and `identity_types_supported`. The proof-of-work extension points to the registration challenge endpoint. The invite field is the Sendmux-specific owner invite extension.

```text theme={null}
https://app.sendmux.ai/.well-known/oauth-authorization-server/agent-auth
```

Sendmux does not publish an OTP `claim_endpoint`. Human ownership uses the invite flow below instead of an OTP claim ceremony.

Unsupported methods today:

* no OTP `claim_endpoint`
* no ID-JAG or verified-email registration
* no `email.send` scope for pre-claim agent tokens

## Register an agent identity

Call the challenge endpoint first, using the same registration body you intend to submit. Do not include `proof_of_work` in the challenge request.

```http theme={null}
POST https://app.sendmux.ai/agent-auth/agent/identity/challenge HTTP/1.1
Content-Type: application/json

{
  "type": "anonymous",
  "mailbox_local_part": "triage-agent",
  "client_name": "Triage Agent",
  "idempotency_key": "idem_agent_registration_123"
}

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "challenge": {
    "parameters": {
      "algorithm": "PBKDF2/SHA-256",
      "cost": <number_from_response>,
      "keyLength": <number_from_response>,
      "keyPrefix": "<prefix_from_response>",
      "nonce": "<nonce_from_response>",
      "salt": "<salt_from_response>",
      "expiresAt": <unix_seconds_from_response>,
      "data": {
        "purpose": "agent_registration",
        "request_hash": "<request_hash_from_response>"
      }
    },
    "signature": "<challenge_signature>"
  },
  "expires_at": "2026-06-22T22:05:00.000Z",
  "payload_field": "proof_of_work",
  "proof_format": {
    "encoding": "base64",
    "content": "UTF-8 JSON",
    "json_shape": {
      "challenge": "exact challenge object from the challenge response",
      "solution": {
        "counter": "integer",
        "derivedKey": "solver-returned hex string"
      }
    },
    "registration_body": "must match the challenge request body exactly, except for the added proof_of_work field"
  }
}
```

Solve the returned challenge and send the solved payload as the `proof_of_work` field on the registration request. Treat the returned `proof_format` object as the source of truth for the proof payload.

Build `proof_of_work` as standard base64 of a UTF-8 JSON payload with this exact shape:

```json theme={null}
{
  "challenge": { "...": "the exact challenge object from the challenge response" },
  "solution": {
    "counter": 12345,
    "derivedKey": "<solver-returned hex string>"
  }
}
```

Use the exact `challenge` object returned by the challenge endpoint. `solution.counter` is an integer, and `solution.derivedKey` is the hex string returned by your solver. The registration body must match the challenge request body exactly, except for the added `proof_of_work` field. If you change `mailbox_local_part`, `client_name`, or `idempotency_key`, request and solve a new challenge.

The public `auth.md` file includes a copy-paste proof helper for agents that need one:

```bash theme={null}
curl https://app.sendmux.ai/auth.md
```

```http theme={null}
POST https://app.sendmux.ai/agent-auth/agent/identity HTTP/1.1
Content-Type: application/json
Idempotency-Key: idem_agent_registration_123

{
  "type": "anonymous",
  "mailbox_local_part": "triage-agent",
  "client_name": "Triage Agent",
  "idempotency_key": "idem_agent_registration_123",
  "proof_of_work": "<base64 UTF-8 JSON proof payload>"
}

HTTP/1.1 201 Created
Content-Type: application/json
Cache-Control: no-store

{
  "registration_id": "areg_...",
  "registration_type": "anonymous",
  "identity_assertion": "<service-signed JWT>",
  "assertion_expires": "2026-06-22T22:00:00.000Z",
  "claim_token": "smx_agent_claim_...",
  "claim_token_expires": "2026-06-23T21:00:00.000Z",
  "pre_claim_scopes": ["mailbox.read", "email.receive"],
  "mailbox": {
    "email": "triage-agent@myagent.mx",
    "status": "provisioning"
  }
}
```

`proof_of_work` is required. `mailbox_local_part`, `client_name`, and `idempotency_key` are optional. If `mailbox.status` is `provisioning`, retry token exchange shortly.

## Save the agent credential bundle

Immediately after registration and before exchanging the identity assertion, inviting the owner, or reporting that setup succeeded, save the agent credential bundle in a secure store controlled by the user or agent environment, such as 1Password, an OS keychain, or the agent platform's encrypted secret store. This bundle is required later for MCP, CLI, SDK, mailbox, and sending work.

Sendmux cannot recover or reveal the raw `claim_token` later. If the raw claim token is lost, rerun registration and invite with a fresh agent identity.

Saving `claim_token` alone is not enough to invite the owner. The invite must use the `identity_assertion`-derived pre-claim access token before that short-lived material is lost. Keep the `identity_assertion` or pre-claim token available until `POST /agent-auth/agent/identity/invite` returns `202 Accepted`.

Do not store these credentials in chat, logs, repo files, screenshots, temporary prompts, or memory-only state that may disappear before owner approval. Store these fields together:

* `claim_token`
* `registration_id`
* `mailbox.email`
* `claim_token_expires`
* `identity_assertion` or the pre-claim `smx_agent_` access token until the owner invite returns `202`
* `token_endpoint=https://app.sendmux.ai/agent-auth/oauth2/token`
* `app_resource=https://app.sendmux.ai/api/v1`
* `sending_resource=https://smtp.sendmux.ai/api/v1`

If no secure store is available, stop and ask the user where to store these credentials. Do not continue to owner invite if you cannot retrieve the bundle later.

## Exchange the assertion

Use the OAuth JWT bearer grant to get an `smx_agent_` access token.

```http theme={null}
POST https://app.sendmux.ai/agent-auth/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<identity_assertion>&resource=https%3A%2F%2Fapp.sendmux.ai%2Fapi%2Fv1

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "access_token": "smx_agent_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "mailbox.read email.receive"
}
```

If the mailbox is still being prepared, the token endpoint returns `503 temporarily_unavailable` with `Retry-After: 10` and `retry_after: 10`. Retry only expected `temporarily_unavailable` responses while exchanging the identity assertion; stop and report a `server_error`.

## Use the mailbox

Pass the `smx_agent_` token as a Bearer token to allowed Mailbox API endpoints.

```http theme={null}
GET /api/v1/mailbox/me HTTP/1.1
Host: app.sendmux.ai
Authorization: Bearer smx_agent_...
```

The token is mailbox-compatible, but permissions still apply. A pre-claim token can read and receive mail. It cannot send through the Mailbox API or Sending API.

Do not stop after `/api/v1/mailbox/me` succeeds. If you know the human owner email, invite them next. If you do not know it, ask: `What owner email should I invite for approval?`

## Invite the owner

Ask Sendmux to invite the human owner.

```http theme={null}
POST https://app.sendmux.ai/agent-auth/agent/identity/invite HTTP/1.1
Authorization: Bearer smx_agent_...
Content-Type: application/json
Idempotency-Key: idem_owner_invite_123

{
  "email": "owner@example.com",
  "requested_role": "owner",
  "idempotency_key": "idem_owner_invite_123"
}

HTTP/1.1 202 Accepted
Content-Type: application/json
Cache-Control: no-store

{
  "invite_id": "ainv_...",
  "status": "pending"
}
```

The invite email goes to the owner. Membership starts only after that person verifies and accepts the invite. One live pre-claim owner invite can be pending per registration; retry the same request with the same `idempotency_key`.

After this endpoint returns `202`, keep waiting for the owner. Before it returns `202`, do not switch to the claim-token grant; `claim_token` cannot create the invite.

## Wait for owner-approved sending

After the owner accepts the invite, they open the team page and use the
**Agent access** card to approve sending. The agent polls the token endpoint
with the original `claim_token` until that approval exists.

Request the app resource for Mailbox API work:

```http theme={null}
POST https://app.sendmux.ai/agent-auth/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aworkos%3Aagent-auth%3Agrant-type%3Aclaim&claim_token=smx_agent_claim_...&resource=https%3A%2F%2Fapp.sendmux.ai%2Fapi%2Fv1

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "access_token": "smx_agent_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "mailbox.read email.receive email.send mailbox.settings.update"
}
```

Request the Sending API resource before calling `smtp.sendmux.ai`:

```http theme={null}
POST https://app.sendmux.ai/agent-auth/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aworkos%3Aagent-auth%3Agrant-type%3Aclaim&claim_token=smx_agent_claim_...&resource=https%3A%2F%2Fsmtp.sendmux.ai%2Fapi%2Fv1

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "access_token": "smx_agent_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "mailbox.read email.receive email.send mailbox.settings.update"
}
```

Use the Sending-resource token for Sending API calls. Use the app-resource token for Mailbox API reads, receives, and mailbox sends.

If the owner has joined but not approved sending yet, the token endpoint returns `503 authorization_pending` with `Retry-After: 10` and `retry_after: 10`. If the owner invite has not been sent yet, go back to the pre-claim token from `identity_assertion`; the `claim_token` cannot create the invite.

```http theme={null}
POST /api/v1/emails/send HTTP/1.1
Host: smtp.sendmux.ai
Authorization: Bearer smx_agent_...
Content-Type: application/json

{
  "from": { "email": "triage-agent@myagent.mx" },
  "to": { "email": "owner@example.com" },
  "subject": "Hello from Sendmux",
  "html_body": "<p>Owner-approved agent sending is active.</p>",
  "text_body": "Owner-approved agent sending is active."
}
```

## Revoke a token

Revoke an agent access token when it is no longer needed.

```http theme={null}
POST https://app.sendmux.ai/agent-auth/oauth2/revoke HTTP/1.1
Content-Type: application/x-www-form-urlencoded

token=smx_agent_...&token_type_hint=access_token

HTTP/1.1 200 OK
Cache-Control: no-store
```

Revocation is idempotent. Unknown or already-revoked access tokens still return `200 OK`.

## Limits

Each anonymous registration gets exactly one constrained `@myagent.mx` mailbox. Proof-of-work challenges are short-lived, and registration plus invite endpoints are rate limited. When an agent is rate limited, `429` responses include `Retry-After`, `retry_after`, and `rate_limit_scope` so the agent can back off before retrying. Unclaimed registrations expire after 24 hours. Sendmux revokes pre-claim access tokens and reclaims the agent mailbox after expiry.

After the owner accepts the invite, the team uses normal Sendmux limits and can request increases like any other team. The owner can approve or revoke agent sending from the **Agent access** card on the team page.

Timing summary:

* identity assertions expire after 15 minutes
* agent access tokens expire after at most 1 hour
* unclaimed agent registrations, claim tokens, and their owner approval window expire after 24 hours
* other owner invitation links can last up to 7 days

## Error reference

| Endpoint                                                     | Status | Error                     | Meaning                                                                                                                                                                                                  | Agent action                                                                                                                                                                        |
| ------------------------------------------------------------ | -----: | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `https://app.sendmux.ai/agent-auth/agent/identity/challenge` |    400 | `invalid_request`         | Body is not JSON, `type` is unsupported, a field is invalid, or `proof_of_work` was included too early.                                                                                                  | Correct the intended registration body and request a new challenge.                                                                                                                 |
| `https://app.sendmux.ai/agent-auth/agent/identity/challenge` |    429 | `temporarily_unavailable` | Registration challenge rate limit reached.                                                                                                                                                               | Back off until `Retry-After`; the body also includes `retry_after` and `rate_limit_scope`.                                                                                          |
| `https://app.sendmux.ai/agent-auth/agent/identity`           |    400 | `proof_of_work_required`  | Registration is missing `proof_of_work`.                                                                                                                                                                 | Request a challenge, solve it, encode `{ "challenge": <exact challenge>, "solution": { "counter": <integer>, "derivedKey": <hex> } }` as base64 UTF-8 JSON, and retry registration. |
| `https://app.sendmux.ai/agent-auth/agent/identity`           |    400 | `invalid_request`         | Body is not JSON, `type` is unsupported, a field is invalid, proof is invalid or expired, proof was replayed, proof does not match the request, or `idempotency_key` was replayed with a different body. | Correct the body or request a fresh challenge for the exact body you will submit.                                                                                                   |
| `https://app.sendmux.ai/agent-auth/agent/identity`           |    409 | `invalid_request`         | Requested mailbox local part is unavailable.                                                                                                                                                             | Retry without `mailbox_local_part` or choose another value.                                                                                                                         |
| `https://app.sendmux.ai/agent-auth/agent/identity`           |    429 | `temporarily_unavailable` | Registration rate limit reached.                                                                                                                                                                         | Back off until `Retry-After`; the body also includes `retry_after` and `rate_limit_scope`.                                                                                          |
| `https://app.sendmux.ai/agent-auth/agent/identity`           |    503 | `server_error`            | Registration is temporarily unavailable.                                                                                                                                                                 | Stop and report the failure; do not retry repeatedly.                                                                                                                               |
| `https://app.sendmux.ai/agent-auth/oauth2/token`             |    400 | `unsupported_grant_type`  | `grant_type` is not one of the documented values.                                                                                                                                                        | Use the assertion grant before owner approval, or the claim grant after owner approval.                                                                                             |
| `https://app.sendmux.ai/agent-auth/oauth2/token`             |    400 | `invalid_grant`           | Identity assertion or claim token is invalid, expired, revoked, or bound to an expired registration.                                                                                                     | Register again, use a fresh assertion, or use the original claim token.                                                                                                             |
| `https://app.sendmux.ai/agent-auth/oauth2/token`             |    503 | `authorization_pending`   | Owner approval for sending is not complete.                                                                                                                                                              | If the owner invite has not been sent, exchange `identity_assertion` for a pre-claim access token and send the invite first. Otherwise retry after `Retry-After` or `retry_after`.  |
| `https://app.sendmux.ai/agent-auth/oauth2/token`             |    503 | `temporarily_unavailable` | Mailbox provisioning is still in progress.                                                                                                                                                               | Retry after the `Retry-After` header or `retry_after` body field.                                                                                                                   |
| `https://app.sendmux.ai/agent-auth/agent/identity/invite`    |    401 | `invalid_token`           | Agent access token is missing, invalid, expired, or revoked.                                                                                                                                             | Exchange a valid identity assertion for a fresh access token.                                                                                                                       |
| `https://app.sendmux.ai/agent-auth/agent/identity/invite`    |    400 | `invalid_request`         | Body is not JSON, email is invalid, or idempotency key conflicts.                                                                                                                                        | Correct the request body.                                                                                                                                                           |
| `https://app.sendmux.ai/agent-auth/agent/identity/invite`    |    403 | `access_denied`           | Requested role is not available, or an owner invite is already pending.                                                                                                                                  | Wait for the human owner or retry the same pending invite email.                                                                                                                    |
| `https://app.sendmux.ai/agent-auth/agent/identity/invite`    |    429 | `temporarily_unavailable` | Invite rate limit reached.                                                                                                                                                                               | Back off until `Retry-After`; the body also includes `retry_after` and `rate_limit_scope`.                                                                                          |
| `https://app.sendmux.ai/agent-auth/oauth2/revoke`            |    400 | `invalid_request`         | Form body is missing or malformed.                                                                                                                                                                       | Send form-encoded `token`.                                                                                                                                                          |
| `https://app.sendmux.ai/agent-auth/oauth2/revoke`            |    400 | `unsupported_token_type`  | `token_type_hint` is not `access_token`.                                                                                                                                                                 | Use `access_token` or omit the hint.                                                                                                                                                |

## Next steps

<CardGroup cols={2}>
  <Card title="Mailbox API" icon="inbox" href="/docs/mailbox-api/introduction">
    Read and sync mailbox data with an agent token.
  </Card>

  <Card title="Agent skills" icon="sparkles" href="/docs/guides/agent-skills">
    Teach AI coding tools the Sendmux agent workflows.
  </Card>

  <Card title="SDKs" icon="code" href="/docs/sdks">
    Use SDK clients with mailbox-compatible tokens.
  </Card>

  <Card title="API keys" icon="key" href="/docs/guides/api-keys">
    Review manual keys, connected apps, and agent tokens.
  </Card>
</CardGroup>
