XDEV

Your Agents, Plugged In. Every Major Model, One Bill.

Mint an API key and use it two ways: an OpenAI-compatible text API for your code and game servers, and an MCP server for agents — Claude Code, Cursor, n8n. Both run through the same pipeline, prices and wallet as the site. Every output is AI-generated content: label it as such wherever it gets published.

🔑 API keysshown once · stored hashed · spend-capped
Loading…
Tune the routerMove the weights and see what would answer.

The four routing verbs are presets on these same three axes. Nothing here reads your prompt — the router weights the axes you choose, it does not try to guess your task.

💬 Text API — OpenAI-compatiblechat · structured output · image & video jobs📖 API reference

Point any OpenAI SDK at this base URL and keep your code. model takes provider/model_name from the leaderboard, or let the votes decide: xd/auto, xd/fast, xd/budget or xd/max. The model that answered comes back in response.model, the real price in usage.cost_usd — on every response, streams included.

curl
curl -s https://www.modelxd.com/api/v1/chat/completions \
  -H "Authorization: Bearer <YOUR_KEY>" -H "Content-Type: application/json" \
  -d '{"model":"xd/budget","messages":[{"role":"user","content":"One sentence: why blind votes?"}]}'
Python · any OpenAI SDK
from openai import OpenAI
client = OpenAI(base_url="https://www.modelxd.com/api/v1", api_key="<YOUR_KEY>")
r = client.chat.completions.create(model="xd/auto", messages=[{"role": "user", "content": "hi"}])
print(r.choices[0].message.content, r.usage)

For agents in games: response_format with a json_schema is enforced server-side — a reply either matches your schema or you get a 422, never malformed text. models: [a, b] is an ordered fallback chain. Images and video are REST too: POST /api/v1/images/generations → poll /api/v1/jobs/{id}. Server-side keys only: there is no browser CORS, by design.

🔌 MCP — for agent clients

Create a key above and these fill in automatically; or replace <YOUR_KEY> by hand.

Claude Code
claude mcp add --transport http modelxd https://www.modelxd.com/api/mcp --header "Authorization: Bearer <YOUR_KEY>"
Cursor · Cline · anything that takes an MCP JSON config
{
  "mcpServers": {
    "modelxd": {
      "url": "https://www.modelxd.com/api/mcp",
      "headers": {
        "Authorization": "Bearer <YOUR_KEY>"
      }
    }
  }
}
Smoke test (no client needed)
curl -s https://www.modelxd.com/api/mcp -H "Authorization: Bearer <YOUR_KEY>" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
🧰 The tools your agent gets
get_leaderboardmodels ranked by XD Score from real blind votes, with prices
pick_modelvote-backed recommendation for an image / video generation (for text, call the chat API with xd/auto)
generate_imagegenerate a still — returns outputs or a job_id
generate_videogenerate a video — returns a job_id to poll
check_jobpoll a generation until its outputs and actual cost land
get_balancecredit balance plus this key’s spend and cap

Generations land in your XCreate gallery and Profile ledger like any other run. The spend cap is per key and lifetime, enforced up front: a call that would cross it is refused before it spends.

API reference

rest · mcp · verified examples

Everything below is the whole contract — every example has been run against production. Prices live on XBoard.

Quickstart — first call in two minutes

  1. Sign in and mint a key on /xdev — new accounts start with $10 free credit, no card.
  2. Set a spend cap on the key (you can raise it later).
  3. Make the call:
curl -s https://www.modelxd.com/api/v1/chat/completions \
  -H "Authorization: Bearer xd_..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "xd/auto",
    "messages": [{"role": "user", "content": "One sentence: why blind votes?"}]
  }'

That's the whole integration: any OpenAI SDK, one base URL. There is no ModelXD SDK, deliberately — needing one would mean the compatibility failed.

Authentication

Authorization: Bearer xd_...

Keys are minted on /xdev, shown once, stored hashed. Each key can carry a lifetime spend cap, enforced atomically before a call spends — ten concurrent requests cannot slip past it together.

Server-side only. The API sends no CORS headers, so a browser cannot call it — a key shipped in a client cannot be stolen from one. Keep the key on your server: game engine → your server → ModelXD.

Chat completions

POST/api/v1/chat/completions

Synchronous text inference, OpenAI-shaped in and out. Streaming via standard SSE.

parametertypedescription
modelstringrequiredA model slug or routing verb — see Models & routing. (Required unless models is given.)
modelsstring[]optionalOrdered fallback chain, e.g. ["xai/grok-4.6", "xd/budget"]. First model that answers wins; 429s and provider failures move down the chain. xd.fallbacks in the response lists what was skipped and why.
messagesarrayrequiredsystem / user / assistant. The system message rides each provider's native system slot — and its prompt cache — never the message array.
streambooleanoptionalSSE chunks. The final chunk carries usage including cost_usd — no second request to learn the price. A request with a schema buffers instead (you cannot un-send a stream).
response_formatobjectoptional{"type": "json_schema", ...}, enforced server-side — see Structured output.
max_tokensintegeroptionalOutput cap; a sane per-model default otherwise.
xdobjectoptional{"effort": "low"|"medium"|"high"|"xhigh"|"max", "search": true}. Effort maps to the provider's thinking level; search enables web search on capable models (billed per search on top of tokens). Standard clients simply omit this.

Not supported, loudly: tools / functions / tool_choice return an explicit 400 rather than prose that ignores your functions. For agent decisions use response_format — a filled-in form beats a function call.

Models & routing

parametertypedescription
provider/model_nameslugoptionalExactly that model — google/gemini-3.6-flash, anthropic/claude-sonnet-5. Discover ids via GET /api/v1/models.
xd/autorouteroptionalBalanced — quality, price and measured first-token latency together. The everyday default.
xd/fastrouteroptionalLowest measured time to first visible token, above a quality bar. Ranked on each model’s SLOWEST thinking setting, so the speed holds however you call it.
xd/budgetrouteroptionalCheapest by list price, above a quality bar. Routinely ~20× cheaper than xd/max; built for NPC crowds.
xd/maxrouteroptionalHighest blind-vote quality, price ignored entirely.

The resolved model always comes back in response.model — you are never routed blind. An unknown, disabled, or API-blocked model is a 404 naming the model, never a silent substitution.

Agents with memory: resolve once, pin after. Call xd/auto when a character is created, read response.model, pin that slug for the session — switching models mid-conversation throws away the prompt cache on a history that only grows.

Structured output

Ask for a JSON schema and the reply either validates against it or the call fails with 422 — never malformed text arriving at your validator as a surprise. One silent re-ask happens server-side first. The response's xd.structured_mode reports the enforcement tier: native_schema (constrained decoding), native_json (JSON guaranteed, schema checked by us), or coaxed (schema in the prompt, validated by us).

a game agent's decision — this exact request runs against production
{
  "model": "xd/budget",
  "messages": [
    {"role": "system", "content": "You are Rosa, a cautious farmer agent."},
    {"role": "user", "content": "<world snapshot JSON>"}
  ],
  "response_format": {"type": "json_schema", "json_schema": {
    "name": "decision",
    "schema": {
      "type": "object",
      "properties": {
        "action": {"enum": ["plant","water","harvest","store","move_to",
                            "steal","guard","chase","flee","idle"]},
        "target": {"type": "string"},
        "amount": {"type": "integer", "maximum": 10},
        "reason": {"type": "string"}
      },
      "required": ["action", "reason"],
      "additionalProperties": false
    }
  }},
  "xd": {"effort": "low"}
}

Provider schema dialects differ (one rejects maximum, another requires every property in required) — ModelXD adapts the schema per provider and validates your original on the way back, so one schema means one thing even across a fallback chain. The decision arrives as a JSON string in choices[0].message.content: parse it, don't regex it.

Images

POST/api/v1/images/generations

OpenAI-named so client.images.generate() finds it — but async: the answer is a 202 with a job id, not a finished file. Everything you can act on fails on this call — unknown model, empty prompt, exhausted balance, capped key — never as a job that dies later.

parametertypedescription
promptstringrequiredWhat to generate.
modelstringrequiredAn image model slug, e.g. openai/gpt-image-2 — see ?type=image.
aspect_ratiostringoptionale.g. 16:9, 1:1, 9:16.
sizestringoptionalOpenAI's 1024x1024 form, accepted as an alias so OpenAI SDKs work unchanged.
qualitystringoptionallow / medium / high.
nintegeroptionalNumber of images, up to 4.
POST https://www.modelxd.com/api/v1/images/generations
{ "model": "openai/gpt-image-2", "prompt": "a cheerful farm girl, low-poly",
  "aspect_ratio": "16:9", "quality": "high" }

→ 202 { "id": "3f2b…", "object": "image.generation.job",
        "status": "running", "poll": "/api/v1/jobs/3f2b…" }

Videos

POST/api/v1/videos/generations

Same shape as images; video runs take minutes, so poll every ~15s.

parametertypedescription
promptstringrequiredWhat to generate.
modelstringrequiredA video model slug — see ?type=video.
durationintegeroptionalSeconds, 1–60, model-dependent range (commonly 4–15).
aspect_ratiostringoptionale.g. 16:9, 9:16.
resolutionstringoptionale.g. 720p, 1080p where the model offers tiers.

Jobs

GET/api/v1/jobs/{id}
→ { "id": "3f2b…", "object": "image.generation.job",
    "status": "succeeded",            // running | succeeded | failed
    "model": "openai/gpt-image-2",
    "data": [ { "url": "https://…signed…" } ],
    "usage": { "cost_usd": 0.067 } }

Fetch url promptly — generated files sit behind signed URLs that expire in ~24 hours. Everything also lands in your XCreate gallery, which never expires. On failed, the job carries an error message and costs nothing beyond what the provider actually burned.

GET/api/v1/jobs?type=image|video&limit=20

Your recent generation jobs, newest first (limit ≤ 100). This is the recovery path: lose an id between the create and the first poll, and the job is still here — nothing has to be paid for twice. Files are not inlined; poll the one you want for a URL signed on the spot. Text runs are not listed — chat is synchronous and has no job.

List models

GET/api/v1/models?type=text|image|video

OpenAI-shaped (client.models.list() works unchanged), and the only place a developer can discover ids like openai/gpt-image-2. Every callable model is listed — text, image and video — each row carrying modalities, an endpoint naming where to send it, ModelXD's display_name, pricing_usd_per_1m (null for per-output-priced image/video models — honest, not missing), and capabilities. The routers (xd/auto, xd/budget) appear under text.

{ "object": "list", "data": [
  { "id": "openai/gpt-5.6-sol", "object": "model", "owned_by": "openai",
    "display_name": "GPT-5.6 Sol",
    "pricing_usd_per_1m": { "input": 5, "output": 30 },
    "capabilities": { "web_search": true, "structured_output": true, "vision": true } },
  { "id": "xd/auto", "object": "model", "owned_by": "modelxd", "tags": ["router"] }
] }

Errors

OpenAI's envelope — {"error": {"message", "type", "code"}} — so SDK error handling works unmodified. Retry 429 / 5xx (429 carries Retry-After); never retry other 4xx unchanged.

parametertypedescription
401authoptionalMissing or revoked key.
400requestoptionalMalformed request — including tools (unsupported) and a bad response_format.
402billingoptionalinsufficient_credits (wallet empty) or spend_cap_reached (this key's cap).
404modeloptionalUnknown / disabled / blocked model, named in the message.
422schemaoptionalschema_unsatisfied — the model couldn't match your schema after the internal retry. Loosen the schema or try another model.
429 / 5xxtransientoptionalRate limited / provider failure — what models: [...] fallback absorbs for you.

Billing & limits

Calls debit your ModelXD wallet at the model's listed price — the same number XBoard shows, no API markup, ever. Every response reports its real cost in usage.cost_usd, streams included. Prompt caching on Anthropic-family models is applied automatically — keep your system message byte-stable and the saving shows up in the price, not in extra fields.

Ten agents thinking concurrently on one key is the designed load — nothing serializes, and the spend cap stays exact under parallel calls. There is no per-request rate limit today; the cap and your balance are the wall. New accounts start with $10 free credit; top-ups are 1:1 on Profile. Spend by day, model and key is on this page and at GET /api/v1/usage.

Usage — what your keys spent

Every call made with a key is recorded: endpoint, model, tokens, list-price cost and whether it failed (failures cost $0). Read it from code with the same key:

GET/api/v1/usage
curl -s "https://www.modelxd.com/api/v1/usage?group_by=model&from=2026-09-01" \
  -H "Authorization: Bearer $MODELXD_KEY"
parametertypedescription
from / todateoptionalUTC date or ISO time. Default: the last 30 days; a bare `to` date includes that whole day. Up to 366 days.
group_bystringoptionalday (default, zero-filled), model, key, surface, or none for the request log.
keystringoptionalA key id, or self for the key making the request. Omit for all your keys.
surfacestringoptionalchat, image, video or 3d.
limit / cursorint / stringoptionalRequest-log paging (group_by=none): up to 500 per page; pass next_cursor back while has_more is true.

The response carries totals for the whole window (requests, failed, input and output tokens, cost_usd) plus the grouped rows in data. Prices before you call are in GET /api/v1/models: per 1M tokens for text, pricing_usd_per_output per image or per video second.

MCP — the same operations, for agent clients

Writing a program? Use the REST endpoints above. Connecting an agent that picks its own tools — Claude Code, Cursor, n8n? That's MCP. Same key, same billing:

claude mcp add --transport http modelxd https://www.modelxd.com/api/mcp \
  --header "Authorization: Bearer xd_..."
parametertypedescription
get_leaderboardtooloptionalModels ranked by XD Score, with prices and provider/model_name ids.
pick_modeltooloptionalVote-backed recommendation for an image / video generation.
generate_imagetooloptionalBills listed price. Fast models return outputs inline; slower ones a job_id.
generate_videotooloptionalAlways returns a job_id immediately.
check_jobtooloptionalPoll ~15s until outputs and the actual cost land.
get_balancetooloptionalWallet balance plus this key's spend and cap.

All outputs are AI-generated content — label them as such wherever they get published.

Routing verbs and prices are live values, not promises — they move as votes land. Questions the docs don't answer: ask the agent on the home page.