One response envelope, one error shape, a request id on every response, and idempotency keys for safe retries on credit charging endpoints.
// Success, every endpoint except /health
{
"success": true,
"data": { }, // object or array, endpoint specific
"meta": { }, // optional: query echo, responseTime, notes
"pagination": { } // search endpoints only
}
// Error raised by a route handler
{
"success": false,
"error": "Case not found"
}
// Validation error, HTTP 400
{
"success": false,
"error": "Validation failed. Please check your request and try again.",
"details": ["query: Search query cannot be empty. Please provide a search term."]
}
// The auth and rate limit layers answer with a bare object and no success field
{
"error": "Rate limit exceeded",
"message": "Too many requests. Maximum 10 requests per minute allowed.",
"retryAfter": 37,
"resetTime": "2026-08-10T09:41:12.884Z"
}Two exceptions to check for in client code. First, GET /health returns a flat object with no data key. Second, POST /search/cases/semantic writes its 200 status line before it starts work, in order to hold the connection open through a slow vector search. If that work then fails you receive HTTP 200 with success: false in the body. Branch on the body, not only on the status code.
Every error body on this API has the same shape: { success: false, code, message, error, requestId }. error mirrors message for backward compatibility with older bare { error } bodies. Every response, success or failure, also carries an X-Request-Id header you can hand to support to join a report to a server side log line.
| Code | HTTP status | Meaning |
|---|---|---|
| API_KEY_MISSING | 401 | No X-API-Key header or Authorization: Bearer token was sent. |
| API_KEY_INVALID_FORMAT | 401 | The key does not match the prefix-32characters-4characters shape. |
| API_KEY_INVALID | 401 | The key does not match any key on record. |
| API_KEY_REVOKED | 401 | The key was deleted or rotated by its owner. |
| API_KEY_EXPIRED | 401 | The key carries an expiry date that has passed. |
| ORGANIZATION_DEACTIVATED | 403 | The organisation that owns this key is deactivated. |
| ACCOUNT_SUSPENDED | 403 | The IAM standing check found the account behind this key suspended. |
| BILLING_INACTIVE | 403 | The IAM standing check found billing inactive for this account. |
| ACCOUNT_NOT_FOUND | 403 | The IAM standing check found no account behind this key any more. |
| ACCOUNT_STANDING_UNAVAILABLE | 503 | The identity service could not verify account standing; the endpoint fails closed rather than serving an unauthorised result. |
| IP_NOT_ALLOWED | 403 | Your organisation has IP allowlisting enabled and the calling address is not on the list. |
| API_ENTERPRISE_ONLY | 403 | Flag off (today's production default): the Enterprise only gate refused this plan. |
| API_NOT_AVAILABLE_ON_TRIAL | 403 | Trial accounts have no API access at all. |
| API_TIER_NOT_ALLOWED | 403 | Free tier calling an AI analysis endpoint (analyze, analyze-consolidated, or the stored analysis read). |
| SEMANTIC_NOT_ALLOWED | 403 | Free tier calling POST /search/cases/semantic. |
| LIVE_FETCH_NOT_ALLOWED | 403 | Free tier sending refresh: true to POST /request-timeline. |
| LIVE_FETCH_LIMIT_REACHED | 429 | Tier's daily live fetch cap already used. |
| PARTY_SCREEN_LIMIT_REACHED | 403 | Free tier's monthly litigation check cap already used. |
| DISTINCT_NAMES_LIMIT_REACHED | 429 | Tier's per minute or per day distinct screened name cap exceeded. |
| DISTINCT_CASES_LIMIT_REACHED | 429 | Tier's distinct case detail fetches per day cap reached. |
| PDF_LIMIT_REACHED | 429 | Tier's PDF calls per month cap reached. |
| CONCURRENT_ANALYSIS_LIMIT | 429 | Tier's concurrent analyze job cap is saturated; Retry-After: 30. |
| TOO_MANY_KEYS_FROM_IP | 429 | One client IP has used more distinct API keys than the daily cap allows. |
| RATE_LIMITED | 429 | The per key or per IP requests-per-minute ceiling was exceeded. |
| INSUFFICIENT_API_CREDITS | 402 | Balance does not cover this call's credit price; nothing is charged. |
| PAGE_LIMIT_EXCEEDED | 400 | limit is above your tier's maxPageSize. |
| PAGINATION_DEPTH_EXCEEDED | 400 | page times limit, or the depth carried in a cursor, exceeds your tier's cap. |
| CURSOR_INVALID | 400 | The cursor failed to decode, or was issued for a different query or filters. |
| REMOTE_FETCH_NOT_ALLOWED | 403 | An analyze allowRemoteFetch: true call targeted a host outside the allowlist. |
| EMAIL_NOT_VERIFIED | 403 | Free tier key creation requires a verified email address. |
| API_KEY_LIMIT_REACHED | 403 | Tier's live API key count is already at the tier's cap. |
| VALIDATION_ERROR | 400 | The request body failed schema validation; details name the offending field. |
| CASE_NOT_FOUND | 404 | The case id in the path does not resolve to a stored case. |
| PDF_NOT_STORED | 404 | The case exists but carries no stored PDF; the body carries a hint. |
| PARTY_SCREEN_SEARCH_DEGRADED | 502 | The underlying case search errored and returned no usable results; not charged, carries a Retry-After header. |
| CASE_RESTRICTED | 403 | The case is withheld under CourtMesh's case removal policy and is not returned by any endpoint. |
| JOB_NOT_FOUND | 404 | The timeline request id in the path does not exist, or its job record has expired. |
| MALFORMED_JSON | 400 | The request body could not be parsed as JSON. |
| PAYLOAD_TOO_LARGE | 413 | The request body is larger than the 256kb limit. |
| UPSTREAM_TIMEOUT | 408 | A dependency (OpenSearch, the embedding service, or a live court portal fetch) did not answer in time. |
| UPSTREAM_UNAVAILABLE | 502 | A dependency failed to respond at all. |
| INTERNAL_ERROR | 500 | An unhandled server error. Retry, and quote the X-Request-Id if it recurs. |
| IDEMPOTENCY_KEY_INVALID | 400 | The Idempotency-Key header is present but is not 1 to 128 characters of A-Z, a-z, 0-9, underscore, period or hyphen. |
| IDEMPOTENCY_KEY_REUSED | 409 | The same Idempotency-Key was sent with a request body that does not match the first call that used it. |
| IDEMPOTENCY_IN_PROGRESS | 409 | A call carrying this Idempotency-Key is still being processed. Wait for it to finish rather than sending a third copy. |
Every response this API sends, success or failure, carries an X-Request-Id header. Failures also echo it in the body, as requestId, alongside the same one error shape used everywhere on this API: { success: false, code, message, error, requestId }. code is the stable machine readable string to branch your error handling on; error mirrors message for backward compatibility with older bare { error } bodies.
Include the X-Request-Id (or the body's requestId), the endpoint, and roughly when the call was made. That is what lets us join your report to the actual server side log line rather than asking you to reproduce it. Contact support@courtmesh.ai, or your Enterprise named contact if you have one. There is no published uptime, latency or freshness SLA today; see API security for exactly what is and is not in place.
Send an Idempotency-Key request header on POST /party/screen, POST /party/screen/batch, POST /cases/:id/analyze, POST /cases/:id/analyze-consolidated or POST /request-timeline and a dropped connection, a client side timeout, or a retry loop can never charge you twice for the same call. The header is 1 to 128 characters of letters, digits, underscore, period or hyphen, scoped to your API key, and honoured for 24 hours from the first call that used it.
Idempotency-Replayed: true header, and nothing is charged a second time.IDEMPOTENCY_KEY_REUSED, so a bug that reuses a key by accident cannot silently serve the wrong stored answer.IDEMPOTENCY_IN_PROGRESS rather than doing the work twice concurrently.IDEMPOTENCY_KEY_INVALID before the request body is even read.curl -X POST https://research.courtmesh.ai/api/v1/prod/party/screen \
-H "X-API-Key: cm-YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: signup-flow-user-88213-kyc-check" \
-d '{ "name": "Rambaksh Enterprises", "entityType": "company", "purpose": "kyc" }'