Use this page when a script, status bar, or CI job needs quota data.
Choose one source:
| Source | Best for |
|---|---|
opencode-quota show --json |
Reading the latest cached data from a command |
| Export file | Reading the same data often without running a command |
Both read OpenCode Quota's local cache. They do not contact providers.
Both sources return schema version: 2.
Each provider has a status and a list of quota rows. A row says what it measures, whether it is a percentage or value, and where the data came from. status: "partial" means some rows worked and some failed.
A row from a configured quotaProviders definition includes sourceId. providers["quota-providers"] also includes a sources list so another tool can match results to definitions.
Secrets, URLs, checked paths, and raw provider responses remain excluded from public JSON. Use /quota_status for live checks. The command and export file only read cached data.
opencode-quota show --jsonCommon variants:
# One provider only
opencode-quota show --json --provider copilot
# Fail if comparable cached quota is below 5%
opencode-quota show --json --threshold 5Threshold exits:
| Exit | Meaning |
|---|---|
0 |
Quota is available and above the threshold |
1 |
At least one comparable cached provider is below the threshold |
2 |
Results were incomplete or no cached percentage was comparable |
Use this when a status bar or background tool reads quota often.
Add this to opencode-quota/quota-toast.json:
Default output path:
$XDG_CACHE_HOME/opencode/quota-export.json
Usually that means:
~/.cache/opencode/quota-export.json
The TUI updates this file after each home-bottom background refresh, about every 60 seconds. Write errors are logged as warnings and never break TUI rendering.
npx @slkiser/opencode-quota show --json --threshold 5Copilot Free, Student, organization, and enterprise usage can be value-only. Select a percentage row instead of assuming the first row has one:
PCT=$(opencode-quota show --json --provider copilot \
| jq -r '.providers.copilot.entries | map(select(.renderType == "percent" and .percentRemaining != null)) | first | .percentRemaining // empty')
if [ -n "$PCT" ] && [ "${PCT%.*}" -lt 10 ]; then
echo "Low Copilot allowance or budget; skipping."
exit 0
fiset -g status-interval 30
set -g status-right '#(jq -r "[.providers|to_entries[]|select(.value.status==\"ok\")|first(.value.entries[]?|select(.renderType==\"percent\" and .percentRemaining!=null))|(.percentRemaining|floor|tostring)+\"%\"]|join(\" · \")" ~/.cache/opencode/quota-export.json 2>/dev/null)'[custom.quota]
command = "opencode-quota show --json 2>/dev/null | jq -r '[.providers|to_entries[]|select(.value.status==\"ok\")|first(.value.entries[]?|select(.renderType==\"percent\" and .percentRemaining!=null))|(.percentRemaining|floor|tostring)+\"%\"]|join(\" \")'"
when = "true"
interval = 60JSON shape
Both show --json and the export file use this v2 structure:
{
"version": 2,
"exportedAt": 1748736000,
"fromCache": true,
"cacheAgeSeconds": 42,
"providers": {
"copilot": {
"status": "ok",
"fetchedAt": 1748735958,
"entries": [
{
"name": "Premium Requests",
"resultType": "quota",
"acquisitionMethod": "remote_api",
"ownership": "maintained",
"authority": "provider_reported",
"window": "Monthly",
"resetAt": 1748908800,
"renderType": "percent",
"percentRemaining": 62.3,
},
],
},
"quota-providers": {
"status": "partial",
"fetchedAt": 1748735958,
"entries": [
{
"name": "OpenRouter Primary budget",
"resultType": "budget",
"acquisitionMethod": "remote_api",
"ownership": "user_configured",
"authority": "provider_reported",
"sourceId": "openrouter-primary",
"renderType": "percent",
"percentRemaining": 40,
},
],
"errors": [
{
"label": "Internal accounting",
"message": "HTTP 503",
},
],
"sources": [
{
"id": "openrouter-primary",
"providerId": "openrouter",
"status": "ok",
"entryCount": 1,
},
{
"id": "internal-accounting",
"providerId": "internal_gateway",
"status": "error",
"entryCount": 0,
},
{
"id": "model-specific",
"providerId": "internal_gateway",
"status": "unavailable",
"entryCount": 0,
},
],
},
"opencode-go": {
"status": "error",
"fetchedAt": 1748735958,
"error": "Request timeout after 5s",
},
"anthropic": {
"status": "unavailable",
},
},
}Provider and quota-provider definition statuses:
| Value | Meaning |
|---|---|
ok |
Provider: cached rows exist with no provider errors. Definition summary: its request succeeded and produced rows; failed mapping candidates can still make the aggregate provider partial. |
partial |
Cached rows and sanitized errors both exist. Consumers may display the rows, but threshold and routing decisions should treat the result as incomplete. |
error |
The cached provider or definition failed and has no successful row. Provider-level errors include a sanitized message; definition summaries do not expose detailed outcomes. |
unavailable |
No matching cached data exists. For a quota-provider definition this also covers an exact runtime providerId that was unavailable when the aggregate cache was written. |
Entry fields:
renderType: "percent"usespercentRemaining;renderType: "value"usesvalue.resultTypeis one ofquota,rate_limit,usage,spend,budget,balance, orstatus.window,resetAt, andobservedAtappear only when provider accounting data supplies them.sourceIdis the stablequotaProvidersdefinition id stamped onto rows from thequota-providersaggregate; it is identity, not a display label.sourcespreservesquotaProvidersdefinition order. Each summary is exactlyid, effectiveproviderId, coarsestatus, andentryCount; the count is the cached normalized row count for that definition, and duplicate labels remain separate by stableid. Ajson-v1source can beokafter producing valid rows while its rejected mapping candidates make the aggregate providerpartial.
More integration ideas
# macOS
fswatch -o ~/.cache/opencode/quota-export.json | xargs -I{} my-status-refresh
# Linux
inotifywait -m -e close_write ~/.cache/opencode/quota-export.json \
| while read; do my-status-refresh; doneimport json, subprocess
data = json.loads(subprocess.check_output(
["opencode-quota", "show", "--json"], timeout=1
))
best = max(
(k for k, v in data["providers"].items() if v["status"] == "ok"),
key=lambda k: next(
(e["percentRemaining"] for e in data["providers"][k]["entries"]
if e["renderType"] == "percent" and e.get("percentRemaining") is not None), 0
),
default=None,
)
{ "export": { "enabled": true, }, }