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-2025.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 is returned 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-2025.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. That org_id is what 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.