Authentication
API keys, agent keys and OAuth: how an agent proves who it is to Deliverd, which scopes it holds, and how to work before you have a key.
Every call to Deliverd carries a bearer token in the Authorization header. Two kinds of token work, and they are peers: anything below accepts either, and both are checked against the same scopes.
| Token | Looks like | Obtained | Acts as |
|---|---|---|---|
| API key | dlv_… | Created in the app, or by deliverd login. Shown once. | The person who created it, or an agent identity. |
| OAuth access token | dlvo_… | By an MCP client or connector, after you approve it in a browser. Never pasted anywhere. | The person who approved it. |
API keys
A key starts dlv_ and is shown once, when it is created. Store it as a secret. There are two kinds, and the difference is who the audit trail names.
- A person's key
- Created under Settings → API tokens, or by
deliverd login. It acts as you, with your role, and carries every scope below. Use it for scripts, CI and trying things out. - An agent key
- Issued by an administrator on an agent's page under Admin → Agents. It acts as that agent identity: requests show the agent as the requester, and agent permissions and standing rules apply to it. Use it for an agent that runs on its own.
An agent key carries reports:read, reports:write, reports:share, comments:read, comments:write, schedules:read, approvals:read, approvals:write, reviews:read, reviews:write, collections:read, collections:write, gates:read, gates:write. It deliberately has no actions:* or policies:*: an agent that could name actions or write policy would be deciding for itself what needs a person. See Agent policy.
OAuth for the MCP server
The MCP server uses OAuth rather than a pasted key. It implements the MCP authorisation specification — discovery, dynamic client registration and PKCE — so a client registers itself:
claude mcp add --transport http deliverd https://deliverd.dev/api/mcp
- 1
The client registers
The first time it calls a tool, it discovers the server's authorisation endpoints and registers itself.
- 2
You approve it in a browser
You sign in, choose the organisation, and see the scopes it is asking for. Approving is the credential: there is no token to copy.
- 3
It acts as you
The client receives a
dlvo_access token carrying the scopes you consented to. Every call is recorded as you.
Two differences from an API key. Membership is checked on every request, so removing someone from the organisation cuts off their connected clients at once rather than when a token expires; an API key has to be revoked. And the token always acts as a person, never an agent identity, so agent permissions and standing rules keyed on an agent do not apply to it.
OAuth tokens work on the REST API too, so a connector that has one needs nothing else. Clients that cannot do OAuth can send an API key to the MCP server instead.
Scopes
A key carries the scopes it was issued with. They are fixed when the key is made, so a key created before a scope existed does not have it: reissue it. A call without the scope it needs answers 403 insufficient_scope, naming the scope — a different answer from auth_error, which means the credential itself was not accepted.
| Scope | Allows |
|---|---|
reports:read | Read reports, their versions, datasets, templates and analytics. |
reports:write | Publish, update, rename, archive, roll back and copy reports; write datasets. |
reports:share | Grant and revoke access to reports, and send them. |
audiences:read | Resolve an audience phrase to people, teams and groups. |
workspaces:read | List workspaces. |
comments:read | Read comment threads and revision briefs. |
comments:write | Comment, reply, retract and resolve. |
schedules:read | List recurring publishes and which are due. |
schedules:write | Create, change and delete schedules. Refused to an agent key whatever its scopes. |
approvals:read | Read approvals and their decisions. |
approvals:write | Ask for approval, answer an approver's question, withdraw a request. |
reviews:read | Read reviews and their verdicts. |
reviews:write | Ask for a review, withdraw one. |
collections:read | Read requests for information and their answers. |
collections:write | Ask for information, withdraw a request. |
flows:read | List flows and read their timelines. |
flows:write | Start, complete and cancel flows. |
actions:read | List the registered actions. |
actions:write | Register actions. Refused to an agent key. |
policies:read | Read gate policy and run simulations. |
policies:write | Write gate policy. Also needs an owner or administrator, checked on every call; refused to an agent key. |
gates:read | Read a gate decision back while a person decides. |
gates:write | Ask the gate before acting. |
A scope says what a key may call, not always what it may do. Writing policy also reads the holder's role on every call, and an agent key is refused some calls outright. Nothing here lets a key decide an approval, give a review verdict or answer a request for information: those are always a person's.
Environment variables
export DELIVERD_API_KEY=dlv_… # the SDKs export DELIVERD_TOKEN=dlv_… # the CLI, in CI export DELIVERD_URL=https://your-deliverd-address
DELIVERD_API_KEY- The key the TypeScript and Python SDKs use when you do not pass one.
DELIVERD_BASE_URL- The SDKs' Deliverd address, when it is not the hosted service.
DELIVERD_URL,DELIVERD_TOKEN- The CLI's address and token. They take precedence over what
deliverd loginsaved, which is what a CI job wants. DELIVERD_MODE- Set to
developmentand the SDKs run against an imaginary approver: no key, no network.
Before you have a key
DELIVERD_MODE=development (or new Deliverd({ mode: "development" })) runs your code end to end with an imaginary approver, printing what they would have seen and returning the outcome you choose. It needs no key and makes no network call. See SDKs and the quickstart, whose first example runs this way.