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_hereKeep 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
permissionsand it has full access. - Supply a
permissionsobject and the key switches to whitelist mode — only the flags set totrueare granted; everything else is denied.
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.
| Flag | Grants |
|---|---|
inbox_read | List and get inboxes |
inbox_create | Create inboxes |
inbox_update | Update inboxes |
inbox_delete | Delete inboxes |
thread_read | List and get threads |
message_read | Get messages and download attachments |
label_spam_read | Include spam-labeled threads in reads |
webhook_read | List and get webhooks |
webhook_create | Create webhooks |
webhook_update | Update webhooks |
webhook_delete | Delete webhooks |
metrics_read | Read /metrics/usage |
api_key_read | List and get API keys |
api_key_create | Create API keys |
api_key_delete | Delete (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 returns401if it's missing or invalid.GET /v1/auth/me— the fuller identity probe: returns the key'sapi_key_id,tenant_id,scope_type(organizationorinbox),scope_id, and — for an inbox-scoped key — itsinbox_id. Identity and scope only; it never returns plan or limits (see/metrics/usagefor usage).
{
"api_key_id": "key_a1b2c3d4",
"tenant_id": "b7e3…",
"scope_type": "organization",
"scope_id": "b7e3…"
}{
"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:
| Status | Error name | Means | Fix |
|---|---|---|---|
401 | unauthenticated | The key is missing, malformed, revoked, or unknown. | Send a valid, active key. |
403 | forbidden | The 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.