Authentication

Authenticate every request with a single API key — plus permissions, inbox scoping, and the whoami identity check.

Every request to the InboxAgents API authenticates with a single API key. There is no OAuth flow: create a key, send it on each request, done.

Sending your key

Send the key as a bearer credential in the Authorization header, or as the x-api-key header — either works, pick one.

# Preferred: bearer header
curl https://app.theinboxagents.com/api/v1/auth/me \
  -H "Authorization: Bearer $INBOXAGENTS_API_KEY"

# Alternative: x-api-key header
curl https://app.theinboxagents.com/api/v1/auth/me \
  -H "x-api-key: $INBOXAGENTS_API_KEY"
await fetch("https://app.theinboxagents.com/api/v1/auth/me", {
  headers: { Authorization: `Bearer ${process.env.INBOXAGENTS_API_KEY}` },
});
import os, requests

requests.get(
    "https://app.theinboxagents.com/api/v1/auth/me",
    headers={"Authorization": f"Bearer {os.environ['INBOXAGENTS_API_KEY']}"},
)
# The CLI reads INBOXAGENTS_API_KEY, or pass --api-key
inboxagents whoami
inboxagents whoami --api-key ia_your_secret_here

Keep your key out of source control

The convention throughout these docs, the SDKs, and the CLI is to store the key in the INBOXAGENTS_API_KEY environment variable and never commit it. Treat the secret like a password — anyone holding it can act as your tenant within the key's scope.

The WebSocket surface is the one exception to the header rule: it takes the key as a query parameter, since browsers can't set headers on a WebSocket handshake.

The key secret

A key's full secret is an ia_… value. It is shown exactly once, in the response to creating the key (or to sign_up). It is never stored in a retrievable form and can never be fetched again — so store it the moment you get it.

Afterward, listings show only a non-secret prefix (e.g. ia_3f9a2b1c), so you can tell keys apart without the secret being recoverable.

Rotation is create-a-new-key-then-delete-the-old — there is no rotate verb. Revocation is deleting the key; it stops working immediately on every surface (REST, SDK, CLI, WebSocket).

Permissions

A key is either full-access or restricted to a whitelist:

  • Create a key with no permissions and it has full access.
  • Supply a permissions object and the key switches to whitelist mode — only the flags set to true are granted; everything else is denied.
A read-only key
curl -X POST https://app.theinboxagents.com/api/v1/api-keys \
  -H "Authorization: Bearer $INBOXAGENTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "read-only agent",
    "permissions": {
      "inbox_read": true,
      "thread_read": true,
      "message_read": true
    }
  }'

A restricted key cannot mint a child key with more permissions or a broader scope than itself — privilege escalation is rejected with a 403.

v1 permission flags

Flags are CRUD-per-resource. Attachment reads ride on message_read.

FlagGrants
inbox_readList and get inboxes
inbox_createCreate inboxes
inbox_updateUpdate inboxes
inbox_deleteDelete inboxes
thread_readList and get threads
message_readGet messages and download attachments
label_spam_readInclude spam-labeled threads in reads
webhook_readList and get webhooks
webhook_createCreate webhooks
webhook_updateUpdate webhooks
webhook_deleteDelete webhooks
metrics_readRead /metrics/usage
api_key_readList and get API keys
api_key_createCreate API keys
api_key_deleteDelete (revoke) API keys

Scoping a key to one inbox

Create a key against a specific inbox (POST /v1/inboxes/{inbox_id}/api-keys) and it is scoped to that inbox — it can only operate within that inbox, no matter what permissions it carries. Use scoped keys to isolate blast radius: one key per agent or per environment.

If the scoped inbox is later deleted, the key's calls to it return the usual not-found.

Checking a key: whoami

Two endpoints answer "is my key valid, and what can it do?":

  • GET /v1/whoami — the lightweight check: resolves the key to a tenant, or returns 401 if it's missing or invalid.
  • GET /v1/auth/me — the fuller identity probe: returns the key's api_key_id, tenant_id, scope_type (organization or inbox), scope_id, and — for an inbox-scoped key — its inbox_id. Identity and scope only; it never returns plan or limits (see /metrics/usage for usage).
GET /v1/auth/me — organization-scoped key
{
  "api_key_id": "key_a1b2c3d4",
  "tenant_id": "b7e3…",
  "scope_type": "organization",
  "scope_id": "b7e3…"
}
GET /v1/auth/me — inbox-scoped key
{
  "api_key_id": "key_9f8e7d6c",
  "tenant_id": "b7e3…",
  "scope_type": "inbox",
  "scope_id": "inb_8f2a1c9d",
  "inbox_id": "inb_8f2a1c9d"
}

Auth errors

Authentication and authorization fail differently, and the status tells you which:

StatusError nameMeansFix
401unauthenticatedThe key is missing, malformed, revoked, or unknown.Send a valid, active key.
403forbiddenThe key is valid but lacks the required permission, is scoped to a different inbox, or the tenant hit a plan limit.Grant the permission, use an in-scope key, or upgrade.

A 401 never leaks whether other keys exist. A 403 is distinct from a 401 on purpose: it tells you the key is fine but this action isn't allowed — a permissions problem, not a bad key. See Errors for the full envelope and error catalog.

On this page