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
The per-request cost and account reads below take an ordinary xpl_ inference key, under one host. Organization Spend reports, linked separately below, require a provisioning key:
- Serving:
https://api-pr-134.preview.experientiallabs.ai/v1(alsohttps://api-pr-134.preview.experientiallabs.ai/api/v1). - Cost & account:
https://api-pr-134.preview.experientiallabs.ai/api/v1.
New to the surface? Start with Integrate the gateway.
Aggregated reports use the Spend API
For exact grouped reports, arbitrary dates, frozen pagination and matching charts, use the Spend API at /api/orgs/{org_id}/spend. It requires a provisioning key and shares its reporting stores with the dashboard. Start with POST /analytics, retain a loading id and poll that id; a ready report supplies the snapshot for every later read.
Spend's all-in value is paid credits plus BYOK estimates plus free promotional list value. Only the paid part is charged; it is not the same quantity as this page's real_cost_usd, which excludes free list value. Spend CSV contains aggregate groups, not the per-request rows of GET /api/v1/usage. The existing Cost API activity and usage export retain their documented org scope and plan-bound identity restrictions; the new provisioning requirement is not a blanket own-key-only rule for inference reads.
Customer-plan allowances
GET /api/orgs/{org_id}/identities/{identity_id}/allowances reads the same counters native admission enforces. Inference keys read their own identity only. Org admins and provisioning management keys can inspect their organization. Plan-bound keys cannot read the org wallet; usage, generation and activity reads are scoped server-side to their identity, including rotated keys.
The response carries assignment, budget_period, period_start, period_end, pricing_basis: frozen_list_value, groups and aggregate. Each group and the shared aggregate report limit_nano_usd, settled_nano_usd, reserved_nano_usd, pending_nano_usd and remaining_nano_usd. The aggregate includes all groups for this identity, even removed groups. Its limit and remaining are null when no aggregate cap is authored; zero denies. Both group and aggregate caps must admit a request. An unassigned identity has no aggregate allowance. Values are integers; $1 = 1,000,000,000 nano-USD. Read remaining directly, without subtracting pending twice. Each group and aggregate also carries exact: decimal integer strings for settled, reserved, pending and remaining (null when unlimited). Browser displays use these withBigInt because accumulated exposure can exceed JavaScript's safe-integer range. Periods are recurring UTC calendar months or explicit source intervals with timezone-aware bounds. An explicit interval expires without auto-renewal; rolling windows are unsupported. Native and imported unresolved exposure survive period changes without erasing settled history.
Allowances measure attributed inference list value at the frozen selected rate schedule, independently of platform charges, promotions and house discounts. BYOK uses the allowance without debiting platform credits; its value is an estimate, never a provider invoice. Unknown prices or missing enforceable request bounds fail closed. Actual overages are recorded and stop further admission, not hidden behind an absolute invoice-ceiling claim.
Failed or disconnected calls without trustworthy complete usage retain pending allowance. The current engine cannot always prove that a provider rejection was unbillable. Measured zero usage releases exposure; missing usage does not. An operator must reconcile it or release it with evidence. Removing a group with outstanding exposure blocks new admission until resolution; the removed group remains visible with a zero current limit. This does not release platform-credit holds or change the wallet.
The settled export remains your billing reconciliation feed, not an admission mechanism. Request labels are attribution, never customer-plan authority. Use a trusted identity mapping and issue the customer's inference keys under that identity. Your application owns subscription events, markup and invoices. See the Account API for immutable policy versions, counter-preserving changes and bounded assignment delegation.
Current-period migration uses the administrator-only GET /api/orgs/{org_id}/identities/{identity_id}/allowance-cutover. It exports allowances, immutable source receipts, native outstanding attempts andsettled_periodswithout content. Historical period rows carry period_start, period_end, nullable source_period_id, group_key and settled_nano_usd. More than 500 external receipts, 500 native outstanding rows or 1,000 settled-period rows sets export_overflow; a partial export is never accepted as a complete handoff. The web host accepts a signed-in administrator's browser session and forwards its verified actor to the API host. Ordinary customer keys, including provisioning keys, cannot use it. Prepare accepts at most 500 distinct source-owned request receipts, not cumulative totals or duplicate destination usage. On activation, their settled and pending legs enter the same counters. Evidence-backed resolution replaces the pending leg only.
Handoff totals can exceed 2^53 - 1. Consume and produce JSON losslessly: Python integers or exact decimals, or a JavaScript lossless JSON parser/serializer withBigInt support. The session proxy preserves raw JSON without parsing and reserializing money. Never use ordinary JSON.parse, response.json() or Number amounts for rollback reconciliation. Preserve the original export and server-issued digest; send exact JSON integers, not rounded numbers or numeric strings.
Rollback requires frozen admission, resolved native reserved and pending exposure, resolved imported pending exposure, and a complete fresh export. export_overflow:true or a changed export_sha256 blocks acknowledgment. Resolve all uncertainty while active or frozen; unresolved native or imported exposure blocks acknowledgment. Exporting and acknowledging never reset history or infer zero usage. The endpoint does not stop or resume Cloudflare. See the handoff lifecycle.
Three ways to read cost
Cheapest-to-wire to most complete:
- Inline, per response. Read
usage.cost(USD) off the response. It is 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-idtoGET /api/v1/generation?id=for cost and token detail. - The settled-usage export (recommended for billing).
GET /api/v1/usageis 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.
providernames the rung that answered. A model's waterfall can fail over to a sibling rung without any change to the reply, so the body carries the serving provider (tencent,openrouter, your own provider on a BYOK request) as a top-level field besideid/model, where OpenRouter puts it. It is the catalog provider name (tencent), and thex-gateway-providerresponse header carries the same public name.- Non-streaming Chat/Responses/Messages bodies always carry
usage(andcost); on a stream thecostrides the final usage chunk when the engine emits one (themessage_deltaevent on the Messages surface), so request that terminal frame the way you already do for OpenAI. - A gateway-run web search is a flat per-search fee inside
cost. When a request asks for web search on a route without native search (Chatweb_search_options/pluginsweb /:online, Responsesweb_search, Messagesweb_search_*), the gateway runs one search (Exa) and charges a flat fee per search (default $0.007) on top of the model's token price, on the finalizing settle of a finished request only. It is never charged on a failed request or on BYOK. It is already included inusage.costand in the export's settled cost; the count isusage.server_tool_use_details.web_search_requests. - BYOK settles cost 0. A bring-your-own-key request reports
cost: 0, setsis_byok: true, and puts the upstream provider’s own attributed charge onusage.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-Idis 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:
# 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-134.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 istotal_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:
curl "https://api-pr-134.preview.experientiallabs.ai/api/v1/usage?limit=1000" \-H "Authorization: Bearer xpl_..."
The response is { "data": [ ... ], "next_cursor": { ... } | null }. Each row in data carries:
| Field | Meaning |
|---|---|
| id | The request id. |
| created_at | Settlement timestamp (ISO 8601). |
| model | The model slug. |
| provider | The winning provider (nullable). |
| attribution_label | The safety_identifier / user you set on the request; null when neither was sent. |
| real_cost_usd | Always-real per-call cost in USD (charged credits + attributed BYOK pass-through). 0 with pricing_known false means “unpriced”, not free. |
| cost_usd | Charged platform-credit cost in USD. |
| estimated_cost_usd | Estimated / attributed cost in USD. |
| pricing_known | Whether a price was known for the request. |
| input_tokens · output_tokens · cached_input_tokens · reasoning_tokens | Token counts. |
| status | Terminal status of the request. |
| api_key_id | The 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.
curl https://api-pr-134.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.
- Tag every request with your customer. Send your customer’s stable id as
safety_identifier(aliasuser) on each call. One org key serves all of them; the id comes back asattribution_labelon the export row. If you need per-customer revocation or daily caps as well, mint a key per customer instead (Account API) and group byapi_key_id. - Bill from the export, not the response.
usage.coston 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 anIdempotency-Keycarries nousage.costat 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 theirstatusand whatever cost settled for them. - Run a scheduled sync, newest first. The export is newest-first inside a lookback window (
window=24h|7d|30d, default7d), andnext_cursorpages older within that one call; it is not a checkpoint to resume from on the next run. Each run therefore starts at the top: callGET /api/v1/usage?limit=1000, follownext_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 rowid. Run at least daily: rows older than the 30-day window are no longer exportable. - 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. - Emit one usage event per row. For Metronome:
customer_idfrom your mapping ofattribution_label,transaction_id= the rowid(the ingest API deduplicates on it, so a redelivery is a no-op),timestamp=created_at, and propertiesinference_cost_usd=real_cost_usdplusmodel,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. - Price it in the billing system. Define a billable metric that sums
inference_cost_usdper 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. - Reconcile. The export’s
cost_usdcolumn (charged credits only) summed over a day should agree with the movement oftotal_usageonGET /api/v1/creditsover that day; both are settled charged spend. Rows with anullattribution_labelare 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.
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_usdis 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(inlineis_byok: true, with the upstream charge oncost_details.upstream_inference_cost) and the export row’sreal_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.