Skip to content

Authentication

The Telbox developer API uses two credentials. API keys (tb_live_…), minted in the developer console, authenticate your own workspace machine-to-machine, gated by deny-by-default scopes (§1–2). OAuth 2.1 tokens authenticate a third-party app or agent acting on behalf of a Telbox user, and are what every MCP endpoint requires (§3). Both are tb_live_… bearers; they differ in how they're minted and what they're bound to.

Gated until launch

The external API-key auth path is behind the apikey_auth_enabled flag and is dark on the public production host until launch. While it's off, point your base URL at your developer preview. Everything below describes the enabled behavior.

1. Create a developer account

The console sign-up is email + password + a 6-digit email code. These four endpoints are public (they mint your console session) and live under /v1/developer/auth.

Endpoint Body Returns
POST /v1/developer/auth/register { email, password, display_name } { email, needs_verification: true, dev_code? }
POST /v1/developer/auth/verify { email, code } { email, access_token }
POST /v1/developer/auth/login { email, password } { email, access_token }
POST /v1/developer/auth/resend { email } { email, needs_verification: true, dev_code? }
  • password must be at least 10 characters; email and display_name are required at registration.
  • register creates the account and emails a 6-digit code. verify confirms the code, marks the email verified, and returns a console session access_token.
  • login requires a verified email; if the account exists but was never verified, it re-sends a code and returns needs_verification: true instead of a token.

dev_code in non-prod

In non-production environments (no SMTP configured) the verification code is echoed back as dev_code so the local preview works without a mail server. Production sends a real email and never echoes the code.

Sign up, then verify
curl -X POST https://api.telbox.ai/v1/developer/auth/register \
  -H "Content-Type: application/json" \
  -d '{ "email": "ada@example.com", "password": "correct-horse-battery", "display_name": "Ada" }'

curl -X POST https://api.telbox.ai/v1/developer/auth/verify \
  -H "Content-Type: application/json" \
  -d '{ "email": "ada@example.com", "code": "123456" }'
# → { "email": "ada@example.com", "access_token": "eyJhbGciOi…" }

The console access_token authenticates the first-party developer endpoints (/v1/developer/*, /v1/agents/*) that you use to manage keys and agents from the portal. For calling the API from your own server, mint an API key (next).

2. API keys

An API key is the credential your integration uses. The format is tb_{env}_{token}tb_live_… on the production host, tb_test_… in non-prod — and only an argon2 hash plus a short display prefix (tb_{env}_{token[:8]}) is ever stored. The env is chosen by the server from the environment; you don't pick it.

Shown exactly once

The raw key is returned only in the create/rotate response. Store it in a secret manager immediately — you cannot retrieve it again. Lost a key? Rotate or revoke it and mint a new one.

Manage keys — /v1/developer/api-keys

These are first-party endpoints, so call them with your console access_token (or from the portal). They manage your own workspace's keys.

Endpoint Purpose
GET /v1/developer/scopes The grantable scope catalog
GET /v1/developer/api-keys List your keys (newest first)
POST /v1/developer/api-keys Create a scoped key → raw key (once)
POST /v1/developer/api-keys/{key_id}/rotate Mint a replacement + revoke the old
DELETE /v1/developer/api-keys/{key_id} Revoke a key

POST /v1/developer/api-keys takes { name, scopes?, expires_at? }. If you omit scopes, the key defaults to ["threads:read", "messages:write", "voice_notes:write"]. expires_at is an optional ISO-8601 expiry. The response is { key, raw_key, env }, where key is the key's metadata view and raw_key is the full secret.

One-shot key

POST /v1/agents/api-keys mints a single default-scoped key (threads:read, messages:write, voice_notes:write) for your workspace in one call — the portal's "Get an API key" button. Use POST /v1/developer/api-keys when you want to choose the name, scopes, or an expiry.

Create a key

curl -X POST https://api.telbox.ai/v1/developer/api-keys \
  -H "Authorization: Bearer <console_access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "crm-sync",
    "scopes": ["threads:read", "messages:write"]
  }'
201 Created
{
  "key": {
    "id": "8f0c…",
    "name": "crm-sync",
    "prefix": "tb_live_a1b2c3d4",
    "scopes": ["messages:write", "threads:read"],
    "revoked": false
  },
  "raw_key": "tb_live_a1b2c3d4e5f6…",
  "env": "live"
}
from telbox import TelboxClient

# Authenticate the SDK with your console session to mint a key, once.
admin = TelboxClient(api_key="<console_access_token>")
created = admin.create_api_key()       # default scopes, returns the raw key once
print(created.raw_key)                  # tb_live_… — stash this now
import { TelboxClient } from "@telbox/sdk";

// Authenticate the SDK with your console session to mint a key, once.
const admin = new TelboxClient({ apiKey: "<console_access_token>" });
const created = await admin.createApiKey();   // default scopes, raw key once
console.log(created.rawKey);                    // tb_live_… — stash this now

Use a key

Send the key as a Bearer token on every request. The SDKs take it once at construction.

curl https://api.telbox.ai/v1/agents \
  -H "Authorization: Bearer tb_live_a1b2c3d4e5f6…"
from telbox import TelboxClient

tb = TelboxClient(api_key="tb_live_…", base_url="https://api.telbox.ai")
agents = tb.list_agents()
import { TelboxClient } from "@telbox/sdk";

const tb = new TelboxClient({ apiKey: "tb_live_…", baseUrl: "https://api.telbox.ai" });
const agents = await tb.listAgents();

Rotate and revoke

Rotating mints a replacement key (same name, scopes, and expiry) and revokes the old one in a single call — update your secret store with the new raw_key, then the old key stops working. Revoking is immediate and irreversible.

Rotate, then revoke
# Rotate: returns a new raw_key; the old key_id is revoked.
curl -X POST https://api.telbox.ai/v1/developer/api-keys/{key_id}/rotate \
  -H "Authorization: Bearer <console_access_token>"

# Revoke: 204 No Content, no body.
curl -X DELETE https://api.telbox.ai/v1/developer/api-keys/{key_id} \
  -H "Authorization: Bearer <console_access_token>"

Every create, rotate, and revoke is rate-limited and recorded in the platform audit log.

Scopes

Keys are deny-by-default: a key can only do what it was explicitly granted. Fetch the live catalog from GET /v1/developer/scopes. The vocabulary:

Scope Grants
threads:read List threads and enumerate message metadata (ids, kinds, sender, timestamps, delivery state). Encrypted content / transcripts / audio URLs are stripped unless the key also holds messages:read.raw.
messages:read.raw Raw decrypted message + voice content (high-privilege).
voice_notes:read Transcripts, summaries, and extracted actions + their metadata.
messages:write Send text messages (non-voice-note kinds).
voice_notes:write Create / send a Telbox voice note.
tasks:write Create a task.
contacts:read Read contacts.
agents:read List templates/tools/agents and read agent run traces.
agents:write Create, compile, publish, test-run, or delete agents.
webhooks:manage Manage webhook endpoints.
scim SCIM provisioning.

Building agents with an API key

The agent-build endpoints (/v1/agents*, /v1/agent-templates, /v1/agent-tools) accept an API key that holds agents:read (reads) or agents:write (mutations) — select those scopes when you create the key. POST /v1/agents/api-keys (minting a key) stays first-party-only, so mint keys from the developer console, not with another key.

messages:read.raw exposes plaintext

threads:read is the metadata-only sibling — "show me what happened" without "show me what was said." Grant messages:read.raw only when the integration genuinely needs decrypted content; it's the high-privilege scope and is audited.

3. OAuth for MCP & third-party apps

API keys authenticate your own workspace. When a third-party app or agent acts on behalf of a Telbox user — and whenever you call an MCP endpoint — it authenticates with an OAuth 2.1 token instead. The token is still a tb_live_… bearer, but it is minted through an OAuth flow and bound to a client plus a user-approved scope ceiling. A console-issued API key is rejected on /mcp with oauth_token_required.

Telbox is a standards OAuth 2.1 authorization server: RFC 7591 dynamic client registration, PKCE-S256 mandatory, refresh-token rotation, the RFC 8628 device flow, and RFC 7009 revocation. Every endpoint lives under /v1/oauth and — like the rest of the platform — returns 404 developer_platform_disabled until apikey_auth_enabled is on. OAuth errors use a { "detail": "<code>: <reason>" } body.

Pick your flow:

  • Authorization code + PKCE (§3.2) — apps that can receive a browser redirect (web, mobile, desktop with a loopback URL).
  • Device flow (§3.3) — CLIs and headless integrations with no redirect; this is how OpenClaw authenticates.

3.1 Register a client (once)

POST /v1/oauth/register (RFC 7591) is public and returns a client_id. Public clients (token_endpoint_auth_method: "none") use PKCE and get no secret; confidential clients ("client_secret_basic") also receive a client_secret.

Register a public PKCE client
curl -X POST https://api.telbox.ai/v1/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "Acme Assistant",
    "redirect_uris": ["https://acme.example/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "token_endpoint_auth_method": "none",
    "scope": "threads:read messages:write"
  }'
# → { "client_id": "…", "client_secret": null,
#     "scope": "threads:read messages:write", … }
  • Register the grant_types you'll actually use (authorization_code, refresh_token, and/or device_code) — a device-only client still registers at least one redirect_uri (a loopback is fine).
  • A loopback redirect_uri (http://127.0.0.1:PORT/cb, RFC 8252) is allowed in dev; production requires https. client_name may not contain reserved words (telbox, official, claude, …).

Approval

software_id: "openclaw" is auto-approved. Every other client is created pending manual review, and /authorize (or the device flow) refuses an unapproved client — so register early.

3.2 Authorization code + PKCE

For an app that can catch a browser redirect.

  1. Build a PKCE pair: code_verifier = 43–128 random unreserved chars; code_challenge = base64url( SHA-256(code_verifier) ), no padding.
  2. Send the user's browser to /authorize (code_challenge_method=S256 is mandatory):
https://api.telbox.ai/v1/oauth/authorize
  ?client_id=<client_id>
  &redirect_uri=https://acme.example/callback
  &response_type=code
  &scope=threads:read%20messages:write
  &state=<opaque-random>
  &code_challenge=<challenge>
  &code_challenge_method=S256
  1. The user reviews the requested scopes and approves (or denies) inside Telbox. Telbox redirects the browser back to https://acme.example/callback?code=<code>&state=<state> — a denial comes back as ?error=access_denied&state=…. Verify state matches what you sent.
  2. Exchange the code (form-encoded) with the same code_verifier:
Exchange the code for tokens
curl -X POST https://api.telbox.ai/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  -d client_id=<client_id> \
  -d code=<code> \
  -d redirect_uri=https://acme.example/callback \
  -d code_verifier=<code_verifier>
# → { "access_token": "tb_live_…", "token_type": "Bearer", "expires_in": 3600,
#     "refresh_token": "…", "scope": "threads:read messages:write" }

A confidential client instead authenticates with -u <client_id>:<client_secret> (HTTP Basic).

3.3 Device flow (CLIs & headless)

For a CLI with no browser redirect — the OpenClaw path.

  1. Request a device + user code (form-encoded, public):
curl -X POST https://api.telbox.ai/v1/oauth/device_authorization \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d client_id=<client_id> \
  -d scope="threads:read messages:write"
# → { "device_code": "…", "user_code": "ABCD-EFGH",
#     "verification_uri": "https://telbox.ai/oauth/device",
#     "verification_uri_complete": "https://telbox.ai/oauth/device?user_code=ABCD-EFGH",
#     "expires_in": 900, "interval": 5 }
  1. Tell the user to open verification_uri and enter user_code (or open verification_uri_complete directly). They approve in the Telbox app.
  2. Poll the token endpoint every interval seconds with the device grant:
curl -X POST https://api.telbox.ai/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=urn:ietf:params:oauth:grant-type:device_code \
  -d device_code=<device_code> \
  -d client_id=<client_id>

While the user hasn't finished, this returns 400 with {"detail":"authorization_pending"} (or slow_down — then back off by the Retry-After seconds). Once approved it returns the same token body as §3.2.

OpenClaw self-configures from the manifest

OpenClaw fetches GET /v1/integrations/openclaw/manifest — a public descriptor listing the exact call order (registerdevice_authorizationdevice/decidetoken/mcp), the scope union, and the six tools — then runs the device flow above with software_id: "openclaw" (auto-approved).

3.4 Use, refresh, and revoke

Send the OAuth access_token as a Bearer to the MCP endpoints:

Call the global MCP server
curl -X POST https://api.telbox.ai/mcp \
  -H "Authorization: Bearer tb_live_…" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

The same token authenticates a published agent's own endpoint at POST /v1/agents/{agent_id}/mcp. See MCP for the tool surface, the per-agent exposed-tool subset, and the outbound broker.

  • Refresh — access tokens live 3600 s. Rotate with the refresh token (single-use; reusing a rotated token revokes the whole token family):

    curl -X POST https://api.telbox.ai/v1/oauth/token \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d grant_type=refresh_token \
      -d refresh_token=<refresh_token> \
      -d client_id=<client_id>
    
  • RevokePOST /v1/oauth/revoke (form token=<token>) always returns 200 with an empty body; it deliberately never reveals whether the token was valid.

OAuth scopes are the same vocabulary

An OAuth token carries the same scopes as an API key (§2 catalog) — the user only ever grants scopes the client registered for. An auto-approved client's ceiling excludes the high-privilege messages:read.raw and scim.

Full request/response schemas for every /v1/oauth/* endpoint (and /mcp) are in the API Reference.

Errors

A missing or malformed credential returns 401 with a machine-readable error code. Unknown scopes at key creation return 400 unknown_scopes: …; calling a first-party endpoint without a valid console session returns 401/403. See Errors and Rate Limits & Quotas.

Next steps

  • Getting Started — base URL, conventions, your first request.
  • MCP — the OAuth-gated per-agent MCP endpoint.
  • Webhooks — subscribe to events with webhooks:manage.
  • Rate Limits & Quotas — per-developer key-mutation and write budgets.