Docs
Status
OverviewQuickstartSetup promptsThe core loopAuthenticationOverviewModelsThe waterfallAdding modelsData controlsOpenAI compatibilityAnthropic APIErrorsIntegrate the gatewayCost APIAccount APICoding agentsCredits & billingSpend & intelligenceTelemetryBecome a providerProvider guideAPI reference

Get started

  • Overview
  • Quickstart
  • Setup prompts
  • The core loop
  • Authentication

Guides

  • Overview
  • Models
  • The waterfall
  • Adding models
  • Data controls
  • OpenAI compatibility
  • Anthropic API
  • Errors

Integrations

  • Integrate the gateway
  • Cost API
  • Account API
  • Coding agents

Billing & usage

  • Credits & billing
  • Spend & intelligence
  • Telemetry

Providers

  • Become a provider
  • Provider guide

Reference

  • API reference
PreviousCost APINextCoding agents

Integrations

Account API

Authenticate with one xpl_ key, inspect what a key can spend, and provision keys programmatically — mint one per end-customer, rotate on a schedule, or cap a key's daily spend, all over the API.

Base URL

The account and key-management routes answer under one host at https://api-pr-2007.preview.experientiallabs.ai/api/v1. New to the surface? Start with Integrate the gateway.

Inference key vs provisioning key

There are two kinds of key: an inference key and a provisioning key. Both are ordinary xpl_keys — “provisioning” is a capability flag on the key (is_provisioning), not a different prefix or a separate account. You set it when you mint the key ("provisioning": true on POST /api/v1/keys); a key without it is a normal inference key.

  • An inference key runs inference and makes the read-only account/cost calls (/credits, /key, /generation, /activity, /usage, GET /models, GET /providers — see the Cost API).
  • A provisioning key does all of that plus the whole key-management family (GET/POST/PATCH/DELETE /keys) and the identity-setup routes (identities, budgets, per-identity usage). Every one of those routes — including the GET reads — is gated behind it; a normal inference key gets a 403 (A provisioning key is required for this action) on all of them. Only an org admin can create a provisioning key, and the keys list marks each one with a Provisioning badge.

For the everyday xpl_ key mechanics — how it looks, the Bearer header, and what one key can and cannot do — see Authentication.

The first provisioning key comes from the dashboard. Key creation is a provisioning-key action, so there is a chicken-and-egg: mint the first one from the dashboard (a signed-in org admin satisfies the same gate a provisioning key does), then that key can mint further keys — provisioning or inference — through POST /api/v1/keys. Keep the provisioning key server-side; do not use it as your day-to-day inference key.

Inspect the presented key

GET /api/v1/key returns usage/limit metadata for the key in the Authorization header. usage, limit, and limit_remaining are real — the cap is a daily one, so limit_remainingis measured against today’s spend. This read requires an xpl_ key credential (it reports on the presented key itself).

Manage keys (provisioning key required)

The {hash} path segment is the key’s hash field (the row’s uuid id, as returned by list/create), never the secret.

RouteNotes
GET /api/v1/keysList keys ({data:[...]}); revoked hidden unless ?include_disabled=true.
POST /api/v1/keysCreate a key (200); returns {data, key:"xpl_..."} — the plaintext once. Body: name (required), limit (daily USD cap), provisioning (bool), identity_id (an active identity of your org; 404 unknown, 409 disabled). Every key resource reports its identity_id.
GET /api/v1/keys/{hash}Read one key.
PATCH /api/v1/keys/{hash}Update name / disabled / limit. disabled:true revokes (terminal).
DELETE /api/v1/keys/{hash}Revoke one key; returns {data:{success:true}}.
POST /api/v1/keys
# Requires a provisioning key as the bearer.
curl https://api-pr-2007.preview.experientiallabs.ai/api/v1/keys \
-H "Authorization: Bearer xpl_...provisioning..." \
-H "Content-Type: application/json" \
-d '{
"name": "cust_8842",
"limit": 25,
"provisioning": false
}'

On the key list/CRUD object, usage and limit_remaining are intentionally null today (there is no cheap per-key lifetime-spend reader for a list; a fabricated 0 would mislead). Real per-key spend lives on GET /api/v1/key (the presented key) and GET /api/v1/usage (filter by api_key_id, on the Cost API). This is a documented follow-up, not a permanent gap.

Set up identities with a provisioning key

An identityis a named spend bucket inside your org (“coding”, “product”, “research”): every key hangs off exactly one, so usage rolls up per identity and a monthly budget can cap an identity or a single key. A provisioning key drives the whole setup an org admin would otherwise click through on the API keys page — create the identities, mint one inference key per identity, set budgets, and read spend per identity — so a coding agent can do it from one prompt.

RouteNotes
GET /api/whoami{org_id, org_slug, org_name} for the key's org — the org_id every route below takes.
GET /api/orgs/{org_id}/identitiesList identities ({identities:[{identity_id, display_name, description, active, is_default, ...}]}). The is_default one is the org's own identity.
POST /api/orgs/{org_id}/identitiesCreate one: {display_name (required), identity_id?, description?}. identity_id starts with a letter, then lowercase letters or digits, groups joined by '.', '-' or '_' (^[a-z][a-z0-9]*([._-][a-z0-9]+)*$), never 'org-…', and is unique PLATFORM-wide. 201 created; 409 code identity_id_unavailable = the id already exists (yours or any org's — the response never says which): reuse it if it is in your own list, else retry with details.suggested_id; 422 bad id.
PATCH /api/orgs/{org_id}/identities/{identity_id}Rename / redescribe: {display_name?, description?}. A provisioning key may NOT send active (403): enabling or disabling an identity is the admin's, on the dashboard. 404 = that identity id does not exist in the org.
POST /api/v1/keysMint one inference key per identity (200): {name, identity_id, limit? (daily USD cap)}. Leave provisioning off.
GET /api/orgs/{org_id}/budgets?period=YYYY-MMThe month's budgets with limit / reserved / settled / remaining (nano-USD). Read-back takes the calendar month, never "*".
PUT /api/orgs/{org_id}/budgetsSet a monthly cap: {period: "*" (recurring) | "YYYY-MM", scope_kind: "identity" | "key", identity_id | api_key_id, limit_nano_usd} ($1 = 1,000,000,000). limit_nano_usd 0 is a HARD $0 cap, never "no cap". A provisioning key may set only identity and key scopes (422 otherwise), never the default identity's (403), and may REPLACE only a budget its own key created — an admin's row answers 403 "set by an administrator; change it in the dashboard". Every row reports created_by_kind (session | api_key) and created_by_api_key_id.
DELETE /api/orgs/{org_id}/budgets/{budget_id}Remove a cap (back to unlimited): {deleted:true}. A provisioning key may delete only identity- and key-scope budgets its own key created (403 on a team/model cap, on the default identity's, or on an admin's or another key's row); 404 = no such budget in the org.
GET /api/orgs/{org_id}/usage/by-identity?window=24h|7d|30dSpend per identity: {identities:[{identity_id, display_name, active, keys:[{api_key_id, key_label, request_count}], totals:{request_count, error_count, input_tokens, output_tokens, cost_usd, estimated_cost_usd, free_usd}, last_used_at}]}. Only keys that carried traffic in the window are listed, each with its own request_count. The identity_id null row is traffic whose key was deleted; a key minted without a group folds under the identity the mint assigned it (the Default identity on most orgs).
The prompt is on the API keys page. Creating or viewing a provisioning key offers “Copy prompt to set up identities”: paste it into your coding agent together with the key (the prompt carries a placeholder, never the key), and the agent proposes identities, creates them, mints a key per identity with optional limits, tells you where each key goes (Claude Code, the OpenAI SDK, OpenCode, a server), and verifies each with one request. The same text is under Setup prompts.
  • The path org_id must be the key’s own org; any other org answers a 404.
  • Disabling an identity, the grant matrix, org-wide (team) budgets, the default identity’s budget, and any budget an administrator set stay the admin’s: the PATCH refuses active from a key (403); the budget routes accept only identity and key scopes from a key (422 on set, 403 on delete), refuse the default identity (403), and let a key replace or delete only the rows its own key created (created_by_api_key_id; 403 otherwise); identity DELETE plus the grant routes are not admitted to keys at all (401 with a valid key — the edge refusing the route, not a key problem). A leaked provisioning key cannot reshape model access, freeze the org, or lift a freeze.
  • Error shapes: a business refusal carries {error}; a request-shape failure (missing display_name, a non-numeric limit, a budgets read without period) carries FastAPI’s {detail:[{loc, msg, type}]} and detail[0].loc[-1] names the field.
  • Every write is audited under the key (actor_kind = api_key), exactly like the dashboard path is audited under the admin. The API does not itself stop a provisioning key from serving inference or minting another provisioning key; the prompt keeps those as the admin’s rules.

Key-per-customer

A key per end-customer is an alternative to safety_identifierattribution: mint a distinct inference key per end-customer, and each customer’s usage is naturally isolated to their key with independent revocation and per-key daily limits. safety_identifier is simpler (one key, per-request label, on the Cost API); you can also combine them.

  • A normal inference key gets a 403 on every /api/v1/keys* route.
  • Keep the provisioning key server-side; never ship it to a client or use it to serve inference traffic.