AuthenticationMenu
On this page

Authentication

Updated

Create an API key, choose its scopes and understand the actions an agent key cannot perform.

Every /v1 request authenticates with a bearer token, usually a workspace API key:

Code
Authorization: Bearer kirora_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Keys begin kirora_ followed by 32 random bytes in base64url. There is one format for every environment: no live or test variant. Use a second workspace for staging (see Staging).

The key identifies the workspace, and every query is scoped to it. A resource belonging to another workspace returns 404, never 403, so that existence is not leaked.

Credentials

Besides API keys, /v1 accepts two other bearer tokens. Each operation’s reference page shows which kinds of key can call it and the scope it needs.

Credential Used on Rules
API key, kind integration /v1 Exactly the scopes it holds
API key, kind agent /v1 and /mcp Its scopes, minus sending, scheduling and activating, contact and consent writes, events, suppressions and webhooks (see Key kinds). What it drafts needs a person to confirm it
API key, kind partner /v1, and /v1 in a child workspace with the Kirora-Workspace header For servers only: any request with an Origin header is refused with 403
Connected-app OAuth access token /v1 and /mcp Acts as an agent key, limited to what the member who approved it may do in the dashboard. A demotion narrows it at once
Embed session token es_… A short list of /v1 routes Issued by a partner key, valid for 15 minutes. Used only from the Kirora builder embedded in a page the partner key allows
Client key plus user token /client/v1 (the web SDK) Not a /v1 credential
Dashboard session The dashboard only Not a /v1 credential

The Kirora-Workspace header names one of a partner’s own workspaces by its external id. Only a partner key may send it. Any other key sending it gets 403, a partner key sent from a browser gets 403, and a workspace that is not one of the partner’s gets 404. The partner’s own workspace routes (/v1/workspaces…) always act in the key’s workspace and ignore the header.

An embed session can reach only the routes of the builder components it was issued for (the segment builder and the email builder). A session lets the chosen builders read what they need, render previews, count audiences, upload images and, when testSendTo lists addresses, send test emails to those addresses only. It never saves, publishes or sends anything else; your server saves what the builders return. testSendTo is empty by default, which hides test sending, and takes at most 5 addresses. Every other route refuses the session with 403, even one inside a scope the partner key holds.

Obtaining a key

Keys are created in the dashboard (Developer → API keys), not over the public API, so a leaked key cannot mint more keys. Only the workspace’s admins and owner can create, rotate or revoke keys.

The plaintext key is shown exactly once, at creation. Only a hash is stored, so a lost key cannot be recovered; issue a new one and revoke the old.

A key can expire. The dashboard sets new keys to expire in 90 days unless you choose another date or none; after it, requests with the key return 401. Rotate a key before it expires, from the same page.

Scopes

A key carries an explicit list of scopes, and almost every operation requires exactly one (/v1/me and /v1/webhooks/events require none). The reference shows which.

Resource Scopes
Campaigns campaigns:read, campaigns:write, campaigns:send
Contacts contacts:read, contacts:write, contacts:delete (deleting contacts and their data)
Events events:read (names and their properties), events:write (track, batch, define and describe names), events:delete (delete an event name and its events)
Attributes attributes:read, attributes:write (describe keys)
Lists lists:read, lists:write
Segments segments:read, segments:write
Templates templates:read, templates:write (templates, assets, shared blocks)
Forms forms:read, forms:write
Journeys journeys:read, journeys:write, journeys:activate
Conversion goals conversion-goals:read, conversion-goals:write — how a campaign or journey is measured
Sending domains domains:read, domains:write (sending domains; domains:read also lists senders)
Suppressions suppressions:read, suppressions:write (add, delete)
Topics topics:read, topics:write (a contact’s topic choice needs contacts:read / contacts:write)
Messaging transactional:send, messages:read (a sent message’s status and content), push:read (push projects)
Tags tags:read, tags:write (create, rename, merge, delete)
Reports reports:read — the overview, campaign, transactional, funnel and RFM reports
Webhooks webhooks:read (webhooks and their deliveries), webhooks:write (create, change, delete, test, redeliver, rotate the secret)
Partner workspaces workspaces:manage (create, change, suspend and delete them; partner keys only), embed:write (embed sessions; partner keys only)

Every :write scope includes its :read: a key with lists:write can read lists too, since every write answers with what it wrote. A read-only key is still possible. Delete and send scopes include nothing: contacts:delete and events:delete are granted on their own.

Sending is a separate scope from writing throughout: campaigns:write lets a key build a campaign but not send it, and journeys:write lets it define a journey but not activate one. Grant the narrowest set that does the job.

A contact’s sub-resource takes the scope of the data it returns: GET /v1/contacts/{id}/events needs events:read, /journeys needs journeys:read and /messages needs messages:read; a contact’s lists, segments, topics and installations need contacts:read.

Presets

For convenience a key can be created from a named preset instead of a scope list, and the preset is expanded at creation. Only the resulting scopes are stored, so a key issued today can never gain a scope added tomorrow — that is the reason presets are not stored as wildcards. A key is created with a scope list or a preset, exactly one: a key with neither is refused (400, issue at scopes), so no key holds a scope nobody chose.

Preset Scopes
read_only Every :read scope
ingestion contacts:read, contacts:write, events:write, lists:read, lists:write
transactional transactional:send, messages:read, templates:read
automation What the Zapier, Make and n8n apps use: attributes:read, contacts:read, contacts:write, events:read, events:write, lists:read, lists:write, transactional:send, webhooks:write
full_access Every scope except workspaces:manage and embed:write, including send and activate

Key kinds

This is the distinction most likely to look like a bug, so handle it explicitly.

Keys are one of three kinds. A partner key belongs to a platform that resells Kirora and acts in the workspaces it manages for its own customers. An integration key is an ordinary server-to-server credential and behaves exactly as its scopes say.

An agent key is issued to an AI agent acting on the workspace’s behalf. It reads, creates, edits and deletes campaigns, segments, lists, templates, journeys and domains like any other key, but it cannot deliver anything on its own:

  • It cannot send, schedule or activate anything. It may not hold campaigns:send, journeys:activate, or transactional:send, so POST /v1/campaigns/{id}/send, /trigger, /schedule, /ab-test/winner, /v1/journeys/{id}/activate and /v1/transactional/messages (with its batch and cancel) all return 403 AGENT_KEY_REFUSED.
  • It can stop a send. Unscheduling or cancelling a campaign needs campaigns:write, and pausing or ending a journey needs journeys:write; stopping never sends anything. Resuming a paused journey is activation, so it stays with a person.
  • It cannot mutate contacts or consent: the contacts:write routes return 403 for an agent key even when it holds that scope, and it may not hold contacts:delete. DELETE /v1/topics/{id} returns 403 too, since it deletes every contact’s choices on the topic.
  • It cannot add contacts to a list, delete them from one, or track events: either can enroll contacts in a journey that then sends. POST /v1/lists/{id}/contacts and /contacts/delete return 403 for an agent key, and it may not hold events:write. Nor may it hold events:delete, which deletes recorded events.
  • It cannot add or delete suppressed addresses: it may not hold suppressions:write.
  • It may not hold webhooks:write: a webhook subscription streams contact data to any address the key names. It may hold webhooks:read.
  • A campaign or journey an agent authored or edited is recorded and needs a human confirmation in the dashboard before it can be delivered. Until then, sending, scheduling or activating it — with any key — returns 409 AGENT_CONFIRMATION_REQUIRED.
  • A template an agent writes, or a copy of one, stays a draft until a person publishes it with a confirmation. An agent key gets 403 on POST /v1/templates/test-send, on publishing and on restoring a version, and cannot change shared blocks.
  • A form an agent creates stays a draft. An agent key gets 403 on publishForm and unpublishForm, on changing a published form, and on unarchiving one.
  • When an agent changes a segment’s rules, everything that uses the segment, directly or through other segments, goes back to waiting for a person. Its scheduled campaigns are unscheduled, its active journeys are paused, and the workspace’s admins are notified. A change to the segment’s name alone does none of this.

What an agent key can still do, because nothing in the list above stops it: delete sending domains nothing sends from (domains:write) and create message exports that include recipient addresses (messages:read). Grant those scopes only if the agent should have them.

Every refusal in this list is 403 AGENT_KEY_REFUSED, the system working, not a scope misconfiguration. Report it to users that way, not as “check your scopes”: the scopes are not the problem. A missing scope is a different code, SCOPE_MISSING.

Note also that full_access cannot be used to create an agent key: the preset includes scopes agent keys may not hold (the three send scopes, contacts:delete, suppressions:write, events:write, events:delete and webhooks:write), so creation fails validation rather than producing the 403 you were trying to test: the error names the scopes it refused, in message and details.refusedScopes. Pass explicit scopes instead.

The boundary is enforced in the API, not in any client. It applies equally to curl.

Failure modes

Status Code Meaning
401 UNAUTHORIZED Header missing or malformed, or the key is unknown, revoked or expired (with WWW-Authenticate: Bearer)
403 SCOPE_MISSING Key is valid but lacks the operation’s scope; details.requiredScope names it
403 AGENT_KEY_REFUSED An agent key on something agents may not do, whatever its scopes (see Key kinds)
403 FORBIDDEN Any other refusal: Kirora-Workspace from a non-partner key, a partner key used from a browser, an embed session off its routes
403 API_KEY_IP_NOT_ALLOWED The request came from an address outside the key’s allowedIps
403 EMBED_ORIGIN_NOT_ALLOWED An embed session was used from a page its partner key’s allowedOrigins does not list
404 NOT_FOUND Resource does not exist or belongs to another workspace

Every /v1 answer carries Cache-Control: no-store: it holds your contacts’ data, so no shared cache or browser keeps it.

Restricting a key to your servers

A key can carry allowedIps: up to 50 addresses or CIDR ranges (203.0.113.7, 10.0.0.0/8, 2001:db8::/32). Requests from anywhere else are refused with 403 API_KEY_IP_NOT_ALLOWED. Set it when creating the key, or change it later from the dashboard; an empty list allows any address. Rotation keeps the list.

Handling keys

Treat a key as a password: environment or secret manager, never in source control, never in a browser or mobile app. A key grants access to the whole workspace within its scopes.

Rotate from the dashboard, where keys are created. The new key replaces the old one. You can keep the old key working for an overlap of up to 7 days (overlapHours, 0 to 168); the default is no overlap, and the old key stops at once. Only one previous key is kept. Rotating again with an overlap while one is running keeps the older key and its original expiry; rotating with no overlap ends it.

Next steps

  • ErrorsEvery code, including 401 UNAUTHORIZED and 403 FORBIDDEN
  • Rate limitsHow much each key may call
  • AgentsKeys for AI assistants and what they may not do
  • QuickstartMake your first requests with a key