Using crowbot with opencode
opencode 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
shcurl -fsSL https://crowbot.sh/install.sh | bashOn Windows, in PowerShell (5.1 or 7):
powershellirm https://crowbot.sh/install.ps1 | iexIt 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:
shcurl -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 nothingThe 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 -PairThe 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.
Do not
curl -oover your global config.~/.config/opencode/opencode.jsonholds 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 theproviderblock 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:
shexport CROWBOT_API_KEY=4192083755612094 # your account number, no spacesWhat 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_balance402 until you top up. See Quickstart. - Your system prompt is honoured. opencode's agent definition reaches the model unchanged and stays first. See API.
- No proof-of-work on the API. The chat requires one per message;
/v1never does. - If a model shows up but every call fails, check
/api/mefor your balance first.