On this page

Build with Phantom

Connect your application or coding agent to hundreds of AI models through one API. Select a model directly, or use phantom-1 for automatic chat routing. Start with an API key and a working request, then explore routing, budgets, and signed receipts.

Base URL
https://phantom.codes/v1
Auth
Authorization: Bearer $PHANTOM_KEY
API format
OpenAI-compatible routes; /v1/messages is Anthropic-compatible. Keep your key secret.

Concepts: routing, receipts, and what links to a key.

Full reference: every field, header and error, with requests you can run.

Make your first request

Choose how you want to use Phantom. Both paths use the same account and balance.

API / SDK

  1. Get an API key: buy credit on /buy and copy the key it shows once, or sign in and create one on /keys. Keep it in an environment variable such as PHANTOM_KEY.
  2. Point an OpenAI SDK at https://phantom.codes/v1 with that key (examples in Call the API).
  3. Send a chat request. Set model to a model ID, or to phantom-1 to let Phantom choose.
  4. Read what it cost and which model ran from the response headers (Response headers).

CLI / coding agent

  1. Install the Phantom CLI.
npm i -g @phantomcodes/cli
  1. phantom login: Sign in, or start a guest workspace.
  2. phantom fund --amount 10 --card: Add $10 in credit by card. Other supported payment methods are available.
  3. phantom claude: Start Claude Code using your Phantom balance. Codex and pi are supported too.

phantom login keeps the CLI's own key in the macOS Keychain, or in a private file on other systems; it doesn't print one for an SDK. Add credit when a call needs it: phantom-1:free runs without credit, within its rate caps.

Call the API from your code

  1. Get a key on /buy (card or crypto) or /keys.
  2. Point any OpenAI SDK at https://phantom.codes/v1 with your key.
  3. Call phantom-1. It works on every endpoint, and each endpoint runs its own model.
from openai import OpenAI

client = OpenAI(
    base_url="https://phantom.codes/v1",
    api_key="sk-phantom-your-api-key"
)

response = client.chat.completions.create(
    model="phantom-1",
    messages=[
        {"role": "user", "content": "Hello!"}
    ],
    stream=True
)

for chunk in response:
    if chunk.choices:
        print(chunk.choices[0].delta.content or "", end="")

Coding agents

# Installs the phantom-ai skill for Claude Code, Codex, Cursor and pi
npm i -g @phantomcodes/cli
phantom login                          # sign in by device code
phantom setup
phantom setup --agent claude --provider   # Claude Code runs on Phantom AI

Claude Code runs non-Claude models such as phantom-1, but Anthropic doesn't support that setup. The Phantom CLI on npm.

Choose how models are selected

phantom-1
Phantom's built-in selection. On chat, Messages and Responses it picks economy, standard or frontier for a new conversation. Chat with images, and each other endpoint, runs one model for that job.
Configurable: Per request: zdr, no_training, x-phantom-free and x-phantom-escalate. A key's route policy can also set zdr and no_training. A key's lineup picks the model in each tier (PUT /v1/key/lineup).
auto
The key's route policy picks the model (GET, PUT, PATCH, DELETE /v1/key/route). On a key with no route policy, auto runs phantom-1's selection.
Configurable: Yes: PUT /v1/key/route. Test it first with POST /v1/key/route/test, which charges nothing.
An explicit model ID
Calls that model directly, such as anthropic/claude-sonnet-5.5.
Configurable: Skips phantom-1 selection. A route policy with applies_to: "all" still routes it.

A route policy normally routes only auto. With applies_to: "all" it also routes requests that name a model, except phantom-1, phantom-1:free and phantom-1:zdr on text chat, which always use their own selection.

Privacy controls

Phantom stores no prompt or completion text: it records the key, model, token counts and cost of each call, and routing keeps a salted hash of each conversation until its routing window ends. Model providers have their own retention and training policies; these controls choose which providers a request may use. Send zdr and no_training on a request, or set them in a key's route policy to apply them to every request on that key.

  • zdr: true (body): only providers that keep no data. It excludes the free model.
  • no_training: true (body): only providers that don't train on prompts. It excludes the free model too.
  • x-phantom-free: off (header): keep this request off the free model.
  • x-phantom-content-logged (response header): false. It covers Phantom's own logging, not what a provider keeps.
  • To pick a model, phantom-1 and phantom-1:zdr (and auto on a key with no route policy) can send up to 1,000 characters of a conversation's first request to a model.

The full policy is on /privacy.


Model calls

POST/v1/chat/completions

phantom-1 chooses between economy, standard, and frontier models for the first request of a new chat conversation. Follow-up requests normally stay on the selected model. Request zero-data-retention routing with zdr: true: phantom-1 then skips the free model, and if the model it picks has no zero-data-retention provider the request fails with 400 zdr_unavailable. phantom-1:zdr picks only from models that have one. Use x-phantom-session when your client needs to keep the same task across requests. See Choose how models are selected and Privacy controls below.

curl https://phantom.codes/v1/chat/completions \
  -H "Authorization: Bearer $PHANTOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "phantom-1",
    "messages": [{"role": "user", "content": "Hello"}],
    "stream": false
  }'
POST/v1/responses

OpenAI's Responses API, for Codex CLI and responses.create. store is always off; send the whole conversation in input.

curl https://phantom.codes/v1/responses \
  -H "Authorization: Bearer $PHANTOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "phantom-1",
    "input": "Write a haiku about receipts."
  }'
POST/v1/messages

The Anthropic-compatible Messages endpoint. It takes your key in x-api-key or Authorization: Bearer. Claude Code: ANTHROPIC_BASE_URL=https://phantom.codes, ANTHROPIC_AUTH_TOKEN set to your key, and ENABLE_TOOL_SEARCH=true if you run MCP servers. Takes any model id, including auto; an Anthropic-style id such as claude-sonnet-5-5 maps to anthropic/claude-sonnet-5.5.

curl https://phantom.codes/v1/messages \
  -H "x-api-key: $PHANTOM_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-5-5",
    "max_tokens": 256,
    "messages": [{"role": "user", "content": "Hello"}]
  }'
POST/v1/embeddings
curl https://phantom.codes/v1/embeddings \
  -H "Authorization: Bearer $PHANTOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "phantom-1",
    "input": "The quick brown fox"
  }'
POST/v1/rerank
curl https://phantom.codes/v1/rerank \
  -H "Authorization: Bearer $PHANTOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "phantom-1",
    "query": "capital of France",
    "documents": ["Paris is the capital.", "Berlin is in Germany."],
    "top_n": 2
  }'
POST/v1/images/generations
curl https://phantom.codes/v1/images/generations \
  -H "Authorization: Bearer $PHANTOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "an isometric server room",
    "n": 1,
    "size": "1024x1024",
    "response_format": "url"
  }'
POST/v1/video/generations

Returns a job id.

curl https://phantom.codes/v1/video/generations \
  -H "Authorization: Bearer $PHANTOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "phantom-1",
    "prompt": "a drone shot over a canyon",
    "seconds": 5,
    "aspect_ratio": "16:9"
  }'
GET/v1/video/generations/{id}

Returns the receipt once the job completes.

curl https://phantom.codes/v1/video/generations/vid_123 \
  -H "Authorization: Bearer $PHANTOM_KEY"
POST/v1/audio/speech
curl https://phantom.codes/v1/audio/speech \
  -H "Authorization: Bearer $PHANTOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "phantom-1",
    "input": "Hello from Phantom AI",
    "voice": "alloy"
  }' --output speech.mp3
POST/v1/audio/transcriptions

Send as multipart/form-data.

curl https://phantom.codes/v1/audio/transcriptions \
  -H "Authorization: Bearer $PHANTOM_KEY" \
  -F file=@audio.mp3 \
  -F model=openai/whisper-1
GET/v1/models/status

Whether a model is answering now: up, degraded or down, with each provider's uptime, speed and price. For phantom-1, job picks which of its models (chat, vision, embedding, rerank, image, video, speech, transcription). No key required.

curl "https://phantom.codes/v1/models/status?model=phantom-1&job=chat"
GET/v1/models

No key required.

curl https://phantom.codes/v1/models
GET/v1

Lists the main routes. No key required.

curl https://phantom.codes/v1 -H "Accept: application/json"

Verification

GET/v1/receipts/key

Public key for checking receipts. No key required.

curl https://phantom.codes/v1/receipts/key
POST/v1/receipts/anchor

Records the receipt hash on Solana. $0.01. Recording the same receipt again is free.

curl https://phantom.codes/v1/receipts/anchor \
  -H "Authorization: Bearer $PHANTOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"receipt": "<x-phantom-receipt value>"}'
GET/v1/receipts/anchor

leaf is the receipt hash, the SHA-256 of the receipt string. No key required.

curl "https://phantom.codes/v1/receipts/anchor?leaf=<sha256-of-receipt>"

Keys

GET/v1/key/balance
curl https://phantom.codes/v1/key/balance \
  -H "Authorization: Bearer $PHANTOM_KEY"
GET/v1/key/budget
curl https://phantom.codes/v1/key/budget \
  -H "Authorization: Bearer $PHANTOM_KEY"
PATCH/v1/key/budget

Set a cap per period (budget_usd) or a per-minute cap (rate_usd_per_min). period_days sets the period length; null means one month. Send null to remove a cap. Setting budget_usd starts a new period.

curl -X PATCH https://phantom.codes/v1/key/budget \
  -H "Authorization: Bearer $PHANTOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"budget_usd": 20, "period_days": 30, "rate_usd_per_min": 0.10}'
GET/v1/key/route

The route policy that picks a model for model "auto".

curl https://phantom.codes/v1/key/route \
  -H "Authorization: Bearer $PHANTOM_KEY"
PUT/v1/key/route

Replace the route policy. models: 1 to 10 ids. Rules run in order; the first match picks the model, else models[0]. Conditions: pace, budget_left_pct_below, days_left_below, has_tools, input_tokens_over, reasoning_requested. use: a model from models, cheapest, first or next. Optional: applies_to (auto, all), on_empty (stop, cheapest), fallback_on_error.

curl -X PUT https://phantom.codes/v1/key/route \
  -H "Authorization: Bearer $PHANTOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"models": ["anthropic/claude-sonnet-5.5", "phantom-1"],
       "rules": [{"if": {"pace": "ahead"}, "use": "cheapest"}]}'
PATCH/v1/key/route

Updates the top-level fields you send and keeps the rest.

curl -X PATCH https://phantom.codes/v1/key/route \
  -H "Authorization: Bearer $PHANTOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"on_empty": "cheapest"}'
DELETE/v1/key/route

Remove the route policy.

curl -X DELETE https://phantom.codes/v1/key/route \
  -H "Authorization: Bearer $PHANTOM_KEY"
POST/v1/key/route/test

Shows the model a chat request would run on now, and the rule that picked it. No charge.

curl -X POST https://phantom.codes/v1/key/route/test \
  -H "Authorization: Bearer $PHANTOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "auto", "messages": [{"role": "user", "content": "hi"}]}'
GET/v1/key/lineup

The model each phantom-1 tier runs on this key, and the models each tier can run, with prices.

curl https://phantom.codes/v1/key/lineup \
  -H "Authorization: Bearer $PHANTOM_KEY"
PUT/v1/key/lineup

Pick the model for economy, standard or frontier from that tier's choices. A tier left out or null runs its default. fallbacks names the model to try when a tier's model fails.

curl -X PUT https://phantom.codes/v1/key/lineup \
  -H "Authorization: Bearer $PHANTOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"frontier": "anthropic/claude-opus-5.5"}'
DELETE/v1/key/lineup

Go back to the default model in every tier.

curl -X DELETE https://phantom.codes/v1/key/lineup \
  -H "Authorization: Bearer $PHANTOM_KEY"
DELETE/v1/key

Can't be undone. Deletes the key. Its leftover credit is lost. 409 while a request on it is running.

curl -X DELETE https://phantom.codes/v1/key \
  -H "Authorization: Bearer $PHANTOM_KEY"
POST/v1/key/rotate

Issues a new key with the same balance, expiry and caps. The old key stops working at once, so update every client that uses it right after.

curl -X POST https://phantom.codes/v1/key/rotate \
  -H "Authorization: Bearer $PHANTOM_KEY"
GET/v1/privacy

Shows what is linked to your key or account. Send a key, a session cookie, or both.

curl https://phantom.codes/v1/privacy \
  -H "Authorization: Bearer $PHANTOM_KEY"

Credit

GET/v1/bundles
curl https://phantom.codes/v1/bundles
GET/v1/coins
curl https://phantom.codes/v1/coins
POST/v1/x402/top-up

Pay with Solana USDC through pay (MPP or x402). The first call answers 402; pay pays it and sends the request again, which returns a key holding the credit, shown once with its recovery code. An empty body buys $1. Set target_api_key in the body to add the credit to a key you already have.

pay curl -X POST https://phantom.codes/v1/x402/top-up \
  -d '{"amount_usd": 1}'
POST/v1/purchase/solana

Pay with USDC, USDT or SOL from a Solana wallet. coin: usdc, usdt or sol. Returns the recipient, the exact amount, a reference to attach to the transfer, and a Solana Pay link. Poll /v1/purchase/{id}/status, which checks the chain. Set target_api_key to add the credit to a key you already have.

curl https://phantom.codes/v1/purchase/solana \
  -H "Content-Type: application/json" \
  -d '{"amount_usd": 5, "coin": "usdc"}'
POST/v1/purchase/card

Pay by card. The card also pays Stripe's 2.9% + $0.30 fee, so $20 charges $20.91. From $50 Phantom pays the fixed part, so $50 charges $51.50. 1% bonus credit from $100, 2% from $500, on card, Solana and x402. Set target_api_key to add the credit to a key you already have. This links the payment to that key.

curl https://phantom.codes/v1/purchase/card \
  -H "Content-Type: application/json" \
  -d '{"amount_usd": 25, "validity_days": 365, "target_api_key": "sk-phantom-..."}'
GET/v1/purchase/{id}/status

Returns the key once paid. Send the recovery code in the x-phantom-recovery-code header.

curl https://phantom.codes/v1/purchase/pay_123/status \
  -H "x-phantom-recovery-code: 5xXj_4..."
POST/v1/purchase/recover
curl https://phantom.codes/v1/purchase/recover \
  -H "Content-Type: application/json" \
  -d '{"recovery_code": "5xXj_4..."}'

Account

GET/v1/account/grant

Historical grant status. New grants are no longer available. Needs a session cookie.

curl https://phantom.codes/v1/account/grant \
  -H "Cookie: better-auth.session_token=..."
DELETE/v1/account

Deletes grants and referrals and signs you out. API keys keep working: to stop one, call DELETE /v1/key with it, or rotate it.

curl -X DELETE https://phantom.codes/v1/account \
  -H "Cookie: better-auth.session_token=..."

Response headers

x-phantom-request-id: req_4f1c9ab2d0e7
x-phantom-content-logged: false
x-phantom-receipt: <base64url payload>.<base64url signature>

# chat/completions
x-phantom-upstream-model: deepseek/deepseek-v4.1-flash
x-phantom-upstream-request-id: ...

# embeddings, rerank, images, audio, video
x-phantom-cost-usd: 0.000042

# every priced call, except a stream
x-phantom-balance-usd: 1.028916
Streaming receipts
A stream sends the receipt as its last event, event: phantom.receipt, right before data: [DONE].
Receipt verification
Check the signature in your own code with WebCrypto, or paste the receipt at /verify.

Generation parameters

ParameterBehavior
reasoning_effort, thinkingPassed to the model provider unchanged.
max_tokens, max_completion_tokensCapped at what your balance can pay for and at the model's output limit. Included in the receipt's params_hash.
model_servedThe model name in the provider's response. /verify flags it when it differs from model_requested.