Docs
Status
OverviewQuickstartSetup promptsThe core loopAuthenticationOverviewModelsThe waterfallAdding modelsAnthropic APIErrorsIntegrate the gatewayCost APIAccount APICoding agentsCredits & billingTelemetryAPI reference

Get started

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

Guides

  • Overview
  • Models
  • The waterfall
  • Adding models
  • Anthropic API
  • Errors

Integrations

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

Billing & usage

  • Credits & billing
  • Telemetry

Reference

  • API reference
PreviousIntegrate the gatewayNextAccount API

Integrations

Cost API

Read what every request cost — inline on the response, per request by id, the credit balance, or as a settled export you poll into your own billing system to rebill end-customers at your margin.

Base URL

Every route here is read-only and takes an ordinary xpl_ inference key, under the one host:

  • Serving: https://api-pr-1743.preview.experientiallabs.ai/v1 (also https://api-pr-1743.preview.experientiallabs.ai/api/v1).
  • Cost & account: https://api-pr-1743.preview.experientiallabs.ai/api/v1.

New to the surface? Start with Integrate the gateway.

Three ways to read cost

Cheapest-to-wire to most complete:

  • Inline, per response. Read usage.cost (USD) off the response — stamped on every Chat/Responses/Messages reply, no request flag needed. Good for showing a cost immediately; not a durable feed on its own.
  • One request by id. Pass the response’s x-request-id to GET /api/v1/generation?id= for cost and token detail.
  • The settled-usage export (recommended for billing). GET /api/v1/usage is a cursor-paginated feed of settled per-request rows with attribution, real cost, and token counts — the feed a billing sync job should poll.

Inline cost on every response

Experiential stamps the settled cost inline on the usage object of every Chat Completions, Responses and Anthropic Messages reply, in USD, as an extension of the standard usage object. No request flag is needed: usage.cost is stamped whenever the engine emits a usage object, so usage is always included (both usage: { include: true } and stream_options: { include_usage: true } are accepted as no-ops). The one exception is a request sent with an Idempotency-Key: it carries no usage.cost on the original or on a replay, so the bytes stay identical; treat the field as optional and read the export for those.

  • Non-streaming Chat/Responses/Messages bodies always carry usage (and cost); on a stream the cost rides the final usage chunk when the engine emits one (the message_delta event on the Messages surface), so request that terminal frame the way you already do for OpenAI.
  • BYOK settles cost 0. A bring-your-own-key request reports cost: 0, sets is_byok: true, and puts the upstream provider’s own attributed charge on usage.cost_details.upstream_inference_cost (present for BYOK only; omitted entirely for platform-funded traffic rather than reported as a wrong or zero value).
  • Keyed / idempotent replays are byte-exact. A replay of an Idempotency-Keyed completion returns the original bytes with no injected fields; Experiential skips annotation on those requests entirely. (X-Client-Request-Id is correlation/affinity, not a replay key, and is still annotated.)

Look up one request by id

Every completion response carries an x-request-id header — the gateway request id. Read it off the response and pass it back to GET /api/v1/generation?id=to pull that one request’s settled cost and token detail. The endpoint accepts both the bare id and the gen-<request_id> form it echoes:

GET /api/v1/generation?id=
# The completion response returns the id in the x-request-id header;
# pass it (bare, or as gen-<id>) to /generation.
curl "https://api-pr-1743.preview.experientiallabs.ai/api/v1/generation?id=$REQUEST_ID" \
-H "Authorization: Bearer xpl_..."

The read is org-scoped: a request id from another organization 404s exactly like an unknown one. The Anthropic Messages surface sets the same x-request-idheader (read it with the Anthropic SDK's with_raw_response); Claude Code exposes no response headers, so read a Claude Code run's cost from the usage export filtered on the key the run used.

Credit balance & activity

Two account-level reads for the running totals:

  • GET /api/v1/credits — {data:{total_credits, total_usage}} in USD; the remaining balance is total_credits − total_usage.
  • GET /api/v1/activity — a recent activity rollup for the org.

The settled-usage export

GET /api/v1/usage returns settled rows, newest first, with a keyset cursor — the feed a billing sync job should poll:

GET /api/v1/usage
curl "https://api-pr-1743.preview.experientiallabs.ai/api/v1/usage?limit=1000" \
-H "Authorization: Bearer xpl_..."

The response is { "data": [ ... ], "next_cursor": { ... } | null }. Each row in data carries:

FieldMeaning
idThe request id.
created_atSettlement timestamp (ISO 8601).
modelThe model slug.
providerThe winning provider (nullable).
attribution_labelThe safety_identifier / user you set on the request; null when neither was sent.
real_cost_usdAlways-real per-call cost in USD (charged credits + attributed BYOK pass-through). 0 with pricing_known false means “unpriced”, not free.
cost_usdCharged platform-credit cost in USD.
estimated_cost_usdEstimated / attributed cost in USD.
pricing_knownWhether a price was known for the request.
input_tokens · output_tokens · cached_input_tokens · reasoning_tokensToken counts.
statusTerminal status of the request.
api_key_idThe key that made the request (nullable).

Filter by ?attribution_label=<id> (plus optional window, model, api_key_id, status, limit) to pull one end-customer’s requests. When a full page comes back, next_cursor is { cursor_ts, cursor_id, cursor_after }; pass those three values back as the cursor_ts, cursor_id, and cursor_after query params for the next page, and persist them so the next run resumes where you stopped. next_cursor is null on the last page. This is a pull/poll model today; there is no push webhook yet.

Attribute each request to your end-customer

Pass the OpenAI-standard safety_identifier on every request (legacy alias: user). Experiential records it as the request’s attribution label and rolls usage up per end-user — no per-customer key required.

safety_identifier
curl https://api-pr-1743.preview.experientiallabs.ai/v1/chat/completions \
-H "Authorization: Bearer xpl_..." \
-H "Content-Type: application/json" \
-d '{
"model": "claude-fable-5.1",
"messages": [{"role": "user", "content": "..."}],
"safety_identifier": "cust_8842"
}'

The label then appears as attribution_label on the matching /api/v1/usage row, so you can group cost and tokens by your own customer id without running a separate key per customer. safety_identifier is one of two attribution models — see key-per-customer on the Account API.

Meter your customers on inference cost

The common shape: you sell consumption-based credits, you charge each customer for what their inference actually cost you (plus your margin), and a billing system such as Metronome, Orb, or Stripe metered billing turns metered usage into invoices. The usual integration is a pull-based sync off the settled usage export, not bookkeeping on each response. This is how it fits together.

  1. Tag every request with your customer. Send your customer’s stable id as safety_identifier (alias user) on each call. One org key serves all of them; the id comes back as attribution_label on the export row. If you need per-customer revocation or daily caps as well, mint a key per customer instead (Account API) and group by api_key_id.
  2. Bill from the export, not the response. usage.cost on the reply is for showing the number; it is not a durable ledger (a client can disconnect mid-stream, a retry can double-count, and a request sent with an Idempotency-Key carries no usage.cost at all, on the original or a replay). The export is the settled ledger: every request settles exactly once, rows never change after they settle, and failed requests appear with their status and whatever cost settled for them.
  3. Run a scheduled sync, newest first. The export is newest-first inside a lookback window (window=24h|7d|30d, default 7d), and next_cursor pages older within that one call; it is not a checkpoint to resume from on the next run. Each run therefore starts at the top: call GET /api/v1/usage?limit=1000, follow next_cursorpage by page, and stop once a whole page is older than your last successful run’s start time minus a grace period that covers your longest request (a few minutes is plenty). Dedupe on the row id. Run at least daily: rows older than the 30-day window are no longer exportable.
  4. Land rows in a local outbox first. Insert each row into a table keyed on the request id (insert, on conflict do nothing) in one local transaction, then have a separate delivery step push unsent rows to the meter and mark them sent. A meter call is an external HTTP write and cannot share your database transaction; the outbox is what makes a crash between the two safe to replay.
  5. Emit one usage event per row. For Metronome: customer_id from your mapping of attribution_label, transaction_id = the row id (the ingest API deduplicates on it, so a redelivery is a no-op), timestamp = created_at, and properties inference_cost_usd = real_cost_usd plus model, provider, the token counts, status, pricing_known, and whether the row was BYOK. Orb and Stripe meters take the same shape: idempotency key = row id, value = cost.
  6. Price it in the billing system. Define a billable metric that sums inference_cost_usd per customer per period, and put your credit rate (the margin) on that metric. The sync job ships raw cost; the markup and the conversion to your credits live where your pricing already lives, so a price change never touches the integration.
  7. Reconcile. The export’s cost_usd column (charged credits only) summed over a day should agree with the movement of total_usage on GET /api/v1/credits over that day; both are settled charged spend. Rows with a null attribution_label are requests your code did not tag: surface them, do not drop them. To audit one customer, pull ?attribution_label=<id>.

Which quantity to bill: cost_usd is the platform credits the request was charged; estimated_cost_usdis the attributed value of a BYOK request at catalog list rates, which is an estimate and never the provider’s invoice; and real_cost_usd is the two added together, so a BYOK row shows its estimated spend instead of a $0 charge. Bill real_cost_usd only if you accept that the BYOK part is estimated; otherwise bill cost_usd and meter BYOK rows on tokens (they carry is_byok, tokens, and attribution). A row with pricing_known: false carries a cost of 0 because no price was known, not because it was free; decide up front whether to bill those on tokens or skip them. Because the feed is settled, you can rebill at any margin.

A new org receives a welcome credit grant at signup, but spending stays locked until the founder proves inbox ownership (verification email) or a card charge settles. Until then GET /v1/models and telemetry work, but a paid completion is refused.

Credits & funding

  • Platform-funded (credits). Your org holds credit balance; each request draws from it and usage.cost / real_cost_usd is the real USD cost. This is the default and what you rebill against.
  • BYOK (bring-your-own-key). A model served through your own provider connection is billed by the provider directly, so Experiential settles cost: 0 (inline is_byok: true, with the upstream charge on cost_details.upstream_inference_cost) and the export row’s real_cost_usdis the attributed pass-through cost. Those rows still carry tokens and attribution, so you can still meter your customers on usage; just don’t expect a platform charge on them.