Usage, Capacity & Flow API
The usage, capacity and flow endpoints answer three different questions about your brand's traffic through the gateway. They are read-only aggregates over the request log, and they are the same numbers the Gate → Usage page draws.
Base URL
https://spideriq.ai/api/v1/brands/{brand_id}/gateThis is not the /api/gate/v1 completion base. These are dashboard-scoped analytics routes and they take your numeric brand_id in the path, not the cli_… workspace slug.
Authentication
Authorization: Bearer <client_id>:<api_key>:<api_secret>
Authorization: Bearer spideriq_pat_…A personal access token works against your own brand. A token for another brand is refused; you never see another tenant's rows.
client_id is the identity ground truth and brand_id is the analytics key. They are different registries and they are not interchangeable. If you pass a brand_id you do not own you get a refusal, never someone else's data.
Which one answers your question
Question | Endpoint |
|---|---|
What did we spend and deliver over a window? |
|
Do we need more keys? |
|
What actually served my alias? |
|
Show me one request end to end |
|
GET /brands/{brand_id}/gate/usage
Spend, volume and delivery for one brand over one window. Scoped WHERE brand_id = …, so this is your spend and never pool capacity.
Parameters
Name | Type | Default | Notes |
|---|---|---|---|
| ISO-8601 datetime | none | Window start, inclusive. A naive value is read as UTC. |
| ISO-8601 datetime | now | Window end, exclusive. |
| integer 1–366 | none | Deprecated rolling lookback. Ignored when |
|
|
| Grain of the |
| boolean |
| Also returns the immediately preceding window of equal length under |
Example — ask for August as a calendar month, then check that the outcome split is trustworthy for it.
curl -s "https://spideriq.ai/api/v1/brands/$BRAND_ID/gate/usage?from=2026-08-01T00:00:00Z&to=2026-09-01T00:00:00Z&bucket=day" \
-H "Authorization: Bearer $SPIDERIQ_PAT" \
| python3 -c '
import json, sys
s = json.load(sys.stdin)["summary"]; o = s["outcome"]; t = s["turn_metrics"]
print("calendar month :", s["range"]["is_calendar_month"])
print("turns/attempts :", t["turns"], "/", t["attempts"], "amplification", t["amplification"])
print("states sum :", o["delivered"] + o["hollow"] + o["failed"] + o["unknown"] == o["total"])
print("trustworthy :", not o["window_predates_measurement"], "measurable_from", o["measurable_from"])
'
# calendar month : True
# turns/attempts : 812 / 1104 amplification 1.359
# states sum : True
# trustworthy : True measurable_from 2026-08-13Response
summary.range echoes what the server resolved: from, to, bucket, duration_hours, is_calendar_month, resolved_from. Read it back. It is how you confirm you measured the window you meant.
summary.turn_metrics separates logical requests from provider attempts:
{ "turns": 812, "attempts": 1104, "amplification": 1.359,
"attempts_without_trace": 0, "turns_complete": true }A turn is one logical request. Attempts include retries and provider fallbacks, so total_requests is an attempt count. Reporting it as user demand overstates demand, here by 36 percent. When turns_complete is false, some attempts carried no trace id and could not be folded into a turn; say so rather than absorbing them silently.
summary.outcome is a four-state split, and the four states are mutually exclusive and sum to total:
{ "delivered": 640, "hollow": 118, "failed": 41, "unknown": 13,
"truncated": 22, "tool_call_turns": 87,
"total": 812, "scope": "request_kind='chat'",
"measurable_from": "2026-08-13", "window_predates_measurement": false,
"delivered_rate": 0.7882, "measured": true }unknownmeans the system cannot tell, not that the request worked. These rows predate the column that records whether anything came back. Folding them intodeliveredproduces a false all-clear.measurable_fromis the date the split became trustworthy. Read the field; do not hard-code the date.window_predates_measurement: truemeans your window reaches back before that date, sodelivered_ratecovers only the classifiable rows. Report the caveat with the number, or do not report the number.truncatedandtool_call_turnsare annotations that overlap the four states, not extra states. Summing all six double-counts. A tool-call turn returns no visible characters by construction and isdelivered, nothollow.measured: falsewith areasonmeans no chat rows were in scope. That is "nothing to classify", not a zero and not a pass.
summary.cost_avoided is a counterfactual against one reference model's public list price, never an invoice. No bill was avoided. When state is unmeasurable, every money field is null with a reason; there is deliberately no fallback price.
summary.by_provider carries the wire prefix, not the resolved provider. Around 75,000 MiniMax rows are stamped openai. For the real provider use /flow.
Also returned: by_model, by_alias, by_agent, by_kind, by_bucket (each bucket carries its own nested outcome), by_day, latency (p50, p95, p99, avg_ms), top_errors, cache_hit_rate, cached_count, streamed_count, error_rate, distinct_aliases.
Errors
Status | Trigger | Resolution |
|---|---|---|
| No token, or a malformed one. | Send a valid bearer token. |
| The token does not own | Use your own brand's numeric id. |
|
| Make |
| The window is longer than 366 days. | Narrow the window. |
| The aggregate query failed. | Retry. This route deliberately does not fall back to a zero-filled payload, because all zeros is indistinguishable on screen from "you used nothing". |
GET /brands/{brand_id}/gate/capacity
Key pressure and subscription windows: whether buying more provider keys would help, and how much of a subscription plan's window you have consumed.
Parameters
Name | Type | Default | Notes |
|---|---|---|---|
| integer 1–90 |
| Lookback window. |
| integer 1–200 |
| Maximum subscription-window rows returned. |
Example — list each provider's verdict and confirm that a verdict which refuses carries no key recommendation.
curl -s "https://spideriq.ai/api/v1/brands/$BRAND_ID/gate/capacity?days=7&key_limit=50" \
-H "Authorization: Bearer $SPIDERIQ_PAT" \
| python3 -c '
import json, sys
for r in json.load(sys.stdin)["key_pressure"]["providers"]:
print(r["provider"], r["verdict"], "suggested_keys=", r["suggested_keys"], "limit_source=", r["limit_source"])
'
# groq add_keys suggested_keys= 6 limit_source= published
# openrouter not_quota suggested_keys= None limit_source= estimated_from_429Response
Two blocks with two different scopes, labelled in the payload. Read the label before quoting a number.
key_pressure carries traffic_scope: "pool_wide". It counts every request on a key regardless of which tenant caused it, because a pooled key serves the whole pool. It is not your usage.
Each key_pressure.providers[] row carries a verdict:
Verdict | Should you buy keys? |
|---|---|
| No. Nothing is under pressure. |
| Yes. Only here is |
| No. Errors are high but they are not rate limits. Keys will not fix it. |
| No. The cap is on the account; one more key is refused identically. |
| No. You already hold more than the traffic needs. |
| Cannot tell. Do not infer either way. |
suggested_keys is null for every verdict except add_keys. Rendering a number on a not_quota row turns a refusal into an upsell.
limit_source is a three-way answer and you should say which one you used: published is a real ceiling, partial is incomplete, and estimated_from_429 is an estimate inferred from rate-limit responses. headroom_pct is populated only when limit_source is published.
Each row also carries attempts, errors, rate_limited, error_pct, rate_limited_pct, quota_share_of_errors_pct, failed_cost_usd, serving_keys, pool_eligible_keys, registered_keys, published_daily_cap, peak_day_attempts, ceiling_coverage_pct and rpd_unpublished.
subscription_windows carries scope: "brand_keys" and covers your own keys:
{ "scope": "brand_keys", "gauge_available": true,
"keys": [ { "provider": "cerebras", "state": "measured",
"window_tokens_counted": 41822, "window_percent": 12.4,
"weekly_percent": 3.1, "verdict": "healthy", "can_set_plan": true } ] }Read gauge_available every time. It is live-changing, not a constant. While a row is state: "unmeasurable", window_percent and weekly_percent are present and null, not absent and not 0. A null rendered as an empty gauge is indistinguishable from "you have used nothing". can_set_plan says whether assigning a package to that key is possible at all. window_tokens_counted is named for what it counts, because the underlying column name is not.
Errors
Status | Trigger | Resolution |
|---|---|---|
| No token, or a malformed one. | Send a valid bearer token. |
| The token does not own | Use your own brand's numeric id. |
|
| Send a value inside the range. |
| The capacity query failed. | Retry. There is no zero-filled fallback. |
GET /brands/{brand_id}/gate/flow
The cross-tab behind a task alias: what you asked for, what actually answered, and which provider it came from. Brand-scoped.
Parameters
Name | Type | Default | Notes |
|---|---|---|---|
| integer 1–90 |
| Lookback window. |
| integer 1–500 |
| Maximum cross-tab rows. |
Example — show where an alias actually landed, and stop early if the cross-tab was cut off.
curl -s "https://spideriq.ai/api/v1/brands/$BRAND_ID/gate/flow?days=7&limit=100" \
-H "Authorization: Bearer $SPIDERIQ_PAT" \
| python3 -c '
import json, sys
d = json.load(sys.stdin)
print("truncated:", d["truncated"], "of limit", d["limit"])
for r in d["rows"][:5]:
print(r["requested_model"], "->", r["actual_model"], "via", r["provider"], r["requests"], "req", r["errors"], "err")
'
# truncated: False of limit 100
# spideriq/coding -> minimax/minimax-m2 via minimax 431 req 2 errResponse
rows[] of {requested_model, actual_model, provider, requests, errors, cost_usd}, plus truncated, limit, provider_source, window_days, scope and generated_at.
The provider here is resolved through the key's integration record, so a MiniMax model reports minimax and never openai. When this disagrees with usage.by_provider, this column is the correct one.
When truncated is true you are seeing a prefix of the cross-tab. Raise limit before drawing any conclusion about the tail.
Errors
Status | Trigger | Resolution |
|---|---|---|
| No token, or a malformed one. | Send a valid bearer token. |
| The token does not own | Use your own brand's numeric id. |
|
| Send a value inside the range. |
| The cross-tab query failed. | Retry. There is no zero-filled fallback. |
Calling this as an agent
Every endpoint above has a matching MCP tool and CLI verb.
Endpoint | MCP tool | CLI |
|---|---|---|
|
|
|
|
|
|
|
|
|
gate_capacity and gate_flow need @spideriq/mcp-gate 1.10.0 or later, or the combined @spideriq/mcp 1.89.0 or later, and @spideriq/cli 1.71.0 or later. On an older client they do not exist, and nothing tells you so. Check your pinned version before concluding a tool is missing.
The tool and the page are advertised as the same numbers over the same window. If they disagree, that is a bug worth reporting rather than a rounding difference.
Related
Traces — one request, end to end.
Usage — the same three surfaces, read from the dashboard.
Rate Limits & Budgets — the per-token limits these numbers are measured against.