Auth & scopes
Every call to the Kaleidr platform API carries one credential. There are three kinds, and the API treats each differently:
- A publishable key identifies your organization in a browser. It cannot call the API by itself; it is exchanged for a session.
- A session token is what a browser actually calls the API with. It is short-lived and bound to one website origin and one product.
- A server key is a secret your backend sends directly on each request.
This page covers how each is sent, what the exchange looks like, what scopes unlock, how the vendor scope is
granted, and every authentication error with its cause and fix. For what each credential can do if it leaks, see
the security model.
Credential types
| Credential | Prefix | Where it lives | How it is used | Plan |
|---|---|---|---|---|
| Publishable key | kld_pk_live_… | Page source | Sent once to the session exchange; refused on every other endpoint | Free, Pro, Enterprise |
| Session token | kld_sess_live_… | Browser memory | Sent on every runtime call from the page it was minted for | Minted from a publishable key or a published map |
| Server key | kld_sk_live_… | Your backend only | Sent on every runtime call; refused when the request carries an Origin header | Pro, Enterprise |
| Legacy key | kld_live_… | Your backend | Works as a direct credential, like a server key. Rotating one produces a server key | Existing keys only |
Keys minted through the key management API with environment: "test" carry test in
place of live (kld_pk_test_…, kld_sk_test_…). A test key cannot load basemap tiles.
A key is shown in full exactly once, when it is created or rotated. Kaleidr stores only a hash, so a lost key cannot be recovered: rotate it.
Sending a credential
Send the credential in either header. Both are accepted on every endpoint.
Authorization: Bearer kld_sk_live_…
X-Api-Key: kld_sk_live_…
If a request carries both, Authorization is used when its value starts with a Kaleidr prefix; otherwise
X-Api-Key is read. Never put a server key in a URL.
What each credential may do on a runtime endpoint (everything except the session exchange):
| Credential | Result on a runtime endpoint |
|---|---|
| Publishable key | 401 publishable_requires_session. Exchange it first. |
Server key, no Origin header | Accepted, then checked for IP allowlist, scope, allowed products, quota and rate limit. |
Server key with an Origin header | 403 server_key_in_browser. Browsers add Origin to cross-site requests, so this is how the API recognizes a server key in a web page. |
| Session token from the origin it was minted for | Accepted. |
Session token with a different Origin | 403 session_origin_mismatch. |
Session token from a published map with no Origin | 403 session_origin_mismatch. |
The JavaScript SDK enforces the same rule earlier: Kaleidr.mount refuses a kld_sk_… key in the browser
without making a request.
Session exchange
A publishable key is traded for a session token at one endpoint. The SDK does this for every keyed mount and keeps the token fresh; you only call it yourself when you build your own browser client.
POST https://api.kaleidr.com/inference-api/b2b/v1/sdk/sessions
X-Api-Key: kld_pk_live_…
Content-Type: application/json
{ "product": "chat" }
product is required and is one of chat, editor, viewer or tile. The request must carry an Origin
header. A browser adds it for you.
{
"session_token": "kld_sess_live_eyJhbGciOi…",
"expires_in": 900,
"product": "chat",
"basemap_style_id": null
}
| Field | Meaning |
|---|---|
session_token | The credential for runtime calls. Format kld_sess_{env}_{token}. |
expires_in | Lifetime in seconds. A keyed session lasts 900 seconds. Read this field; do not hard-code the number. |
product | The product the session is bound to. |
basemap_style_id | Your organization's default basemap, or null when none is set. |
What a session is bound to
- One origin. The
Originof the exchange request. A call from any other origin is refused. - One product. A
tilesession cannot call chat endpoints. - A narrowed set of scopes. The session carries only the scope its product needs:
aifor chat,designfor editor,mapsfor tile, none for viewer. A chat session also keepsvendorwhen the key holds it and your plan admits it (Enterprise). No other product's session ever carriesvendor.
Refreshing
A client cannot ask for a longer lifetime. Exchange again before expires_in runs out. The SDK refreshes 90
seconds before expiry, when a background tab becomes visible with the session within 90 seconds of expiry or
past it, and every 30 seconds after a failed refresh. If the very first exchange fails, the mount fails and is not retried.
Each exchange for product: "tile" uses one map load from your organization's allowance; other products cost
nothing to exchange. The exchange has its own rate limit per key, separate from runtime calls. See
Quota and rate limits.
Order of checks
The exchange stops at the first check that fails:
| Order | Check | Failure |
|---|---|---|
| 1 | The key is valid | 401 Invalid or missing API key |
| 2 | The key's exchange rate limit | 429 rate_limited |
| 3 | The key is a publishable key | 403 publishable_key_required |
| 4 | The request has an Origin header | 403 origin_required |
| 5 | The origin is in the key's allowed origins, if the key has any | 403 origin_not_allowed |
| 6 | The key is not restricted away from this product | 403 product_not_allowed |
| 7 | Your plan includes the product | 403 tier_capability_not_allowed |
| 8 | The key holds the scope the product needs | 403 insufficient_scope, or tier_capability_not_allowed if the key holds it but your plan no longer includes it |
| 9 | For tile: a map load is available | 429 map_load_quota_exceeded |
Trying it from a terminal
curl -s https://api.kaleidr.com/inference-api/b2b/v1/sdk/sessions \
-H "X-Api-Key: kld_pk_live_…" \
-H "Origin: https://www.example.com" \
-H "Content-Type: application/json" \
-d '{"product":"chat"}'
This works because Origin is an ordinary header that any non-browser client can set. That is the reason the
allowed-origins list is described as a browser control and not as a secret:
see CORS and allowed origins.
Sessions for a published map
A map published from Studio whose Kaleidr AI tile turns on Control Tower or Popup Window can provide chat to its visitors with no key in the page. The page exchanges the map's public id, and the feature it opens, instead:
POST https://api.kaleidr.com/inference-api/b2b/v1/sdk/share-sessions
Content-Type: application/json
{ "share_id": "your-map-id", "feature": "control_tower" }
{ "session_token": "kld_sess_live_eyJhbGciOi…", "expires_in": 300, "product": "chat" }
This session is chat only, carries the ai scope, lasts 300 seconds, and is charged to the map owner's
organization. It never carries vendor. It must be used from the page that requested it: a call with no
Origin, or a different one, is refused. The refusals are:
| Status | Code | Cause |
|---|---|---|
| 403 | origin_required | The request has no Origin header. |
| 404 | not_found | No published map or share link has this id, or the share link's map is unpublished. |
| 403 | chat_disabled | The map's Kaleidr AI does not turn on the feature asked for. |
| 403 | origin_not_allowed | The page is not in the map's allowed domains. |
| 403 | owner_has_no_organization | The map's owner has no organization to charge. |
| 403 | tier_capability_not_allowed | The owner's plan does not include chat. |
| 429 | rate_limited | Too many exchanges for this map or from this address. |
Setting it up is covered in Kaleidr AI on a published map.
Scopes
A key carries scopes. Each endpoint family needs one.
| Scope | Unlocks | Needed by |
|---|---|---|
ai | /chat/* and /retrieval/* endpoints | Chat, and any backend that calls the chat or lookup endpoints |
design | /design/* endpoints | Editor sessions, and any backend that calls the design endpoints |
maps | Basemap sessions | Tile |
vendor | Nothing by itself. It lets chat answers draw on your organization's own published data. | Chat, when you have uploaded data |
A new key gets ai, maps and design unless you ask for something else. On the Free plan a key always carries
the whole Free surface: exactly those three scopes, and all four products (chat, editor, viewer, tile),
whichever of them the request names. vendor is never a default, and only an Enterprise
organization can create a key that carries it.
A request to create a key that names a scope or a product your plan does not admit is refused with 403
tier_capability_not_allowed. Nothing is dropped from a request without an error. In practice that is a
request for vendor on Free or Pro.
Scopes are fixed when the key is created. No endpoint and no setting edits them afterward, and a rotated key inherits them, minus any scope your current plan no longer admits. To change a key's scopes, create a new key and revoke the old one.
Rotation on Free follows the Free rule. On the Free plan the replacement of a rotated publishable key
carries the whole Free surface (ai, maps, design, and all four products), whatever the old key held. A
server key or a legacy key cannot be rotated on Free: the request is refused with 403
free_plan_publishable_only.
What a key can do is the smaller of the key and the plan
Every request checks the key's stored scopes and products against what your organization's plan includes today. Changing plan never edits a key, but it does change what the key can do:
insufficient_scope: the key was never given the scope. Fix: use a key that has it.tier_capability_not_allowed: the key has it, but your current plan does not include it. Fix: change plan.
vendor is the exception to the second line, because it opens no endpoint. A key that stores vendor on a
plan below Enterprise is not refused: its requests succeed, and its answers are built without your data.
A key with an empty product list is allowed every product your plan includes. A key with no scopes is refused everywhere.
A product limit applies to every key
allowed_products limits a key to some of chat, editor, viewer and tile. The limit is enforced for
every key type, at the point where that key is used:
| Key | Where the limit is checked | Refusal |
|---|---|---|
| Publishable key | At the session exchange, against the product requested | 403 product_not_allowed |
| Server key or legacy key | On every runtime request, against the product the endpoint belongs to | 403 product_not_allowed |
Each endpoint family belongs to the product whose scope it needs:
| Endpoints | Scope | Product a limited key must allow |
|---|---|---|
/chat/*, /retrieval/* | ai | chat |
/design/* | design | editor |
So a server key created with "allowed_products": ["tile"] is refused on /chat/control/stream with 403
product_not_allowed, even though it carries the ai scope. A server key limited to viewer alone can call no
runtime endpoint, because a published map needs no key. Leave the list empty when a key should reach every
endpoint its scopes allow.
A session token is not checked again here: it was bound to its one product when it was exchanged.
| Free | Pro | Enterprise | |
|---|---|---|---|
| Publishable keys | Yes | Yes | Yes |
| Server keys | No | Yes | Yes |
ai, maps, design | Yes | Yes | Yes |
vendor | No | No | Yes |
Pro adds server keys and a higher limit on active keys to what Free admits. The full plan table lives in Get an API key.
The vendor scope
vendor does not open an endpoint. It decides whether a chat answer made with this key may draw on your
organization's own published data, alongside Kaleidr's general knowledge. The whole path, from upload to
answer, is in Your data: overview.
Who can use it:
- Free and Pro cannot hold
vendor. A request to create a key with it is refused with 403tier_capability_not_allowed, whether or notvendor_acknowledgedis sent. No key is created. - Enterprise can create
vendorkeys.
The scope is about reading the data through a key. Uploading the data is a separate permission, held by an owner or admin of the organization and not by any key, and it is Enterprise only as well: Free and Pro cannot upload business data. The files a signed-in user adds to a map in Kaleidr Studio are a different feature, available on every plan, and no scope reads them: see Datasets on a map.
The API Keys page cannot grant vendor. Create the key through the key management API:
curl -s https://api.kaleidr.com/shared-api/api-keys \
-H "Authorization: Bearer $KALEIDR_ACCOUNT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "store-locator-chat",
"key_type": "publishable",
"scopes": ["ai", "vendor"],
"vendor_acknowledged": true,
"allowed_origins": ["https://www.example.com"]
}'
vendor_acknowledged: true is required for a publishable key that carries vendor. Without it the request
fails with 422 vendor_scope_requires_acknowledgement. The field exists because a publishable key sits in
public page source, so data that key can read is readable by anyone who visits the page. A server key needs
no acknowledgement.
Before you put vendor on a publishable key:
- Use one key per website. Only the site that must answer from your data gets
vendor. - Treat everything in your published data as public on that site. Do not publish cost prices, unreleased rates or anything you would not print on the page.
- Remember that sessions already in flight do not gain the scope. A key created with
vendorgives it to sessions exchanged from then on.
To withdraw vendor, revoke the key and create a replacement without it. Revoking takes effect on the next
request, including for sessions already issued.
When an organization leaves Enterprise
A move from Enterprise to Pro or Free does not edit your keys, and it does not make them fail:
| What | After the move |
|---|---|
A key that carries vendor | Keeps working for everything else. Its answers no longer draw on your data. No error, status or field says so. |
| Browser sessions issued before the move | Ended. Their next request returns 401, and the SDK obtains a new session, which does not carry vendor. |
Rotating a key that carries vendor | On Pro: succeeds with 201. The replacement has the same scopes without vendor; its product list is unchanged. On Free: a publishable key's replacement carries ai, maps, design and all four products, whatever the old key held, and rotating a server or legacy key is refused with 403 free_plan_publishable_only. |
Creating a key with vendor | Refused with 403 tier_capability_not_allowed. |
| Your releases | Owners and admins can still list them, and can unpublish and delete them, on any plan, with the upload API. On Free and Pro, uploading and publishing are refused with 403 vendor_data_not_on_plan. |
On a return to Enterprise, a key that still stores vendor reaches the data again. A replacement rotated on
the lower plan does not regain the scope: create a new key.
Restricting a key
| Restriction | Applies to | Set on the API Keys page | Set through the key management API | Refusal |
|---|---|---|---|---|
| Allowed origins | Publishable keys | Yes | Yes (allowed_origins) | 403 origin_not_allowed at the exchange |
| Allowed products | Any key | No | Yes (allowed_products) | 403 product_not_allowed: at the exchange for a publishable key, on each runtime request for a server or legacy key. See product limits. |
| IP allowlist | Server and legacy keys | No | Yes (allowed_ips) | 403 ip_not_allowed |
| Monthly cap | Any key | No | Yes (monthly_cap_tokens) | 429 quota_exceeded |
| Expiry | Any key | No | Yes (expires_at) | 401 after the date |
| Scopes | Any key | No | At creation only (scopes) | 403 insufficient_scope |
Allowed origins. Up to 100 entries, each scheme://host[:port] with no path. An empty list on a
publishable key means any origin. Details and failure modes are in
CORS and allowed origins.
IP allowlist. Up to 100 IP addresses or CIDR blocks. A block that matches every address (/0) is refused:
leave the list empty to mean "any address". When a key has a list and the caller's address cannot be
established, the request is refused.
Monthly cap. A ceiling on what this one key may spend from your organization's AI credits in a billing period. See Quota and rate limits.
What reaches a session that is already issued
A session re-reads its parent key on every request, so removing access is fast. Adding access needs a new exchange.
| Change to the key or account | Effect on a session already issued |
|---|---|
| Key revoked, or rotated with no grace period | 401 on its next request. |
| The session's origin removed from the key's allowed origins | 401 on its next request. |
| The session's product removed from the key's allowed products | 401 on its next request. |
| Plan downgraded | Sessions issued before the downgrade get 401. Sessions exchanged afterward get 403 tier_capability_not_allowed for what the plan no longer includes. |
| Monthly cap added or lowered | Applies on the next request. |
| Monthly cap removed | Applies from the next exchange. |
| Plan upgraded | Applies from the next exchange. |
Because a session lasts minutes, "from the next exchange" is a short wait. The SDK's refresh picks it up with no code change.
Key management API
The API Keys page at kaleidr.com/api-keys sets a key's name, its type and its allowed origins. Everything else on this page is set through the same API that page uses.
- Base URL:
https://api.kaleidr.com/shared-api/api-keys - Authentication:
Authorization: Bearerwith the access token of a signed-in Kaleidr account that is an owner or admin of the organization. A platform key is not accepted here. There is no separate long-lived credential for key management today. - Organization: without an organization named, your primary organization is used. To name another, send
org_idin the JSON body when you create a key, and as the?org_id=query parameter on every other route. A queryorg_idon the create route is ignored.
| Method and path | Purpose | Success |
|---|---|---|
POST / | Create a key | 201, the key object plus plaintext_key |
GET / | List active keys, newest first | 200 |
GET /{id}/events?limit=1..200 | A key's audit events | 200 |
PATCH /{id} | Change restrictions | 200, the key object |
POST /{id}/rotate | Replace the key | 201, the new key plus plaintext_key |
POST /{id}/revoke | Revoke the key | 200, the key object |
There is no delete route: revoke a key to retire it.
Fields accepted when creating a key:
| Field | Type | Default | Notes |
|---|---|---|---|
name | string, 1 to 200 characters | required | |
org_id | UUID | your primary organization | The organization the key is created in. Sent in the body: a query parameter is ignored on this route. |
key_type | "publishable" or "server" | "publishable" | "server" is refused on Free with 403 free_plan_publishable_only. |
environment | "live" or "test" | "live" | |
scopes | string array | ["ai", "maps", "design"] | From ai, maps, design, vendor. Anything else is 422 unknown_scope. A scope your plan does not admit (vendor below Enterprise) is 403 tier_capability_not_allowed. |
allowed_origins | string array, up to 100 | [] | Empty means any origin, for a publishable key. |
allowed_products | string array | [] | From chat, editor, viewer, tile. Empty means all. Anything else is 422 unknown_product. Enforced for publishable, server and legacy keys: see product limits. |
allowed_ips | string array, up to 100 | [] | IP addresses or CIDR blocks. |
monthly_cap_tokens | integer, 0 or more | none | 1 or more caps the key. 0 or null means no cap. |
expires_at | date and time in the future | none | |
vendor_acknowledged | boolean | false | Required for a publishable key with vendor (Enterprise). |
PATCH accepts name, allowed_origins, allowed_products, allowed_ips, monthly_cap_tokens and
expires_at. Send only the fields you want to change; an explicit null clears a value. It does not accept
scopes.
rotate accepts { "grace_hours": 0 }, from 0 to 8760. The replacement inherits the old key's scopes, except a
scope your current plan no longer admits, which is left off. On Free the replacement always carries the whole
Free surface, and a server or legacy key cannot be rotated (403 free_plan_publishable_only). With 0, the old key and its sessions stop working at
once. With a grace period, the old key keeps working until the period ends, still counts toward your plan's
limit on active keys, and cannot be rotated again. The API Keys page rotates with a 24-hour grace period.
Errors from these routes are listed in Errors: key management.
Authentication errors
A 401 means the API does not accept the credential at all. A 403 means it knows who you are and the
answer is no. Every one has the shape {"detail": "<code>"}.
In a browser, a request refused for its credential or origin carries no CORS header, so the page sees a CORS
error instead of the body. A request refused later, for scope or plan, is readable, and the SDK reports
kaleidr session exchange failed (403). In both cases, open the request in the browser's Network tab to read
the code. See CORS and allowed origins.
| Status | Code | Cause | Fix |
|---|---|---|---|
| 401 | Invalid or missing API key | No credential; an unknown, revoked or expired key; an expired session; a session whose key was revoked, lost this origin or product, or was issued before a plan downgrade; an organization that is suspended or has no plan. | Check the key value and its status on the API Keys page. For a session, exchange again. |
| 401 | publishable_requires_session | A publishable key was sent to a runtime endpoint. | Exchange it for a session, or call from a backend with a server key. |
| 403 | publishable_key_required | The session exchange was called with a server or legacy key. | Use a kld_pk_… key at the exchange. |
| 403 | origin_required | A session exchange with no Origin header. | Call it from a browser page. |
| 403 | origin_not_allowed | The page's origin is not in the key's allowed origins, or not in the published map's allowed domains. | Add the exact origin to the key, or the domain to the map. |
| 403 | product_not_allowed | The key's allowed products exclude this product: the product requested at the session exchange, or, for a server or legacy key, the product the called endpoint belongs to. | Change allowed_products, or use another key. |
| 403 | insufficient_scope | The key was created without the scope this endpoint or product needs. | Use a key that has the scope. Scopes cannot be edited. |
| 403 | tier_capability_not_allowed | The key has the scope or product, but your current plan does not include it. | Change plan at kaleidr.com/billing. |
| 403 | server_key_in_browser | A server key was sent with an Origin header. | Move the call to your backend. If the key was in page source, rotate it. |
| 403 | session_origin_mismatch | A session token was used from an origin other than the one it was minted for, or a published-map session was used with no Origin. | Exchange a session on the page that uses it. |
| 403 | ip_not_allowed | A server or legacy key with an IP allowlist was used from an address outside it. | Call from an allowed address, or change allowed_ips. |
| 403 | chat_disabled | Published-map exchange: the map's Kaleidr AI does not turn on the feature asked for. | Turn on Control Tower or Popup Window in the map's Kaleidr AI tile in Studio. |
| 403 | owner_has_no_organization | Published-map exchange: the map's owner has no organization. | The owner must create or join an organization. |
| 404 | not_found | Published-map exchange: no published map or share link with that id. | Check the id and that the map is published. |
The complete catalog, including 429 and 5xx responses, is in Errors.
Related
- Security model: what each credential can do if it leaks.
- CORS and allowed origins: how the origin list is enforced.
- Endpoints: what each scope lets you call.
- Get an API key: creating a key on the API Keys page.