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

# Managing Tokens & the REST API

> Provision and revoke scoped agent keys programmatically, and drive the same scoped surface over raw HTTP without MCP.

Everything the MCP server does, it does by relaying a **scoped agent key** to the Sapot Chat REST API. That means two things you can do without ever touching MCP: mint and revoke keys over HTTP, and call the same scoped endpoints directly with `curl` or any HTTP client. The exact same default-deny allowlist and safety boundaries apply either way.

## The admin token API

Agent keys are managed under a standard account-scoped REST resource. All four actions are **admin-only** and must be called with a **full-access personal access token** (or a dashboard session) — a scoped agent key cannot manage keys, because the token-management endpoints are deliberately kept off the agent allowlist.

<Warning>
  Provision and revoke keys with an **admin's personal access token**, not with a scoped agent key. Handing an agent the ability to mint its own keys would defeat the scope model.
</Warning>

| Method   | Path                                                     | Action                                             |
| -------- | -------------------------------------------------------- | -------------------------------------------------- |
| `GET`    | `/api/v1/accounts/{account_id}/agent_access_tokens`      | List keys (masked)                                 |
| `POST`   | `/api/v1/accounts/{account_id}/agent_access_tokens`      | Create a key — plaintext token returned **once**   |
| `GET`    | `/api/v1/accounts/{account_id}/agent_access_tokens/{id}` | Show one key (masked)                              |
| `DELETE` | `/api/v1/accounts/{account_id}/agent_access_tokens/{id}` | Revoke a key — it stops authenticating immediately |

### Create a key

```bash theme={null}
curl -X POST https://<your-api-host>/api/v1/accounts/YOUR_ACCOUNT_ID/agent_access_tokens \
  -H "api_access_token: YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "agent_access_token": {
          "name": "triage-bot",
          "scopes": ["conversations:read", "conversations:write"],
          "expires_at": "2026-12-31T00:00:00Z"
        }
      }'
```

The request body accepts `name` (required), `scopes` (array, at least one; unknown values are rejected), and an optional `expires_at`. Valid scope values are the capability grammar: `conversations:read`, `conversations:write`, `contacts:manage`, `knowledge:manage`, `flows:manage`, `flows:publish`, `campaigns:draft`, `templates:manage`, `automation:manage`, `reports:read`, plus `*` for full access. See the Authentication & Scopes page for what each grants.

The create response is the only place the plaintext `token` is ever returned:

```json theme={null}
{
  "id": 42,
  "name": "triage-bot",
  "token": "hE4fLv…full-secret-shown-once…QwGJw",
  "token_preview": "hE4fLv…QwGJw",
  "scopes": ["conversations:read", "conversations:write"],
  "expires_at": "2026-12-31T00:00:00Z",
  "revoked_at": null,
  "last_used_at": null,
  "usage_count": 0,
  "active": true,
  "created_at": "2026-07-06T10:00:00Z",
  "created_by": { "name": "Ada Admin", "email": "ada@acme.inc" }
}
```

<Note>
  Copy `token` now. Every subsequent list/show returns only `token_preview` (first 6 + last 4 characters) — the full secret is never retrievable again. `usage_count` is the metered call count for that key; `last_used_at` reflects its most recent successful call.
</Note>

## Direct REST without MCP

The same scoped key works against the documented Sapot Chat REST endpoints — no MCP server in the path. Two differences from the MCP transport:

* Send the key in the **`api_access_token`** header (not `Authorization: Bearer` — that form is the MCP transport's, which the server renames on relay).
* The account id lives in the **URL path**, not the `X-Sapot-Account-Id` header.

```bash theme={null}
curl "https://<your-api-host>/api/v1/accounts/YOUR_ACCOUNT_ID/conversations?status=open" \
  -H "api_access_token: YOUR_AGENT_KEY"
```

### MCP vs. direct REST

|             | MCP                                    | Direct REST                        |
| ----------- | -------------------------------------- | ---------------------------------- |
| Auth header | `Authorization: Bearer YOUR_AGENT_KEY` | `api_access_token: YOUR_AGENT_KEY` |
| Account id  | `X-Sapot-Account-Id` header            | in the URL path                    |
| Endpoint    | `https://mcp.sapot.ai/mcp`             | your API base URL                  |
| Interface   | 46 named intent-level tools            | documented REST endpoints          |

For reference, the MCP connect command is:

```bash theme={null}
claude mcp add --transport http sapot https://mcp.sapot.ai/mcp --header "Authorization: Bearer YOUR_AGENT_KEY" --header "X-Sapot-Account-Id: YOUR_ACCOUNT_ID"
```

## Same boundaries, either path

A scoped key is governed identically over REST and over MCP — the enforcement lives in the Rails API, not the MCP layer:

<CardGroup cols={2}>
  <Card title="Default-deny allowlist" icon="lock">
    A key reaches only the endpoints its scopes map to; a missing scope returns <code>401</code> naming the required scope. The key is bound to the one account it was minted in — a path account id that doesn't match is refused.
  </Card>

  <Card title="Flow graph stripped" icon="diagram-project">
    Flow-agent reads serve an agent-safe view with the Designer graph removed, and <code>publish</code> failures return an opaque error to agent keys. No create/export/import.
  </Card>

  <Card title="Campaigns forced to draft" icon="paper-plane">
    Agent-authored campaigns are pinned to a non-sending <code>draft</code> server-side, so a key can prepare a blast but never send it. A human sends from the dashboard.
  </Card>

  <Card title="Metered per plan" icon="gauge">
    Only allowed, successful (2xx) calls consume quota — a denied, rejected, or errored request counts nothing. Over the plan's per-period allowance, requests return <code>429</code>.
  </Card>
</CardGroup>

<Tip>
  Because the API is the single enforcement point, testing a scope over `curl` is a faithful preview of how the same key behaves through an MCP tool.
</Tip>

## Reference

<CardGroup cols={2}>
  <Card title="REST API reference" icon="book" href="/api-reference/introduction">
    The full documented endpoint surface a scoped key can drive over HTTP.
  </Card>

  <Card title="Authentication & Scopes" icon="key" href="/ai-agents/authentication">
    The capability grammar and the default-deny scope model in detail.
  </Card>
</CardGroup>
