Skip to main content

Auth & scopes

The platform API authenticates with a key from your org. It comes in two forms — same org, same scopes, different runtime:

FormCredentialSent as
Server (backend)kld_sk_live_…Authorization: Bearer … or X-Api-Key
Publishable (browser)kld_pk_live_…exchanged by the SDK for a short-lived session; never sent as a raw bearer
Authorization: Bearer kld_sk_live_…
# or
X-Api-Key: kld_sk_live_…

Server keys are the bearer you send from your backend on each request. Publishable keys are for the browser: the SDK trades one for a short-lived, origin-bound session token at runtime, so the publishable key itself is never a standing credential in page source. A publishable key presented directly as a bearer is rejected — use it through the SDK. A server key gets no CORS grant, so a page can never read a response made with one — but CORS cannot stop the request from leaving the browser, so a server key pasted into page source has already leaked by the time the API refuses it. The SDK refuses kld_sk_… keys at mount for exactly this reason: keep server keys server-side, always.

Existing kld_live_… legacy keys keep working as a direct bearer in both places.

Scopes

A key carries capability scopes; each route family requires one:

ScopeRoute familyUsed by
ai/inference-api/b2b/v1/chat/*, /retrieval/*chat embed
design/inference-api/b2b/v1/design/*editor embed
mapsdesigned basemapsthe tile embed
vendor(modifier, not a route)the chat embed, on your own data

By default a Pro/Enterprise key is minted with ai, design and maps. Free-plan keys are clamped to maps + the tile product (details).

vendor — request it deliberately, and only where you mean it

vendor is not a route gate. It does not admit or deny a request; it decides whether the AI chat may consult your organization's uploaded vendor data when it answers. Every other scope answers "may this key call this endpoint"; vendor answers "may this key speak for our internal data".

It is therefore never included by default — ask for it explicitly at mint:

{ "name": "our-site-widget", "scopes": ["ai", "vendor"] }

Which key carries vendor is the entire security decision. The chat embed is a browser widget: it exchanges a publishable key for a short-lived session, so a vendor-enabled key necessarily sits in a public page. That is fine for data you are happy to show every visitor of that site — a property list, opening hours, public rates. It is not fine for anything you would not publish. The origin allowlist that guards the exchange is a browser convention, not a confidentiality boundary: a caller that sets its own Origin header is not stopped by it.

So:

  • One key per surface. A marketing site, a demo page and your customer-facing app should not share a key. Only the one that must answer from your data gets vendor.
  • Never put a rate sheet, cost basis, or anything unreleased behind a publishable key. If the answer would embarrass you on a public page, the data does not belong in a vendor-scoped embed.
  • Sessions are narrowed to their product, so a tile or viewer session never carries vendor even when its parent key holds it.

To withdraw it, revoke the key rather than editing its scopes. Scopes are stamped into a session at mint and a session lives up to 30 minutes, so an edit takes effect only as live sessions expire; a revoke is immediate.

401 vs 403

These are distinct on purpose:

  • 401 Unauthorized — missing / invalid / revoked / expired key. Re-check the key value and that it isn't revoked.
  • 403 Forbidden (insufficient_scope) — the key is valid but lacks the scope the route needs. Mint (or re-mint) a key with the right scope.

Both fail closed: a key with no scopes is denied everywhere.

See Errors for the full status table.