DocumentationBrowse
Chat completions
OpenAI-compatible request and response, plus one request field and one response field of our own. Everything the grounding layer does is driven by the docketrouter object on the request and reported in the docketrouter object on the response.
Request parameters
| Field | Type | Default | Notes |
|---|---|---|---|
model | string | required | Must be on the callable allowlist. Today that is deepseek/deepseek-v4-flash. A key with a private-pod data policy uses local/… ids instead. |
messages | array | required | At least one. Each item is { role, content } where role is system, user or assistant and content is a plain string. Array content parts and the tool and function roles are rejected by the schema. |
stream | boolean | false | Server-sent events. See Streaming. |
temperature | number | provider default | Passed through unchanged. The revise pass always uses 0 regardless. |
max_tokens | number | provider default | Output token ceiling. Values above 8,192 are rejected with 400. Use 1,000 or more: reasoning tokens come out of this budget. |
models | string[] | none | Up to 5 fallback models. Every entry is checked against the same allowlist as model, so a fallback cannot reach a model that is off. |
provider | object | none | Upstream provider preferences, passed through: order, only, ignore, quantizations, sort, max_price, data_collection, zdr. Key-level routing policy is applied on top and wins. |
response_format | object | none | Structured output, for example a json_schema block. Setting it also requires parameter support upstream, so the request is only routed to endpoints that can honour it. |
docketrouter | object | all defaults | The grounding layer controls. See below. |
n, tools, tool_choice, stop, top_p, seed, logprobs, frequency_penalty and their relatives are not forwarded today, even when the underlying model supports them. Sending them is not an error and has no effect, which is the failure mode most likely to waste your afternoon.
The docketrouter options block
Every field defaults independently, so you can send only the one you care about.
| Field | Type | Default | What it does |
|---|---|---|---|
juice | boolean | true | Master switch. When false nothing is retrieved, no citations are checked, your messages go to the model untouched, and both sources and verification come back null. |
rules | boolean | true | Retrieve up to 6 rule and statute excerpts and inject them verbatim. |
cases | boolean | true | Retrieve up to 5 related opinions from the fused index, with fallback sources when the index returns nothing. |
verify | boolean | true | Check reporter citations found in your prompt, and again in the answer. |
revise | boolean | true | When verification finds a citation that affirmatively does not exist, spend one extra model call rewriting the answer without it. Non-streaming only. |
case_file | boolean | false | Retrieve up to 6 chunks from your own uploaded documents and quarantine them into the prompt. Requires juice: true. |
injection_policy | "flag" | "block" | "flag" | What to do when a retrieved case-file document scores hostile. flag proceeds and reports; block refuses with 422. |
jurisdiction | tx | ca | ny | fed | the key setting, else tx | Selects which shards of the fused case index are searched, and is echoed and logged. See Grounding for what each value actually reaches. |
session_id | string | none | Up to 128 characters, echoed back in the response metadata. Useful for correlating a thread of requests in your own logs. |
pin | string | none | Pin one upstream provider endpoint by slug (for example deepinfra/fp8) and disable fallbacks, so answers come from a single provider and quantization. |
Omitting the block entirely gives you the full default behaviour. Sending { "case_file": true } leaves every other field at its default, because the defaults are per field rather than per object.
The response
A standard chat.completion object with two additions: usage.cost (billed US dollars for this request) and the docketrouter block. The request id is also returned in the x-docketrouter-request-id response header.
{
"id": "chatcmpl-fee163e8-b48",
"object": "chat.completion",
"created": 1787848438,
"model": "deepseek/deepseek-v4-flash",
"choices": [
{ "index": 0,
"message": { "role": "assistant", "content": "Under the Federal Rules of Civil Procedure, …" },
"finish_reason": "stop" }
],
"usage": {
"prompt_tokens": 2921, // includes the injected authorities
"completion_tokens": 271,
"total_tokens": 3192,
"cost": 0.00030483 // billed USD, upstream price x 1.25
},
"docketrouter": {
"sources": { // null when juice is false
"rules": [ … ], // excerpts injected verbatim
"cases": [ … ], // opinions offered to the model as leads
"citations": [ // citations found in YOUR prompt, each checked
{ "input": "556 U.S. 662", "status": "found", "note": "in DocketRouter index" }
],
"ms": 2108 // retrieval wall clock
},
"injection": null, // case-file screening report; null unless case_file was used
"degraded": null, // set when grounding was asked for and came back short
"upstream": "openrouter",
"juiced": true,
"data_policy": "shared",
"jurisdiction": "tx",
"session_id": null,
"verification": { // citations found in the ANSWER, each checked
"checked": [ { "input": "556 U.S. 662", "status": "found", "note": "in DocketRouter index" } ],
"fabricated": [], // status "not_found": treat as a hard stop
"unverified": [], // status "unverified" or "ambiguous": no signal either way
"revised": false // whether the revise pass rewrote this answer
},
"request_id": "req_ad378ebfe63d41778571"
}
}Field reference
| Field | Type | Meaning |
|---|---|---|
usage.cost | number | Billed US dollars for this request, including the revise call when one ran. Upstream price times 1.25. |
docketrouter.sources | object | null | What was retrieved: rules, cases, citations, ms. Null when juice is false. |
docketrouter.injection | object | null | Screening report for the case-file chunks that were retrieved. Null when none were. |
docketrouter.degraded | object | null | Set when grounding was requested and produced nothing, or when the primary index was unreachable. See Degraded retrieval. |
docketrouter.upstream | string | openrouter normally, private_pod for an in-house pod key. |
docketrouter.juiced | boolean | Whether the grounding layer ran at all. |
docketrouter.data_policy | string | shared or private_pod. A property of the key, not of the request. |
docketrouter.jurisdiction | string | The resolved jurisdiction, after the request value, the key setting and the default. |
docketrouter.session_id | string | null | Echoed back exactly as sent. |
docketrouter.verification | object | null | Answer-side citation report. Null when juice or verify is false. |
docketrouter.request_id | string | The req_… handle. Also in the x-docketrouter-request-id header, and the key into GET /usage/:request_id. |
docketrouter.generation_id | string | null | The upstream generation id for the exact model call behind this answer. Also in the x-docketrouter-generation-id header. Quote it when disputing a charge. |
docketrouter.provider | string | null | The upstream provider that served the call. Also in the x-docketrouter-provider header. |
When reasoning tokens consume the whole max_tokens budget you get HTTP 200, an empty choices[0].message.content and finish_reason: "length". Check for it explicitly. A client that only branches on the status code will ship an empty answer to a user.
Provider routing
Your preferences pass through; key policy is applied on top and wins.
| Setting | Where it comes from | Effect |
|---|---|---|
provider | The request | Passed to the upstream router unchanged. |
docketrouter.pin | The request | Sets the provider order to that one slug and disables fallbacks. |
routing: "no-train" | The key | Forces data_collection: "deny". |
routing: "zdr" | The key | Forces data_collection: "deny" and zero data retention. |
routing: "pinned" | The key | Pins the key's configured provider and disables fallbacks, unless the request already sent pin. |
data_policy: "private_pod" | The key | Forces data_collection: "deny" and restricts the request to in-house models. See below. |
The provider that actually served a request is not knowable at request time. It appears on the usage row as provider once reconciled, and two identical requests can be served by different providers.
Private pod keys
- A key whose data policy is
private_podmay only call in-house models under thelocal/namespace. Anything else returns 403, and the prompt never leaves the pod. - If the host has no pod endpoint configured, the request returns 503.
- Citation verification against anything outside the pod is switched off for pod keys unless the firm has explicitly opted in.
Examples
curl https://docketrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer dr-…" \
-H "content-type: application/json" \
-d '{
"model": "deepseek/deepseek-v4-flash",
"max_tokens": 1200,
"messages": [
{ "role": "user",
"content": "Summarize the pleading standard in Ashcroft v. Iqbal, 556 U.S. 662 (2009)." }
],
"docketrouter": { "jurisdiction": "fed", "session_id": "matter-4471" }
}' | jq '{cost: .usage.cost, verification: .docketrouter.verification}'Structured output works the same way as upstream. Send a response_format with a json_schema and the request is routed only to endpoints that support it. Grounding still runs, and the verification report still comes back beside your parsed object.
Validation order
Worth knowing, because it determines what a bad request costs you.
- authentication → 401
- body size, 512KB → 413
- JSON parse and schema → 400
- message count and total characters → 413
- rate limit → 429
- private-pod policy → 403 or 503
- grounding runs
- model allowlist and max_tokens ceiling → 400
- injection policy → 422
- model call → 200 or 502
A request naming a model that is not callable still pays for retrieval before it returns 400, and it still consumes a rate-limit token. If you route across models dynamically, check callable_models from GET /models in your own client rather than discovering the allowlist through 400s.
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.