Overview
An API key authenticates a script, backend service, or other direct integration with the Platform API. Each key you create belongs to one user and one organisation, carries a read or read-write scope, and can carry an expiry. A key created before the platform required an organisation can have none, as Organisation scope describes. Create one key per integration and give it only the access that integration needs. A key is a credential of its own: a password change, a ban, an account closure or an administrator's revoke stops it, but the end of the user's sessions does not, as What stops a key lists.
Send the key as the X-Api-Key header. The platform does not accept the key as a bearer token or a query parameter, so a client that copies a bearer-token pattern from another API fails authentication. Many Platform API routes, those whose OpenAPI operation carries x-, also accept a valid Console session cookie, so a browser client that already has a session does not need a long-lived key for those routes. When a request carries an X-Api-Key header, the platform authenticates it with the key alone: a key that fails leaves the request unauthenticated even if a valid session cookie came with it.
curl "https://your-instance.example.com/api/v2/tokens" \
-H "X-Api-Key: sm_dalp_..."Some routes accept only a signed-in session. A key on such a route receives DALP-0036, HTTP 403. The private file routes under /dalp/private/* ignore an API key and answer 401, the same answer as a request with no credential, as Files describes. Make those calls with a Console session instead.
Create a key
Create a key in the Console on API keys, run dalp login for the browser device-code flow, or call POST /api/. Install and set up the command-line tool explains the dalp login flow, which asks for your wallet code and creates a read-write key for the selected organisation that expires after 90 days. The /api/auth/* routes are unversioned. The key they create authenticates against the versioned /api/v2 Platform API. The v2 API reference lists the complete request and response for each /api/auth/api-key route.
The create response is the only time the full secret value appears. Store it when you receive it, because a later read of the same key returns metadata only.
A direct create call needs a signed-in session, and a request authenticated with an API key cannot create a key. A backend service that holds only a key therefore gets its keys from the Console or dalp login. The body carries:
name, required, 1 to 32 characters. Spaces at either end are trimmed before the check, and the key stores the trimmed name. A missing name, or one of only spaces, returns 400NAME_REQUIREDand creates no key. The name is checked beforewalletVerification, so a refused name never uses up a recovery or authenticator code.metadata, required, an object with theorganizationIdof an organisation you are a member of and an optionalscope, such as{"organization.Id": "<organisation id>", "scope": "read"} GET /api/returns the ids of your organisations. Theauth/ organization/ list scopeisreadorread-write, and a key with noscopeisread-write. A call with nometadata, or withmetadatathat names no organisation, returns 400API_KEY_METADATA_INVALIDand creates no key. The platform checksmetadatabeforewalletVerification, so a call refused for itsmetadatanever uses up a recovery or authenticator code.expiresInin seconds, at most 365 days. Omit it for a key that never expires. A longer value returns 400EXPIRES_IN_IS_TOO_LARGE.walletVerification, required, the wallet step-up that also guards signing: proof that the person creating the key controls the account's wallet. Send{"wallet, withVerification": {"secret Verification Code": "<code>", "verification Type": "PINCODE"}} PINCODEfor the wallet PIN,OTPfor a wallet authenticator code,OIDC_MFAfor an identity-provider authenticator code orSECRET_CODESfor a recovery code.
An account that holds an authenticator must send its code: a PIN or recovery code returns 400 WALLET_. Without a proof the call returns 400 WALLET_STEP_UP_REQUIRED, or 401 WALLET_ when the account has no wallet factor yet. The account password is not accepted in its place. During the cooling-off period after a wallet security change the call returns 403 WALLET_STEP_UP_HELD with heldUntil and creates no key. When the deployment sets enforce2fa, an account without two-factor sign-in gets 403 LOGIN_ and no key, unless it signs in only through a configured identity provider with no password or passkey. A key would otherwise let the account reach the API without the second factor. Every new key emails the account holder.
The platform sets the rate-limit, request-budget and permission fields itself. A create or update call that sets rateLimitEnabled, rateLimitMax, rateLimitTimeWindow, remaining, refillAmount, refillInterval or permissions, or a create call that sets prefix, returns 400 SERVER_ONLY_PROPERTY.
A key is 54 characters: sm_dalp_, then a random label of 6 characters, then a random secret of 40 characters. Every character after the prefix is a letter a to z or A to Z, or a digit 0 to 9. The platform stores a digest of the full key, plus the key's first 14 characters in clear: the prefix and the label. The label is not part of the secret. GET /api/ and GET /api/ return those 14 characters to identify the key. A key issued before the label existed has a secret of 16 letters, keeps working, and is identified by the prefix alone.
Scope: read or read-write
Every key carries a scope of read or read-write. Every Platform API route declares whether it reads or writes. A read key can call routes declared as reads. On a route declared as a write, it receives DALP-0036 SESSION_POLICY_DENIED, HTTP 403, before the handler runs. The platform checks the route's declaration and ignores the HTTP method the request sends.
DALP-0036 is not a transient failure: retrying with the same key returns the same result. Issue a read-write key for that integration, or change the key's scope as Manage keys describes. A key stays bounded by its creator's permissions in the organisation. Changing a key from read to read-write does not give the creator a wider organisation role.
Organisation scope
A key carries the id of the organisation it was created for in its metadata. You can create a key only for an organisation you are a member of, and an update can move it only to another such organisation. Naming any other organisation returns 400 API_KEY_METADATA_INVALID and stores nothing. The platform reads the organisation from the key and scopes every request the key makes to it, so the caller does not repeat an organisation id on each request.
A key with no organisation is one whose metadata gives the platform no organisation to read: it is null or has no organizationId. Only a key created before the platform required an organisation can be one. Such a key works on the /api/auth routes that accept a key, where its only write is deleting itself. It is not held to one organisation there: on get-full-organization, list-members and has-permission it reads the organisation the request names, which can be any organisation its holder is a member of. A request that names no organisation gets null from get-full-organization and 400 NO_ACTIVE_ORGANIZATION from list-members and has-permission. On /api/v2 it does not authenticate: a route that needs a caller answers DALP-14386, HTTP 401. A key with no organisation keeps working when its holder leaves an organisation. A ban, an account closure or an administrator's revoke stops it, and a password change turns it off until its holder turns it on again, as What stops a key lists.
A key whose metadata names an organisation but has a scope other than read or read-write was created before the platform checked the scope. It has an organisation: on the /api/auth routes that accept a key it is held to the organisation its metadata names, like any other key, so naming another organisation on get-full-organization, list-members or has-permission answers 403 YOU_, and GET /api/ and GET /api/ return only that organisation and your keys for it. On /api/v2 it does not authenticate: a route that needs a caller answers DALP-14386, HTTP 401. Set its scope to read or read-write with an update, as Manage keys describes, to make it work on /api/v2.
The API keys screen in the Console does not list a key whose metadata names no organisation, and it lists one with an unknown scope under the organisation its metadata names, as Read. Its holder finds a key with no organisation with GET /api/ from a signed-in session, which returns its metadata as null or without an organizationId. The holder deletes it with POST /api/, from that session or from the key itself with its id as keyId. To keep an integration that uses it working, create a key for the integration's organisation and move the integration to the new key before you delete the old one, as Store and rotate a key describes. On Platform users, a platform administrator sees it in the holder's “API keys” section with a dash under Organisation, and a key with an unknown scope shows a dash there too. Revoke all API keys there deletes it with the holder's other keys.
API keys and delegated authority
An API key identifies the caller. Delegated Authority lets an entitled caller act in another participant's context. An API-key request can send X-Participant, the header that names the participant to act for, when the key's user holds the required authority. The key does not grant that authority by itself.
Without that header, an API-key request uses the acting participant selected for its user in the key's organisation: the person, organisation or account record whose authority the user acts with. After you select another acting participant, requests with the same key use that selection within 30 seconds.
What stops a key
A key is a credential of its own, separate from the user's sign-in. Most actions that end a person's sessions leave the person's keys working. Each Platform API server keeps a key's resolved identity for up to 30 seconds, so a change that stops a key can take that long to reach every server, or 35 seconds for a change to the holder's memberships.
A write that a key submits for maker-checker approval runs as the key's holder once it is approved. If by then the key is revoked, turned off, expired or read-only, or its holder is banned or no longer a member of the organisation, the approved write does not run.
Manage keys
Create and update need a signed-in session. List and read one also accept an API key. An update that only renames a key or sets enabled to false needs nothing more, even during the cooling-off period. Any other update, such as enabling a key or changing its scope, organisation or expiry, needs the same walletVerification block as create, is refused with the same errors, and emails the account holder. An update that sends no metadata keeps the key's organisation and scope. An update can set a new expiry of up to 365 days from the update, or null to remove the expiry. It can also enable a key again. Extend a key before it expires: the platform deletes the records of expired keys as keys are listed, read, created, updated or deleted, so an expired key soon has no record to update. For a key that has already expired, issue a new key.
Delete accepts a signed-in session, which can delete any key its user holds, or an API key that names itself as keyId. An API key that names any other key gets 403 API_, and that other key keeps working. No user can delete another user's key.
Turning a key off is the reversible way to retire it. The record survives and the secret stops working, and enabling it again restores the same key, so the integration resumes without a redeploy. Deleting is permanent, and the right step once you know the secret has leaked.
The server that handles the change drops its copy of the key's identity at once, and every other server reads the record again when its copy runs out, so nothing has to be restarted for the cut-off to hold. A key turned off or deleted fails with DALP-14386 no later than 30 seconds after the change lands.
Read a key's own record before you conclude that an integration lost its secret. The caller sees the same DALP-14386 when a key is expired, turned off or unknown, has no organisation or an unknown scope, or has a holder who left its organisation. A turned-off key's record shows enabled: false, and a key with no organisation shows metadata that is null or has no organizationId, and a key with an unknown scope shows a scope other than read or read-write. An expired key's record is deleted soon after it expires, so a key that GET /api/ does not return has expired or been deleted.
API keys on /api/auth routes
The /api/auth routes accept an API key on these nine routes only:
GET /api/auth/ get- session GET /api/auth/ organization/ list GET /api/auth/ organization/ get- full- organization GET /api/auth/ organization/ list- members POST /api/auth/ organization/ has- permission GET /api/auth/ api- key/ list GET /api/auth/ api- key/ get POST /api/, for the presenting key onlyauth/ api- key/ delete GET /api/auth/ passkey/ list- user- passkeys
A key reads only the organisation it was created for on these routes. GET /api/ returns that one organisation, and GET /api/ returns only your keys for that organisation, with total, limit and offset counting that list. A signed-in session still lists all of your organisations and keys.
A key on any other /api/auth route receives API_, HTTP 403, before the route runs. The refusal covers routes that need no sign-in, POST /api/, and any path the v2 API reference does not list. The same request with a signed-in Console session still works. A key therefore cannot create or update a key, approve a device sign-in, register a passkey or sign in. The API reference lists eight of the nine routes. It leaves out GET /api/, which accepts a key, because GET /api/ lists the same members.
On get-full-organization, list-members and has-permission, a key with an organisation reads only that organisation. A request that names another organisation by organizationId, or by organizationSlug on the two GET routes, receives YOU_, HTTP 403, even when the key's holder is a member of that organisation. A request that names no organisation reads the key's organisation. A key with no organisation reads any organisation its holder is a member of that the request names, as Organisation scope describes.
Rate limit
A key that the platform creates is not rate limited. Each key stores whether its limit is on, the length of its window, and how many requests that window admits. A key created through the Console, dalp login or POST /api/ stores a 60-second window that admits 10,000 requests, with the limit off. No API call switches the limit on, so limit an integration by its scope and expiry instead. Only a key whose record has the limit on is counted. Read the record with GET /api/: rateLimitEnabled says whether the limit is on, rateLimitMax and rateLimitTimeWindow give the budget and the window in milliseconds, lastRequest shows the last accepted request, and requestCount counts the requests in the current window only while the limit is on.
The count belongs to that key alone. Separate keys for separate integrations get separate budgets rather than sharing one ceiling across the organisation or the user.
The count resets only after a full window passes with no accepted request, so continuous traffic does not receive a fresh budget every minute. A refused request is not counted and does not extend the window.
A request over the key's limit receives HTTP 429 with this body:
{
"code": "auth.apikey.THROTTLED",
"status": 429,
"message": "This API key filled its request window",
"data": {
"id": "DALP-12136",
"why": "This key used its full request allowance for the current window, so the platform did not authenticate the request.",
"fix": "Wait for the key's request window to reopen, then send the request again.",
"retryable": false,
"category": "permission"
}
}The body sets retryable to false because an immediate retry meets the same refusal, not because the key is spent. The response carries no Retry-After header. Stop sending requests with that key, then retry once the window has reopened. In the key's record, lastRequest plus rateLimitTimeWindow marks when that happens.
The platform counts requests in memory on each API server and writes the count to the database in batches, so a request does not wait for a database write. With a limit of N requests, a key admits at most N requests plus 50 for each server beyond the first. Each server can admit up to 50 requests beyond its share before it reconciles with the stored count, and a single server enforces N exactly.
The key's last-used time updates within about a second of an accepted request. It records when the platform wrote the batch, so it can trail the request by up to about a second.
Store and rotate a key
Store the secret value in a secrets manager or environment variable, not in source control or a shared document. Rotate a key by creating a replacement, updating your integration to use it, then deleting the old key once the new one works. The platform does not roll a key's secret value in place: delete and recreate instead.
Error codes
Errors a request authenticated with an API key can receive on /api/v2:
Errors the key management routes under /api/auth return:
These are the auth layer's own codes. They come from the routes under /api/auth and carry no DALP-#### id, so match on the code string. Platform API routes answer with the catalogued DALP- codes, whose full entries and recovery copy are on the DALP error index.
Next steps
- Pagination covers listing resources once you can authenticate.
- Errors covers the full error envelope and how to branch your integration on it.
- Install and set up the command-line tool covers
dalp login, which creates aread-writekey through a browser device-code flow instead of a direct API call.