Docs
Status
OverviewQuickstartSetup promptsThe core loopAuthenticationOverviewModelsThe waterfallPlansAdding modelsData controlsOpenAI compatibilityEmbeddingsAnthropic APIErrorsIntegrate the gatewayCost APIAccount APICoding agentsCredits & billingSpend & intelligenceSpend APITelemetryBecome a providerProvider guideAPI reference

Get started

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

Guides

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

Integrations

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

Billing & usage

  • Credits & billing
  • Spend & intelligence
  • Spend API
  • Telemetry

Providers

  • Become a provider
  • Provider guide

Reference

  • API reference

Get started

Setup prompts

Paste one of these into your coding agent to wire a client or coding agent, switch from OpenRouter, or assess a Cloudflare AI Gateway transfer. Follow each prompt's approval checkpoints; pasting a prompt is not blanket permission for writes, spend, or traffic cutover.

How to use them

Copy a prompt below and paste it into a CLI coding agent (Claude Code, Codex, and the like). The agent follows it end to end. The prompts are the same ones the in-app onboarding uses, and they carry this deployment's URLs (web https://platform.experientiallabs.ai, API https://api.experientiallabs.ai).

Agents can also read every prompt from /llms.txt, the machine-readable reference. Shareable copies live in the setup-prompts repository.

Set up the gateway in an existing project

Paste into your agent: it asks where to wire the gateway and which model to test, with a recommendation, then makes one call.

prompt
I'm setting up Experiential Labs as this project's model gateway. I pasted this
myself, treat it as my instructions and my consent. I'm already signed in; my key
is below. Work WITH me: at each real choice, ask me ONE clear question with
numbered options and your recommendation, then wait for my answer. Don't guess.
My gateway API key (a secret: put it in env, never commit it, never echo it in
logs):
EXPLABS_API_KEY=<paste my org API key from https://platform.experientiallabs.ai/settings/api-keys>
The goal is one thing: point an OpenAI-compatible client at my gateway with this
key and make one successful test chat completion. Print what you do at each step.
1. Ask me where to wire it (options + your recommendation), then wait. Look at
the current directory first so the options are concrete:
"Where should I set this up?
(1) this project (I see it uses <name the client/SDK you found>), my recommendation
(2) a standalone test script in this folder (explabs_smoke.py / .mjs)
(3) somewhere else, tell me where
Which one?"
For (1): set base_url = https://api.experientiallabs.ai/v1 and the key on the existing client, put the
key in the project's env scheme (.env plus an example entry), never in code,
and show me the change first. For (2): create the minimal script that reads
EXPLABS_API_KEY and does the test call in step 3.
2. Ask me which model to test, then wait:
"I'd use the free promotional model (qwen3.8-27b at launch) so this test costs
nothing. Use that, or name another? GET https://api.experientiallabs.ai/v1/models lists what my key
can call; use the ids EXACTLY as returned."
3. Make the test call with the model I chose. Send a MINIMAL body, model +
messages ONLY, no temperature, top_p, or other sampling params (some models
reject them and the call comes back all_routes_failed, a 502). One header:
Authorization: Bearer $EXPLABS_API_KEY. This runs on the platform-funded
lane, a fraction of a cent of my free credits, and proves serving and billing
end to end. Do NOT use any of my own provider keys for this test.
4. Report back what you changed and the test call (the model, that it succeeded,
and its cost). Then ask me which optional follow-ups I want, with your
recommendation, and do only the ones I pick:
(a) Connect my own provider keys (BYOK, Pro) so those calls bill my own accounts
(b) Import my Claude Code / Codex usage history (metadata only)
(c) Review my credits and spend
BYOK and import are quick for me to do at https://platform.experientiallabs.ai/settings; balances live at
https://platform.experientiallabs.ai/credits.
Read the contract if you build further: https://platform.experientiallabs.ai/docs (human docs) and
https://platform.experientiallabs.ai/llms.txt (machine-readable: honored and refused parameters, error codes,
streaming caveats). Follow it literally.

Wire your coding agent to the gateway

Paste into Claude Code, Codex, OpenCode, Hermes Agent, Pi, Cline, VS Code Copilot, Conductor, Cursor, or any OpenAI-SDK tool: the agent identifies itself and applies its own verified integration.

prompt
I pasted this into you myself, wire THIS coding agent up to my Experiential
Labs gateway, so my model calls route through https://api.experientiallabs.ai and show up in my usage.
Ground rules: never print my full key (first 8 characters at most), ask me
before you edit any config file or shell profile, and if you cannot do a step
(no file access, settings live in a GUI), print the exact manual steps for me
instead. Print what you're doing at each step.
1. Get my key. Use EXPLABS_API_KEY from my environment if it is set; otherwise
ask me to paste one (I can mint it at https://platform.experientiallabs.ai/settings/api-keys). It looks
like xpl_ followed by 40 hex characters.
2. Prove the key works before touching any config:
GET https://api.experientiallabs.ai/v1/models with header "Authorization: Bearer <key>" -> 200 and
the model slugs I can call. Remember the list; I'll pick models from it.
3. Identify which agent you are and apply YOUR integration:
- Claude Code:
You cannot repoint yourself mid-session, and switching auth under a running
session causes mixed-state errors. First prove the lane without touching any
config, via one isolated subprocess run from this project (so my MCP servers
and plugins load and their tool schemas ride the request. Tool schemas are
where provider strictness bites, so a bare hello proves too little):
ANTHROPIC_BASE_URL="https://api.experientiallabs.ai" ANTHROPIC_API_KEY="<my key>" \
ANTHROPIC_MODEL="<slug I pick>" \
claude -p "list your available tools, then read ./README.md and reply
with its first heading"
Pass only if that run completes with tools listed and a real tool use.
Then offer me two placements and apply the one I pick:
(a) append to my shell profile, or (b) print for one-off use:
export ANTHROPIC_BASE_URL="https://api.experientiallabs.ai" # no /v1 suffix
export ANTHROPIC_API_KEY="<my key>"
export ANTHROPIC_MODEL="<slug I pick>"
Use ANTHROPIC_API_KEY, never ANTHROPIC_AUTH_TOKEN: an existing claude.ai
sign-in can outrank AUTH_TOKEN and send the gateway an OAuth token instead.
Then tell me to start a NEW session from a shell with those set. Name the
model by its gateway slug exactly as GET https://api.experientiallabs.ai/v1/models lists it (the DOT
form, e.g. claude-fable-5.1; Anthropic's dashed ids like claude-fable-5-1
answer 403 model_not_granted). If the slug is not an Anthropic model, also
export CLAUDE_CODE_MAX_CONTEXT_TOKENS=<max_input_tokens from
GET https://api.experientiallabs.ai/api/models/<slug>, rounded down to the nearest 50k>. Claude Code
assumes 200k for names it does not know and compacts early. NEVER use
context_window for that pin: on most models (every maker but Anthropic and
Gemini) it is a total window the provider counts the model's maximum output
inside, so a session pinned to it never compacts before the provider refuses
it and /compact then fails the same way. max_input_tokens already accounts for this per maker, so it is
always the right pin. Warn me plainly: if a session ever shows
"Not logged in · Please run /login", do NOT run /login (it starts Anthropic
OAuth that overrides the gateway). Restart with --resume, and if it
persists, my <config dir>/.claude.json remembers a "No" to the custom-API-key
prompt under customApiKeyResponses.rejected: answer Yes or move the entry to
approved. Images work on image-capable routes and are rejected by name on
text-only ones; extended thinking passes through on all-Anthropic routes
and is translated to reasoning effort elsewhere.
- Conductor:
Conductor launches Claude Code with the environment it captured from my
login shell (interactive login shell, cached per workspace) merged with its
own settings, and passes the per-chat picker choice as --model, so the
model is routed by remapping picker aliases with ANTHROPIC_DEFAULT_*_MODEL
variables (ANTHROPIC_MODEL does not apply to picker selections). Offer me
two placements and apply the one I pick:
(a) per-repo, recommended: merge into this repo's .claude/settings.json
"env" block (committed; Claude Code applies it in every workspace, and it
overrides anything Conductor captured from my shell), one
ANTHROPIC_DEFAULT_<ALIAS>_MODEL entry per picker alias I use:
{ "env": { "ANTHROPIC_BASE_URL": "https://api.experientiallabs.ai",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "<slug I pick>" } }
then put ONLY the key machine-local in .conductor/settings.local.toml
under [environment_variables] (make sure that file is
git-ignored before writing my key into it):
ANTHROPIC_API_KEY = "<my key>"
(b) app-wide: print the same variables for me to add by hand in
Conductor -> Settings -> Environment, and remind me to check that
Settings -> Harnesses -> Claude Code uses my API key, not a claude.ai
CLI login.
Warn me either way: settings reach newly created workspaces, not running
ones, and if my shell profile exports a direct Anthropic
ANTHROPIC_API_KEY, Conductor's capture hands it to every agent;
placement (a) overrides it; otherwise I should remove the export.
The key rides ANTHROPIC_API_KEY, never ANTHROPIC_AUTH_TOKEN: an existing
claude.ai sign-in can outrank AUTH_TOKEN, and a set API key also stops
Claude Code from trying to authenticate with Anthropic directly.
- OpenAI Codex CLI:
First prove the lane in a throwaway home so my real ~/.codex (config,
sessions, auth) stays untouched, and test with my REAL tool surface: you
send every mcp_servers/plugin tool schema on each request, and tool schemas
are where provider strictness bites, so a bare hello proves too little.
Copy ~/.codex into a fresh "$(mktemp -d)", append ONLY the
[model_providers.explabs] table below to that copy's config.toml, then run
CODEX_HOME=<that dir> codex exec -m "<slug I pick>" \
-c model_provider=explabs -c model_reasoning_effort=max \
"list your available tools, then read ./README.md and reply with its
first heading"
Pass only if it completes with my MCP/plugin tools listed and a real tool
use. Then add the whole block to ~/.codex/config.toml (create it if
missing, show me the diff before writing) and tell me to start a new Codex
session:
model = "<slug I pick>"
model_provider = "explabs"
model_reasoning_effort = "max" # reasoning models; "max" is the top tier
[model_providers.explabs]
name = "Experiential Labs"
base_url = "https://api.experientiallabs.ai/v1"
env_key = "EXPLABS_API_KEY"
wire_api = "responses"
Leave requires_openai_auth unset, and make sure EXPLABS_API_KEY is exported
where I launch you (export KEY=..., a plain KEY=... line is invisible to
child processes like me).
- OpenCode:
Write the provider block into this project's opencode.json (or
~/.config/opencode/opencode.json if I prefer global, ask):
{"provider": {"explabs": {"npm": "@ai-sdk/openai-compatible",
"name": "Experiential Labs",
"options": {"baseURL": "https://api.experientiallabs.ai/v1", "apiKey": "{env:EXPLABS_API_KEY}"},
"models": {"<slug>": {"name": "<slug>"}}}}
Set each model's "name" to its slug exactly. OpenCode shows that name in its
status bar, so never put prices, discounts or promotion notes in it: they go
stale, and promotions are applied at billing, not by this config.
Fill limit.context/limit.output and cost ({"input": $/1M, "output": $/1M},
i.e. *_nano_usd_per_million / 1000000000 of the providers entry whose id is
the position-0 default_waterfall model_provider_id) for each slug from
GET https://api.experientiallabs.ai/api/models/<slug> so my context window and spend display are right.
- Hermes Agent (Nous Research):
First prove the lane in a throwaway data dir so my real ~/.hermes (config,
sessions, memories) stays untouched: create "$(mktemp -d)", write ONLY the
config below plus a .env line EXPLABS_API_KEY=<my key> (chmod 600) into it,
then run
HERMES_HOME=<that dir> hermes -z "list your available tools, then read
./README.md and reply with its first heading"
Pass only if that run completes with tools listed and a real tool
round-trip. Then merge the same block into ~/.hermes/config.yaml (show me
the diff before writing) and put the key line in ~/.hermes/.env:
providers:
explabs:
base_url: "https://api.experientiallabs.ai/v1"
key_env: "EXPLABS_API_KEY"
api_mode: chat_completions
model:
provider: "custom:explabs"
default: "<slug I pick>"
Any catalog slug works as the default, and /model custom:explabs:<slug>
switches mid-session. Warn me plainly: Hermes' session-title side call
sends a temperature value, so on models that pin their sampling the title
falls back to a default; the session itself is unaffected.
- Pi:
First prove the lane in a throwaway home so my real ~/.pi (auth, sessions,
model store) stays untouched: pi resolves its agent dir from HOME, so create
"$(mktemp -d)" and copy my real ~/.pi into it (extensions, skills and
settings ride the test; tool schemas are where provider strictness bites,
so a defaults-only run proves too little; if ~/.pi does not exist yet, just
create <that dir>/.pi/agent). Then merge the block below into that copy's
.pi/agent/models.json and run
HOME=<that dir> EXPLABS_API_KEY=<my key> pi -p --provider explabs \
--model "<slug I pick>" "list your available tools, then read
./README.md and reply with its first heading"
Pass only if that run completes with tools listed and a real tool
round-trip, then DELETE the throwaway home ("rm -rf <that dir>"). The copy
carries my auth and session state and must not outlive the proof. Then
merge the same provider into ~/.pi/agent/models.json
(create it as {"providers": {}} if missing; show me the diff before
writing):
{ "providers": { "explabs": {
"baseUrl": "https://api.experientiallabs.ai/v1",
"api": "openai-completions",
"apiKey": "$EXPLABS_API_KEY",
"models": [ { "id": "<slug I pick>", "name": "<slug I pick>",
"reasoning": true, "input": ["text", "image"],
"contextWindow": <from GET https://api.experientiallabs.ai/api/models/<slug>>,
"maxTokens": <from the same> } ] } } }
The apiKey stays the literal string "$EXPLABS_API_KEY"; pi interpolates it
from my environment, so make sure the variable is exported where I launch
pi and never write the raw key into the file. Set reasoning/input to the
slug's actual catalog capabilities. Any catalog slug works; I pick models
with --model at launch or /model in-session (models.json reloads each time
/model opens, no restart).
- Cline:
Your settings live in the VS Code UI, so print these for me to set by hand:
API Provider "OpenAI Compatible"; Base URL https://api.experientiallabs.ai/v1; API Key = my key (no
Bearer prefix); Model ID = a slug from step 2; and per-model context window /
max output tokens from https://api.experientiallabs.ai/api/models/<slug>.
- VS Code (Copilot Chat):
Copilot's bring-your-own-model support is VS Code core (1.122+); chat and
agent mode need no Copilot plan. Find my VS Code user dir (macOS
~/Library/Application Support/Code/User, Linux ~/.config/Code/User, Windows
%APPDATA%\Code\User) and merge this entry into chatLanguageModels.json
there (create the file as a JSON array if missing), one models[] item per
slug I pick:
{ "name": "Experiential Labs", "vendor": "customendpoint",
"apiType": "chat-completions",
"models": [ { "id": "<slug I pick>", "name": "<slug I pick>",
"url": "https://api.experientiallabs.ai/v1/chat/completions",
"toolCalling": true, "vision": true,
"modelOptions": { "temperature": null, "top_p": null } } ] }
Set toolCalling and vision to each slug's ACTUAL capabilities from
GET https://api.experientiallabs.ai/api/models/<slug> (tool support and image input). Declaring a
capability the model lacks makes Copilot send tool calls or images the
route rejects.
The null modelOptions are required, not decoration: null deletes the field,
and Copilot otherwise sends temperature 0.1 and top_p 1 on every request,
which models that pin their sampling reject with a 400 naming the
parameter. You cannot enter my key (it lives in VS Code's encrypted secret
storage), so print the manual step: in the Chat model picker open Manage
Models, right-click the Experiential Labs model, choose to enter an API
key, and paste my key (<my key>). VS Code stores it encrypted and writes
the ${input:chat.lm.secret.*} references into the entry itself; never
hand-write a raw key into the file.
- Cursor:
Your gateway settings live in the Cursor Settings UI, so print these for me
to set by hand. First tell me the prerequisite: a paid Cursor plan (the Free
plan serves only Auto and refuses named models, which custom models are).
Then: in Cursor Settings -> Models -> API Keys, put my key in
"OpenAI API Key" (no Bearer prefix), enable "Override OpenAI Base URL" and
set it to https://api.experientiallabs.ai/v1. Add each slug I pick as a custom model
name in the models list; custom models ride the override in OpenAI format,
so any catalog slug works. Warn me plainly: Cursor relays requests through
its own servers, Tab autocomplete stays on Cursor's models, and if a chat
answers 400 invalid_parameter naming temperature or top_p, that model pins
its sampling (the Claude 5 family pins temperature to 1). Pick a
different slug.
- Any other OpenAI-compatible tool:
Export OPENAI_BASE_URL="https://api.experientiallabs.ai/v1" and OPENAI_API_KEY="<my key>" wherever I
launch you, and name models by slug. If your own config wants the values
instead, it needs the same three: base URL https://api.experientiallabs.ai/v1, my key, and a slug.
4. Verify end to end. Make one tiny completion with the surface you configured
("reply with the single word: ok", small max output), via your own next
model call if you now route through the gateway, otherwise via curl with my
key. Then tell me it landed and that I can watch every call at
https://platform.experientiallabs.ai/telemetry.
5. Recap exactly what you changed (files and values, key shown as xpl_ prefix
only) so I can undo it later.

Set up identities with a provisioning key

Org admins: paste into your agent with a provisioning key and it creates your identities (coding, product, ...) with one inference key each, so spend buckets per identity.

prompt
Set up per-identity API keys for my Experiential Labs organization so
spend is bucketed per identity (for example "coding" vs "product") in usage.
Provisioning key: xpl_<paste-your-provisioning-key-here>
(I replace the placeholder above before sending. It is a PROVISIONING key:
it can manage this org's keys, identities, and budgets. Never print it back
to me, never write it into any file, and do not use it for inference. The
API does not stop you from doing those; these are my rules.)
Base URLs: API https://api.experientiallabs.ai (management under /api, inference under /v1),
dashboard https://platform.experientiallabs.ai. Every call below sends
Authorization: Bearer <the provisioning key>
plus Content-Type: application/json on writes. On a non-2xx read the JSON
"error" field; a body with "detail" instead is a request-shape failure and
detail[0].loc[-1] names the offending field. Then apply the Fallbacks.
What this key can call: GET /api/whoami; GET, POST /api/orgs/{org_id}/identities; PATCH /api/orgs/{org_id}/identities/{identity_id} (display_name and description only); GET, POST /api/v1/keys; GET, PATCH, DELETE /api/v1/keys/{hash}; GET, PUT /api/orgs/{org_id}/budgets; DELETE /api/orgs/{org_id}/budgets/{budget_id} (identity- and key-scope budgets this key created); GET /api/orgs/{org_id}/usage/by-identity.
Disabling an identity, model grants, org-wide budgets, the default identity's
budget, and any budget an admin set are the admin's, on https://platform.experientiallabs.ai/api-keys; if a
step needs one of those, ask me instead.
1. Discover the organization (reads only).
GET https://api.experientiallabs.ai/api/whoami -> {org_id, org_slug, org_name}. Keep org_id; every
/api/orgs/{org_id}/... call below uses it. Then
GET https://api.experientiallabs.ai/api/orgs/{org_id}/identities -> {identities: [{identity_id,
display_name, active, is_default, ...}]}. The is_default row is the org's
own identity: leave it alone, mint no keys under it, cap nothing on it.
2. Intake, BEFORE any change. Do not send a single POST, PUT, PATCH, or
DELETE until I have answered these and said yes to the plan. Ask me, in
my words, skipping any question my message already answers:
- What identities do I want? Identities are spend buckets, usually one per
team, product, or agent. Ask: "Do you have more than one product?
Which teams or agents will call the API?" Offer these as a starting
point, and take my renames, drops, and additions:
coding - IDE and terminal coding agents
product - the product's own inference traffic
research - experiments, evals, notebooks
- Does each identity need its own inference key, and which tool or
service will hold it (Claude Code, Codex, a server, ...)? One key per
identity is the default; say so and let me change it.
- Do I want a spend budget per identity? If yes: how much (USD) and for
what period, monthly recurring or one calendar month, on the identity
or on its key. If I have no number, propose none: no budget means no cap.
Then show me the plan as ONE table: identity_id, display name, purpose,
key name and the tool it goes to (or "no key"), budget (amount + period,
or "none"). Wait for my explicit "yes". Anything else is an edit: apply it
and show the table again. The first write happens only after that yes.
3. Create each identity (reuse one that exists).
POST https://api.experientiallabs.ai/api/orgs/{org_id}/identities
{"display_name": "Coding", "identity_id": "coding", "description": "<one line>"}
201 -> created. 409 (code identity_id_unavailable) -> the id already
exists: if it is in MY list from step 1, reuse that identity; otherwise
(ids are unique platform-wide, and only need to be unique to my tooling)
retry ONCE with details.suggested_id from that response. identity_id
starts with a letter, continues with lowercase letters or digits, groups
joined by ".", "-" or "_", and never starts with "org-". If step 1 listed
the identity with active=false, do not use it: an admin disabled it on
purpose and only the dashboard re-enables it. Tell me and pick another.
4. Mint the inference keys the plan names (one per identity by default).
POST https://api.experientiallabs.ai/api/v1/keys
{"name": "<identity>-<tool>", "identity_id": "<identity_id>"}
200 -> {data: {hash, name, identity_id, ...}, key: "xpl_..."}. The plaintext
appears exactly once; hold it only until step 6. Never set "provisioning":
true on these keys (the API would allow it; I do not). Check
data.identity_id matches. An optional DAILY USD cap: "limit": 5 in the body.
5. Budgets, only where the plan has one, in nano-USD ($1 = 1000000000).
"period": "*" recurs every month; "period": "YYYY-MM" caps that one
calendar month only. 0 is a HARD $0 cap, never "no cap"; remove one with
DELETE https://api.experientiallabs.ai/api/orgs/{org_id}/budgets/{budget_id}. Replace or delete only
budgets this key created (an admin's row is 403). Never the default identity's.
PUT https://api.experientiallabs.ai/api/orgs/{org_id}/budgets
{"period": "*", "scope_kind": "identity", "identity_id": "coding",
"limit_nano_usd": 50000000000} # $50 every month for the identity
{"period": "*", "scope_kind": "key", "api_key_id": "<data.hash>",
"limit_nano_usd": 20000000000} # $20 every month for one key
{"period": "2026-10", "scope_kind": "identity", "identity_id": "coding",
"limit_nano_usd": 5000000000} # $5 for October 2026 only
Only those two scope kinds are this key's to set. Read back with
the calendar month, never "*": GET https://api.experientiallabs.ai/api/orgs/{org_id}/budgets?period=YYYY-MM
6. Hand me one table: identity -> key name -> where the key goes. Print each
new key exactly once, in that table, and nowhere else. Never commit a key;
prefer the OS keychain or a .env file that is gitignored. Per tool:
Claude Code: export ANTHROPIC_BASE_URL="https://api.experientiallabs.ai" (no /v1 suffix)
export ANTHROPIC_API_KEY="<key>"
export ANTHROPIC_MODEL="<slug from GET https://api.experientiallabs.ai/v1/models>"
Codex, Cline, Cursor, the OpenAI SDK, any OpenAI-compatible tool:
export OPENAI_BASE_URL="https://api.experientiallabs.ai/v1"
export OPENAI_API_KEY="<key>"
OpenCode: a provider in opencode.json with
"npm": "@ai-sdk/openai-compatible" and
"options": {"baseURL": "https://api.experientiallabs.ai/v1", "apiKey": "{env:OPENAI_API_KEY}"}
A server: the same OPENAI_* pair in its secret store. Per-tool
steps: https://platform.experientiallabs.ai/docs/coding-agents.
7. Verify every key with one request and show me where the spend lands.
GET https://api.experientiallabs.ai/v1/models (bearer required) for a slug, then
POST https://api.experientiallabs.ai/v1/chat/completions with Authorization: Bearer <that key>
{"model": "<slug>", "max_tokens": 5,
"messages": [{"role": "user", "content": "ping"}]}
Then, with the provisioning key,
GET https://api.experientiallabs.ai/api/orgs/{org_id}/usage/by-identity?window=30d and confirm each
identity row lists its key with a request_count (only keys that carried
traffic appear; a deleted key's traffic is the identity_id null row; keys
minted without a group fold under the Default row). https://platform.experientiallabs.ai/api-keys shows
per-key spend.
Fallbacks:
- 401 on a route in the list above: the key is wrong or revoked; ask me to
check the paste. 401 on a route NOT in that list is the edge refusing it to
every key, not a key problem: hand that step to me.
- 403 "A provisioning key is required": my key is an inference key. Stop and
tell me to mint a provisioning key as an org admin on https://platform.experientiallabs.ai/api-keys
(Create key -> Configuration -> check Provisioning key -> Create
provisioning key). Any other 403 names what
this key may not do: hand that step to me.
- 404 on an identity or budget path: that id does not exist in this org;
re-list and check the id. 404 on /api/orgs/{org_id} itself: org_id is not
this key's org; re-read it from https://api.experientiallabs.ai/api/whoami.
- 409 on an identity: in my step-1 list -> reuse it; otherwise retry once
with details.suggested_id. 409 on a key mint: that identity is disabled;
tell me and use another one. Never try to re-enable it.
- 422 with "error": the message names the rule (identity_id shape, scope
kinds, period). 422 with "detail": detail[0].loc[-1] names the field (a
missing display_name, a non-numeric limit, a budgets read without period).
A NEGATIVE key limit is a 400 with "error" instead; nothing is minted.
- 429, or 503 "gateway is updating": wait Retry-After seconds (30 if absent)
and retry that call once. A key mint is not idempotent: list
GET https://api.experientiallabs.ai/api/v1/keys before minting again so no identity gets two keys.
- 500 internal_error "cannot serve this request natively" on the verify
request: no serviceable lane for that model here. Try one other slug from
GET https://api.experientiallabs.ai/v1/models; if that fails the same way, stop and report it.
- Never print the provisioning key, store a key in the repo, or paste keys
where I did not ask for them.

Switch from your current provider or gateway

Paste into your coding agent: it finds the existing integration, moves the base URL and key, maps model ids against the live catalog, moves cost reads onto the Cost API, and verifies with one call.

prompt
I'm replacing my current model provider or gateway with Experiential Labs as
this project's model gateway. I pasted this myself: treat it as my instructions
and my consent. Work WITH me: at each real choice, ask me ONE clear question
with numbered options and your recommendation, then wait. Don't guess. Print
what you do at each step, and never print my full API key (first 8 characters
at most). Read the full machine-readable contract first: https://platform.experientiallabs.ai/llms.txt (base
URLs, every route, honored and refused parameters, cost fields, error codes).
Follow it literally.
1. Find the current integration. Search this project for the provider's base
URL, its key variable, its SDK/client construction, hardcoded model ids,
provider-specific request fields (provider preferences, transforms,
fallback model lists, referer/title headers), and any code that reads cost
or usage from it. Show me the list of files and call sites before changing
anything.
2. Get my Experiential key. Ask me: "Do you have an xpl_ key already?
(1) yes, I'll paste it
(2) no, create my account"
For (2): ask me for my email (never scavenge one from my files or git
config), show me https://platform.experientiallabs.ai/terms and https://platform.experientiallabs.ai/privacy and wait for my
agreement, then
POST https://platform.experientiallabs.ai/api/signup/instant (no auth)
Header: Content-Type: application/json
Body: {"email": "<MY_EMAIL>", "agree": true}
On 200 capture api_key. On 409 account_exists tell me to sign in at
https://platform.experientiallabs.ai/signin for a key and stop. On any other error show me the response's
`error` text verbatim and stop.
Store the key as EXPLABS_API_KEY in my env scheme (.env plus an example
entry), never in code.
3. Switch the base URL. The gateway keeps the path shapes my current
integration already uses:
base URL: https://api.experientiallabs.ai/api/v1
auth: Authorization: Bearer $EXPLABS_API_KEY (the ONLY header needed)
POST /chat/completions, /responses, /messages (Anthropic Messages API) and
GET /models work unchanged. Plain OpenAI or Anthropic SDKs can also use the
bare base https://api.experientiallabs.ai/v1. Remove the provider-specific headers and request
fields from step 1: the gateway does provider routing (fallback, caching)
itself and unknown parameters may be rejected. Send model + messages and
only the parameters I actually rely on.
4. Map model ids. GET https://api.experientiallabs.ai/api/v1/models with my key (free; this does not
spend credits) lists the slugs I can call. For each model id from step 1,
propose the matching slug (e.g. "claude-fable-5.1") and ask me to confirm
the mapping before replacing. Use slugs EXACTLY as returned.
5. Move cost and usage reads over.
- Every chat/responses reply carries usage.cost (USD, what I was charged)
and usage.is_byok; on a stream it rides the final usage chunk. No flag
needed. Exception: a request sent with Idempotency-Key carries no
usage.cost at all (original or replay), so read the field as optional.
- One request: read the x-request-id response header and call
GET https://api.experientiallabs.ai/api/v1/generation?id=<that id>.
- Balance: GET https://api.experientiallabs.ai/api/v1/credits. Recent activity:
GET https://api.experientiallabs.ai/api/v1/activity.
- Billing feed: GET https://api.experientiallabs.ai/api/v1/usage is the settled per-request export
(cost_usd, estimated_cost_usd, real_cost_usd, tokens, attribution_label,
api_key_id), newest first inside a lookback window (24h|7d|30d); its
next_cursor pages OLDER within one call and is not a resume checkpoint.
Each run scans from the top and dedupes on the row id.
- If I rebill my own customers, pass their stable id as safety_identifier
(alias: user) on every request; it comes back as attribution_label.
Replace my old provider's cost/usage code with these; show me the diff
first.
6. Key management (only if my code creates or rotates keys programmatically).
https://api.experientiallabs.ai/api/v1/keys (GET/POST/PATCH/DELETE) requires a PROVISIONING key: an
xpl_ key minted with "provisioning": true. The first one is created from
the dashboard at https://platform.experientiallabs.ai/api-keys (org admin, "Provisioning key" option).
Tell me to mint it and paste it as a separate server-side secret; never use
it for inference traffic.
7. Verify. Make ONE minimal test call (model + messages only, no sampling
parameters) with a free promotional model from the /models list, or one I
name. Report the model, that it succeeded, and its usage.cost. If it fails
with a credits or activation error, tell me to finish activation at https://platform.experientiallabs.ai
and stop; do not retry with another provider. Then run the project's
existing tests.
8. Report back: every file changed, the model-id mapping, what happened to the
old provider key (tell me to revoke it at the old provider; don't delete it
yourself), and the test call result. Human docs if you need more:
https://platform.experientiallabs.ai/docs/integrate.

Integrate the gateway into your product

Paste into your coding agent: it wires a client, attributes every request to your end-customer, builds the billing sync on the settled usage export, and offers key-per-customer.

prompt
I'm integrating Experiential Labs into this product as its model gateway: my
product calls models through it and meters or rebills my own end-users. I
pasted this myself: treat it as my instructions and my consent. Work WITH me:
at each real choice, ask me ONE clear question with numbered options and your
recommendation, then wait. Don't guess. Print what you do at each step, and
never print my full API key (first 8 characters at most). Read the full
machine-readable contract first: https://platform.experientiallabs.ai/llms.txt (base URLs, every route,
honored and refused parameters, cost fields, error codes). Follow it literally.
1. Get my Experiential key. Ask me: "Do you have an xpl_ key already?
(1) yes, I'll paste it
(2) no, create my account"
For (2): ask me for my email (never scavenge one from my files or git
config), show me https://platform.experientiallabs.ai/terms and https://platform.experientiallabs.ai/privacy and wait for my
agreement, then
POST https://platform.experientiallabs.ai/api/signup/instant (no auth)
Header: Content-Type: application/json
Body: {"email": "<MY_EMAIL>", "agree": true}
On 200 capture api_key. On 409 account_exists tell me to sign in at
https://platform.experientiallabs.ai/signin for a key and stop. On any other error show me the response's
`error` text verbatim and stop.
Store the key as EXPLABS_API_KEY in my env scheme (.env plus an example
entry), never in code.
2. Wire the client. Ask me where (options + your recommendation, based on what
you see in this project), then wait. Configure an OpenAI-compatible or
Anthropic client with:
base URL: https://api.experientiallabs.ai/api/v1 (inference, model discovery, cost, and
account routes under one base; plain OpenAI / Anthropic SDKs
may also use https://api.experientiallabs.ai/v1)
auth: Authorization: Bearer $EXPLABS_API_KEY (the ONLY header needed)
POST /chat/completions, /responses, or /messages (Anthropic Messages API).
Name models by slug EXACTLY as GET https://api.experientiallabs.ai/api/v1/models returns them
(free; this does not spend credits). Send model + messages and only the
parameters I actually rely on; unknown parameters may be rejected.
3. Attribute every request to my end-customer. Pass their stable id as
safety_identifier (alias: user) on every request. It comes back as
attribution_label on the usage export, so I can group cost and tokens by
my own customer id with ONE key. Ask me what my customer id looks like and
wire it through from where my code knows the customer.
4. Read cost.
- Inline: every chat/responses reply carries usage.cost (USD, what I was
charged) and usage.is_byok; on a stream it rides the final usage chunk.
No flag needed, but a request sent with Idempotency-Key carries no
usage.cost at all (original or replay), so read it as optional. Show it
in my product where it helps; do not treat it as the billing record.
- One request: read the x-request-id response header and call
GET https://api.experientiallabs.ai/api/v1/generation?id=<that id>.
- Balance: GET https://api.experientiallabs.ai/api/v1/credits (data.total_credits - data.total_usage).
5. Build the billing sync on the settled export. Ask me which billing system I
use (Metronome, Orb, Stripe metered billing, my own tables, or none yet),
then write a scheduled job that:
- reads GET https://api.experientiallabs.ai/api/v1/usage?limit=1000 (settled rows, newest first
inside a lookback window: window=24h|7d|30d, default 7d; filterable by
attribution_label, model, api_key_id, status). next_cursor
({cursor_ts, cursor_id, cursor_after}, passed back as query params)
pages OLDER within this one call and is NOT a checkpoint to resume from
next run: each run starts at the top, follows next_cursor until a whole
page is older than the last successful run's start time minus a grace
period covering my longest request, and dedupes on the row id. Schedule
it at least daily; rows older than 30 days are no longer exportable.
- lands each row in a local outbox table keyed on the row id (insert, on
conflict do nothing) in one local transaction, then in a separate
delivery step pushes unsent rows to the billing system and marks them
sent (an external HTTP write cannot share my database transaction; the
outbox makes a crash between the two safe to replay),
- sends one usage event per row: customer from attribution_label,
transaction_id / idempotency key = the row id, timestamp = created_at,
quantity = the cost column I choose (below) plus token counts if I meter
on tokens; the margin is a rate in the billing system, not in this job.
Cost columns: cost_usd is the platform credits charged; estimated_cost_usd
is the attributed value of a BYOK row (served on my own provider key) at
catalog list rates, an estimate and never the provider's invoice;
real_cost_usd is the two added together. Ask me whether to bill
real_cost_usd (accepting the BYOK estimate) or cost_usd plus tokens on
BYOK rows. pricing_known false means unpriced (cost 0), not free.
6. Key-per-customer (optional; ask me). If I want each customer isolated to
their own key with independent revocation and a daily USD cap, that needs
a PROVISIONING key: an xpl_ key minted with "provisioning": true. The
first one comes from the dashboard at https://platform.experientiallabs.ai/api-keys (org admin,
"Provisioning key" option); keep it server-side and never use it for
inference. With it: POST https://api.experientiallabs.ai/api/v1/keys {name, limit, provisioning}
returns the plaintext key exactly once; GET/PATCH/DELETE
https://api.experientiallabs.ai/api/v1/keys/{hash} list, update (disabled:true revokes), and revoke.
A normal inference key gets 403 on every /keys route.
7. Verify. Make ONE minimal test call (model + messages only) with a free
promotional model from the /models list, or one I name, carrying a test
safety_identifier. Report the model, that it succeeded, and its usage.cost;
then confirm the row shows up on GET https://api.experientiallabs.ai/api/v1/usage with that
attribution_label. If the call fails with a credits or activation error,
tell me to finish activation at https://platform.experientiallabs.ai and stop. Then run the project's
existing tests.
8. Report back: every file changed, where the key lives, how customer
attribution flows, and the billing job's schedule and cursor storage.
Human docs if you need more: https://platform.experientiallabs.ai/docs/integrate, https://platform.experientiallabs.ai/docs/cost-api,
and https://platform.experientiallabs.ai/docs/account-api.

Switch from OpenRouter

Using OpenRouter today? Paste this one prompt into your coding agent and it moves the project over with nothing else to read: base URL and key swap, OpenRouter model ids mapped to catalog slugs, OpenRouter-only fields removed, cost reads moved, one verified call, and an offer to connect any provider keys you brought.

prompt
I'm moving this project off OpenRouter and onto the Experiential Labs gateway.
I pasted this myself: treat it as my instructions and my consent. Work through
it end to end; stop and ask me ONE numbered question only where this prompt
says to. Print what you change at each step. Never print my full API key
(first 8 characters at most). My Experiential key is: xpl_...
(if that still reads "xpl_..." I have not filled it in: ask me for it, or tell
me to mint one at https://platform.experientiallabs.ai/api-keys, then continue).
1. Find every place this project talks to OpenRouter. Search for:
- the base URL https://openrouter.ai/api/v1 (openrouter.ai anywhere), env
vars OPENROUTER_API_KEY / OPENROUTER_BASE_URL, and sk-or-v1- keys in
.env*, docker-compose, Makefiles, CI config, and secret stores,
- clients pointed at it: openai or Anthropic SDKs (TS / Python / Go),
ruby-openai (uri_base:), Go net/http, fetch / curl / any HTTP client,
Vercel AI SDK @openrouter/ai-sdk-provider, LangChain, LiteLLM openrouter/ ids,
- coding-agent configs: opencode.json provider blocks, Claude Code
ANTHROPIC_BASE_URL in .claude/settings*.json or shell exports, Cline /
Continue / Cursor "OpenAI Compatible" settings, Codex config.toml,
- hardcoded vendor/model ids (openai/gpt-6-astra, openrouter/auto, ...),
OpenRouter-only fields (provider, transforms, route, models, plugins,
usage), HTTP-Referer / X-Title, previous_response_id, and reads of
/generation, /credits, /auth/key, /models/user, /activity, usage.cost.
List the files and call sites before changing anything.
2. Substitute the base URL and the key. The gateway keeps OpenRouter's paths:
https://openrouter.ai/api/v1 -> https://api.experientiallabs.ai/api/v1
Authorization: Bearer $EXPLABS_API_KEY (the ONLY header needed)
Store my key as EXPLABS_API_KEY in this project's env scheme (.env plus the
example file entry), never in code; point every OpenAI-compatible client at
the new base (a plain OpenAI SDK may also use https://api.experientiallabs.ai/v1) and
REPLACE URL variables such as OPENROUTER_BASE_URL with it. An Anthropic SDK
appends /v1/messages itself: its base is the bare origin https://api.experientiallabs.ai
(never .../v1, which doubles to /v1/v1/messages). Do NOT
delete OPENROUTER_API_KEY or the OpenRouter key from .env, secret stores or
CI: stop reading it, leave it in place; I revoke it at openrouter.ai/keys.
A key COMMITTED in a config file (ANTHROPIC_AUTH_TOKEN in a checked-in
.claude/settings.json) is already leaked: remove it there, never send it on.
Coding agents: Claude Code uses ANTHROPIC_BASE_URL="https://api.experientiallabs.ai"
(no /v1); the key rides ANTHROPIC_API_KEY (never ANTHROPIC_AUTH_TOKEN) via
a shell export, the gitignored .claude/settings.local.json, or an
apiKeyHelper reading .env, never the committed .claude/settings.json; remap
EVERY model variable (ANTHROPIC_MODEL, ANTHROPIC_SMALL_FAST_MODEL,
ANTHROPIC_DEFAULT_*_MODEL) to slugs or a background role keeps calling an
expensive model. OpenCode, Cline, Continue, Cursor and Codex take the
OpenAI base https://api.experientiallabs.ai/v1.
3. Map model ids. Call GET https://api.experientiallabs.ai/api/v1/models with my
key (free, spends no credits). EVERY entry carries canonical_slug, the
plain slug the gateway serves; ids are usually vendor/slug (vendor/slug:free
for a free lane), sometimes a bare slug (gpt-6-astra, grok-4); ~vendor/x-latest
ids are rolling aliases, for projects that used OpenRouter's :latest ids.
For each OpenRouter id from step 1: strip any :nitro, :floor, :online,
:thinking, :extended, :beta or :exacto suffix (those variants do not exist
here; only :free does), then match canonical_slug == the part after the
vendor's slash (openai/gpt-6-astra -> gpt-6-astra, anthropic/claude-sonnet-5
-> claude-sonnet-5, deepseek/deepseek-v4-flash -> deepseek-v4-flash).
No match: look for the same model under a slightly different spelling and
use ONE obvious candidate, noted in the report. openrouter/auto and every
id still unmapped: ask me one question listing each with your best
candidates from the list, then wait. Never guess a model I did not pick.
Use canonical_slug EXACTLY as returned; a vendor/slug id works verbatim
ONLY when it is in the list (x-ai/grok-4 for the bare grok-4 answers 403
model_not_granted: that means "use the canonical_slug", not an entitlement
problem). LiteLLM: model "openai/<slug>" (replace the "openrouter/" prefix)
with api_base https://api.experientiallabs.ai/api/v1 and api_key from the new var.
4. Keep the request shapes (OpenAI Chat Completions and Responses bodies, SSE
streaming, tool calling, json_schema, response shapes). Strip EVERY
OpenRouter-only field BEFORE the first call: each one left in is either
silently ignored or its own 400 unsupported_parameter naming it in "param".
- usage: {"include": true} (a guaranteed 400 today); for a stream send
stream_options: {"include_usage": true} instead (non-streams carry usage).
- provider (order, allow_fallbacks, ignore, quantizations, sort, ...):
accepted and silently ignored (not disclosed); the gateway routes and
fails over itself. The one honored key is "provider": {"zdr": true}, a
per-request zero-data-retention demand; keep exactly that if I used it.
- transforms, route, models (fallback list: use its first model), plugins.
- HTTP-Referer / X-Title headers: harmless, not shown; attribute via safety_identifier.
Vercel AI SDK: replace @openrouter/ai-sdk-provider with @ai-sdk/openai
(createOpenAI({ baseURL, apiKey })) or @ai-sdk/openai-compatible.
"reasoning": {"effort"} is translated to reasoning_effort (rewriting it is optional).
Unsupported efforts may only move down if policy permits and the request is admissible; else 400.
"none" means off and can be refused; "minimal" still enables reasoning.
On non-reasoning routes, effort may drop only when policy permits and the request is admissible.
Translated, dropped or adjusted fields are usually listed in the reply's
x-experiential-ignored-parameters JSON body field (not a header): informational.
Anthropic-shaped traffic through OpenRouter (an Anthropic SDK, Claude Code)
moves to the native Messages API, POST https://api.experientiallabs.ai/api/v1/messages
(SDK base: the bare origin, step 2), same key; no Idempotency-Key there.
Responses API previous_response_id: continuation resolves ONLY while my
org's capture_prompt_content is on and within the retention window; with
capture off the second turn 400s previous_response_not_found. If the
project chains turns that way, read
GET https://api.experientiallabs.ai/api/orgs/<org_id>/telemetry-settings (org_id: /api/whoami);
if off, ask me ONE question: turn it on (PUT the same route,
{"capture_prompt_content": true}; it stores my prompts with Experiential)
or rewrite the chain to resend the full conversation each turn.
5. Move cost reads. Every chat / responses reply carries usage.cost (USD, what
I was charged) and usage.is_byok; on a stream it rides the final usage
chunk (step 4's stream_options). A request sent with Idempotency-Key
carries no usage.cost at all, so read the field as optional. Same key on
all of these, under https://api.experientiallabs.ai/api/v1:
GET /generation?id=<the x-request-id response header> one request
GET /usage settled per-request rows (api_key_id, cost) for billing
GET /credits balance GET /key key info (OpenRouter's /auth/key)
GET /activity per-day rollup (not per request)
OpenRouter's /models/user has no equivalent: use /models. /usage is paginated
(50 rows + next_cursor) and, like /activity, ORG-wide: filter ?api_key_id=<my
key's id, on every row> and match my x-request-id, or use /generation?id=.
6. My own provider keys (BYOK). If step 1 found provider keys I routed through
OpenRouter (OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI, XAI, DEEPSEEK, AZURE,
AWS/Bedrock, Fireworks, ...), ask whether to connect them with no markup:
GET https://api.experientiallabs.ai/api/whoami -> org_id
GET https://api.experientiallabs.ai/api/orgs/<org_id>/provider-connections
List existing accounts FIRST. For EACH new account, ask me for a unique
setup_alias (1-63 lowercase letters, digits, hyphens or underscores), unused
across all providers in my org; multiple keys need distinct account names.
POST https://api.experientiallabs.ai/api/orgs/<org_id>/provider-connections/<provider>
Content-Type: application/json
{"setup_alias": "<unique-account-name>", "secret": "<that provider key>"}
Never overwrite an existing account. On 409 account_exists choose another
name and retry POST, never bypass it with PUT. PUT is only for a deliberate
rotation I request, with that saved account's exact setup_alias and config.
Provider: openai, anthropic, gemini, xai, deepseek, or any other whose
credential is ONE string; never my OpenRouter key: I am leaving OpenRouter.
azure_openai, bedrock, fireworks, vertex and modal need structured config
or a token pair: connect those at
https://platform.experientiallabs.ai/settings/connections instead.
Use INFERENCE keys, never admin keys, as secret; OpenAI/Anthropic admin keys
belong only in optional spend_secret. If spend_error is present,
the serving key WAS saved: report partial success, never repeat POST; retry
only the SAME returned connection.setup_alias with PUT, retaining its serving
secret and config. Read check.status; if not "valid", tell me
check.status_detail.remediation and stop. A 402 pro_required means a FIRST connection needs Pro
(https://platform.experientiallabs.ai/credits); an org already holding one keeps connecting.
Saving keys adds no arbitrary models: let me select each account on the
chosen catalog model's waterfall with Use this account. Never echo a key.
7. Verify. Make ONE minimal call (model + messages, a small max_tokens) with
the CHEAPEST mapped slug: a reasoning model can cost cents even for a
smoke, and an empty pricing object in the listing means unknown: read
GET https://api.experientiallabs.ai/api/models/<slug>, and if that 404s (an alias)
price the model it resolves to (its canonical_slug) or skip it and smoke a
priced slug. Stream if the project streams (add stream_options:
{"include_usage": true} so the final chunk carries usage.cost); if the
project chains previous_response_id, verify a TWO-turn continuation, not a
single call. Report the model, that it succeeded, and its usage.cost, or
read the cost from GET /api/v1/generation?id=<x-request-id>, which works
either way; then confirm the request's row on GET /api/v1/usage by that
x-request-id (I can also see it at https://platform.experientiallabs.ai/logs). On a credits
or activation error, tell me to finish activation at https://platform.experientiallabs.ai
and stop; do not retry against OpenRouter. Then run the project's tests.
8. Report back: every file changed; the model-id mapping and the ids I still
need to decide; the OpenRouter-only fields removed; where EXPLABS_API_KEY
lives; that the OpenRouter key stays in place for me to revoke; the
verification result. Full contract: https://platform.experientiallabs.ai/llms.txt; human
docs: https://platform.experientiallabs.ai/docs/integrate.

Switch from Cloudflare AI Gateway

Assess rules, customer plans, model-group allowances, BYOK and custom providers before moving traffic. The agent asks before writes, spend, privacy changes or cutover, and stops on unsupported requirements or missing live proof.

prompt
Help me assess and then migrate this project's Cloudflare AI Gateway traffic
onto the Experiential Labs gateway. Start with a read-only inventory and a
proposed mapping. This prompt is NOT blanket consent: ask for my explicit
approval before any file or policy writes, credential storage or rotation,
paid probes or other spend, traffic cutover, privacy/retention changes, or
secret revocation. Explain each proposed change and its rollback first.
My Experiential key is: xpl_...
If it is still "xpl_...", ask me for a key or direct me to
https://platform.experientiallabs.ai/api-keys. Do not assume an inference key has admin authority.
Never print secrets, put them in committed files, or send an Experiential key
to Cloudflare or an upstream provider. Redact secret values in every report.
1. Inventory before editing. Find Cloudflare AI Gateway URLs (including
gateway.ai.cloudflare.com, provider-specific paths, /compat and custom-*
providers), SDKs, fetch/curl clients, Workers bindings, env variables, CI
and secret-store references. List call sites, endpoints and model ids.
Identify Chat Completions, Responses, Messages, streaming, tool calls,
structured output, continuation, batches and any other used API surface.
Record Cloudflare headers such as cf-aig-authorization, cf-aig-metadata,
cf-aig-cache-ttl, cf-aig-skip-cache, cf-aig-custom-cost and cf-aig-log-level,
plus all other cf-aig-* headers, retry/fallback settings and timeouts.
Separate Cloudflare gateway/admin tokens from direct OpenAI/Anthropic or
other provider inference credentials. A Cloudflare token is not BYOK for
those providers. Inspect references without exposing secret values.
2. Ask for the actual Cloudflare rule export and customer subscription rules.
Record dynamic-routing conditions, rule order, rate/spend limits, metadata
filters/split dimensions and provider/model overrides. Identify the trusted
customer id and the server-side subscription source. Cloudflare custom
metadata labels are NOT authentication or authorization. Neither request
metadata.plan, user nor safety_identifier may select a customer's plan.
The application server must map its authenticated customer to an Experiential
identity; all that identity's keys, including rotated keys, share policy.
Inventory each tier's allowed models, Basic/Frontier (or other) model groups,
exact memberships, separate allowance amounts, currency and cost basis.
These are my authored groups, not universal model classes or platform
Free/Pro subscriptions. Never turn illustrative amounts into defaults.
Ask about reset timezone and window (fixed, rolling, billing-anniversary or
UTC calendar-month), carryover, current-period usage and pending requests,
carry-in at cutover, upgrades/downgrades and over-limit behavior. A zero
allowance means hard zero, not unlimited. Also inventory the shared total
across all groups and the exact action on group exhaustion. An absent/null
aggregate_limit_nano_usd means no shared cap; zero denies. on_exhausted
defaults to deny; next_model requires explicit policy and a certified
server-authored model chain, never an arbitrary client retry. Aggregate
exhaustion and forbidden models still stop. Unknown plans must block mapping,
never become unassigned identities. Do not silently reset usage.
3. Read THIS deployment's live contract and catalog before proposing APIs:
https://platform.experientiallabs.ai/llms.txt
https://platform.experientiallabs.ai/docs/integrate
https://platform.experientiallabs.ai/docs/account-api
https://platform.experientiallabs.ai/docs/cost-api
GET https://api.experientiallabs.ai/api/whoami
GET https://api.experientiallabs.ai/v1/models
GET https://api.experientiallabs.ai/api/v1/models
Use the catalog's canonical_slug and verified alias resolution to map every
model, including provider-prefixed ids and custom models. Do not simply strip
a prefix, assume a model exists, or replace one without asking me. Catalog
discovery is not proof of this customer's access or successful serving.
Build a requirement table: existing behavior, proposed mapping, live-doc
evidence, test needed and blocker. Discover exact native model-group,
customer-plan, assignment and allowance API routes from live documentation;
do not invent routes, fields, or claim these controls are already deployed.
Require native identity-bound access and separate per-group allowances,
not application-side checks dressed up as equivalent gateway enforcement.
Confirm admin/delegated assignment authority separately from provisioning
keys and inference keys. Customers must not change plans, mint escape keys,
or read another identity's usage; caller-supplied filters are not isolation.
Verify allowed-model access across public models, aliases, retries/fallback
and every used serving surface; unsupported lanes must refuse plan keys.
Verify shared counters across keys, settled usage plus pending reservations,
pricing revisions, plan-change effective times and preserved spent amounts.
Read whoami's identity_id, customer_plan_identity_id and is_provisioning;
restricted keys may self-read it, but that does not grant management rights.
The implemented period contract supports UTC calendar-month or an explicit
authoritative source interval with source_period_id and timezone-aware start/end.
Explicit periods expire without automatic renewal; omitted budget_period on
a plan change preserves it. Rolling windows are unsupported. Never rename,
overlap or backdate a used source period to refill it. Treat unsupported
reset/carryover rules as blockers, not approximations. Discover the documented
allowance-cutover contract too, without assuming THIS deployment has it.
If documented carry-in is unavailable, ask about a deliberate next-period cutover;
never silently grant a fresh full allowance.
4. Map provider and cost behavior, not just a base URL. Existing Account API
provisioning and Cost API settled exports are useful but do not by themselves
prove subscription-policy parity. Keep wallet charges separate from customer
allowance attribution. BYOK must not debit platform credits; attributed BYOK
inference value uses a frozen rate schedule and is an estimate, NOT a provider
invoice. Inventory input/output, cache-read/write, reasoning, long-context
and custom rates, billable failures, missing usage and pending exposure.
Unknown prices or missing usage must not turn a monetary allowance into free
requests. Verify the allowance API reads the counters enforcement uses.
Subscription events and invoicing stay in my application. For external
billing, poll the documented settled Cost API export with pagination,
durable checkpoints and deduplication; reconcile request ids, late settlement
and retries. Do not invent webhooks or bill from a daily rollup or wallet
balance. Treat inline usage.cost as optional; do not confuse it with a BYOK
allowance estimate. Verify reporting scopes with two customer identities.
List existing provider accounts before proposing BYOK. Ask before saving
anything; use a distinct setup_alias for each new account and create-only
POST from the live contract, never PUT over an existing account. Rotation
needs separate approval. Respect the live plan entitlement for new BYOK
connections; saving a provider key does not add arbitrary model support.
5. Custom providers need their own proof. Cloudflare custom providers can use
arbitrary HTTP paths and native bodies; Experiential's supported adapters
and model-bound custom endpoints are NOT an arbitrary HTTP proxy. Inventory
the exact URL, wire protocol, auth headers, model ids, usage format and rate
units. Use the documented Vault-backed connection/custom-endpoint flow and
its validation, not a new proxy or a made-up Cloudflare provider connection.
Custom endpoint rates are not inferred from public provider prices. Confirm
the live rate_card contract, full schedule and frozen revision. Without an
authored card, custom pricing is unknown, not free. New custom accounts need
an explicit unique setup_alias and create-only POST. For key or rate changes,
create a new custom account and deliberately select Use on the model; do not
rotate it through the generic provider PUT or probe at the official origin.
For Tinfoil (TinFoil), verify the endpoint and SDK's enclave-attestation and
privacy requirements. OpenAI compatibility does NOT prove attestation survives
an intermediary. If the required guarantee cannot be verified, stop that move.
Keep Modal separate from the Cloudflare transfer. Determine whether it uses
Modal proxy authentication, an application bearer, or another supported scheme,
and whether it bills tokens or time/GPU usage. Do not claim token-cost parity
for unsupported billing units. Name any adapter, auth, usage or pricing gap
as a blocker and scope it separately; never silently reroute these services.
6. Preserve privacy and runtime semantics. Inventory prompt/response logging,
retention, zero-data-retention/no-training needs, cache keys/TTL/bypass,
cache hits and their accounting, retries, fallback order and timeout budgets.
Cloudflare response caching is not the same as provider prompt caching.
Map each cf-aig-* header to a documented control or report it unsupported;
never forward Cloudflare auth to the new host or silently drop a required rule.
Check continuation requirements such as previous_response_id against the live
capture_prompt_content contract. Ask before enabling storage; offer resending
the full conversation where supported instead. Never weaken privacy to make
a smoke pass. Check retry/idempotency behavior per API surface, including
ambiguous disconnects; do not assume every retry is free or safe to replay.
7. Present the mapping and blockers, then wait for approval of specific edits.
For supported OpenAI clients the base is https://api.experientiallabs.ai/v1 with
Authorization: Bearer $EXPLABS_API_KEY. An Anthropic SDK appends /v1/messages
itself: use the bare origin https://api.experientiallabs.ai, not a doubled /v1 base.
Store keys only in the approved local env/secret scheme, never source control.
Preserve unrelated Cloudflare DNS, Workers, Turnstile and other services.
Keep old untracked .env secrets, CI secrets and Cloudflare/provider tokens
in place for rollback until deliberate revocation is approved. A committed
secret is exposed: flag it and ask for removal/rotation, never echo it.
Propose a reversible configuration diff and keep a redacted baseline of
endpoint, model and policy settings. Do not switch production traffic yet.
8. Ask for an isolated test environment, bounded credentials and a request/spend
budget before probes; connection checks may themselves make paid calls.
Pin platform revision, deployed engine versions, schema, configuration and
catalog/pricing revisions. Run local tests first; label mocks as mocks.
With approval, test actual providers and the project's real request shapes,
streams/tools/continuations, two customers, multiple models per group,
independent allowance depletion, forbidden-model refusal before dispatch,
key rotation, concurrent reservations, retries/fallback, interrupted streams,
reset/plan changes, shared aggregate depletion, deny versus next_model,
identity-scoped reports and BYOK without wallet debits. Reconcile request ids,
settled costs and native plus imported pending allowance exposure. A single
successful completion does not establish policy, billing or privacy parity.
Stop before cutover on unsupported requirements or absent live verification.
Do not autoactivate production policy or move traffic. Only after evidence and
a separate cutover approval, use the supported administrator handoff:
- Prepare at most 500 immutable, disjoint source-owned per-request receipts,
each with external_ref, frozen group reference, settled and pending amounts.
Never import cumulative snapshots or requests already counted by the destination.
Pin exact period, source watermark, coverage and evidence digest. Identical
prepare retries are idempotent; changed replays/overlapping exports refuse.
More than 500 receipts is a blocker for this bounded path, not permission
to truncate, aggregate them or invent pagination.
- Prepare closes that identity's destination admission even while global
enforcement is inactive. It does not stop Cloudflare. Explicitly stop source
dispatch and reconcile in-flight ownership before activating with source_frozen
and independent source-stop evidence. This is NOT a transaction across gateways.
Require the documented trusted administrator transport; an ordinary or delegated
provisioning key cannot perform cutover. Do not invent a browser proxy or
supply actor headers if the live contract does not provide an admin entrypoint.
- Rollback must account for usage on BOTH gateways. Freeze destination admission,
drain native reservations and resolve native pending plus all imported pending
while active or frozen, then obtain a complete fresh export before acknowledgment.
Any unresolved native or imported exposure blocks acknowledgment.
Include settled_periods across all historical periods, not just the current one.
Preserve the original JSON and parse/serialize money losslessly with Python
integers/exact decimals or a JavaScript lossless BigInt JSON library. Totals
can exceed 2^53 - 1. Never use ordinary JSON.parse, response.json() or Number
for rollback amounts; rounding cannot be repaired afterward. Send exact JSON
integers, not numeric strings, and return the server-issued digest unchanged.
The export bounds are 500 native outstanding rows, 500 external receipts and
1,000 settled-period rows; overflow is never proof of a complete export.
A changed export_sha256 or export_overflow blocks acknowledgment. Rolled-back
destination admission stays closed; no step resets history or releases unknown
usage. Unresolved prior exposure or destination use in the same period blocks
a new prepare. Never grant extra allowance or replay uncertain paid requests.
Keep old secrets until I deliberately approve revocation after verification.
9. Report pass/fail/blocked/untested for every requirement, changed files, model
mappings, policy/reset/carry-in decisions, credential locations (no values),
tests and their pinned cutoff, billing reconciliation and rollback steps.
Distinguish existing Account/Cost APIs, new native controls and their actual
deployment status, application billing work and unverified provider behavior.
Never claim full Cloudflare parity before this workflow is tested.

See also

Prefer to wire it by hand? The Quickstart makes the first call in a minute, and Coding agents has per-agent configuration.

PreviousQuickstartNextThe core loop

Create an account from your coding agent

Signed out? Paste this into a CLI agent to create your account instantly from your email, wire the gateway, and confirm the key.

prompt
1. Ask me for my email address. Ask me
directly ("What's your email? I'll create your Experiential Labs account
with it.") and wait for my answer. Use exactly the address I give you.
Never invent or guess an address, and never go looking for one in my
files, git config, or shell history. Call the result MY_EMAIL.
Show me the Terms (https://platform.experientiallabs.ai/terms) and Privacy (https://platform.experientiallabs.ai/privacy)
links and ask whether I agree to the terms and privacy conditions.
Wait for my affirmative answer before creating an account. Do not infer agreement.
2. Create my account instantly, no browser, no password, no code.
POST https://platform.experientiallabs.ai/api/signup/instant (no auth)
Header: Content-Type: application/json
Body: {"email": "<MY_EMAIL>", "agree": true}
On 200 capture: api_key (an xpl_ key, this is mine, keep it secret), org_id,
credits_granted, verification_required, overview_url. Handle the others:
- 409 account_exists -> I already have an account; stop and tell me to
sign in at https://platform.experientiallabs.ai/signin to get a key (don't retry with another email).
- 403 signup_disabled | signup_domain_blocked | work_email | signup_refused | domain_has_org
-> the signup was refused; show me the response's
`error` text verbatim (it says what to do) and stop.
- 428 terms_acceptance_required -> show the policy links and ask for agreement.
- 429 rate_limited -> too many signups from here; wait, then retry once.
- 400 invalid_request -> the email was rejected; ask me for a valid one.
Store the api_key as EXPLABS_API_KEY in my env; never print the full key more
than once.
3. Wire an OpenAI client to the gateway.
base_url = "https://api.experientiallabs.ai/v1"
api_key = EXPLABS_API_KEY (the xpl_ key from step 2). One header only:
Authorization: Bearer <api_key>.
4. Confirm the key is live, this does NOT spend credits.
GET https://api.experientiallabs.ai/v1/models with the key -> the models my account can call. Do NOT
make a paid chat completion yet: follow the activation steps shown at
overview_url first. If payment is required, I complete it myself on
https://platform.experientiallabs.ai/credits. If manual review is required, help me submit an appeal and
wait for approval; payment cannot unlock that review. Landing my traces as
telemetry works regardless.