# crowbot > One OpenAI-compatible API for every direction an AI user wants: frontier models (OpenAI, Moonshot, DeepSeek, Google) > resold at a fraction of their list price, and crowbot's own uncensored CROW models, > behind one key. Billed from a prepaid USD wallet, or from a plan's pass that pays first for > the models it covers; one base URL reaches every model. An account > is a 16-digit number that doubles as the API key — no email, no password, no recovery. Payment > is by crypto or card. Base URL `https://api.crowbot.sh/v1`. Currently live: CROW 2, CROW 1, GPT 6 Astra, GPT 6 Sol, Kimi K3, GPT 5.6 Sol, DeepSeek V4.1 Flash, Gemini 3.8 Flash. --- # Quickstart ## 1. Get an account number Open [https://chat.crowbot.sh](https://chat.crowbot.sh) and press **Create a new account**. You get a 16-digit number like `4192 0837 5561 2094`. That number is the whole account: no email, no password, **no recovery**. It is also your API key. Write it down — we store only a hash, so it cannot be shown again. ## 2. Fund it A new account starts empty, so the chat and the API both answer `402 insufficient_balance` until you fund it. Press **Top up** in the chat UI and name an amount. Payment is by crypto or card; crypto settles in stablecoin, and balances never expire. Without a browser: `GET https://chat.crowbot.sh/api/funding` for the allowed range, then `GET https://chat.crowbot.sh/checkout?usd=25` with your key as the Bearer returns `{"checkout_url"}`; pay it, and poll `GET https://api.crowbot.sh/api/me` until the balance rises. The [API](https://crowbot.sh/docs/api.md) page has the whole flow, signup included. ## 3. Call it ```sh curl https://api.crowbot.sh/v1/chat/completions \ -H "Authorization: Bearer $CROWBOT_KEY" \ -H 'content-type: application/json' \ -d '{ "model": "gpt-6-astra", "messages": [{"role": "user", "content": "Say hello in five words."}] }' ``` With the OpenAI SDK, change two settings and nothing else: ```python from openai import OpenAI client = OpenAI(base_url="https://api.crowbot.sh/v1", api_key="4192083755612094") print(client.chat.completions.create( model="gpt-6-astra", messages=[{"role": "user", "content": "Say hello in five words."}], ).choices[0].message.content) ``` ## 4. Point a coding agent at it See [opencode](https://crowbot.sh/docs/opencode.md) for a one-line installer. Any agent that speaks chat-completions needs only the base URL and the key. **Codex** (the OpenAI CLI): add a provider to `~/.codex/config.toml` and select it. ```toml model_provider = "crowbot" model = "gpt-6-astra" [model_providers.crowbot] name = "crowbot" base_url = "https://api.crowbot.sh/v1" env_key = "CROWBOT_API_KEY" wire_api = "chat" ``` **Claude Code** speaks the Anthropic Messages API, which crowbot does not serve: pointing `ANTHROPIC_BASE_URL` here gets a 404. Use a translating proxy (LiteLLM), opencode, or Codex. ## Checking your balance ```sh curl https://api.crowbot.sh/api/me -H "Authorization: Bearer $CROWBOT_KEY" ``` Returns the balance, total spent and request count. Every chat-completions response also carries `X-Balance-Microdollars`. --- # API reference crowbot speaks the OpenAI chat-completions dialect. Anything that can talk to OpenAI can talk to crowbot by changing two settings: the base URL and the API key. ``` base URL https://api.crowbot.sh/v1 auth Authorization: Bearer ``` Your account number *is* your API key. There is no separate key to generate. Spaces are accepted, so you can paste it exactly as it was shown to you. ## Where things live Three origins, each serving only its own paths; a path asked of the wrong one is a plain 404. | origin | serves | |---|---| | `https://api.crowbot.sh` | `/v1/*`, `/api/me` and `/api/plans` — what an agent configures and reads | | `https://chat.crowbot.sh` | the web chat, signup, funding (`/api/funding`, `/checkout`), plans, pairing | | `https://crowbot.sh` | these docs, `/llms.txt`, the installer | `GET https://api.crowbot.sh/api/me` returns `site` and `api`, so a client given one origin can find the others. ## Endpoints | method | path | origin | auth | what | |---|---|---|---|---| | `POST` | `/v1/chat/completions` | https://api.crowbot.sh | key, paid balance | chat completions — streaming, tool calling, `reasoning_effort` | | `GET` | `/v1/models` | https://api.crowbot.sh | none | live models with limits and per-direction rates | | `POST` | `/v1/tools/codesearch` | https://api.crowbot.sh | key, funded account | run a `codesearch` tool call server-side; see *Code search* | | `GET` | `/api/me` | https://api.crowbot.sh, https://chat.crowbot.sh | key or chat cookie | balance, spend, request count, key hint, `can_spend`, the active `pass` (plan, scope, `ends_at`, `allowance_bp`, `refills`) or null, the balance-fallback switch, and the `site`/`api` origins | | `POST` | `/api/me/balance-fallback` | https://api.crowbot.sh, https://chat.crowbot.sh | key or chat cookie | body `{"on": bool}`: whether the balance pays once a plan's allowance is spent | | `GET` | `/api/me/usage` | https://api.crowbot.sh, https://chat.crowbot.sh | key or chat cookie | this account's own spend, by model and by day, over `days` (default 30, 0 is all time): `{days, models: [{model, label, requests, prompt_tokens, completion_tokens, cached_tokens, spent_usd}], series: [{day, requests, spent_usd}]}` | | `GET` | `/api/me/usage/log` | https://api.crowbot.sh, https://chat.crowbot.sh | key or chat cookie | one row per request, paged 50 at a time. `page`, `sort` (`ts`/`model`/`tokens`/`cost`), `dir` (`asc`/`desc`), `days`. `totals` covers the whole window, never the page | | `GET` | `/api/me/payments` | https://api.crowbot.sh, https://chat.crowbot.sh | key or chat cookie | this account's payments, newest first `{payments: [{at, usd, kind}]}` | | `GET` | `/api/me/keys` | https://api.crowbot.sh, https://chat.crowbot.sh | key or chat cookie | the keys on this account (paired machines) | | `POST` | `/api/me/keys/{ref}/revoke` | https://api.crowbot.sh, https://chat.crowbot.sh | key or chat cookie | revoke one key by its `ref`; the key you are using is refused (409) | | `POST` | `/api/me/delete` | https://api.crowbot.sh, https://chat.crowbot.sh | key or chat cookie | close this account for good; body {account_number}; any balance is forfeited | | `POST` | `/api/pair/start` | https://api.crowbot.sh, https://chat.crowbot.sh | none (per-address cap) | open a pairing: `{device_code, user_code, ttl}`; body `{"hostname"}` optional | | `GET` | `/api/pair/{device_code}` | https://api.crowbot.sh, https://chat.crowbot.sh | none (per-address cap) | poll: 202 `pending`, 200 `{status: ready, api_key}` once, 410 expired | | `GET` | `/pair` | https://chat.crowbot.sh | none | the page a person types the `user_code` into | | `POST` | `/pair` | https://chat.crowbot.sh | chat cookie | the same page, showing Approve for a typed code | | `POST` | `/api/pair/approve` | https://api.crowbot.sh, https://chat.crowbot.sh | chat cookie | approve `{user_code}` (mints a key for that machine) or `{user_code, deny: true}` | | `POST` | `/api/signup` | https://chat.crowbot.sh | none + `X-Pow` (kind `signup`) | mint an account: `{account_number, formatted, api_base}` | | `GET` | `/api/pow` | https://chat.crowbot.sh | none for `?kind=signup`; key or cookie for `chat` | a proof-of-work challenge `{challenge, bits, ttl}` | | `POST` | `/api/chat` | https://chat.crowbot.sh | key or chat cookie + `X-Pow` | the web chat's own streaming door; agents use `/v1/chat/completions` | | `GET` | `/api/models` | https://chat.crowbot.sh | none | the chat picker's model list (same rows as `/v1/models`) | | `POST` | `/api/me/billing-portal` | https://api.crowbot.sh, https://chat.crowbot.sh | key or chat cookie | a session in the processor's portal for the cards it keeps for you `{url}` | | `GET` | `/api/live` | https://chat.crowbot.sh | none | server-sent events: each live topic (`?topics=tokens`) now, then on every change | | `GET` | `/api/live/{topic}` | https://chat.crowbot.sh, https://crowbot.sh | none | a live topic's latest payload; `tokens` is `{total}`, every token served, all time | | `POST` | `/api/tools/codesearch` | https://chat.crowbot.sh | key, funded account | `/v1/tools/codesearch`, reachable from the chat origin | | `GET` | `/api/funding` | https://chat.crowbot.sh | none | what a top-up may be and how to pay for it `{min_usd, max_usd, pay_with, rails}` | | `GET` | `/api/plans` | https://api.crowbot.sh, https://chat.crowbot.sh | none, or key or chat cookie | the plans on sale to this account; `{plans: []}` while none is | | `POST` | `/api/plans/{plan_id}/buy` | https://api.crowbot.sh, https://chat.crowbot.sh | key or chat cookie | buy that plan's pass with credit already in the wallet; `{ok, pass_id}`, 402 when short | | `GET` | `/api/me/subscription` | https://api.crowbot.sh, https://chat.crowbot.sh | key or chat cookie | the renewing agreement this account holds, or `{subscription: null}` | | `POST` | `/api/me/subscription/cancel` | https://api.crowbot.sh, https://chat.crowbot.sh | key or chat cookie | stop renewing; the pass already paid for runs to its end | | `GET` | `/checkout` | https://chat.crowbot.sh | key or chat cookie | `?usd=N` for credit, `?plan=ID` for a pass, `&renew=1` for one that renews → `{checkout_url}` (JSON) or a 303 to the processor (browser) | | `GET` | `/paid` | https://chat.crowbot.sh | none | where the processor sends a person back after paying | | `GET` | `/` | https://chat.crowbot.sh, https://crowbot.sh | none | the landing page on the site origin, the web chat on the chat origin | | `GET` | `/install.sh` | https://crowbot.sh | none | the opencode installer (bash) | | `GET` | `/install.ps1` | https://crowbot.sh | none | the opencode installer (PowerShell, Windows) | | `GET` | `/llms.txt` | https://crowbot.sh | none | this document | | `GET` | `/llms-full.txt` | https://crowbot.sh | none | every docs page inlined | | `GET` | `/docs` | https://crowbot.sh | none | 301 to the first docs page | | `GET` | `/docs/{name}` | https://crowbot.sh | none | one docs page, rendered for people and search engines | | `GET` | `/robots.txt` | https://crowbot.sh | none | what search engines may crawl, and where the sitemap is | | `GET` | `/sitemap.xml` | https://crowbot.sh | none | the public pages, from content/pages.yaml | | `GET` | `/docs/{name}.md` | https://crowbot.sh | none | one docs page as markdown | | `GET` | `/docs/opencode.json` | https://api.crowbot.sh, https://crowbot.sh | none | generated opencode provider config | | `GET` | `/docs/opencode-plugin.js` | https://api.crowbot.sh, https://crowbot.sh | none | the plugin the installer drops in | | `POST` | `/webhooks/payment` | https://crowbot.sh | processor HMAC | the crypto processor's callback | | `POST` | `/webhooks/stripe` | https://crowbot.sh | Stripe signature | the card processor's callback | | `GET` | `/healthz` | every origin | none | liveness, on every host | | `GET` | `/privacy` | https://crowbot.sh | none | the Privacy page | | `GET` | `/terms` | https://crowbot.sh | none | the Terms page | | `GET` | `/refunds` | https://crowbot.sh | none | the Refunds page | | `GET` | `/why` | https://crowbot.sh | none | the Why it's cheap page | `/v1/chat/completions` is a passthrough: **your `system` message is sent to the model unchanged and stays first**, and so are your `tools`, `tool_choice` and `response_format`. Your system prompt is never replaced, edited, reordered or moved behind anything of ours. Some models add a short instruction block of their own **alongside** it — formatting or refusal style. Your prompt keeps its exact bytes and its position; ours goes after it. ## Request ```json { "model": "gpt-6-astra", "messages": [ {"role": "system", "content": "You are a careful assistant."}, {"role": "user", "content": "Hello"} ], "max_tokens": 1024, "stream": true } ``` An **unrecognised** `model` falls back to the default rather than erroring, because agentic clients often send names from other providers. A model id we **do** recognise but are not serving returns `404 model_not_found` and lists the live ids: falling back there would bill you for a model you did not ask for. **Sampling parameters are forwarded** as you send them, including `temperature` and `top_p`. Which of them a model honours is up to its vendor. **`reasoning_effort`** we do interpret. Send `low`, `medium`, `high` or `max`: it overrides the model's configured default, a model without that tier gets its own ceiling (`max` reaches a vendor with three tiers as `high`), and a value none can honour is a `400` naming what the model accepts. Reasoning tokens bill as **output**, so this is a spend control, not a quality slider. ## Streaming Set `stream: true` and read Server-Sent Events. Chunks are `chat.completion.chunk` objects terminated by `data: [DONE]`, exactly as OpenAI emits them. Usage arrives on a final chunk with an empty `choices` array — you do not need to ask for it. Reasoning models put their thinking trace in `choices[].delta.reasoning_content`, alongside the usual `content`. Clients that don't know the field simply ignore it. ## Tool calling Standard OpenAI shape in both directions: ```json { "tools": [{"type": "function", "function": { "name": "bash", "description": "Run a shell command", "parameters": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]} }}], "tool_choice": "auto" } ``` Replies carry `message.tool_calls[]` with `function.arguments` as a JSON **string**, and `finish_reason: "tool_calls"`. Return results as `{"role": "tool", "tool_call_id": …, "content": …}` messages — one per call, as OpenAI expects. Parallel calls work; send every result before the next turn. Streaming tool calls arrive as `delta.tool_calls[]` fragments. Concatenate `function.arguments` per `index` to rebuild the JSON. ## Code search `codesearch` runs on our side: give it a query, get matching code from public repositories. No search account of your own. ``` POST https://api.crowbot.sh/v1/tools/codesearch Authorization: Bearer ``` `query` is required; `lang`, `repo` and `count` are optional. `query` may itself carry search filters (`repo:`, `file:`, `lang:`, `patterntype:regexp`, `patterntype:structural`, …), which compose with the fields; `count` is capped. ```json {"query": "epoll_wait", "lang": "c", "count": 5} ``` The reply is `{"query", "results", "truncated"}`, each result carrying `repo`, `path`, the first matching `line`, a `url` to the source on its own host, and a `snippet` (for a `type:symbol` query, the symbol's kind, name and line): ```json {"results": [{"repo": "github.com/x/y", "path": "loop.c", "line": 42, "url": "https://github.com/x/y/blob//loop.c#L42", "snippet": "int n = epoll_wait(epfd, events, MAX_EVENTS, timeout_ms);"}], "truncated": false} ``` Write queries the way code is written: exact identifiers find code, sentences rarely do. `lang` narrows by language; `repo` as `org/name` is anchored to that one repository, which turns a pile of forks into the canonical file. A backend rate limit comes back as `429 rate_limited` with `Retry-After`. **Wiring it into an agent.** Register the tool below; when the model calls it, POST the arguments to the endpoint above and feed the reply back as the tool result. ```json { "type": "function", "function": { "name": "codesearch", "description": "Search public source code and get back matching snippets with repo, path and line. Use it when a real implementation answers better than a description. Write the query the way code is written, not the way a question is asked: two or three exact identifiers (function, class, option or constant names) find code; sentences almost never do. Set lang when you know the language. Set repo when you know the project - org/name is enough - which turns a pile of forks into the one canonical file. Quote an exact phrase \"like this\" to match it verbatim; add type:symbol to find where a name is defined rather than used (slower, but finds the defining file even when the canonical repo is not indexed); add patterntype:regexp for a pattern. If nothing comes back, or the repository is reported as not found, drop the repo filter or a term and search again.", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "Identifiers and exact phrases, optionally with file:/type:symbol/patterntype:regexp filters" }, "lang": { "type": "string", "description": "Language filter, e.g. python, c++, typescript, go" }, "repo": { "type": "string", "description": "Repository: org/name (anchored to that repo) or a regexp over full names like github.com/org/name" }, "count": { "type": "integer", "description": "Maximum results, capped by the server" } }, "required": [ "query" ] } } } ``` Snippets bill as ordinary input tokens on your next turn; there is no search fee. Calls are rate-limited. This is the same definition the web chat gives the model. ## Errors Errors are `{"error": {"message": …, "type": …}}`. The `type` is the field to branch on — not the status, because a status can be ambiguous. | status | type | meaning | |---|---|---| | 400 | `bad_request` | the body was not a JSON object, or a parameter is invalid | | 400 | `bad_amount` | `/checkout`: not an amount to the cent within `min_usd`..`max_usd` | | 400 | `bad_ref` | `/api/me/keys/{ref}/revoke`: not a key reference | | 400 | `number_mismatch` | `/api/me/delete`: the account number typed is not this account's | | 400 | `cannot_renew` | `/checkout?renew=1`: the chosen payment method cannot hold a card to charge again | | 401 | `invalid_api_key` | account number unknown or revoked | | 403 | `account_disabled` | the account is frozen (a card chargeback or refund); it can read its status but not spend or add funds | | 402 | `insufficient_balance` | out of funds, or this request's input alone costs more than the balance — top up | | 402 | `plan_allowance_spent` | the plan's allowance is spent and this account is set not to fall back on its balance | | 404 | `model_not_found` | a model we know but are not serving; the message lists the live ids | | 404 | `not_found` | no such page, key or route on this host | | 404 | `no_such_plan` | the plan named is not on sale to this account | | 404 | `no_subscription` | `/api/me/subscription/cancel`: this account has no renewing plan | | 409 | `cannot_revoke_current` | that key authenticated this request; revoke it from another | | 409 | `subscription_active` | `/api/me/delete`: a renewing plan is still active; cancel it first | | 410 | `pair_expired` | the pairing code expired or was already used | | 413 | `body_too_large` | over the 16 MiB request-body ceiling | | 428 | `pow_required` | proof of work missing or expired (chat and signup only, never `/v1`) | | 429 | `rate_limited` | back off; honour `Retry-After` when present | | 502 | `tool_error` | a tool (e.g. `codesearch`) failed upstream; the message says how | | 502 | `checkout_failed` | the payment processor did not answer | | 502 | `cancel_failed` | the payment processor did not answer a cancellation; retry shortly | | 404 | `no_customer` | `/api/me/billing-portal`: no card is saved yet; pay by card once | | 502 | `portal_failed` | `/api/me/billing-portal`: the payment processor did not answer; retry shortly | | 503 | `live_full` | `/api/live`: every live stream is taken; poll `/api/live/{topic}` and retry after `Retry-After` | | 503 | `live_warming` | `/api/live/{topic}`: the topic has not been read since the app started; retry shortly | | 503 | `tool_unavailable` | a tool is not configured or is temporarily off | | 503 | `checkout_unconfigured` | payments are not configured on this deployment | | 503 | `no_model` | nothing is live right now | | 503 | `no_provider` | that model's upstream is not configured | | 503 | `no_price` | a feed-priced model with no quote yet — retry shortly | | 503 | `price_fault` | a feed-priced model whose quote moved past our ceiling; off sale until a sane one lands | | 503 | `upstream_unavailable` | our problem, not yours — try another model | | vendor's | `upstream_error` | the upstream's own reason for refusing, with its `Retry-After` if it sent one | Every `/v1/chat/completions` answer, error or not, carries an `X-Request-Id` header. Quote it when reporting a failure: it names the one record we keep of that call. A streamed reply may open with an SSE comment line (`:`) and carry one during any pause: a keepalive, which every SSE client ignores. A failure after the stream has opened arrives as that same `{"error": {"message": …, "type": …}}` envelope in a `data:` frame, followed by `data: [DONE]`. An upstream vendor's own 401/402/403 is never relayed to you as-is. If our account with a vendor lapses you get a `503 upstream_unavailable`: the fault is ours, not your key's or balance's. ## Billing Input, cached input and output bill at separate per-million rates — see [Models](https://crowbot.sh/docs/models.md). Every token sent upstream is billed, and cached input is discounted at the same ratio the upstream gives us. Two things can pay for a request, and they are asked in a fixed order. A **pass** — what buying a plan gives you, see *Plans* — pays first, for any model in its scope. Your **balance** pays for everything else: a model the pass does not cover, or every request when you hold no pass. The two never mix inside one request; whichever said yes at the door settles it. A pass does not bill at the rates above. It carries an allowance, sized from what the plan cost, that requests draw down at what the model costs *us* and that refills continuously at the plan's rate — so it absorbs a burst and comes back over the day. Once the allowance cannot cover a request, that request is the balance's, unless you have said otherwise: `POST /api/me/balance-fallback` with `{"on": false}` caps you at the plan, and a request that would then need the balance is refused with `402 plan_allowance_spent` and a message saying roughly when the allowance is back. `GET /api/me` shows the pass while you hold one: `allowance_bp` is how much of the allowance is left, in basis points of the whole, and `refills` says roughly when it is full again. Non-streamed responses carry `X-Balance-Microdollars` with your remaining paid balance in microdollars (1e-6 USD); a request the pass paid for leaves it where it was. A streamed response does not carry it — headers are sent before the body starts, so the figure would be stale before the first token. Read `/api/me` after a stream, or use the `usage` block on the final chunk. Every request is sized to whatever is paying for it before it is sent upstream: `max_tokens` (or `max_completion_tokens`) is capped to what the pass's remaining allowance or the balance can pay for after the estimated input, and a request whose input alone costs more than that is refused with `402 insufficient_balance` before any vendor is called. Concurrency follows the payer too — a balance gets as many parallel requests as it covers in worst-case requests, a pass at most the slots its plan sold, one at minimum either way — and past that a `429 rate_limited` with `Retry-After`. ## Plans A plan is one price for a fixed number of days. Buying one gives the account a **pass**: an allowance that pays first for every model in the plan's scope, the way *Billing* describes, while the balance goes on paying for everything else. What is on sale is public: ``` GET https://chat.crowbot.sh/api/plans Authorization: Bearer (optional) ``` The reply is `{"plans": [...]}`, one entry per plan you may buy: ```json {"id": "starter", "label": "Starter", "tagline": "Hard working or hardly working?", "price_usd": 15, "days": 30, "concurrency": 2, "scope": ["frontier"], "features": ["Every frontier model", "Refills every day", "Cancel any time"], "badge": null} ``` `price_usd` buys `days` days. `concurrency` is how many requests the pass runs at once; `scope` names the directions on the [Models](https://crowbot.sh/docs/models.md) page it covers (`frontier` is the resold frontier models, `crow` our own). `features` and `badge` are the card's own words. A pass fixes its allowance in money when it is bought, so nothing on a plan moves when a vendor reprices. The list is what *your* account may buy. Signed out, it is whatever is on general sale; while plans are in a closed beta it is empty unless your account is in it. `{"plans": []}` means nothing is on sale to you, not that the call failed. ## Funding, programmatically The chat UI does all of this with buttons; an agent can do it with four calls, all on `https://chat.crowbot.sh` except the last. 1. **Create an account.** `GET https://chat.crowbot.sh/api/pow?kind=signup` returns `{"challenge", "bits", "ttl"}`. Find a `nonce` (≤ 40 chars) such that `sha256(":")` has at least `bits` leading zero bits, then `POST https://chat.crowbot.sh/api/signup` with header `X-Pow: .`. The reply is `{"account_number", "formatted", "api_base"}` — the only time the number is ever shown. Each signup from an address raises the next one's `bits` for a day, and signups are capped per address; a `428` means the work grew meanwhile: fetch again. 2. **Pick an amount.** `GET https://chat.crowbot.sh/api/funding` → `{"min_usd", "max_usd", "pay_with", "rails"}` (`rails`: `[{"name", "label", "renews"}]`). Any amount in range is accepted, to the cent (`usd=3.50`), credited 1:1. 3. **Get an invoice.** `GET https://chat.crowbot.sh/checkout?usd=` with `Authorization: Bearer ` returns `{"checkout_url"}` (a browser `Accept: text/html` request is redirected there instead). Payment is by crypto or card; crypto settles in stablecoin. 4. **Confirm.** There is no payment-status endpoint. The processor's callback credits the wallet; poll `GET https://api.crowbot.sh/api/me` until `balance_microdollars` rises. Balances never expire. ## Device pairing and keys A machine can hold its own key, so a laptop can be revoked later without changing the account number. This is what the installer does; the raw flow: 1. `POST https://chat.crowbot.sh/api/pair/start` (optional body `{"hostname": "…"}`) → `{"device_code", "user_code", "ttl"}`. Unauthenticated, capped per address. 2. Show the person the `user_code` and send them to `https://chat.crowbot.sh/pair`, where they type it and press Approve while signed in (programmatically: `POST https://chat.crowbot.sh/api/pair/approve` with the chat cookie and `{"user_code"}`, or `{"user_code", "deny": true}`). 3. Poll `GET https://chat.crowbot.sh/api/pair/`: `202 {"status": "pending"}` while waiting, then `200 {"status": "ready", "api_key"}` exactly once, then `410 pair_expired`. Codes last 15 minutes. `GET /api/me/keys` lists the account's live keys with a `ref`, `name`, `hint`, `created_at` and whether each is the one making the request; `POST /api/me/keys//revoke` revokes one. The key that authenticated the request cannot be revoked (`409 cannot_revoke_current`): there is no recovery, so that click would be a permanent lockout. --- # Models Everything below is rendered from the live model table, so it is what the gateway will actually charge and serve right now. | model | id | in | cached in | out | list in | list out | context | max output | status | |---|---|---|---|---|---|---|---|---|---| | CROW 2 | `crow-2` | $6.00 | $0.60 | $10.00 | — | — | 1M | 32.8K | live | | CROW 1 | `crow-1` | $3.00 | $0.30 | $5.00 | — | — | 262K | 32.8K | live | | GPT 6 Astra | `gpt-6-astra` | $0.30 | $0.03 | $1.50 | $10.00 | $50.00 | 1M | 272K | live | | Claude Fable 5.1 | `claude-fable-5.1` | $1.50 | $0.0375 | $7.50 | $10.00 | $50.00 | 1M | 128K | live | | GPT 6 Sol | `gpt-6-sol` | $0.18 | $0.018 | $0.90 | $2.00 | $10.00 | 1M | 128K | live | | Kimi K3 | `kimi-k3` | $0.27 | $0.027 | $1.35 | $3.00 | $15.00 | 1M | 1M | live | | GPT 5.6 Sol | `gpt-5.6-sol` | $0.08 | $0.008 | $0.40 | $4.00 | $20.00 | 1M | 128K | live | | DeepSeek V4.1 Flash | `deepseek-v4.1-flash` | $0.045 | $0.0009 | $0.18 | $0.30 | $1.20 | 1M | 128K | live | | Gemini 3.8 Flash | `gemini-3.8-flash` | $0.0675 | $0.0067 | $0.3375 | $0.75 | $3.75 | 1M | 65.5K | live | | Claude Opus 5.5 | `claude-opus-5-5` | — | — | — | — | — | 1M | 128K | coming_soon | Rates are US dollars per million tokens. Input, cached input and output are billed separately at their own rate — there is no single blended price, because a request's cost depends on its shape. **Cached input** bills at the cached rate whenever the upstream reports a hit, at the ratio we get. Not every model reports hits, so budget for the full input rate, and keep the stable part of your prompt first: one changed token near the front invalidates everything after it. ## Directions One key reaches every direction below. **Frontier models, resold.** The frontier models are sold at a fixed discount off the vendor's own published price. What you pay therefore moves only when the vendor moves its list price; what it costs us to serve you is our problem and changes our margin instead of your bill. The `list in` and `list out` columns are that published price, so you can check the discount yourself; a row without them is not resold. How the resellers are tested, and what we keep: [why it's this cheap](https://crowbot.sh/why). **Uncensored models, our own.** The CROW line runs abliterated weights with no policy layer at the model level, at fixed rates. Same endpoint, same key, same wallet. There is no cached *output* rate, here or at any vendor. Caching stores the state of a prompt prefix; output tokens are generated fresh on every call, so there is nothing to reuse. ## Statuses | status | in the chat picker | callable via `/v1` | |---|---|---| | `live` | selectable | yes | | `coming_soon` | shown, greyed out | no | | `deprecated` | not shown | no | | `hidden` | not shown | no | A retired model keeps its row, so past usage stays attributable. Naming a non-live model over `/v1` is a `404 model_not_found` listing what is live; an id we do not recognise at all falls back to the default, since agentic clients routinely send other providers' names. ## Choosing - **CROW 2** (`crow-2`) — Zero Refusal. Deepest Reasoning. Ideal for Red Teaming. $6.00 in / $10.00 out per 1M, 1M context. - **CROW 1** (`crow-1`) — The cheaper one. Surprisingly creative, though. $3.00 in / $5.00 out per 1M, 262K context. - **GPT 6 Astra** (`gpt-6-astra`) — OpenAI's latest baby. Good for anything. $0.30 in / $1.50 out per 1M, 1M context (list price $10.00 in / $50.00 out). - **GPT 6 Sol** (`gpt-6-sol`) — OpenAI's fast one, a generation on. $0.18 in / $0.90 out per 1M, 1M context (list price $2.00 in / $10.00 out). - **Kimi K3** (`kimi-k3`) — Moonshot's coding specialist. $0.27 in / $1.35 out per 1M, 1M context (list price $3.00 in / $15.00 out). - **GPT 5.6 Sol** (`gpt-5.6-sol`) — OpenAI's fast one. Cheap and cheerful. $0.08 in / $0.40 out per 1M, 1M context (list price $4.00 in / $20.00 out). - **DeepSeek V4.1 Flash** (`deepseek-v4.1-flash`) — DeepSeek's fast one. Cheap enough to leave running. $0.045 in / $0.18 out per 1M, 1M context — cached input $0.0009, 50x cheaper (list price $0.30 in / $1.20 out). - **Gemini 3.8 Flash** (`gemini-3.8-flash`) — Google's fast one. A million tokens of context, quick to answer. $0.0675 in / $0.3375 out per 1M, 1M context (list price $0.75 in / $3.75 out). Every live model streams. Reasoning models return their thinking trace in `reasoning_content`; `/v1/models` says per model which of tool calling, reasoning, temperature and prompt caching it has, under `capabilities`. Pick on price; the lineup above says which is the default, and omitting `model` gets it. ## Limits `context` is the prompt window; `max output` is the ceiling on one response, clamped rather than rejected. Both are editable without a deploy, so treat `/v1/models` as authoritative. --- # Using crowbot with opencode [opencode](https://opencode.ai) reaches crowbot through the AI SDK's OpenAI-compatible provider, which is a config block rather than an integration to build. The installer also drops in a small plugin. A provider declared in `opencode.json` has a **frozen** model list, because opencode never asks us what we serve; the plugin refreshes it at startup. ## Install ```sh curl -fsSL https://crowbot.sh/install.sh | bash ``` On Windows, in PowerShell (5.1 or 7): ```powershell irm https://crowbot.sh/install.ps1 | iex ``` It asks whether to configure opencode globally or for one project, then takes your account number or pairs through the browser. Pairing creates a **separate key for that machine**, so a laptop is revocable without changing your account number. Non-interactive: ```sh curl -fsSL https://crowbot.sh/install.sh | bash -s -- --global --key 4192083755612094 curl -fsSL https://crowbot.sh/install.sh | bash -s -- --local --pair curl -fsSL https://crowbot.sh/install.sh | bash -s -- --global --pair --dry-run # print, write nothing ``` The PowerShell installer takes the same flags as `-Global`, `-Local`, `-Key`, `-Pair` and `-DryRun`; `iex` cannot pass them, so use the script-block form: ```powershell & ([scriptblock]::Create((irm https://crowbot.sh/install.ps1))) -Global -Key 4192083755612094 & ([scriptblock]::Create((irm https://crowbot.sh/install.ps1))) -Local -Pair ``` The installer **merges** into an existing config and keeps a timestamped backup. It only ever writes the `provider.crowbot` key, and it refuses to touch a config it cannot parse. Then run `/models` inside opencode and pick a crowbot model. ## Keeping the model list current Two files get written: | | | |---|---| | `opencode.json` | the provider block, including a snapshot of the models | | `plugin/crowbot.js` | refreshes that model list at startup | The plugin loads by sitting in that directory, so there is nothing to add to your config and re-running the installer cannot duplicate it. It fetches `https://api.crowbot.sh/docs/opencode.json`, replaces the model list and touches nothing else — never your key. If we cannot be reached it does nothing, capped at a couple of seconds so an outage here cannot stop opencode starting. Delete `plugin/crowbot.js` if you would rather pin the model list. Re-run the installer when it needs updating. A 404 naming a model you thought you had means your config predates a rename; the error says what replaced it, and re-running the installer repoints you. ## Config, by hand The generated block with current models, limits and prices is always at **[https://api.crowbot.sh/docs/opencode.json](https://api.crowbot.sh/docs/opencode.json)**. > **Do not `curl -o` over your global config.** `~/.config/opencode/opencode.json` holds > your other providers, agents, keybinds and MCP servers, and opencode merges settings > across levels rather than replacing them. Redirecting the generated file over it > deletes all of that. Paste the `provider` block into your existing file instead, or let > the installer merge it. ```json { "$schema": "https://opencode.ai/config.json", "provider": { "crowbot": { "npm": "@ai-sdk/openai-compatible", "name": "crowbot", "options": { "baseURL": "https://api.crowbot.sh/v1", "apiKey": "{env:CROWBOT_API_KEY}" }, "models": { "crow-2": { "name": "CROW 2", "limit": { "context": 1000000, "output": 32768 }, "cost": { "input": 6.0, "output": 10.0, "cache_read": 0.6 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false }, "crow-1": { "name": "CROW 1", "limit": { "context": 262144, "output": 32768 }, "cost": { "input": 3.0, "output": 5.0, "cache_read": 0.3 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false }, "gpt-6-astra": { "name": "GPT 6 Astra", "limit": { "context": 1048576, "output": 272000 }, "cost": { "input": 0.3, "output": 1.5, "cache_read": 0.03 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false }, "gpt-6-sol": { "name": "GPT 6 Sol", "limit": { "context": 1048576, "output": 128000 }, "cost": { "input": 0.18, "output": 0.9, "cache_read": 0.018 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false }, "kimi-k3": { "name": "Kimi K3", "limit": { "context": 1048576, "output": 1048576 }, "cost": { "input": 0.27, "output": 1.35, "cache_read": 0.027 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false }, "gpt-5.6-sol": { "name": "GPT 5.6 Sol", "limit": { "context": 1048576, "output": 128000 }, "cost": { "input": 0.08, "output": 0.4, "cache_read": 0.008 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false }, "deepseek-v4.1-flash": { "name": "DeepSeek V4.1 Flash", "limit": { "context": 1048576, "output": 128000 }, "cost": { "input": 0.045, "output": 0.18, "cache_read": 0.0009 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false }, "gemini-3.8-flash": { "name": "Gemini 3.8 Flash", "limit": { "context": 1048576, "output": 65536 }, "cost": { "input": 0.0675, "output": 0.3375, "cache_read": 0.0067 }, "tool_call": true, "reasoning": true, "interleaved": "reasoning_content", "temperature": true, "attachment": false } } } } } ``` Set the key the config expects: ```sh export CROWBOT_API_KEY=4192083755612094 # your account number, no spaces ``` ## What each field is doing | field | why it matters | |---|---| | `npm` | `@ai-sdk/openai-compatible` is the right driver: we serve `/v1/chat/completions`, not `/v1/responses` | | `apiKey` | `{env:CROWBOT_API_KEY}` keeps your account number out of the file | | `tool_call` | true where the model calls tools; we translate them in both directions, so agent mode works | | `reasoning` + `interleaved` | `"reasoning_content"` is the field our SSE actually emits, so opencode renders the thinking trace instead of discarding it | | `temperature` | true where the model takes a sampling temperature; sampling parameters are forwarded as you send them | | `cost` | **our** rates, not the upstream vendor's — so opencode's spend display matches what you are actually charged | | `limit` | real context and output ceilings, so opencode truncates before we have to | ## Notes - **Fund the account first.** A new account is empty, so opencode gets an `insufficient_balance` 402 until you top up. See [Quickstart](https://crowbot.sh/docs/quickstart.md). - **Your system prompt is honoured.** opencode's agent definition reaches the model unchanged and stays first. See [API](https://crowbot.sh/docs/api.md). - **No proof-of-work on the API.** The chat requires one per message; `/v1` never does. - If a model shows up but every call fails, check `/api/me` for your balance first. ## Docs - [Quickstart](https://crowbot.sh/docs/quickstart.md): Create an account, fund it, make the first call - [API](https://crowbot.sh/docs/api.md): Endpoints, auth, streaming, tool calling, error types - [Models](https://crowbot.sh/docs/models.md): Live lineup with per-direction rates and limits - [opencode](https://crowbot.sh/docs/opencode.md): Paste-ready opencode provider config ## Machine-readable - [https://api.crowbot.sh/v1/models](https://api.crowbot.sh/v1/models): live models with limits and per-direction rates - [https://api.crowbot.sh/docs/opencode.json](https://api.crowbot.sh/docs/opencode.json): generated opencode provider config ## Optional - [https://crowbot.sh/llms-full.txt](https://crowbot.sh/llms-full.txt): every page inlined, one fetch