REST API guide
Base URL, authentication, the case model, responses and errors, rate limits, uploads, and working examples for the Custody Commander REST API.
The REST API gives you the same operations the app uses. This guide covers the conventions; for every operation's fields, see the Swagger reference or the live OpenAPI document.
Base URL and authentication#
https://app.custodycommander.com/api/v1
Authenticate every request with a personal API token:
export CASE_COMMANDER_URL="https://app.custodycommander.com"
export CASE_COMMANDER_API_TOKEN="cc_live_…" # keep this out of shell history and code
curl "$CASE_COMMANDER_URL/api/v1/case" \
-H "Authorization: Bearer $CASE_COMMANDER_API_TOKEN"
Cookies are never used for API authentication. A missing or invalid token returns 401 with a WWW-Authenticate: Bearer header.
Cases and court cases#
Two IDs appear everywhere:
caseId— your case (the matter). Get it fromGET /case.courtCaseId— one court file inside it. Get them fromGET /court-cases?caseId=….
Evidence, documents, to-dos, the timeline, hearings, and messages accept an optional courtCaseId to narrow to one court file; leave it out to cover the whole case.
Working in a shared case#
Requests use your own case by default. To work in a case someone shared with you, send an X-Case-Context header — matter:<caseId> (or ws:<workspaceId>). POST /workspaces/switch returns the value to use. The header selects which view you're working in; the token's access and your sharing permissions still apply.
curl "$CASE_COMMANDER_URL/api/v1/case" \
-H "Authorization: Bearer $CASE_COMMANDER_API_TOKEN" \
-H "X-Case-Context: matter:CASE_ID"
Requests and responses#
- Send JSON bodies with
Content-Type: application/json(up to 4 MB). File uploads usemultipart/form-data. - Responses are the same JSON the app receives — there's no wrapper. Some list endpoints return a bare array (
GET /evidence); others return an object. - File downloads stream as binary with a
Content-Dispositionfilename. - Creates usually return
201. - Every response includes
x-api-operation(the operation ID) andcache-control: no-store.
Errors#
Errors return JSON with an error message and usually a code:
{ "error": "This operation requires evidence:write.", "code": "insufficient_scope" }
| Status | Common codes | What to do |
|---|---|---|
| 400 | invalid_request, invalid_context, invalid_file | Fix the request body, query, or context header. |
| 401 | invalid_token | Check the token; it may be expired or revoked. |
| 402 | quota, evidence_limit, storage limit | AI allowance used up, free-plan evidence limit, or storage full. The message explains which. |
| 403 | insufficient_scope, forbidden, feature, email_unverified | Add the ability to the token, check sharing permissions, or the plan lacks that AI feature. |
| 404 | operation_not_found, not_found | Check the path and method against the reference. |
| 409 | — | The record changed since you read it. Re-fetch and retry. |
| 413 | file_too_large, request_too_large, resource_limit | Split the upload or the file. |
| 429 | rate_limited, rate, resource_limit | Wait for Retry-After seconds, then retry. |
Rate limits#
- 120 requests per minute per account, shared by every token and by REST and MCP together. The window resets on the clock minute; a
429includesRetry-After. - Built-in AI operations are also limited to 20 per 10 minutes per user, as in the app.
Pagination#
Most list endpoints return everything you can see. The exceptions:
GET /messagesreturns up to 2,000 messages per call — passoffsetfor the next page.GET /documents/{id}/versionsreturns 50 versions per page withnextCursor; pass it back asbefore.
Safe retries and conflicts#
There are no idempotency keys, so don't automatically retry a change after a timeout — read the resource first to see whether it went through. Some operations protect against overwriting someone else's changes:
PATCH /documentsand version restore acceptexpectedUpdatedAt.POST /preparationrequires the currentrevision.- Court-document operations require
expectedHash.
A mismatch returns 409.
Uploading files#
Uploads use multipart/form-data. Limits: 25 MB per file, 30 files and 32 MB per request.
| Operation | Fields |
|---|---|
POST /evidence | caseId, files (repeat for each file); optional folderId, courtCaseId, paths (folder paths, parallel to files). recordingConsent=yes is required if any file is audio or video. |
POST /documents/upload | Same as evidence. Text is extracted without AI. |
POST /messages/import | caseId, file, and attest=yes (confirms the export is from your own device or account, or one you're authorized to access). Optional platform (sms, whatsapp, facebook, instagram, email, other), selfName, otherName, device, courtCaseId. |
Examples#
curl "$CASE_COMMANDER_URL/api/v1/court-cases?caseId=CASE_ID" \
-H "Authorization: Bearer $CASE_COMMANDER_API_TOKEN"
curl -X POST "$CASE_COMMANDER_URL/api/v1/folders" \
-H "Authorization: Bearer $CASE_COMMANDER_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"caseId":"CASE_ID","name":"School expenses","kind":"evidence"}'
curl "$CASE_COMMANDER_URL/api/v1/evidence" \
-H "Authorization: Bearer $CASE_COMMANDER_API_TOKEN" \
-F "caseId=CASE_ID" -F "folderId=FOLDER_ID" -F "courtCaseId=COURT_CASE_ID" \
-F "files=@./receipt.pdf"
curl -X PATCH "$CASE_COMMANDER_URL/api/v1/evidence/EVIDENCE_ID" \
-H "Authorization: Bearer $CASE_COMMANDER_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"folderId":"FOLDER_ID","tagNames":["expenses"],"description":"School receipt","eventDate":"2026-08-21","exhibitNumber":"07"}'
curl -X POST "$CASE_COMMANDER_URL/api/v1/hearings" \
-H "Authorization: Bearer $CASE_COMMANDER_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"caseId":"CASE_ID","courtCaseId":"COURT_CASE_ID","date":"2026-11-03","time":"09:30","purpose":"Status review"}'
curl -X POST "$CASE_COMMANDER_URL/api/v1/todos" \
-H "Authorization: Bearer $CASE_COMMANDER_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"caseId":"CASE_ID","title":"Gather school records","due":"2026-10-15","courtCaseId":"COURT_CASE_ID"}'
curl -G "$CASE_COMMANDER_URL/api/v1/timeline" \
-H "Authorization: Bearer $CASE_COMMANDER_API_TOKEN" \
--data-urlencode "caseId=CASE_ID" \
--data-urlencode "kinds=event,hearing" \
--data-urlencode "years=2025,2026"
curl "$CASE_COMMANDER_URL/api/v1/messages/import" \
-H "Authorization: Bearer $CASE_COMMANDER_API_TOKEN" \
-F "caseId=CASE_ID" -F "attest=yes" -F "platform=sms" \
-F "file=@./sms-backup.xml"
curl -o receipt.pdf "$CASE_COMMANDER_URL/api/v1/files/FILE_ID" \
-H "Authorization: Bearer $CASE_COMMANDER_API_TOKEN"
Operations by area#
There are 140 operations. Paths are relative to /api/v1; the ability needed is <area>:read for GET and <area>:write otherwise.
| Area | Key operations |
|---|---|
| case | GET /case · POST /case/update (caption, role, case numbers, key dates — arrays replace existing values) |
| court-cases | GET · POST · PATCH ?id= · DELETE ?id= on /court-cases |
| evidence | GET /evidence (filters: q, issueId, priority, tag, courtCaseId, folderId) · POST /evidence · GET|PATCH|DELETE /evidence/{id} · POST /evidence/{id}/extract (AI) · POST /evidence/{id}/transcribe · POST /evidence/extract-bulk (AI) |
| files | GET /files/{id} (original; ?preview=1 for a browser-friendly copy) · GET /files/{id}/text |
| folders | GET /folders?kind=evidence|document · POST · PATCH ?id= · DELETE ?id=&deleteItems= |
| issues | POST /issues |
| documents | GET|POST|PATCH|DELETE /documents · POST /documents/upload · POST /documents/draft (AI) · GET /documents/{id}/export?format=pdf|md · GET|POST /documents/{id}/versions · court formatting, review, signing, and final export under /documents/{id}/court and POST /documents/{id}/export |
| templates | GET /templates · GET /templates/{id} · GET /templates/{id}/pdf |
| messages | GET /messages (many filters, 2,000 per call) · POST /messages (log a sent/received message — nothing is transmitted) · PATCH /messages?id= (tags) · POST /messages/import · GET /messages/threads · GET /messages/search · POST /messages/bulk · POST /messages/analyze (AI) · POST /messages/check (AI tone check) |
| timeline | GET /timeline (kinds, years, months) · POST · DELETE ?id= |
| hearings | GET|POST|PATCH|DELETE /hearings · GET|POST /hearings/{id}/resources |
| todos | GET|POST|PATCH|DELETE /todos |
| contacts | GET|POST|PATCH|DELETE /contacts |
| comms | POST /comms/draft (AI) · POST /comms/pull-imported |
| export | GET|POST /export/court-packet · GET /export/evidence-index · GET /export/message-log |
| preparation | GET /preparation · POST /preparation (typed actions with revision) · GET /preparation/export (binders and backups) |
| mediation | Sessions, transcript turns, and suggestions under /mediation/* (suggestions, redo, and simulate use AI) |
| shares | GET /shares · POST /shares/invite (emails the recipient) · PATCH|DELETE /shares · comments, defaults, per-item sharing, and GET /shares/inbox |
| collaboration · packages | Client-exchange requests, submissions, and messages · release, view, acknowledge, and revoke packages |
| billing | GET /billing/status (plan, entitlements, and usage) · GET /billing/invoices · checkout and portal URLs · POST /billing/upgrade (changes your subscription immediately) |
| account | GET /account (email, name, member since) · security settings · terms status and acceptance |
| workspaces | GET /workspaces · POST /workspaces/switch |
| tokens | GET|POST|PATCH|DELETE /developer/tokens |
| support | GET|POST /support/tickets |
| assistant (Full access only) | POST /assistant/chat (AI) · two-step deletion: POST /assistant/deletions, then POST /assistant/deletions/{id}/confirm after a person confirms |
For plan and usage information, use GET /billing/status rather than GET /account.
DELETE /evidence/{id}, DELETE /documents, DELETE /folders with deleteItems=true, DELETE /messages/threads, and POST /messages/bulk with delete-batch can't be undone. For deletions a person should approve, use the two-step /assistant/deletions flow.
Not available through the API#
Sign-up, sign-in, password reset, and email verification (integrations use tokens instead), payment webhooks, analytics beacons, and the MCP and OpenAPI transports themselves.
Versioning#
There's one version, v1. The operation catalog is generated from the app's own routes, so new app features appear in the API as they ship. Regenerate your client from the live OpenAPI document when you upgrade. Request fields in the reference are discovery hints; the server validates the full request.