STOP WASTING PAID TOKENS. START POOLING ACCOUNTS TODAY. [ GET YOUR VAULT ]

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}/gate

This 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?

GET /usage

Do we need more keys?

GET /capacity

What actually served my alias?

GET /flow

Show me one request end to end

GET /traces/{trace_id} — see Traces


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

from

ISO-8601 datetime

none

Window start, inclusive. A naive value is read as UTC.

to

ISO-8601 datetime

now

Window end, exclusive.

days

integer 1–366

none

Deprecated rolling lookback. Ignored when from or to is supplied. A lookback cannot express a calendar month.

bucket

day or hour

day

Grain of the by_bucket series.

compare

boolean

false

Also returns the immediately preceding window of equal length under comparison. Doubles the query cost.

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-13

Response

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 }
  • unknown means the system cannot tell, not that the request worked. These rows predate the column that records whether anything came back. Folding them into delivered produces a false all-clear.

  • measurable_from is the date the split became trustworthy. Read the field; do not hard-code the date.

  • window_predates_measurement: true means your window reaches back before that date, so delivered_rate covers only the classifiable rows. Report the caveat with the number, or do not report the number.

  • truncated and tool_call_turns are 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 is delivered, not hollow.

  • measured: false with a reason means 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

401

No token, or a malformed one.

Send a valid bearer token.

403

The token does not own brand_id.

Use your own brand's numeric id.

422

to is at or before from.

Make to strictly later than from.

422

The window is longer than 366 days.

Narrow the window.

500

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

days

integer 1–90

7

Lookback window.

key_limit

integer 1–200

50

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_429

Response

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?

healthy

No. Nothing is under pressure.

add_keys

Yes. Only here is suggested_keys a number, and it is the recommended total.

not_quota

No. Errors are high but they are not rate limits. Keys will not fix it.

account_capped

No. The cap is on the account; one more key is refused identically.

over_provisioned

No. You already hold more than the traffic needs.

unmeasurable

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

401

No token, or a malformed one.

Send a valid bearer token.

403

The token does not own brand_id.

Use your own brand's numeric id.

422

days outside 1–90, or key_limit outside 1–200.

Send a value inside the range.

500

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

days

integer 1–90

7

Lookback window.

limit

integer 1–500

100

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 err

Response

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

401

No token, or a malformed one.

Send a valid bearer token.

403

The token does not own brand_id.

Use your own brand's numeric id.

422

days outside 1–90, or limit outside 1–500.

Send a value inside the range.

500

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

/usage

gate_usage

spideriq gate usage

/capacity

gate_capacity

spideriq gate capacity

/flow

gate_flow

spideriq gate flow

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.