docketrouter
DocumentationBrowse
API reference

Matters

A matter is a session that stays open. It holds the conversation so far and the case files that belong to it. Pass its id on a chat request and the API prepends the stored turns, narrows case-file retrieval to the attached files, and stores the new exchange when the model answers. Close the laptop, come back next week, send the next question with the same id.

Matters are scoped to the same owner as case files: the account behind a key, or the key itself when it was minted outside an account. Nothing about a matter is visible to any other owner.

POST/matterskey or sessionCreate a matter.
GET/matterskey or sessionList your matters, most recent activity first.
GET/matters/:idkey or sessionOne matter with its messages and attached files.
PATCH/matters/:idkey or sessionRename or archive.
DELETE/matters/:idkey or sessionDelete the matter, its messages and its attachment rows.
POST/matters/:id/fileskey or sessionAttach one of your files.
DELETE/matters/:id/files/:file_idkey or sessionDetach a file.

POST /matters

FieldTypeNotes
titlestring, optional1 to 200 characters. Defaults to Untitled matter.
201 Created
{
  "id": "9b1c2e4a-0f6d-4b7e-9a21-6c3d1e8f5a70",
  "title": "Smith v. Jones",
  "created_at": "2026-08-27T18:24:34.235Z",
  "updated_at": "2026-08-27T18:24:34.235Z",
  "archived_at": null,
  "message_count": 0,
  "file_count": 0
}

GET /matters

QueryDefaultMeaning
archivedfalseArchived matters are hidden unless you pass archived=true.
limit1001 to 500.
200 OK
{
  "data": [
    { "id": "9b1c2e4a-…", "title": "Smith v. Jones",
      "created_at": "2026-08-27T18:24:34.235Z", "updated_at": "2026-08-27T19:02:11.804Z",
      "archived_at": null, "message_count": 6, "file_count": 2 }
  ]
}

Ordered by updated_at descending. Any stored exchange, rename, attach or detach bumps it, so the list reads as "what did I touch last".

GET /matters/:id

QueryDefaultMeaning
limit50Messages per page, 1 to 200.
beforenoneA message id. Returns the page of messages older than it. Pass the smallest id you already hold to walk backwards.
200 OK
{
  "id": "9b1c2e4a-…", "title": "Smith v. Jones", "created_at": "…", "updated_at": "…", "archived_at": null,
  "message_count": 4, "file_count": 1,
  "messages": [                                     // chronological, oldest first
    { "id": 41, "matter_id": "9b1c2e4a-…", "role": "user",
      "content": "What does the tolling agreement in my case file say?",
      "request_id": null, "verification": null, "created_at": "…" },
    { "id": 42, "matter_id": "9b1c2e4a-…", "role": "assistant",
      "content": "The agreement executed March 3, 2024 tolls …",
      "request_id": "req_4f1e…",                     // the chat request that produced this turn
      "verification": { "checked": 2, "fabricated": [], "revised": false, … },
      "created_at": "…" },
    …
  ],
  "has_more": false,                                // true when an older page exists
  "files": [
    { "file_id": "cd044ac7-…", "name": "smith-v-jones-tolling.txt", "chars": 2410, "chunks": 2, "attached_at": "…" }
  ]
}

Message ids are integers that only ever increase within your account, which is what makes them a stable cursor. A user turn carries no request_id; the assistant turn that answered it carries the request id and the verification report from that call. A file deleted after it was attached still appears in files withname set to null; detach it or leave it, it no longer affects retrieval either way.

PATCH /matters/:id

FieldTypeMeaning
titlestring1 to 200 characters.
archivedbooleanSets or clears archived_at. An archived matter still answers chat requests; it is only hidden from the default list.

At least one of the two must be present. Returns the updated matter with counts.

DELETE /matters/:id

200 OK
{ "ok": true, "id": "9b1c2e4a-…" }

Removes the matter, every stored message and every attachment row. The files themselves are not touched; delete those through DELETE /files/:id if you want them gone.

Attaching files

POST /matters/:id/files
{ "file_id": "cd044ac7-85ec-4430-b37b-1bf6a966cf5f" }
200 OK
{
  "ok": true, "matter_id": "9b1c2e4a-…", "file_id": "cd044ac7-…",
  "files": [ { "file_id": "cd044ac7-…", "name": "smith-v-jones-tolling.txt", "chars": 2410, "chunks": 2, "attached_at": "…" } ]
}

The file must be yours. Attaching is idempotent. Detach with DELETE /matters/:id/files/:file_id, which returns { "ok": true }, or 404 when the file was not attached.

How attachments change retrieval

With case_file: true and no attachments, a request on a matter searches every file you own, exactly as it would without a matter. Attach one or more files and the search is confined to them. That is the whole point of attaching: opposing counsel's exhibit in one matter never surfaces in another.

Asking with matter_id

Add docketrouter.matter_id to a chat completion. Three things happen:

StepWhat the API does
Before the modelThe last 20 stored turns are placed in front of your messages. Your messages always come last.
RetrievalWhen case_file is true and the matter has attachments, only those files are searched.
After a successful answerYour non-system messages from this request and the assistant's answer are appended, with the request_id and the verification report. Nothing is stored when the request fails.
response, docketrouter block
"docketrouter": {
  "matter_id": "9b1c2e4a-…",        // echoed; null when the request carried none
  "request_id": "req_4f1e…",
  "verification": { … },
  …
}
Send only the new turn

The API stores every non-system message you send on a matter request. If you keep sending the whole transcript, OpenAI style, every turn is stored again on every call and the history doubles. On a matter, send the system prompt if you use one and the new user message, nothing else.

Streaming

Streamed requests store the exchange after the stream completes. If the connection drops mid-stream nothing is stored, so a retry does not leave a half answer in the history.

The full workflow

# 1. create a matter
MATTER=$(curl -s https://docketrouter.ai/api/v1/matters \
  -H "Authorization: Bearer dr-…" -H "content-type: application/json" \
  -d '{ "title": "Smith v. Jones" }' | jq -r .id)

# 2. attach a file you uploaded earlier (POST /files)
curl -s https://docketrouter.ai/api/v1/matters/$MATTER/files \
  -H "Authorization: Bearer dr-…" -H "content-type: application/json" \
  -d '{ "file_id": "cd044ac7-…" }'

# 3. ask; only the new turn goes in messages
curl -s 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\": \"What does the tolling agreement say about limitations?\" }],
        \"docketrouter\": { \"case_file\": true, \"matter_id\": \"$MATTER\" } }" \
  | jq '{answer: .choices[0].message.content, matter: .docketrouter.matter_id}'

# 4. next week: continue. The earlier exchange is already in front of this message.
curl -s 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\": \"Does that change if the agreement was never countersigned?\" }],
        \"docketrouter\": { \"case_file\": true, \"matter_id\": \"$MATTER\" } }"

# 5. read the history back
curl -s https://docketrouter.ai/api/v1/matters/$MATTER -H "Authorization: Bearer dr-…" | jq '.messages[] | {role, content}'

Errors and limits

SituationStatusBody
No key and no session401{"error":{"message":"API key or sign-in required","type":"authentication_error"}}
Matter id you do not own, or that does not exist404{"error":{"message":"matter not found","type":"not_found_error"}}
Attaching a file you do not own404{"error":{"message":"file not found","type":"not_found_error"}}
Detaching a file that is not attached404{"error":{"message":"file is not attached to this matter","type":"not_found_error"}}
Chat request with an unknown matter_id404Same envelope as above. The model is not called and nothing is billed.
Bad body400{"error":{"message":"invalid body: …","type":"invalid_request_error"}}

A matter you do not own answers 404, not 403, on every endpoint, so ids cannot be probed.

Limits

LimitValue
Turns prepended to a chat requestthe last 20 stored messages
Messages per page on GET /matters/:id50 by default, 200 max
Matters per list call100 by default, 500 max
Stored content per message200,000 characters, then clipped
Title200 characters
Retentionnone; a matter lives until you delete it
History counts toward the request

The 20 prepended turns are sent to the model and billed as prompt tokens like any other message. They are not counted against the 64-message or 200,000-character request limits, which apply to what you send.

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.