API keys & authentication
Create a Dairo API key, scope it to least privilege, and use it across the API, the SDKs, the CLI, and the MCP server.
Every request to Dairo authenticates with an API key, sent as a bearer token
in the Authorization header. One key works across the REST API, the SDKs,
the CLI, and the MCP server.
The fastest way to confirm a key works is whoami — it returns your userId,
your plan, the key's scopes as apiKey.scopes, and your current usage. It
answers for any valid key, whatever its scopes.
Create a key
API keys are long-lived credentials for server-side code, CI, agents, and the
CLI. Create one from the dashboard, or over the API with a key that carries
keys:write.
The secret (dairo_live_…) is shown once, at creation, and never again.
Capture it immediately. If you lose it, revoke the key and create a new one.
List and revoke keys whenever you need to:
Lock a key to trusted IPs
Pass allowedIps at creation — up to 50 IPv4/IPv6 addresses or CIDR ranges —
to bind a key to specific source addresses. Enforcement is deny-by-default:
the correct secret from an unlisted IP is rejected with a 403. See
Audit logs for the full
setup and lockout recovery.
Restrict a key to specific inboxes
Pass inboxIds at creation to pin a key to some of your inboxes. A pinned key
sends only as those inboxes, and its mailbox reads — messages, threads,
attachments, delivery events, and contact history — only see their mail. Its
inbox calls see only those inboxes too: listing inboxes returns just them, and
updating or deleting any other inbox of the organization, or opening a
verification wait on it, answers 404. It is also refused (403) every change
that spans the whole organization: adding or changing domains and webhooks,
creating inboxes, creating, deleting, or binding agent passports and clearing an
agent's breaker (DELETE /v1/agents/{id}/reputation), budget changes, buying,
updating, or releasing phone numbers, Slack app changes, the organization's
vanity slug, and creating or deleting organizations; reading or exporting the
audit log and subject erasure jobs are refused too. Omit inboxIds (or pass
[]) for a key that works across every inbox.
Change the restriction later with PATCH /v1/api-keys/{id} and a new inboxIds
array ([] lifts it). A pinned key only creates keys pinned to its own inboxes,
and it lists, changes, and revokes only keys pinned within them: itself and any
other key pinned only to inboxes it holds, whoever created that key. Every other
key, including an unpinned one, answers 404, and a pinned key can never set a
restriction wider than its own inboxes. Unknown body fields are
rejected with a 400, so a misspelled restriction never mints an unrestricted
key. whoami reports a key's inboxIds.
Scope every key to what it needs
Each key carries a set of scopes that decide what it can do. Every resource
owns its own read/write pair, and a read is never gated behind a write
scope. The ones you'll reach for first:
| Scope | Grants |
|---|---|
messages:read | Read messages, threads, attachments, and delivery events. |
messages:send | Send, schedule, and cancel outbound messages. |
inboxes:read / inboxes:write | Read / create, update, and delete inboxes. |
contacts:read / contacts:write | Read / create, update, and delete contacts and their handles. |
domains:read / domains:write | Read / add, verify, update, and delete domains. |
webhooks:read / webhooks:write | Read / create, update, and delete webhooks. |
templates:read / templates:write | Read / author and version templates. |
audiences:read / audiences:write | Read / create and add members to audiences (a broadcast send needs messages:send). |
keys:read / keys:write | Read / create, update, and revoke API keys. |
Bundles save you the enumeration: request messages and it expands to
messages:read + messages:send; letters and phone bundle the same way,
and admin expands to every scope. See Permissions & scopes
for the full list and the exact scope each endpoint needs.
Grant the narrowest set a workload needs. Creating a key takes keys:write, and
a key can only mint child keys whose scopes are a subset of its own, so no key
can create one with broader access than it holds.
Scopes say what a key may do; the current role of the person who created it says
whether it may change the organization's settings. Creating, changing, or deleting
domains, webhooks, and API keys, budget changes, buying, updating, or releasing
phone numbers, Slack app changes, the organization's vanity slug, reading or
exporting the audit log, and listing, reading, or enqueuing erasure jobs need a
key whose creator is an organization owner or admin; a member's key gets
a 403 there, whatever its scopes.
The same role sets what a new key may hold. POST /v1/api-keys with a
projectId mints the key into that organization with only the scopes your role
there allows: a member cannot give it domains:write, webhooks:write,
keys:write, account:write, phone:write, budgets:write, or
compliance:read, and the request answers 403 naming the scopes to leave out.
Use your key from any surface
The Python SDK reads DAIRO_API_KEY from the environment, or takes the key
explicitly:
The JavaScript SDK never reads the environment on its own — so browser and edge runtimes stay predictable — and the other SDKs also take the key explicitly:
The Python SDK reads DAIRO_API_KEY from the environment, or takes the key
explicitly:
The JavaScript SDK never reads the environment on its own — so browser and edge runtimes stay predictable — and the other SDKs also take the key explicitly:
The CLI reads DAIRO_API_KEY first, then the local config. To store a key
in the config, pipe it to dairo auth token set — the token is read from
stdin only, never a positional argument, so your secret stays out of shell
history and process listings:
In CI, prefer the environment variable.
The hosted MCP endpoint is https://mcp.dairo.app/mcp. It authenticates
with the same bearer key and advertises typed tool schemas, so an agent
discovers required fields before it calls:
See the MCP server guide for full setup.
Handle a failed request
Auth failures — and every other error — come back in one shape:
{ "error": { "type", "code", "message", "param" } }. Branch on the stable
machine code, never on the prose message. See Errors
for the full code list.
Verify webhook signatures
Webhook deliveries are signed so you can confirm they came from Dairo and are not replayed. Verify the signature over the timestamp, event ID, and raw request body, and check the timestamp — see Verify the signature.
Keep your secrets safe
- Store keys in a secret manager — Vault, Doppler, AWS Secrets Manager, or your host's environment config. Never commit a key or ship one in a client-side bundle.
- Keep keys out of URLs and logs. Keys belong in the
Authorizationheader, never a query string. Scrub them from logs, screenshots, and error reports. - Rotate after tests and on suspicion. Revoke keys used for temporary tests, and rotate immediately if a key may have been exposed.
- Use one key per workload. A separate key per service makes revocation surgical and usage traceable.