DocumentationBrowse
Apps and attribution
One key often serves several products: a drafting tool, an intake bot, a nightly batch job. Attribution lets each request say which one it came from, so the usage log, the CSV export, the daily rollups and GET /apps can all be broken down by app without minting a key per product.
The headers are the OpenRouter convention plus one DocketRouter addition, so a client that already sends HTTP-Referer and X-Title upstream is attributed here with no change.
The three request headers
| Header | Becomes | Rules |
|---|---|---|
HTTP-Referer | app_url | Must parse as an http or https URL, otherwise it is dropped. A plain Referer header is accepted as a fallback. Trimmed to 512 characters. |
X-Title | app_name | The display name of the calling app. Control characters are stripped, whitespace trimmed, and the result is cut at 80 characters. |
X-DocketRouter-App | app_name | Same meaning as X-Title and wins when both are sent. Use it when you cannot control what an SDK puts in X-Title. |
- A URL with no name attributes to its hostname, so
HTTP-Referer: https://intake.example.com/newon its own givesapp_name: "intake.example.com". - A name with no URL is fine. Most server-side jobs send only
X-DocketRouter-App. - No usable header at all leaves both fields
null, and the request lands in the unattributed bucket described below. - The headers are read on every call, so one key can carry different apps on different requests.
curl https://docketrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer dr-…" \
-H "content-type: application/json" \
-H "HTTP-Referer: https://intake.example.com" \
-H "X-Title: Intake Bot" \
-d '{
"model": "deepseek/deepseek-v4-flash",
"messages": [{ "role": "user", "content": "Under TRCP 21a, when is service by email complete?" }]
}'
# X-DocketRouter-App takes precedence over X-Title when both are present
curl https://docketrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer dr-…" \
-H "content-type: application/json" \
-H "X-DocketRouter-App: nightly-batch" \
-d '{ "model": "deepseek/deepseek-v4-flash", "messages": [{ "role": "user", "content": "…" }] }'Anyone holding the key can send any app name. Use attribution to split a bill or a dashboard, and use separate keys with their own caps when two products must not be able to spend each other's budget. See limit_reset for per-key period caps.
Where the app shows up
On the response
The parsed attribution is echoed back as docketrouter.app on the JSON response and on the first chunk of a stream, so you can confirm what was recorded without reading the log.
"docketrouter": {
"request_id": "req_f60437cb80a541d8912c",
"app": { "name": "Intake Bot", "url": "https://intake.example.com" },
…
}On every usage row
Each row in GET /usage, GET /usage/:request_id and GET /generation?id= carries app_name and app_url. The CSV export puts the same two columns right after key_name. The per-generation view additionally groups them as app: { name, url }.
{
"request_id": "req_f60437cb80a541d8912c",
"key_name": "prod-web",
"app_name": "Intake Bot",
"app_url": "https://intake.example.com",
"model": "deepseek/deepseek-v4-flash",
"cost_usd": 0.00007482,
…
}Filtering usage by app
GET /usage and GET /usage/daily both take an app parameter, an exact match on app_name. The usage envelope also carries by_app, a whole-scope rollup per app, and echoes the filter back under filters.app. GET /usage/daily?group=app splits each day's bucket by app.
# rows for one app GET https://docketrouter.ai/api/v1/usage?app=Intake%20Bot&from=2026-08-01 # one bucket per day per app, last 30 days GET https://docketrouter.ai/api/v1/usage/daily?group=app # the same as CSV GET https://docketrouter.ai/api/v1/usage/daily?group=app&format=csv
The full parameter tables are on Usage and billing.
GET /apps
One row per app, sorted by spend, plus one bucket for everything unattributed.
| Parameter | Values | Notes |
|---|---|---|
from | ISO timestamp | Only usage at or after this time is counted. |
to | ISO timestamp | Only usage before this time is counted. |
owner | account id | Admin token only. Narrows the platform-wide view to one account. |
A key sees only its own rows; a signed-in session sees every key on the account. Requests with no attribution are never mixed into data; they are summed into the single unattributed row, which is null when there are none.
{
"data": [
{ "name": "Intake Bot", "url": "https://intake.example.com",
"requests": 412, "cost_usd": 0.0311, "prompt_tokens": 501220, "completion_tokens": 88410,
"last_used": "2026-08-27T18:25:19.695Z" },
{ "name": "nightly-batch", "url": null,
"requests": 96, "cost_usd": 0.0074, "prompt_tokens": 120050, "completion_tokens": 20115,
"last_used": "2026-08-27T03:00:41.008Z" }
],
"unattributed": { "name": null, "url": null,
"requests": 3, "cost_usd": 0.0002, "prompt_tokens": 3900, "completion_tokens": 610,
"last_used": "2026-08-20T11:02:55.117Z" }
}| Field | Meaning |
|---|---|
name, url | The attributed app. url is the most recently seen URL for that name. |
requests, cost_usd | Request count and billed spend in the window. |
prompt_tokens, completion_tokens | Token totals in the window. |
last_used | When this app last made a request. |
curl "https://docketrouter.ai/api/v1/apps?from=2026-08-01" -H "Authorization: Bearer dr-…"
Something here wrong or missing? Mail hello@docketrouter.ai with the request_id and we will fix the docs or the API, whichever is broken.