Skip to content

Menu

Docs menu

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.

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

Make your first request

Choose how you want to use Phantom. A key from /checkout holds its own credit; keys in one signed-in workspace share a balance.

API / SDK

  1. Get an API key: buy credit on /checkout 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

Or run the one-line installer, curl -fsSL https://phantom.codes/install | sh. It installs the CLI, and Node 24.22.1 if yours is older, then runs phantom setup. To check the script before it runs, compare its SHA-256 with this one, which this page reads from the script when the site is built:

curl -fsSL https://phantom.codes/install -o phantom-install.sh && \
echo "97bc74b9672e86bbca1235db73733aa7b500a2ce5a4317918d603f663c0a666e  phantom-install.sh" | shasum -a 256 -c - && \
sh phantom-install.sh
  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. Until you add credit or a plan, phantom-1 runs as phantom-1:free: free models only, within its rate caps.

phantom-1 runs its default model in each tier. To pick your own, set your key's lineup on /keys, with phantom lineup, or with PUT /v1/key/lineup.

Call the API from your code

  1. Get a key on /checkout (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 on text chat, and phantom-1:free and phantom-1:zdr on any 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 and the daily cleanup removes it. 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 skips the economy model unless that model's provider keeps no data.
  • no_training: true (body): only providers that don't train on prompts. It skips the economy model on the same terms.
  • x-phantom-free: off (header): keep this request off the economy model when that model is free.
  • x-phantom-content-logged (response header): false on every call that reaches a model, and on an upstream error passed through. A refusal before that, such as a bad key or no credit, doesn't send it. 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

Create a chat completion

Auth: API key

phantom-1 chooses between economy, standard, and frontier models for each new user message. Its tool calls keep the selected model, including Anthropic messages containing only tool_result blocks. Request zero-data-retention routing with zdr: true: phantom-1 then skips the economy 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 to identify the conversation across requests. Send x-phantom-escalate: 1 to move the current user message up a tier; the next user message gets a fresh decision. See Choose how models are selected and Privacy controls below.

Request

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
  }'

Streaming request

curl -N 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": true
  }'

Streaming response

data: {"choices":[{"delta":{"content":"Hello"}}], ...}

data: {"choices":[{"delta":{"content":"!"}}], ...}

data: {"choices":[], "usage":{"prompt_tokens":8, "completion_tokens":2}, ...}

event: phantom.receipt
data: {"receipt":"<base64url payload>.<base64url signature>", ...}

data: [DONE]
POST/v1/responses

Create a response (Responses API)

Auth: API key

OpenAI's Responses API, for Codex CLI and responses.create. Routing, holds and receipts work as on chat. Phantom keeps no conversation: store is always off, and previous_response_id is a 400, so send the whole conversation in input. Add stream: true to stream.

Request

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

Create a message (Anthropic)

Auth: API key

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.

Request

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

Create embedding vectors for text input

Auth: API key

Request

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

Rerank documents by relevance to a query

Auth: API key

Request

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

Generate images from a text prompt

Auth: API key

Request

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

Start a video generation job, paid up front

Auth: API key

Answers 202 with a job id. Poll it with GET /v1/video/generations/{id}. A job that fails is refunded when you poll.

Request

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}

Poll a video generation job for its result

Auth: API key

status is pending while the upstream works, completed with the video URL and receipt, or failed and refunded.

Request

curl https://phantom.codes/v1/video/generations/vid_123 \
  -H "Authorization: Bearer $PHANTOM_KEY"
POST/v1/audio/speech

Generate speech audio from text

Auth: API key

Request

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

Transcribe speech audio into text

Auth: API key

Send as multipart/form-data.

Request

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

Check whether a model is answering right now

Auth: None

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.

Request

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

List models with their prices and limits

Auth: None

No key required.

Request

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

Describe the API and where its docs are

Auth: None

Lists the main routes. No key required.

Request

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

Verification

GET/v1/receipts/key

Read the public key that signs receipts

Auth: None

Public key for checking receipts. No key required.

Request

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

Write a receipt's hash to Solana for $0.01

Auth: API key

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

Request

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

Look up whether a receipt is anchored on Solana

Auth: None

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

Request

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

Keys

GET/v1/key/balance

Read the key's balance and expiry

Auth: API key

Request

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

Read the workspace's plan and this week's use

Auth: API key or workspace credential or workspace session or session cookie

The Pro or Max plan that pays for this key's phantom-1 calls, its Stripe status, and model cost used against the limit in the last 7 days.

Request

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

Start a Stripe checkout for a Pro or Max plan

Auth: API key or workspace credential or workspace session or session cookie

Returns a Stripe Checkout link that puts the plan on this key. Open it in a browser and pay by card.

Request

curl -X POST https://phantom.codes/v1/plan/checkout \
  -H "Authorization: Bearer $PHANTOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"plan": "pro"}'
POST/v1/plan/portal

Open the Stripe billing portal for the plan

Auth: Workspace credential or workspace session or session cookie

Returns a link to Stripe's billing page for the plan on this key: change the card, switch plan or cancel.

Request

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

Read the key's budget and spend-rate caps

Auth: API key

Spent this period, days left, and whether spending is on_pace or ahead.

Request

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

Set the key's budget and spend-rate caps

Auth: API key

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. Over the budget a paid call answers 402; over the per-minute cap it answers 429 with Retry-After. Rotating a key keeps both caps.

Request

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

Read the key's route policy for model auto

Auth: API key

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

Request

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

Replace the route policy

Auth: API key

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.

Request

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

Update part of the route policy

Auth: API key

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

Request

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

Clear the key's route policy for model auto

Auth: API key

Remove the route policy.

Request

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

Test which model a request would be routed to

Auth: API key

Shows the model a chat request would run on now, and the rule that picked it. No charge. With a route policy it calls no model; for phantom-1, or auto on a key with no policy, picking the model can call one.

Request

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

Read the models each phantom-1 tier may use

Auth: API key

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

Request

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

Set the models each phantom-1 tier may use

Auth: API key

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. phantom-1:zdr ignores the lineup. The Phantom CLI does the same with phantom lineup. How a lineup works.

Request

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",
       "fallbacks": {"economy": "openai/gpt-6-luna"}}'
DELETE/v1/key/lineup

Reset the phantom-1 lineup to the defaults

Auth: API key

Go back to the default model in every tier.

Request

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

Revoke the calling key; its workspace keeps credit

Auth: API key

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

Request

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

Rotate the key: new secret, same settings

Auth: API key

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.

Request

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

Fetch what is stored about you

Auth: API key or session cookie

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

Request

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

Credit

GET/v1/bundles

Purchase limits and presets

Auth: None

Request

curl https://phantom.codes/v1/bundles
GET/v1/coins

List the coins a Solana payment accepts

Auth: None

Request

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

Buy credit with Solana USDC over MPP or x402

Auth: None. Paid with a 402 payment.

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.

Request

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

Start a Solana Pay payment for prepaid credit

Auth: None

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.

Request

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

Start a card payment for prepaid credit

Auth: None

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.

Request

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

Poll a purchase and collect its key once paid

Auth: Recovery code in x-phantom-recovery-code, once paid

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

Request

curl https://phantom.codes/v1/purchase/pay_123/status \
  -H "x-phantom-recovery-code: 5xXj_4..."
POST/v1/purchase/recover

Recover a purchased key from its recovery code

Auth: Recovery code in the body

Request

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

Account

GET/v1/account/grant

Read the account's past grant and referrals

Auth: Session cookie

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

Request

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

Erase the signed-in account and its links

Auth: Session cookie

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

Request

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.