# ScaledThought

> The newest open-weight models on one OpenAI-compatible API, set up by your agent in one prompt. Calls are paid from prepaid credits your human approves. Hosted in the US, with Canada coming soon. Your agent handles signup, payment and account management.

You are reading the agent surface of this site. Signup, keys, budgets, billing and inference all work through the CLI, the API and the MCP server described below. Your human also has a dashboard (billing, keys, teammates, connecting agents) that you open for them with `dashboard`.

## Setup protocol

If your human asked you to set them up on ScaledThought, follow these steps in order. Every command is non-interactive. It prints one JSON object on stdout and exits 0. On failure it prints `{"error":{"code","message","hint"}}` on stderr and exits non-zero; look the code up in [Errors](#errors).

### 0. Check what's live first (no account, nothing written)

Before creating anything, check that the live models and prices suit the task. This needs no key and no account, and writes nothing to disk:

```bash
npx -y scaledthought@0.3 models --live --region us
```

Success: a JSON array, newest first, with each model's `id`, `modality` and prices (USD per 1M tokens). If nothing fits the task, stop here and tell your human instead of signing up.

**What the CLI does on this machine.** It runs from npm as `scaledthought@0.3`: pinned to this major.minor, so it only takes patch releases and can't change under you mid-setup (these instructions move to the next pin when it ships). It writes nothing except `./.env` (mode 0600: `ST_API_KEY` and `ST_BASE_URL`, other lines kept) and one `.env` line in `./.gitignore`, both from `init` (or `login`). `init` and `login` also keep the account's key in this machine's credential store (macOS Keychain or the Linux Secret Service, else `~/.config/scaledthought/credentials.json`, mode 0600) so another folder on the same machine reuses the account instead of making a new one; `--no-save` opts out. `login` keeps its one-time device code in `./.scaledthought/login.json` (mode 0600, added to `./.gitignore`) until the key arrives. `billing topup`, `billing auto-reload setup` and `login` open one page in the default browser (`--no-open` or `ST_NO_BROWSER=1` to stop that). It talks only to `api.us.scaledthought.com` / `api.ca.scaledthought.com` (and npm, to download itself). No telemetry, no analytics. Keys come from `ST_API_KEY` or `./.env`, never from flags, and are never printed.

### 1. Pick the home region

- **Only the US region is open.** Canada (`ca`) is coming soon and isn't accepting accounts yet: its API doesn't answer yet: `init --region ca` fails with `network_error`, and requests to the Canadian host get no answer (its hostname doesn't resolve yet). Use `--region us`.
- If the data could be Canadian-regulated (Canadian personal, health or financial data; PIPEDA, Quebec Law 25, provincial health privacy laws), stop and ask one question first: "The Canada region isn't open yet. Is US hosting acceptable for this data?" Continue with `us` only if the answer is yes.
- Otherwise use `us` without asking.
- The region is permanent. Prompts, completions, logs and account records are processed and stored in that country. Card payments are processed by Stripe in the US. Keys start with `st_us_` (or `st_ca_` once Canada opens), and that prefix decides where every request goes.

### 2. Create the account

If your human already has a ScaledThought account (they say so, or this is a new machine, a container or a teammate's laptop), don't create another: connect to it with `npx -y scaledthought@0.3 login` (see [Connect this agent to an existing account](#connect-this-agent-to-an-existing-account)).

Run this in the human's project directory, where their code will read `.env`:

```bash
npx -y scaledthought@0.3 init --region us
```

Creating an account accepts nothing on your human's behalf: no terms, no payment. They accept the [Terms of Service](https://scaledthought.com/legal/terms) and the Credits & Refunds policy themselves, on the payment page, when they buy credits. Until then the account can't be used for anything billed.

If your human gave you a `scaledthought.com/go/<code>` link, fetch that instead: it is this page with `--source <code>` added to the line above (an optional campaign code, stored on the account).

Success: exit code 0 and JSON with `"already_initialized": false`, `account_id`, `region`, `base_url`, `terms` (`url`, `version`, `refund_policy_url`, `notice`), `balance_usd`, `needs_credits`, `next_command`, `newest_models`, `suggested_model` and `next_steps`. A new account has no credits (`"needs_credits": true`), so `next_command` is the top-up below. It also:

- writes `ST_API_KEY` and `ST_BASE_URL` to `./.env` (mode 0600), keeping any other lines;
- adds `.env` to `./.gitignore`;
- never prints the key. Don't print it, paste it into chat or commit it.

Re-running is safe. If `ST_API_KEY` is already set (in the environment or `./.env`), init creates nothing and returns the existing account with `"already_initialized": true`. Use that account. If it exits with `region_mismatch`, a key from the other region already exists: ask the human before doing anything else.

On a machine where an account is already saved (an earlier `init` or `login`), init doesn't create a new one. With one saved account it writes a new project key for this folder and returns `"reused_account": true` with `account_name`. With several it returns `choose_account` (names, balances, last used) and a `message_for_human` question, and creates nothing: ask it, then run `init --account "<name or id>"`. `init --new-account` makes a separate account anyway (for separate billing).

### Connect this agent to an existing account

When your human already has an account (a new machine, a second tool, a container), connect to it instead of running init:

```bash
npx -y scaledthought@0.3 login --region us
```

It prints `user_code` (like `WDJB-MJHT`), `verification_url` (the dashboard's connect page, opened in their browser; the code is never in the URL) and `message_for_human`. Tell them the message: they sign in by email and type the code. In the same turn run:

```bash
npx -y scaledthought@0.3 login --wait
```

It returns `"status": "connected"` with `account_id`, `account_name`, `key_name` and `scopes` once they approve, and writes a new key to `./.env` (never printed). The key can do what the approver can: an owner's approval gives every scope, a member's only `inference` and `read`. On `authorization_pending` run it again, for up to 10 minutes. If your human gives you a line from the dashboard like `Connect to my ScaledThought account: npx -y scaledthought@0.3 login --code stl_…`, pass the code on stdin so it stays out of your shell history: `echo '<the stl_ code>' | npx -y scaledthought@0.3 login --code -`. It is already approved and works once, for 15 minutes. To point this folder at another account saved on this machine: `use "<name>"`.

A new account's `init` also prints `existing_account_code`, and `billing topup` adds it to `message_for_human`: if the human already has an account, they click **Use it instead** on the funding page, pick it and type that code. Then `billing wait` returns `"status": "connected_existing"` with that account's `account_name` and `balance_usd`: the new, empty account init made is closed (nothing is charged) and the key in `./.env` now belongs to their existing account. Carry on with it.

### 3. Pick a model that is live in the region

Pick from what is live in the region right now:

```bash
npx -y scaledthought@0.3 models --live
```

Success: a JSON array, newest first. Each item has `id`, `category`, `modality`, `live: true` and prices. Use the first item whose `modality` is `chat` (init's `suggested_model` is the same pick). Without the CLI, `GET $ST_BASE_URL/models` returns the live model ids in OpenAI list format (no auth needed). The same list with prices, for a human: https://scaledthought.com/pricing

### Before step 4: get credits (your human pays)

Inference is prepaid and a new account has no credits: until your human pays, every completion returns `402 insufficient_credits`. That is expected, not a broken setup. Don't stop at the 402, don't retry the call and don't create another account: create the top-up link right away and give it to your human. Creating a link charges nothing (only the human can pay, on the page it opens), so you don't need to ask before creating one. Skip this step if `billing status` already shows `balance_usd` above 0.

```bash
npx -y scaledthought@0.3 billing topup --amount-usd 20
```

It opens the funding page in your human's browser (`opened_in_browser: true`; `--no-open` skips that). There they confirm the amount ($20, or another from $20 to $2,000), can turn on auto-reload (recommended: below $5, add $20, so you don't stall later), then accept the Terms and the Credits & Refunds policy and pay by card on Stripe's checkout. Nothing is charged until they approve, and we email them a receipt. Unused purchased credits can be refunded within the policy's refund window. Success: JSON with `topup_id`, `approval_url`, `expires_at`, `opened_in_browser` and `message_for_human` (it says all that and recommends auto-reload; add `--auto-reload-below 5 --auto-reload-amount 20` to pre-select it instead).

**If `opened_in_browser` is `true`** (the usual case), the funding page is already in front of your human, so don't make them tell you when they've paid:

1. Tell them with `message_for_human` (it has the `approval_url` in case the page didn't open; if you use your own words, copy the link exactly: it is a one-time payment link).
2. In the same turn, run:

```bash
npx -y scaledthought@0.3 billing wait   # {"status":"paid", ...} with the balance, as soon as they pay
```

   It polls for about 90 seconds (under a shell tool's usual 2-minute limit). On `topup_pending`, run it again, for up to 15 minutes in all. Then go to step 4.
3. If it's still unpaid after 15 minutes, end your turn with `message_for_human` and run `billing wait` again when they're back.

**If `opened_in_browser` is `false`** (a headless machine, a container, `--no-open`), they don't have the page yet: end your turn with `message_for_human`, because many agent tools show your messages only when your turn ends and a wait started first would hold the link back. When they're back, run `billing wait`, then go to step 4.

Don't create a new link while one is pending (`topup_pending` repeats its `approval_url`). If the link expired or failed (`topup_expired`, `topup_failed`), run `billing topup` again. To turn auto-reload on later without buying anything: `billing auto-reload setup` (same flow; the human saves a card). Without the CLI: `POST /v1/billing/topup {"amount_usd": 10}` (or `POST /v1/billing/auto-reload/setup`), open the `approval_url` in their browser yourself (macOS `open`, Linux `xdg-open`, Windows `start`) and poll `GET /v1/billing/topups/{topup_id}` until `status` is `paid`; over MCP, `billing_topup`.

### 4. Verify with one real completion

```bash
set -a; . ./.env; set +a   # load ST_API_KEY and ST_BASE_URL
curl -sS "$ST_BASE_URL/chat/completions" \
  -H "Authorization: Bearer $ST_API_KEY" \
  -H "content-type: application/json" \
  -d '{"model":"<id from step 3>","max_tokens":32,"messages":[{"role":"user","content":"Reply with OK"}]}'
```

Success: HTTP 200, a non-empty `choices[0].message.content`, and a `usage` object. A `402` here means the top-up above isn't paid yet: go back to it. The CLI does the same in one step:

```bash
npx -y scaledthought@0.3 chat -m deepseek-v4-1-flash "Reply with OK"   # or the id from step 3; JSON: model, region, text, usage
```

### 5. Optional: connect MCP

Only if the human wants account tools (usage, keys, budgets, billing) inside their MCP client. MCP needs a key, so it comes after step 2.

```bash
npx -y scaledthought@0.3 mcp
```

Success: JSON with `url` (the region's `/mcp`), `claude_code` (a ready `claude mcp add --transport http scaledthought … --header "Authorization: Bearer $ST_API_KEY"` command) and `json_config` for other clients. Run `claude_code` in a shell where `ST_API_KEY` is loaded (see step 4), because the shell expands it.

### 6. Tell the human

Report in a few lines:

- **Region**: `us`. Prompts, completions and account records stay in the US; card payments are processed by Stripe in the US. Canada is coming soon.
- **Credits**: the balance from `npx -y scaledthought@0.3 billing status` (`balance_usd`), and that inference is paid from it per token. Until the first credit purchase the account has 60 requests/min per key and up to 5 keys.
- **Key**: in `./.env` as `ST_API_KEY` (gitignored), with `ST_BASE_URL`. Any OpenAI SDK works with those two values.
- **Verified**: which model answered, from step 4.
- **More credits**: `npx -y scaledthought@0.3 billing topup --amount-usd <n>` ($20 to $2,000 per top-up, at most $2,000 per 24 hours and 2 open approval links at a time) returns an `approval_url` for them; nothing is charged without their approval. Optional auto-reload: see Pricing.
- **Dashboard**: `npx -y scaledthought@0.3 dashboard` opens it in their browser (billing, keys, teammates). The person who paid is its owner and got a sign-in email; if they're not signed in, one click emails a link (no password). US accounts only for now: sign-in links are emailed only in the US region.
- **Terms**: they accepted the [Terms of Service](https://scaledthought.com/legal/terms) and the [Credits & Refunds policy](https://scaledthought.com/legal/credits-and-refunds) themselves when they paid; nothing was accepted on their behalf. Unused purchased credits can be refunded within the policy's refund window.

## Use it from code

OpenAI-compatible. Read the base URL from `ST_BASE_URL`; don't hard-code a region's host.

```python
import os
from openai import OpenAI  # pip install openai; load .env first, e.g. python-dotenv's load_dotenv()

client = OpenAI(base_url=os.environ["ST_BASE_URL"], api_key=os.environ["ST_API_KEY"])
resp = client.chat.completions.create(
    model="deepseek-v4-1-flash",  # or a model id from `npx -y scaledthought@0.3 models --live`
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)
```

```ts
import OpenAI from "openai"; // npm i openai

process.loadEnvFile(); // Node 20.12+: reads ./.env
const client = new OpenAI({ baseURL: process.env.ST_BASE_URL, apiKey: process.env.ST_API_KEY });
const resp = await client.chat.completions.create({
  model: "deepseek-v4-1-flash", // or a model id from `npx -y scaledthought@0.3 models --live`
  messages: [{ role: "user", content: "Hello" }],
});
console.log(resp.choices[0].message.content);
```

```bash
curl -sS "$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":"hi"}]}'
```

Streaming (`stream: true`), tools, `response_format` and embeddings (`POST $ST_BASE_URL/embeddings` with an embedding model) use the OpenAI request shapes. Tool calling and `json_schema` output work on models that support them; validate structured output before relying on it. Requests without `max_tokens` get a default cap. Every inference response carries `x-st-region` and `x-st-request-id`.

## Responses API and 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.

```toml
# ~/.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_url
```

```bash
set -a; . ./.env; set +a  # puts ST_API_KEY in the environment
codex
```

- Codex 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).

## Errors

API errors are `{"error":{"message","type","code"}}` with the HTTP status below. CLI errors are `{"error":{"code","message","hint"}}` on stderr; for API failures the CLI passes the API's `code` through and adds `status` (and `retry_after` or `base_url` when present).

| status | code | from | what to do |
|---|---|---|---|
| 400 | `invalid_request` | API | Fix the field named in message. Don't retry unchanged. |
| 400 | `invalid_body` | API | Fix the field named in message. Don't retry unchanged. |
| 400 | `invalid_json` | API | Send valid JSON with `content-type: application/json`. |
| 401 | `invalid_api_key` | API | Key missing, mistyped or revoked. Load ST_API_KEY from .env; don't create a new account. |
| 401 | `missing_api_key` | API | Send `Authorization: Bearer $ST_API_KEY`. |
| 401 | `key_expired` | API | 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` | API | expires_in or expires_at must be in the future and at most 365 days ahead. Send one, not both. |
| 400 | `expiry_exceeds_parent` | API | A key can't outlive the key that creates it. Ask for at most max_expires_at from the body. |
| 403 | `scope_not_allowed` | API | Ask only for scopes your own key has (allowed_scopes in the body). |
| 400 | `budget_exceeds_parent` | API | 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` | API | 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` | API | Sub-agent keys nest at most 16 levels. Create the key from a key higher up. |
| 404 | `key_not_found` | API | No such key on this account, or (for a sub-agent key) it's outside the keys you created. |
| 409 | `key_not_active` | API | Only an active key can be rotated. Create a new key instead. |
| 402 | `budget_exceeded` | API | This key's daily budget is spent. Resets 00:00 UTC. Tell your human; don't retry now. |
| 402 | `insufficient_credits` | API | 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` | API | An operator-set daily cap on this account is reached. Resets 00:00 UTC. Tell your human. |
| 402 | `credits_required_for_batches` | API | Batches are paid from prepaid credits. Run `billing topup --amount-usd 20`, then retry once your human approves. |
| 402 | `credits_required_for_fine_tuning` | API | Fine-tuning is paid from prepaid credits. Run `billing topup --amount-usd 20`, then retry once your human approves. |
| 409 | `too_many_pending_topups` | API | 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` | API | 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` | API | 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` | API | Accounts that haven't bought credits have 5 active keys. Revoke one (`keys revoke <id>`) or buy credits. |
| 402 | `free_tier_limit` | API | 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` | API | 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` | API | That model isn't served in your region yet. Pick one from `models --live`. |
| 400 | `invalid_video_url` | API | video_url must be a public https URL or a data:video/mp4 (webm, mov) URL within the size limit. |
| 403 | `insufficient_scope` | API | This key lacks the scope. Use the default key from init. |
| 403 | `account_suspended` | API | Stop. Tell your human to email hello@scaledthought.com. |
| 404 | `model_not_found` | API | Not live here. Pick an id from `models --live` or `GET $ST_BASE_URL/models`. |
| 404 | `not_found` | API | Wrong path. ST_BASE_URL already ends in /v1; append /chat/completions. |
| 421 | `wrong_region` | API | The key belongs to the other region. Use the `base_url` in the error body; remove any hard-coded host. |
| 429 | `rate_limit_exceeded` | API | Wait `Retry-After` seconds, then retry. |
| 429 | `capacity_exhausted` | API | 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` | API | 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` | API | An account has at most 5 webhook endpoints. Remove one (`webhooks remove <id>`) first. |
| 404 | `webhook_not_found` | API | No such webhook on this account. List them with `webhooks list`. |
| 503 | `webhooks_unavailable` | API | Webhooks aren't enabled in this region yet. Poll `GET /v1/account` for `notices` instead. |
| 502 | `upstream_error` | API | Transient. Retry with backoff, or use another model from `models --live`. |
| 503 | `region_not_open` | API | 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` | API | Transient. Retry in a few seconds. |
| exit 1 | `no_key` | CLI | 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` | CLI | login: this folder already has a key. Use it, or pass --force to replace it. |
| 428 | `authorization_pending` | API | login --wait: your human hasn't typed the code yet. Run it again (codes last 10 minutes). |
| 410 | `expired_token` | API | The connect code expired. Run `login` again for a new one. |
| 409 | `already_claimed` | API | A connect code works once. Run `login` again (or ask for a new connect line). |
| 403 | `access_denied` | API | Your human denied the connection. Ask them before trying again. |
| exit 1 | `region_mismatch` | CLI | 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` | CLI | ST_BASE_URL / ST_API_URL points at the other region. Unset it. Nothing was sent. |
| exit 1 | `network_error` | CLI | Couldn't reach the API. Check the network, then retry. |
| exit 1 | `bad_region` | CLI | Use `--region us`. Canada (`ca`) is coming soon. |
| exit 2 | `usage_error` | CLI | Unknown command, flag or missing argument. Run the command with --help. |

Buying credits: `billing topup --amount-usd <n>` (CLI), `POST /v1/billing/topup {"amount_usd": n}` (API) or `billing_topup` (MCP) returns an `approval_url`, the checkout page. The CLI opens it in the human's browser itself; from MCP or the API, open it yourself (macOS `open <url>`, Linux `xdg-open <url>`, Windows `start "" <url>`), then give the human the link too in case it didn't open. The credits land as soon as they pay. `billing topup --wait` polls until the top-up is paid, expired or failed.

## Models

We add new open-weight models as soon as we can serve them well. `npx -y scaledthought@0.3 models --live` lists every model live in your region, newest first, with its prices (USD per 1M tokens, or per unit for models billed by the minute, character or second); `--modality <chat|embedding|...>` filters it. Without the CLI: `GET $ST_BASE_URL/models` (live ids, OpenAI list format) and `GET /v1/models/<id>` (one model's prices, context length, release date, benchmarks and live status); over MCP, `models_search`. For example `GET $ST_BASE_URL/models/deepseek-v4-1-flash`. https://scaledthought.com/pricing has the same list for humans: every model live in the US region with its price, refreshed at most every 5 minutes. Each model is used under its publisher's licence, which you must follow: https://scaledthought.com/legal/acceptable-use#model-licences

## Pricing

Prepaid credits from $20; your human approves each purchase via a link, and nothing is served before it is paid for. Optional auto-reload on amounts your human approves. When credits run out, requests return 402 insufficient_credits.

- Accounts that haven't bought credits get 60 requests/min per key and up to 5 keys.
- Top-ups: $20 to $2,000 per top-up, at most $2,000 per 24 hours and 2 open approval links at a time. Credits never expire. Usage is charged at each model's per-token price (USD per 1M tokens): `models --live`, or https://scaledthought.com/pricing for every model live in the US. Cached input is billed at the model's cached rate, or at the input rate when the model has none.
- Batches and fine-tuning are paid from credits: without credits they return `credits_required_for_batches` or `credits_required_for_fine_tuning`.
- Auto-reload (recommended): your human can turn it on on the funding page of any top-up, or without buying anything with `npx -y scaledthought@0.3 billing auto-reload setup --below 5 --amount 20` (a link where they save a card; `billing wait` returns once it's on). It adds $10 whenever the balance drops below $5 (the threshold is at least $5; on the funding page the reload amount defaults to the amount bought); loads are limited to $2,000 per rolling 24 h, and there is no monthly limit unless you set one (`--auto-reload-monthly-max`, or later with `PATCH /v1/billing/auto-reload`). Turning it on or raising it needs your human's approval; `npx -y scaledthought@0.3 billing auto-reload off` needs none. If a reload is declined, auto-reload turns itself off and the next 402 says to get a new approval link.

## MCP server

Remote MCP at `https://api.{us|ca}.scaledthought.com/mcp` (Streamable HTTP, 2026-07-28), authenticated with your API key as a bearer token. **MCP can't create accounts**: sign up first with `npx -y scaledthought@0.3 init` or `POST /v1/accounts`, then run `npx -y scaledthought@0.3 mcp` for the exact config. Inference stays on the OpenAI-compatible API. Tools:

- `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.

## Batches, fine-tuning, billing

- **Batch inference**: OpenAI-compatible Files + Batches (`POST /v1/files` purpose=batch, `POST /v1/batches`). 50% of the normal price. A 24h completion window: batches typically finish within 24 hours, but finishing in time isn't promised. Paid from prepaid credits. CLI: `npx -y scaledthought@0.3 batches create requests.jsonl`.
- **Fine-tuning (LoRA)**: `POST /v1/files` purpose=fine-tune (chat JSONL, at least 10 examples), then `POST /v1/fine_tuning/jobs`. A base model can be trained only while it runs on our own GPUs in your region (otherwise `409 fine_tuning_requires_self_hosted`); the job's response has `estimated_cost_usd`, held from credits until it settles. The resulting model is private to your account and billed at its base model's price. Adapters aren't downloadable. Paid from prepaid credits. CLI: `npx -y scaledthought@0.3 fine-tune create examples.jsonl --model <base-id>`.
- **Billing**: prepaid. `GET /v1/billing` returns `balance_usd`, `auto_reload`, `pending_topups` (each with its `approval_url`) and `recent_transactions`. `POST /v1/billing/topup {"amount_usd": n, "auto_reload"?: {"threshold_usd", "amount_usd", "monthly_limit_usd"}}` returns an `approval_url` for your human; poll `GET /v1/billing/topups/{id}` or `GET /v1/billing`. `PATCH /v1/billing/auto-reload` disables or lowers auto-reload without approval. `GET /v1/billing/transactions` is the full credit ledger.
- **Video input**: chat models whose modalities include video take `{"type": "video_url", "video_url": {"url": "https://..."}}` parts (or a `data:video/mp4;base64,` URL), one video per request unless the model says otherwise. Video is billed as prompt tokens from the engine's count. Other models answer `400`.
- **OpenAPI**: the control-plane spec is at `/openapi.json` on your region's API host.

## Notices, webhooks and request headers

- **Notices: tell your human.** API responses may carry `x-st-notice` with comma-separated codes: `low_balance` (a paid account whose credit balance is under $5). When you see one, tell your human what it means and what to approve (a top-up or auto-reload); don't just keep going until requests fail. `GET /v1/account` (`notices`, `notice_text`), MCP `account_status`, `status` and `doctor` in the CLI show the same codes.
- **Webhooks** instead of polling: `webhooks add https://hooks.example.com/st` (CLI), `POST /v1/webhooks {"url", "events"}` (API) or MCP `webhooks` (action add). Events: `balance.low`, `key.expiring` (3 days before a key expires) and `budget.exhausted` (a key's daily budget or the account's daily cap is spent). Public https URLs on port 443 only; at most 5 per account; needs the `billing` scope. The response has `signing_secret`, shown once: store it like a key.
- Each delivery is a POST to your URL, wherever it is hosted (even outside your account's region), with JSON `{id, type, created_at, region, account_id, data}` and nothing else. `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}`. Ids, integer micro-dollar amounts, counts and timestamps only, never prompts, names or emails. Verify `x-st-signature: t=<unix seconds>,v1=<hex>`, where v1 is HMAC-SHA256 of `<t>.<raw body>` keyed with the signing secret, reject t older than 5 minutes and deduplicate on `id`. Answer 2xx within 10 seconds; failures are retried with backoff for about two days, and an endpoint that keeps failing is disabled (remove and add it again). Deliveries are kept 30 days.

```python
import hashlib, hmac, time

def verify(secret: str, header: str, body: bytes) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    mac = hmac.new(secret.encode(), parts["t"].encode() + b"." + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, parts["v1"])
```

- Optional request headers on inference: `x-st-app: <slug>` (lowercase letters, digits, `.`, `_` and `-`, up to 64 characters) names your app in usage records; it isn't published anywhere. `x-st-session: <id>` (up to 128 characters, e.g. one per conversation) keeps a conversation on the same replica so its cached prompt prefix is reused; it is held in memory only, never stored or logged.
- Capacity in Canada: Canadian requests never overflow to another country. When every replica serving a model is busy, a request waits briefly, then gets `429 capacity_exhausted` with `Retry-After`. Retry with backoff, or use another model.

## Hosting and data

- The US region is open: `https://api.us.scaledthought.com/v1`. Canada (`https://api.ca.scaledthought.com/v1`) is coming soon; its API doesn't answer until it opens. `ST_BASE_URL` in `.env` is already the right one.
- Every key is pinned to its region. Requests are served and logged, and account and usage records are stored, only in that country. A key sent to the other region's host gets `421 wrong_region` and the body is never read. Card payments are processed by Stripe in the US for both regions; prompts and completions never go to Stripe.
- Ad measurement: nothing about an account is ever sent to an ad platform unless the human chose an opt-in ad-measurement prompt on a US campaign; then only the ad's click id, the signup and the first purchase go to that one platform, never from or about `ca` accounts. Opt out at any time with `account ad-measurement off` (`PATCH /v1/account` with `{"ad_measurement": false}`).
- Prompts and completions of API requests aren't logged or stored, and nothing you send is used for training. Batch and fine-tuning files are stored in your region until you delete them or for 30 days. Details: https://scaledthought.com/legal/privacy
- Status: https://scaledthought.com/status shows whether each region's account API (`GET /healthz`) and model API (`GET /v1/models`) answered when the site last checked them (at most once a minute; no model request is sent), and every incident we have reported. Check the account API yourself: `GET https://api.us.scaledthought.com/healthz` answers `{"ok":true,"region":"us"}`.

## Security

Machine-readable facts: https://scaledthought.com/security.json (the same facts as https://scaledthought.com/security). Vulnerability reports: security@scaledthought.com.

- `x-st-region: us`: The region that served the request. On every response, errors included.
- `x-st-data-retention: none`: Prompts and completions from this request are not stored. On every response, errors included.
- `x-st-request-id: 01K5Z3T8R2V9Q4M6N1P7B0C8XD`: The request's id, for usage records and support. On inference responses.
- Keys are stored only as hashes and shown once. Scopes: `inference`, `read`, `keys`, `billing`; a key can only grant scopes it has itself (`403 scope_not_allowed`).
- Expiry: `keys create --expires-in 30d` (or `expires_in` / `expires_at` on `POST /v1/keys`), at most 365 days; then `401 key_expired`. A key never outlives the key that created it.
- Sub-agent keys: `keys create --name <name> --child --budget-usd 2` (`"child": true`). Scopes, daily budget (within what the parent has left today), rate limits and expiry stay within the parent's; revoking a key revokes every key below it.
- Rotation: `keys rotate <id> --grace 24h` (`POST /v1/keys/{id}/rotate`) prints a replacement once; the old key keeps working for the grace period (default 24h, 0 to 7d).
- If you think the key in `.env` leaked: if it has the `keys` scope, `keys rotate <id> --grace 0` gives you a replacement and stops the old key at once; write the new key to `.env` as `ST_API_KEY` without printing it. Otherwise run `keys revoke-self` (`DELETE /v1/keys/self`, no scope needed; it also revokes the key's sub-agent keys) and tell your human. Either way, tell them what happened.
- A current list of the companies that process data for us, with what each receives and where, is available on request. Email privacy@scaledthought.com.

## Keys for other places

Give every place that runs code its own named sub-agent key, put straight into that platform's secret store. The key goes to the platform's own CLI on stdin: never printed, never in argv or shell history.

- `npx -y scaledthought@0.3 keys put vercel --env production` (in the linked app directory), `keys put github --repo acme/app --budget-usd 2`, `keys put cloudflare --worker summariser`; also `fly`, `gcp`, `aws`, `k8s`, `doppler`. It stores `ST_API_KEY`, names the key after the place and records it in `lives_in` (`GET /v1/keys`), so a human knows what revoking it breaks. The platform's CLI must be installed and logged in; if storing fails, the new key is revoked. The base URL isn't a secret: set `ST_BASE_URL` as a plain variable if your code reads it.
- `npx -y scaledthought@0.3 keys rotate --all --push --grace 1h` rotates every key that lives somewhere and puts each replacement in the same place; the old keys stop after the grace period. Nothing is printed but names and where they went.
- `npx -y scaledthought@0.3 connect opencode` (or `aider`) writes the base URL and a new key into that tool's config (files 0600). `connect cursor` and `connect continue` print steps for your human instead; no key is shown to you.

## Legal

Creating an account (CLI `init` or `POST /v1/accounts`) accepts nothing on anyone's behalf. The human or organisation you act for is the customer: they accept the Terms of Service on the payment page when they first buy credits (or save a card for auto-reload), and each payment approval is their authorisation of that payment. They are responsible for your use of the account from then on.

Current version: `2026-10-11`. Latest Privacy Policy change (October 11, 2026): the policy now covers ad measurement, an opt-in that may be offered on the landing pages of some of our ads targeted to people in the United States: if you choose it, we report your signup and first purchase to the advertising platform whose ad you clicked. It is never the default, never applies to Canadian accounts, and you can opt out at any time. The list of our service providers is no longer a separate page; a current list is available on request at privacy@scaledthought.com. The policy now also says where email you send us is kept.

- [Terms of Service](https://scaledthought.com/legal/terms): The agreement between ScaledThought and its customers. An agent that creates an account accepts these terms for the human or organisation it acts for, who is the customer.
- [Privacy Policy](https://scaledthought.com/legal/privacy): What we collect, why, where it's stored and for how long. Prompts and completions aren't logged or stored, data stays in your home region (card payments go through Stripe in the US), and we never train on your content.
- [Acceptable Use Policy](https://scaledthought.com/legal/acceptable-use): What you, your agents and your users must not do with ScaledThought, and your obligations under each model's licence.
- [Credits & Refunds Policy](https://scaledthought.com/legal/credits-and-refunds): How prepaid credits and auto-reload work, what happens when credits run out, and how refunds and disputes are handled.
- [Vulnerability Disclosure Policy](https://scaledthought.com/legal/security): How to report a security vulnerability in ScaledThought, what's in scope, and what we promise in return.

## Trademarks

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. Model licences are the publishers'; listing a model doesn't imply its publisher endorses ScaledThought.

## CLI reference

`npx -y scaledthought@0.3 <command>`. Output is JSON whenever stdout isn't a terminal (or with `--json`). Keys come from `ST_API_KEY` or `./.env`, never from flags. `--help` works on every command.

- `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 endpoints

On your region's host (`$ST_BASE_URL` is `https://api.{us|ca}.scaledthought.com/v1`). Authenticate with `Authorization: Bearer $ST_API_KEY` unless noted.

- `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.

Human docs: https://scaledthought.com/docs
