Documentation
Start in one prompt.
ScaledThought is agent-first: your agent signs up and does the work, and you get a dashboard for billing, keys and teammates when you want it. Everything here is written for you and for the agent. The API is OpenAI-compatible: most OpenAI SDK code works by changing the base URL and key.
Quickstart
Easiest: ask your agent
Paste this into Claude Code, Codex, Cursor or any agent that can run commands. It reads /llms.txt and runs the setup steps.
Deep links into apps you have installed. Claude and Claude Code are trademarks of Anthropic; Codex is a trademark of OpenAI. ScaledThought is not affiliated with or endorsed by either.
Not affiliated with or endorsed by Anthropic, OpenAI, Cursor or any model publisher. Claude, Claude Code, Codex, Cursor, OpenAI and model names are trademarks of their owners.
Or run one command
Creates an account in the US region, writes ST_API_KEY and ST_BASE_URL to .env, and lists the models live there. Safe to re-run: with a key already present it just reports the account. The Canada region is coming soon; until it opens, its API doesn't answer and --region ca fails with network_error.
npx -y scaledthought@0.3 init --region usBuy credits
Every call is paid from prepaid credits, $20 to $2,000 per top-up, at most $2,000 per 24 hours and 2 open approval links at a time. The agent asks for a top-up and sends the approval_url it gets back to its human, who pays by card. Until the credits land, calls return 402 insufficient_credits. More in Credits & approval.
npx -y scaledthought@0.3 billing topup --amount-usd 20Make your first call
curl "$ST_BASE_URL/chat/completions" \
-H "Authorization: Bearer $ST_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "deepseek-v4-1-flash",
"messages": [{"role": "user", "content": "Hello"}]
}'Authentication
Send your key as a bearer token. Keys are scoped to one region and can carry their own budget. The base URL is your region's host, saved to .env as ST_BASE_URL: https://api.us.scaledthought.com/v1 (Canada's https://api.ca.scaledthought.com/v1 is coming soon).
Authorization: Bearer $ST_API_KEY
Content-Type: application/jsonChat completions
POST /v1/chat/completions accepts the OpenAI request shape. Use a model id from npx -y scaledthought@0.3 models --live or /pricing.
curl "$ST_BASE_URL/chat/completions" \
-H "Authorization: Bearer $ST_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "deepseek-v4-1-flash",
"messages": [{"role": "user", "content": "Hello"}]
}'Streaming
Set stream: true to receive server-sent events in the OpenAI delta format.
stream = client.chat.completions.create(
model="deepseek-v4-1-flash",
messages=[{"role": "user", "content": "Write a haiku about open weights"}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")Tool calling
Pass tools in the OpenAI format. Models that support tool calling return standard tool_calls. Support and quality vary by model, so check a model's card before relying on it.
resp = client.chat.completions.create(
model="deepseek-v4-1-flash",
messages=[{"role": "user", "content": "Weather in Toronto?"}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
},
}],
)
print(resp.choices[0].message.tool_calls)Structured output
Use response_format with a JSON schema. On models that support it, output is constrained to the schema during decoding. Validate the result before relying on it.
resp = client.chat.completions.create(
model="deepseek-v4-1-flash",
messages=[{"role": "user", "content": invoice_text}],
response_format={
"type": "json_schema",
"json_schema": {"name": "invoice", "schema": {
"type": "object",
"properties": {"vendor": {"type": "string"}, "total": {"type": "number"}},
"required": ["vendor", "total"],
}},
},
)Responses API & Codex
POST $ST_BASE_URL/responses speaks the OpenAI Responses API, so clients that use only that protocol, such as Codex, can use ScaledThought as their model provider. It takes input (a string, or messages, function_call and function_call_output items), instructions, function and custom (freeform) tools, tool_choice, max_output_tokens, text.format for JSON output and stream: true (Responses events, ending with response.completed or response.incomplete with its usage, or response.failed with an error).
It is stateless. We store no responses, so send the whole conversation in input every turn (Codex does) and leave out previous_response_id, which returns 400 invalid_request; store is always false. Hosted tools such as file search return 400; a web search tool is ignored. Requests without max_output_tokens get a cap of what the model's context leaves after your prompt, at most 32,768 tokens.
Billing is the same as chat completions: per token, from the usage the model server reports, paid from your credits.
# ~/.codex/config.toml
model = "deepseek-v4-1-flash" # or a model id from `npx -y scaledthought@0.3 models --live`
model_provider = "scaledthought"
[model_providers.scaledthought]
name = "ScaledThought"
base_url = "https://api.us.scaledthought.com/v1" # your ST_BASE_URL
env_key = "ST_API_KEY"
wire_api = "responses"
model_catalog_url = "https://api.us.scaledthought.com/v1/codex/models" # same host (and region) as base_urlCodex reads ST_API_KEY from the environment, so load ./.env into the shell first. wire_api = "responses" is required: ScaledThought serves Codex over the Responses API.
model_catalog_url tells Codex the context windows of the models live in your region, so it compacts long sessions in time, and gives it its apply_patch editing tool. It is built from the live models on every request; Codex sends your key with it, so keep it on the same host as base_url (your region's).
Models & benchmarks
GET /v1/models lists the model ids live in your region, in OpenAI's format. That list is the truth about what you can call. GET /v1/models/{id} returns one model's pricing, context length, release date, benchmark scores and live status, so an agent can choose without a human reading a web page. To search and filter, use npx -y scaledthought@0.3 models --live or the MCP tool models_search. For people, /pricing lists every model live in the US with its price, from the same API.
curl "$ST_BASE_URL/models"
curl "$ST_BASE_URL/models/deepseek-v4-1-flash"Regions & retention
Every key is pinned to its region. The US region is open now; Canada (ca) is coming soon; its API doesn't answer until it opens. Requests are served and logged, and account and usage records stored, only in that country. Card payments for every region are processed by Stripe in the US. Prompts and completions are not stored after the response is returned, and are never used for training. Batch and fine-tuning files are erased after 30 days, or sooner with DELETE /v1/files/{id}. Every response carries x-st-region and x-st-data-retention: none. See security (or /security.json) and the Privacy Policy.
Keys & budgets
Agents can create scoped keys with hard daily spending limits. When a budget is hit, calls return 402 instead of spending more. A revoked key stops working within about 30 seconds.
npx -y scaledthought@0.3 keys create --name support-bot --budget-usd 40Scopes are inference, read, keys, billing. A key can only give a new key scopes it has itself (otherwise 403 scope_not_allowed). Expiry: send expires_in (like "30d") or expires_at, up to 365 days; after that the key gets 401 key_expired. A key never outlives the key that created it.
Sub-agent keys: with child: true the new key records the calling key as its parent. Its daily budget fits in what the parent has left today, its rate limits and expiry are no higher than the parent's, and it can manage only its own sub-keys. Revoking a key revokes every key below it.
Rotation: POST /v1/keys/{id}/rotate mints a replacement with the same name, scopes, budget and limits, returned once. The old key keeps working for grace (default 24h, "0" to "7d"). Self-revocation: DELETE /v1/keys/self revokes the calling key and its sub-agent keys, with no scope needed, so an agent that suspects a leak can act at once.
npx -y scaledthought@0.3 keys create --name sub-agent --child --scopes inference --budget-usd 2 --expires-in 12h
npx -y scaledthought@0.3 keys rotate key_… --grace 24h
npx -y scaledthought@0.3 keys revoke-selfMore on how keys and data are protected: security.
Credits & approval
Usage is paid from prepaid credits, $20 to $2,000 per top-up, at most $2,000 per 24 hours and 2 open approval links at a time. The agent asks for a top-up ($20 by default) and gets back an approval_url: a funding page where its human confirms or changes the amount, can turn on auto-reload, and pays by card. The credits land once the payment is confirmed, and we email a receipt. Nothing is ever charged without that approval. Credits don't expire. When they run out, requests return 402 insufficient_credits with the balance and the amount needed.
npx -y scaledthought@0.3 billing topup --amount-usd 20Auto-reload is optional and recommended: when the balance drops below a threshold (default $5), the saved card adds a set amount (default $20), so the agent never stalls. The human turns it on on the funding page of any top-up, or without buying anything through an auto-reload setup link (billing auto-reload setup), where they save a card. Loads are limited to $2,000 per 24 hours. Turning it on or raising any limit needs a new approval; turning it off or lowering it doesn't. If a reload is declined, auto-reload switches itself off and the next 402 tells the agent to get a new link. billing status shows the balance and any pending top-ups; billing transactions lists every purchase, reload and charge.
npx -y scaledthought@0.3 billing topup --amount-usd 20 --auto-reload-below 5 --auto-reload-amount 20
npx -y scaledthought@0.3 billing auto-reload setup --below 5 --amount 20 # no purchase: they save a card
npx -y scaledthought@0.3 billing auto-reload offNotices & webhooks
Responses can carry x-st-notice with comma-separated codes: low_balance (a paid account whose balance is under $5). An agent that sees one should tell its human what to approve. GET /v1/account, MCP account_status,status and doctor show the same codes with the words to relay.
Webhooks push the same signals instead: balance.low, key.expiring and budget.exhausted. Endpoints must be public https URLs on port 443. Each POST carries x-st-signature: t=…,v1=…, an HMAC-SHA256 of <t>.<raw body> keyed with the endpoint's signing secret, which is shown once when you add it. Deliveries go to your URL wherever it is hosted, even outside your account's region, and carry only id, type, created_at, region, account_id and data: ids, integer micro-dollar amounts, day counts and timestamps, never prompts, names or emails. data per event: balance.low {balance_micros, threshold_micros}, key.expiring {key_id, expires_at}, budget.exhausted {scope, key_id, budget_micros, spent_today_micros, resets_at}. Failed deliveries are retried with backoff for about two days.
npx -y scaledthought@0.3 webhooks add https://hooks.example.com/st --events balance.low,budget.exhaustedOptional request headers: x-st-app names your app in usage records (not published), and x-st-session keeps one conversation on the same replica so its cached prefix is reused (memory only, never stored). In Canada, requests never overflow to another country: when every replica of a model is busy a request waits briefly, then gets 429 capacity_exhausted with Retry-After.
Batch inference
Upload a JSONL file of requests and get results at 50% of the normal price. Batches run in a 24-hour completion window and typically finish within it, but finishing in time isn't promised. The Files and Batches endpoints follow OpenAI's, so client.batches.create works. Every line is validated up front, so a bad file fails within seconds.
batch_file = client.files.create(file=open("requests.jsonl", "rb"), purpose="batch")
batch = client.batches.create(
input_file_id=batch_file.id,
endpoint="/v1/chat/completions",
completion_window="24h",
)
# later
batch = client.batches.retrieve(batch.id)
results = client.files.content(batch.output_file_id).textEmbeddings
POST /v1/embeddings with an embedding model (find one with models --live --modality embedding). Input can be a string or an array of strings.
vecs = client.embeddings.create(model="<embedding-model-id>", input=["open weights", "served hot"])Fine-tuning
Train a LoRA adapter on your own examples. The result is a private model id that only your account can call, served on the same replicas as its base model and billed at the base model's per-token price. Only base models running on our own GPUs in your region can be trained, and adapters can't be downloaded. Training is billed per 1M training tokens (dataset tokens × epochs); the job response includes an estimate before anything runs. Paid from prepaid credits. See pricing.
f = client.files.create(file=open("examples.jsonl", "rb"), purpose="fine-tune")
job = client.fine_tuning.jobs.create(model="<base-id>", training_file=f.id, suffix="triage")
# poll
job = client.fine_tuning.jobs.retrieve(job.id)
resp = client.chat.completions.create(model=job.fine_tuned_model, messages=[...])MCP server
Remote MCP on your region's API host: https://api.us.scaledthought.com/mcp (Canada's https://api.ca.scaledthought.com/mcp is coming soon; Streamable HTTP, 2026-07-28 spec). Authenticate with your API key as a bearer token. MCP can't create accounts: sign up with npx -y scaledthought@0.3 init or POST /v1/accounts first, then npx -y scaledthought@0.3 mcp prints the exact command and config for your region. The agent gets these tools:
npx -y scaledthought@0.3 mcp- account_status
- Region, spend, credit balance, auto-reload, key count and notices (tell your human about each). Call it first.
- account_settings
- Read or change account settings. ad_measurement: false opts the account out of ad measurement; any key can do it.
- keys_manage
- List, create, rotate or revoke keys (action: list | create | rotate | revoke | revoke_self). create and rotate return the full key once; create takes scopes, expires_in, child (a sub-agent key) and lives_in (where it will be stored); rotate takes grace. revoke_self revokes the key in use.
- budget_manage
- Set or clear a key's hard daily budget (USD) and rate limits.
- webhooks
- Signed HTTPS callbacks for balance.low, key.expiring and budget.exhausted (action: add | list | remove). add returns signing_secret once.
- models_search
- Models with prices, context, benchmarks and live status in your region (live_only, category, search).
- benchmarks_get
- Our own per-task scores and speed for one model.
- usage_query
- Requests, tokens and cost over the last N days, by model, day or key.
- billing_topup
- With amount_usd (and optional auto_reload): an approval_url for your human to buy credits. action: status | transactions | auto_reload_off.
- fine_tuning
- List, get, cancel fine-tuning jobs and their events; list your fine-tuned models.
- docs_search
- Where the docs, llms.txt and your base URL are.
| Tool | What it does |
|---|---|
| account_status | Region, spend, credit balance, auto-reload, key count and notices (tell your human about each). Call it first. |
| account_settings | Read or change account settings. ad_measurement: false opts the account out of ad measurement; any key can do it. |
| keys_manage | List, create, rotate or revoke keys (action: list | create | rotate | revoke | revoke_self). create and rotate return the full key once; create takes scopes, expires_in, child (a sub-agent key) and lives_in (where it will be stored); rotate takes grace. revoke_self revokes the key in use. |
| budget_manage | Set or clear a key's hard daily budget (USD) and rate limits. |
| webhooks | Signed HTTPS callbacks for balance.low, key.expiring and budget.exhausted (action: add | list | remove). add returns signing_secret once. |
| models_search | Models with prices, context, benchmarks and live status in your region (live_only, category, search). |
| benchmarks_get | Our own per-task scores and speed for one model. |
| usage_query | Requests, tokens and cost over the last N days, by model, day or key. |
| billing_topup | With amount_usd (and optional auto_reload): an approval_url for your human to buy credits. action: status | transactions | auto_reload_off. |
| fine_tuning | List, get, cancel fine-tuning jobs and their events; list your fine-tuned models. |
| docs_search | Where the docs, llms.txt and your base URL are. |
CLI
Run it as npx -y scaledthought@0.3 <command>. Output is JSON whenever stdout isn't a terminal (or with --json), and errors are one JSON line on stderr with code, message and hint. Nothing prompts. Keys come from ST_API_KEY or ./.env, never from flags.
- init --region us
- Account and key in the US. Writes ST_API_KEY and ST_BASE_URL to .env. Safe to re-run. Canada (ca) is coming soon.
- init --account Acme
- In a new folder on a machine with saved accounts: use that account with a new project key (init asks which when there are several). --new-account makes a separate account; --no-save keeps the key out of this machine's credential store.
- login --region us
- Connect to an existing account: prints a code your human types on the dashboard's connect page (opened in their browser). Then run login --wait.
- login --wait
- Waits about 90 s for your human to approve the code, then writes a new key to .env (never printed). Re-run on authorization_pending.
- login --code -
- Use a connect line made on the dashboard (its stl_ code on stdin, out of shell history): already approved, works once for 15 minutes.
- use "Side project"
- Point this folder at another account saved on this machine: a new project key in .env; the folder's old project key is revoked.
- init --region us --source a-rd
- The same, recording the campaign code of the scaledthought.com/go/<code> link that brought you (optional). An invalid code is dropped with a warning.
- models --live
- Models serving in your region now, newest first, with prices. Filter with --category or --search. The US list is also on /pricing.
- models <id>
- Pricing, context, benchmarks and live status for one model.
- chat -m <id> "prompt"
- Send one message to a model id from models --live.
- account
- Region, spend and credit balance.
- account ad-measurement off
- Opt out of ad measurement: nothing about the account is ever sent to an ad platform. on turns it back on.
- keys create --name <name> --budget-usd 5
- New key with a hard daily budget in USD. The key is printed once.
- keys create --name <name> --expires-in 30d --scopes inference,read
- A key that expires (at most 365d) with a subset of your key's scopes.
- keys create --name <name> --child --budget-usd 2
- A sub-agent key: scopes, budget, rate limits and expiry within your key's; revoked when your key is.
- keys list
- Your keys (prefixes only).
- keys rotate <id> --grace 24h
- Replace a key: the new key is printed once; the old one keeps working for the grace period (0 to 7d).
- keys revoke <id>
- Revoke a key and its sub-agent keys. They stop working within about 30 seconds.
- keys revoke-self
- Revoke the key you are using now (and its sub-agent keys), e.g. if it may have leaked. Irreversible.
- keys put github --repo <owner/repo> --budget-usd 2
- Put a new sub-agent key straight into a platform's secret store as ST_API_KEY, through its own CLI with the key on stdin (never printed). Destinations: github, vercel, fly, cloudflare, gcp, aws, k8s, doppler. The key is named after the place and records where it lives (lives_in). If storing fails, the new key is revoked.
- keys rotate --all --push --grace 1h
- Rotate every key that keys put stored somewhere and put each replacement in the same place; the old keys stop after the grace period. Prints no keys. One key: keys rotate <id> --push.
- connect opencode
- Point an OpenAI-compatible tool at ScaledThought: opencode and aider get the base URL and their own sub-agent key in their config (files 0600, key never printed); cursor and continue get steps for your human.
- usage --days 7 --by model
- Requests, tokens and cost by model, day or key.
- billing status
- Credit balance, auto-reload, pending top-ups.
- billing topup --amount-usd 20
- Buy prepaid credits ($20 to $2,000 per top-up, at most $2,000 per 24 hours and 2 open approval links at a time; default 20): opens the funding page in your human's browser, where they confirm the amount, can turn on auto-reload and pay.
- billing wait
- Returns as soon as your human pays the open top-up, with the new balance. Polls about 90 seconds a run; run it again on topup_pending.
- billing topup --amount-usd 20 --auto-reload-below 5 --auto-reload-amount 20
- Top up with auto-reload pre-selected on the funding page (your human can untick it).
- billing auto-reload setup --below 5 --amount 20
- Turn auto-reload on without buying anything: opens a page where your human saves a card. Then billing wait.
- billing auto-reload off
- Turn auto-reload off. No approval needed.
- billing transactions
- The credit ledger: purchases, auto-reloads, usage, batches, fine-tuning, refunds.
- batches create requests.jsonl
- Upload a JSONL file and start a batch at 50% off. Needs prepaid credits.
- fine-tune create examples.jsonl --model <base-id>
- Upload a dataset and start a LoRA job. Needs prepaid credits.
- webhooks add https://hooks.example.com/st --events balance.low,budget.exhausted
- Get a signed POST when the balance is low, a key is about to expire or a budget is spent (default: every event). Prints signing_secret once.
- webhooks list
- Your webhook endpoints.
- webhooks remove <id>
- Delete a webhook endpoint.
- doctor
- Checks the key in .env, its region, the account and its credit, and lists notices to tell your human.
- mcp
- Prints the `claude mcp add` command and JSON config for your region.
- dashboard --page billing
- Opens your human's dashboard (overview, billing, keys, members, connect) in their browser. If they aren't signed in, one click emails them a link.
- members
- Who can sign in to the account's dashboard: email, role (owner or member), status.
- status
- Per-model health in your region over the last hour, plus your account's notices when a key is set.
| Command | What it does |
|---|---|
| init --region us | Account and key in the US. Writes ST_API_KEY and ST_BASE_URL to .env. Safe to re-run. Canada (ca) is coming soon. |
| init --account Acme | In a new folder on a machine with saved accounts: use that account with a new project key (init asks which when there are several). --new-account makes a separate account; --no-save keeps the key out of this machine's credential store. |
| login --region us | Connect to an existing account: prints a code your human types on the dashboard's connect page (opened in their browser). Then run login --wait. |
| login --wait | Waits about 90 s for your human to approve the code, then writes a new key to .env (never printed). Re-run on authorization_pending. |
| login --code - | Use a connect line made on the dashboard (its stl_ code on stdin, out of shell history): already approved, works once for 15 minutes. |
| use "Side project" | Point this folder at another account saved on this machine: a new project key in .env; the folder's old project key is revoked. |
| init --region us --source a-rd | The same, recording the campaign code of the scaledthought.com/go/<code> link that brought you (optional). An invalid code is dropped with a warning. |
| models --live | Models serving in your region now, newest first, with prices. Filter with --category or --search. The US list is also on /pricing. |
| models <id> | Pricing, context, benchmarks and live status for one model. |
| chat -m <id> "prompt" | Send one message to a model id from models --live. |
| account | Region, spend and credit balance. |
| account ad-measurement off | Opt out of ad measurement: nothing about the account is ever sent to an ad platform. on turns it back on. |
| keys create --name <name> --budget-usd 5 | New key with a hard daily budget in USD. The key is printed once. |
| keys create --name <name> --expires-in 30d --scopes inference,read | A key that expires (at most 365d) with a subset of your key's scopes. |
| keys create --name <name> --child --budget-usd 2 | A sub-agent key: scopes, budget, rate limits and expiry within your key's; revoked when your key is. |
| keys list | Your keys (prefixes only). |
| keys rotate <id> --grace 24h | Replace a key: the new key is printed once; the old one keeps working for the grace period (0 to 7d). |
| keys revoke <id> | Revoke a key and its sub-agent keys. They stop working within about 30 seconds. |
| keys revoke-self | Revoke the key you are using now (and its sub-agent keys), e.g. if it may have leaked. Irreversible. |
| keys put github --repo <owner/repo> --budget-usd 2 | Put a new sub-agent key straight into a platform's secret store as ST_API_KEY, through its own CLI with the key on stdin (never printed). Destinations: github, vercel, fly, cloudflare, gcp, aws, k8s, doppler. The key is named after the place and records where it lives (lives_in). If storing fails, the new key is revoked. |
| keys rotate --all --push --grace 1h | Rotate every key that keys put stored somewhere and put each replacement in the same place; the old keys stop after the grace period. Prints no keys. One key: keys rotate <id> --push. |
| connect opencode | Point an OpenAI-compatible tool at ScaledThought: opencode and aider get the base URL and their own sub-agent key in their config (files 0600, key never printed); cursor and continue get steps for your human. |
| usage --days 7 --by model | Requests, tokens and cost by model, day or key. |
| billing status | Credit balance, auto-reload, pending top-ups. |
| billing topup --amount-usd 20 | Buy prepaid credits ($20 to $2,000 per top-up, at most $2,000 per 24 hours and 2 open approval links at a time; default 20): opens the funding page in your human's browser, where they confirm the amount, can turn on auto-reload and pay. |
| billing wait | Returns as soon as your human pays the open top-up, with the new balance. Polls about 90 seconds a run; run it again on topup_pending. |
| billing topup --amount-usd 20 --auto-reload-below 5 --auto-reload-amount 20 | Top up with auto-reload pre-selected on the funding page (your human can untick it). |
| billing auto-reload setup --below 5 --amount 20 | Turn auto-reload on without buying anything: opens a page where your human saves a card. Then billing wait. |
| billing auto-reload off | Turn auto-reload off. No approval needed. |
| billing transactions | The credit ledger: purchases, auto-reloads, usage, batches, fine-tuning, refunds. |
| batches create requests.jsonl | Upload a JSONL file and start a batch at 50% off. Needs prepaid credits. |
| fine-tune create examples.jsonl --model <base-id> | Upload a dataset and start a LoRA job. Needs prepaid credits. |
| webhooks add https://hooks.example.com/st --events balance.low,budget.exhausted | Get a signed POST when the balance is low, a key is about to expire or a budget is spent (default: every event). Prints signing_secret once. |
| webhooks list | Your webhook endpoints. |
| webhooks remove <id> | Delete a webhook endpoint. |
| doctor | Checks the key in .env, its region, the account and its credit, and lists notices to tell your human. |
| mcp | Prints the `claude mcp add` command and JSON config for your region. |
| dashboard --page billing | Opens your human's dashboard (overview, billing, keys, members, connect) in their browser. If they aren't signed in, one click emails them a link. |
| members | Who can sign in to the account's dashboard: email, role (owner or member), status. |
| status | Per-model health in your region over the last hour, plus your account's notices when a key is set. |
API reference
- POST /v1/accounts
- Create an account in this host's region. Returns api_key (once) and base_url. No auth. Optional source: the campaign code from a /go/<code> link.
- POST /v1/chat/completions
- Chat, tools, JSON output, streaming. OpenAI-compatible.
- POST /v1/responses
- The OpenAI Responses API (Codex): function and custom tools, streaming. Stateless: send the whole conversation each turn; previous_response_id isn't supported.
- POST /v1/completions
- Raw text completion.
- POST /v1/embeddings
- Embeddings for embedding models.
- GET /v1/models
- Model ids live in this region (OpenAI list format). No auth.
- GET /v1/models/{id}
- One model: pricing, context, benchmarks and live_in_region. No auth.
- GET /v1/models/{id}/benchmarks
- Our own per-task scores and speed.
- GET /v1/account
- Plan, region, balance, spend, key count and notices (low_balance) with notice_text to relay to your human.
- PATCH /v1/account
- Account settings: {"ad_measurement": false} opts out of ad measurement (any key); true needs the keys or billing scope.
- GET /v1/keys
- List keys.
- POST /v1/keys
- Create a key: scopes (a subset of yours), daily_budget_usd, rate limits, expires_in or expires_at (max 365d), child: true for a sub-agent key, lives_in (where it will be stored, display only).
- POST /v1/keys/{id}/rotate
- Replace a key. The new key is returned once; the old one works for grace (default 24h, 0 to 7d).
- PATCH /v1/keys/{id}
- Change a key's name, daily budget, rate limits or lives_in (null clears it).
- DELETE /v1/keys/{id}
- Revoke a key and its sub-agent keys.
- DELETE /v1/keys/self
- The calling key revokes itself and its sub-agent keys. No scope needed.
- GET /v1/usage
- Requests, tokens and cost (days, group_by).
- GET /v1/billing
- Credit balance, auto-reload, pending top-ups and recent transactions.
- POST /v1/billing/topup
- Buy credits: {amount_usd, auto_reload?} returns an approval_url for your human.
- GET /v1/billing/topups/{id}
- One top-up's status (open, paid, expired, failed).
- PATCH /v1/billing/auto-reload
- Disable or lower auto-reload. Raising it needs a new top-up approval.
- GET /v1/billing/transactions
- The credit ledger, newest first (cursor).
- POST /v1/webhooks
- Add a webhook: {url (public https, port 443), events}. Returns signing_secret once. Billing scope; at most 5.
- GET /v1/webhooks
- Your webhook endpoints (status, last success and failure).
- DELETE /v1/webhooks/{id}
- Delete a webhook endpoint.
- POST /v1/files
- Upload JSONL (purpose=batch or fine-tune).
- POST /v1/batches
- Start a batch. GET /v1/batches/{id} to poll, POST /v1/batches/{id}/cancel to stop.
- POST /v1/fine_tuning/jobs
- Start a LoRA job from an uploaded purpose=fine-tune file.
- GET /v1/status
- Per-model health in this region, last hour.
- POST /v1/dashboard/link
- A URL for your human's dashboard (optional page), the owners, and a message to relay. billing or keys scope.
- POST /v1/device/codes
- Ask to connect to an existing account. No key. Returns device_code (secret), user_code for your human to type at verification_url, expires_in, interval.
- POST /v1/device/token
- Collect the key once approved: {device_code}, or {connect_code} (stl_…) for a dashboard connect line. 428 authorization_pending until then; one key per code, with the approver's scopes.
- GET /v1/members
- Who can sign in to the dashboard (email, role, status). read scope.
| Endpoint | Description |
|---|---|
| POST /v1/accounts | Create an account in this host's region. Returns api_key (once) and base_url. No auth. Optional source: the campaign code from a /go/<code> link. |
| POST /v1/chat/completions | Chat, tools, JSON output, streaming. OpenAI-compatible. |
| POST /v1/responses | The OpenAI Responses API (Codex): function and custom tools, streaming. Stateless: send the whole conversation each turn; previous_response_id isn't supported. |
| POST /v1/completions | Raw text completion. |
| POST /v1/embeddings | Embeddings for embedding models. |
| GET /v1/models | Model ids live in this region (OpenAI list format). No auth. |
| GET /v1/models/{id} | One model: pricing, context, benchmarks and live_in_region. No auth. |
| GET /v1/models/{id}/benchmarks | Our own per-task scores and speed. |
| GET /v1/account | Plan, region, balance, spend, key count and notices (low_balance) with notice_text to relay to your human. |
| PATCH /v1/account | Account settings: {"ad_measurement": false} opts out of ad measurement (any key); true needs the keys or billing scope. |
| GET /v1/keys | List keys. |
| POST /v1/keys | Create a key: scopes (a subset of yours), daily_budget_usd, rate limits, expires_in or expires_at (max 365d), child: true for a sub-agent key, lives_in (where it will be stored, display only). |
| POST /v1/keys/{id}/rotate | Replace a key. The new key is returned once; the old one works for grace (default 24h, 0 to 7d). |
| PATCH /v1/keys/{id} | Change a key's name, daily budget, rate limits or lives_in (null clears it). |
| DELETE /v1/keys/{id} | Revoke a key and its sub-agent keys. |
| DELETE /v1/keys/self | The calling key revokes itself and its sub-agent keys. No scope needed. |
| GET /v1/usage | Requests, tokens and cost (days, group_by). |
| GET /v1/billing | Credit balance, auto-reload, pending top-ups and recent transactions. |
| POST /v1/billing/topup | Buy credits: {amount_usd, auto_reload?} returns an approval_url for your human. |
| GET /v1/billing/topups/{id} | One top-up's status (open, paid, expired, failed). |
| PATCH /v1/billing/auto-reload | Disable or lower auto-reload. Raising it needs a new top-up approval. |
| GET /v1/billing/transactions | The credit ledger, newest first (cursor). |
| POST /v1/webhooks | Add a webhook: {url (public https, port 443), events}. Returns signing_secret once. Billing scope; at most 5. |
| GET /v1/webhooks | Your webhook endpoints (status, last success and failure). |
| DELETE /v1/webhooks/{id} | Delete a webhook endpoint. |
| POST /v1/files | Upload JSONL (purpose=batch or fine-tune). |
| POST /v1/batches | Start a batch. GET /v1/batches/{id} to poll, POST /v1/batches/{id}/cancel to stop. |
| POST /v1/fine_tuning/jobs | Start a LoRA job from an uploaded purpose=fine-tune file. |
| GET /v1/status | Per-model health in this region, last hour. |
| POST /v1/dashboard/link | A URL for your human's dashboard (optional page), the owners, and a message to relay. billing or keys scope. |
| POST /v1/device/codes | Ask to connect to an existing account. No key. Returns device_code (secret), user_code for your human to type at verification_url, expires_in, interval. |
| POST /v1/device/token | Collect the key once approved: {device_code}, or {connect_code} (stl_…) for a dashboard connect line. 428 authorization_pending until then; one key per code, with the approver's scopes. |
| GET /v1/members | Who can sign in to the dashboard (email, role, status). read scope. |
Rate limits
Limits are per key. Over the limit, calls return 429 rate_limit_exceeded with a Retry-After header in seconds. Until the account's first credit purchase, keys can lower their limits but not raise them.
- New account
- Requests / min: 60
- Tokens / min: 200,000
- Spend: Until the first credit purchase: up to 5 keys
- Prepaid credits
- Requests / min: 3,000
- Tokens / min: 5,000,000
- Spend: Credits your human buys, $20 to $2,000 per top-up, $2,000 per 24 h
- Scale
- Requests / min: Custom
- Tokens / min: Custom
- Spend: Talk to us
| Plan | Requests / min | Tokens / min | Spend |
|---|---|---|---|
| New account | 60 | 200,000 | Until the first credit purchase: up to 5 keys |
| Prepaid credits | 3,000 | 5,000,000 | Credits your human buys, $20 to $2,000 per top-up, $2,000 per 24 h |
| Scale | Custom | Custom | Talk to us |
Errors
API errors are {"error":{"message","type","code"}}. CLI errors add a hint and exit non-zero.
- 400
- Code: invalid_request
- What to do: Fix the field named in message. Don't retry unchanged.
- 400
- Code: invalid_body
- What to do: Fix the field named in message. Don't retry unchanged.
- 400
- Code: invalid_json
- What to do: Send valid JSON with `content-type: application/json`.
- 401
- Code: invalid_api_key
- What to do: Key missing, mistyped or revoked. Load ST_API_KEY from .env; don't create a new account.
- 401
- Code: missing_api_key
- What to do: Send `Authorization: Bearer $ST_API_KEY`.
- 401
- Code: key_expired
- What to do: The key's expires_at has passed. Use another key, or create a new one with a key that has the keys scope.
- 400
- Code: invalid_expiry
- What to do: expires_in or expires_at must be in the future and at most 365 days ahead. Send one, not both.
- 400
- Code: expiry_exceeds_parent
- What to do: A key can't outlive the key that creates it. Ask for at most max_expires_at from the body.
- 403
- Code: scope_not_allowed
- What to do: Ask only for scopes your own key has (allowed_scopes in the body).
- 400
- Code: budget_exceeds_parent
- What to do: A sub-agent key's daily budget must fit in what its parent has left today (max_daily_budget_usd in the body).
- 400
- Code: limit_exceeds_parent
- What to do: A sub-agent key's rate limits and budget can't exceed its parent's (or, when it changes itself, its own).
- 400
- Code: key_depth_exceeded
- What to do: Sub-agent keys nest at most 16 levels. Create the key from a key higher up.
- 404
- Code: key_not_found
- What to do: No such key on this account, or (for a sub-agent key) it's outside the keys you created.
- 409
- Code: key_not_active
- What to do: Only an active key can be rotated. Create a new key instead.
- 402
- Code: budget_exceeded
- What to do: This key's daily budget is spent. Resets 00:00 UTC. Tell your human; don't retry now.
- 402
- Code: insufficient_credits
- What to do: The credit balance can't cover the request (the body has balance_usd and required_usd). Run `billing topup --amount-usd 20` and send your human the approval_url.
- 402
- Code: daily_cap_exceeded
- What to do: An operator-set daily cap on this account is reached. Resets 00:00 UTC. Tell your human.
- 402
- Code: credits_required_for_batches
- What to do: Batches are paid from prepaid credits. Run `billing topup --amount-usd 20`, then retry once your human approves.
- 402
- Code: credits_required_for_fine_tuning
- What to do: Fine-tuning is paid from prepaid credits. Run `billing topup --amount-usd 20`, then retry once your human approves.
- 409
- Code: too_many_pending_topups
- What to do: The account already has 2 unpaid approval links. Ask your human to pay one (`billing status` lists them), or wait for them to expire.
- 409
- Code: daily_topup_limit
- What to do: Top-ups are limited to $2,000 per account per rolling 24 hours, open links and auto-reloads included. Retry with at most available_usd from the body, or wait.
- 403
- Code: approval_required
- What to do: Turning auto-reload on or raising it needs your human. Run `billing auto-reload setup --below <usd> --amount <usd>` (a link your human approves; nothing is bought) and give them the approval_url. Disabling or lowering needs no approval.
- 402
- Code: free_tier_key_limit
- What to do: Accounts that haven't bought credits have 5 active keys. Revoke one (`keys revoke <id>`) or buy credits.
- 402
- Code: free_tier_limit
- What to do: Accounts that haven't bought credits can't raise budgets or rate limits. Buy credits first (`billing topup --amount-usd 20`).
- 409
- Code: fine_tuning_requires_self_hosted
- What to do: This base model isn't served on our own GPUs in your region, so it can't be fine-tuned here. Pick another base model.
- 409
- Code: model_not_live
- What to do: That model isn't served in your region yet. Pick one from `models --live`.
- 400
- Code: invalid_video_url
- What to do: video_url must be a public https URL or a data:video/mp4 (webm, mov) URL within the size limit.
- 403
- Code: insufficient_scope
- What to do: This key lacks the scope. Use the default key from init.
- 403
- Code: account_suspended
- What to do: Stop. Tell your human to email hello@scaledthought.com.
- 404
- Code: model_not_found
- What to do: Not live here. Pick an id from `models --live` or `GET $ST_BASE_URL/models`.
- 404
- Code: not_found
- What to do: Wrong path. ST_BASE_URL already ends in /v1; append /chat/completions.
- 421
- Code: wrong_region
- What to do: The key belongs to the other region. Use the `base_url` in the error body; remove any hard-coded host.
- 429
- Code: rate_limit_exceeded
- What to do: Wait `Retry-After` seconds, then retry.
- 429
- Code: capacity_exhausted
- What to do: Canada: every replica of the model is busy and Canadian requests never overflow to another country. Wait `Retry-After` seconds and retry with backoff, or use another model.
- 400
- Code: invalid_webhook_url
- What to do: Webhook URLs must be public https endpoints on port 443 (no other port, no private, loopback or internal hosts). Fix the URL.
- 409
- Code: too_many_webhooks
- What to do: An account has at most 5 webhook endpoints. Remove one (`webhooks remove <id>`) first.
- 404
- Code: webhook_not_found
- What to do: No such webhook on this account. List them with `webhooks list`.
- 503
- Code: webhooks_unavailable
- What to do: Webhooks aren't enabled in this region yet. Poll `GET /v1/account` for `notices` instead.
- 502
- Code: upstream_error
- What to do: Transient. Retry with backoff, or use another model from `models --live`.
- 503
- Code: region_not_open
- What to do: The Canada region isn't open yet. Check with your human that US hosting is acceptable, then use `init --region us` and the US host.
- 503
- Code: service_unavailable
- What to do: Transient. Retry in a few seconds.
- exit 1
- Code: no_key
- What to do: No ST_API_KEY in the environment or ./.env. For an existing account run `login`; for a new one, `init --region us`.
- exit 1
- Code: key_exists
- What to do: login: this folder already has a key. Use it, or pass --force to replace it.
- 428
- Code: authorization_pending
- What to do: login --wait: your human hasn't typed the code yet. Run it again (codes last 10 minutes).
- 410
- Code: expired_token
- What to do: The connect code expired. Run `login` again for a new one.
- 409
- Code: already_claimed
- What to do: A connect code works once. Run `login` again (or ask for a new connect line).
- 403
- Code: access_denied
- What to do: Your human denied the connection. Ask them before trying again.
- exit 1
- Code: region_mismatch
- What to do: A key from the other region already exists here. Use it, or ask your human before creating a second account elsewhere.
- exit 1
- Code: wrong_region_endpoint
- What to do: ST_BASE_URL / ST_API_URL points at the other region. Unset it. Nothing was sent.
- exit 1
- Code: network_error
- What to do: Couldn't reach the API. Check the network, then retry.
- exit 1
- Code: bad_region
- What to do: Use `--region us`. Canada (`ca`) is coming soon.
- exit 2
- Code: usage_error
- What to do: Unknown command, flag or missing argument. Run the command with --help.
| Status | Code | What to do |
|---|---|---|
| 400 | invalid_request | Fix the field named in message. Don't retry unchanged. |
| 400 | invalid_body | Fix the field named in message. Don't retry unchanged. |
| 400 | invalid_json | Send valid JSON with `content-type: application/json`. |
| 401 | invalid_api_key | Key missing, mistyped or revoked. Load ST_API_KEY from .env; don't create a new account. |
| 401 | missing_api_key | Send `Authorization: Bearer $ST_API_KEY`. |
| 401 | key_expired | The key's expires_at has passed. Use another key, or create a new one with a key that has the keys scope. |
| 400 | invalid_expiry | expires_in or expires_at must be in the future and at most 365 days ahead. Send one, not both. |
| 400 | expiry_exceeds_parent | A key can't outlive the key that creates it. Ask for at most max_expires_at from the body. |
| 403 | scope_not_allowed | Ask only for scopes your own key has (allowed_scopes in the body). |
| 400 | budget_exceeds_parent | A sub-agent key's daily budget must fit in what its parent has left today (max_daily_budget_usd in the body). |
| 400 | limit_exceeds_parent | A sub-agent key's rate limits and budget can't exceed its parent's (or, when it changes itself, its own). |
| 400 | key_depth_exceeded | Sub-agent keys nest at most 16 levels. Create the key from a key higher up. |
| 404 | key_not_found | No such key on this account, or (for a sub-agent key) it's outside the keys you created. |
| 409 | key_not_active | Only an active key can be rotated. Create a new key instead. |
| 402 | budget_exceeded | This key's daily budget is spent. Resets 00:00 UTC. Tell your human; don't retry now. |
| 402 | insufficient_credits | The credit balance can't cover the request (the body has balance_usd and required_usd). Run `billing topup --amount-usd 20` and send your human the approval_url. |
| 402 | daily_cap_exceeded | An operator-set daily cap on this account is reached. Resets 00:00 UTC. Tell your human. |
| 402 | credits_required_for_batches | Batches are paid from prepaid credits. Run `billing topup --amount-usd 20`, then retry once your human approves. |
| 402 | credits_required_for_fine_tuning | Fine-tuning is paid from prepaid credits. Run `billing topup --amount-usd 20`, then retry once your human approves. |
| 409 | too_many_pending_topups | The account already has 2 unpaid approval links. Ask your human to pay one (`billing status` lists them), or wait for them to expire. |
| 409 | daily_topup_limit | Top-ups are limited to $2,000 per account per rolling 24 hours, open links and auto-reloads included. Retry with at most available_usd from the body, or wait. |
| 403 | approval_required | Turning auto-reload on or raising it needs your human. Run `billing auto-reload setup --below <usd> --amount <usd>` (a link your human approves; nothing is bought) and give them the approval_url. Disabling or lowering needs no approval. |
| 402 | free_tier_key_limit | Accounts that haven't bought credits have 5 active keys. Revoke one (`keys revoke <id>`) or buy credits. |
| 402 | free_tier_limit | Accounts that haven't bought credits can't raise budgets or rate limits. Buy credits first (`billing topup --amount-usd 20`). |
| 409 | fine_tuning_requires_self_hosted | This base model isn't served on our own GPUs in your region, so it can't be fine-tuned here. Pick another base model. |
| 409 | model_not_live | That model isn't served in your region yet. Pick one from `models --live`. |
| 400 | invalid_video_url | video_url must be a public https URL or a data:video/mp4 (webm, mov) URL within the size limit. |
| 403 | insufficient_scope | This key lacks the scope. Use the default key from init. |
| 403 | account_suspended | Stop. Tell your human to email hello@scaledthought.com. |
| 404 | model_not_found | Not live here. Pick an id from `models --live` or `GET $ST_BASE_URL/models`. |
| 404 | not_found | Wrong path. ST_BASE_URL already ends in /v1; append /chat/completions. |
| 421 | wrong_region | The key belongs to the other region. Use the `base_url` in the error body; remove any hard-coded host. |
| 429 | rate_limit_exceeded | Wait `Retry-After` seconds, then retry. |
| 429 | capacity_exhausted | Canada: every replica of the model is busy and Canadian requests never overflow to another country. Wait `Retry-After` seconds and retry with backoff, or use another model. |
| 400 | invalid_webhook_url | Webhook URLs must be public https endpoints on port 443 (no other port, no private, loopback or internal hosts). Fix the URL. |
| 409 | too_many_webhooks | An account has at most 5 webhook endpoints. Remove one (`webhooks remove <id>`) first. |
| 404 | webhook_not_found | No such webhook on this account. List them with `webhooks list`. |
| 503 | webhooks_unavailable | Webhooks aren't enabled in this region yet. Poll `GET /v1/account` for `notices` instead. |
| 502 | upstream_error | Transient. Retry with backoff, or use another model from `models --live`. |
| 503 | region_not_open | The Canada region isn't open yet. Check with your human that US hosting is acceptable, then use `init --region us` and the US host. |
| 503 | service_unavailable | Transient. Retry in a few seconds. |
| exit 1 | no_key | No ST_API_KEY in the environment or ./.env. For an existing account run `login`; for a new one, `init --region us`. |
| exit 1 | key_exists | login: this folder already has a key. Use it, or pass --force to replace it. |
| 428 | authorization_pending | login --wait: your human hasn't typed the code yet. Run it again (codes last 10 minutes). |
| 410 | expired_token | The connect code expired. Run `login` again for a new one. |
| 409 | already_claimed | A connect code works once. Run `login` again (or ask for a new connect line). |
| 403 | access_denied | Your human denied the connection. Ask them before trying again. |
| exit 1 | region_mismatch | A key from the other region already exists here. Use it, or ask your human before creating a second account elsewhere. |
| exit 1 | wrong_region_endpoint | ST_BASE_URL / ST_API_URL points at the other region. Unset it. Nothing was sent. |
| exit 1 | network_error | Couldn't reach the API. Check the network, then retry. |
| exit 1 | bad_region | Use `--region us`. Canada (`ca`) is coming soon. |
| exit 2 | usage_error | Unknown command, flag or missing argument. Run the command with --help. |