Conventions
Rules that apply across the whole API, so each endpoint page does not have to repeat them. The API follows the OpenAI conventions wherever it offers the same operation, and extends them in the same style elsewhere.
Base URL and paths
https://api.chatbots.ecourtdate.com/v1
- Every endpoint lives under
/v1; the OpenAI SDKs take this full prefix as theirbase_urland append/chat/completions,/models, and/embeddingsthemselves. - Path segments are lowercase and kebab-case; path parameters are ids
(
/v1/conversations/{conversationId},/v1/documents/{documentId},/v1/ingest/jobs/{jobId},/v1/ingest/crawl/{crawlJobId},/v1/models/{modelId}). - Call paths without a trailing slash. A trailing slash answers with a
307redirect to the canonical path, and some HTTP clients drop the body when following a redirect onPOST. GET /health(outside/v1) is the only unauthenticated route and returns{"status": "ok"}.
JSON only
Request bodies are JSON and must be sent with Content-Type: application/json
(a charset parameter is fine). There is no 415: valid JSON sent with
another content type, or with none, is rejected with 400 and the message
Input should be a valid dictionary or object to extract fields from, and
malformed JSON is rejected with 400 and the message
Invalid JSON in request body. (an empty body gets Field required, except
on POST /v1/conversations, where a missing or empty body is treated as
{}). All three are validation errors with
param: null. The JSON syntax check runs before authentication; the other
two run after it (evaluation order).
The one exception is POST /v1/ingest/files, which takes
multipart/form-data (Ingesting files).
Responses are JSON (Content-Type: application/json) except streamed chat,
which is text/event-stream (Streaming), and
DELETE /v1/conversations/{conversationId}, which returns 204 with an
empty body and no Content-Type header. Every error, on every route, is the
error envelope.
Naming
- Field names are
snake_case(finish_reason,chunk_count,tool_call_id). - Enumerated values are lowercase strings (
completed_with_errors,search_query,json_schema). - Every resource carries an
objectstring naming its shape:
object | Returned by |
|---|---|
chat.completion | POST /v1/chat/completions |
chat.completion.chunk | Each streamed chunk of POST /v1/chat/completions |
model | Items of GET /v1/models, GET /v1/models/{modelId} |
embedding | Items of POST /v1/embeddings |
conversation | POST /v1/conversations, GET /v1/conversations/{conversationId} |
document | Items of GET /v1/documents, GET /v1/documents/{documentId} |
document.deleted | DELETE /v1/documents/{documentId} |
list | Every list response |
Job status bodies, the ingest and crawl submission responses, conversation
message responses, and streamed conversation frames are plain objects
without an object field.
List envelopes
Every list is wrapped in the OpenAI list envelope:
{
"object": "list",
"data": [
{ "id": "court-assistant", "object": "model", "created": 1784812800, "owned_by": "maple-county-courts" }
]
}
GET /v1/models and POST /v1/embeddings return the whole result in one
page. GET /v1/documents is paginated: it takes limit (1 to 1000, default
100), an after cursor (the id of the last item you saw; an id that is not
a document on your account is a 400 with param: after), and optional
namespace, status, and source_type filters, and adds has_more,
first_id, and last_id to the envelope (first_id and last_id are
null on an empty page). Keep requesting with after=<last_id> until
has_more is false. See Documents.
Identifiers
| Identifier | Format | Example |
|---|---|---|
Chat completion id | chatcmpl- plus 24 lowercase hex characters (33 characters) | chatcmpl-8a317b1a95a74321b73b6567 |
Conversation id | UUID, hyphenated, no prefix | c856e096-99bb-45c1-a0df-044dbbe0aeb7 |
Document id | UUID, hyphenated, no prefix | 17136f01-972d-4956-868e-8159833072e4 |
Ingest job_id | UUID, hyphenated, no prefix | bfcde661-9eec-4142-a790-ca9a62c8e0f8 |
Crawl crawl_job_id | UUID, hyphenated, no prefix | 2d6f8c0e-5b1a-4f3c-9e7d-0a1b2c3d4e5f |
Model id | The bot's slug or an alias, as configured for your account | court-assistant |
Tool call id | Opaque string assigned by the model; pass it back unchanged in tool_call_id | (varies) |
| API key | ecd_sk_ plus 59 characters | ecd_sk_... |
Treat every id as an opaque, case-sensitive string: do not parse structure
out of a UUID or rely on its length beyond what the table states. Ids are
scoped to your account; another account's id behaves exactly like an unknown
one (not_found).
Timestamps
Two styles, following the shape each object imitates:
- Unix seconds (integer, UTC) on the OpenAI-style objects:
createdon chat completions, chunks, and models;createdandupdatedon conversations.updatedis bumped on every stored turn and is never less thancreated. - RFC 3339 UTC with a
Zdesignator on documents:created_atandupdated_at, with millisecond precision rendered as six fractional digits (the last three are always0), for example2026-08-21T14:03:11.214000Z(a value on a whole second renders as2026-08-21T14:03:11.000000Z).
All clocks are UTC. The daily token quota also resets on the UTC day (Rate limits).
Request ids
Every response from an API operation carries an X-Request-ID header,
including errors, 204 responses, and streamed responses (the only
exception is a CORS preflight response, which is outside the API contract;
see Browser clients). The value is the id to quote in support
requests: it is recorded with the request's usage entry and, for ingestion
and crawl jobs, carried through to the background processing logs.
You can supply your own id on the request. It is echoed back when it matches
^[a-zA-Z0-9_-]{1,64}$; any other value is replaced, never rejected, by a
server-minted 32-character hex id.
curl -s "https://api.chatbots.ecourtdate.com/v1/models" \
-H "Authorization: Bearer $API_KEY" \
-H "X-Request-ID: clerk-portal-7f3e2a" \
-D - -o /dev/null
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-ID: clerk-portal-7f3e2a
RateLimit-Limit: 60
RateLimit-Remaining: 59
RateLimit-Reset: 41
Log the request id next to your own correlation data on every call; it is the fastest route from a user report to the exact request.
Response headers
| Header | When | Meaning |
|---|---|---|
X-Request-ID | Every response from an API operation | The request id (above) |
RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset | Every authenticated /v1 response (not on 401, 403, 413, a routing error, a body that is not valid JSON, or a malformed Content-Length header, all decided before the limiter runs; absent only if the limiter itself is unavailable) | Your per-minute request budget, what is left (never negative), and seconds until the window resets, or until the daily quota resets when that is what blocks you (Rate limits) |
Retry-After | 429 and 503 | Seconds to wait before retrying (Rate limits) |
WWW-Authenticate: Bearer | 401 | The request did not authenticate (Authentication) |
Allow | 405 | The methods the path supports |
Cache-Control: no-cache, Connection: keep-alive, X-Accel-Buffering: no | Streamed responses | Disable buffering between the API and your client (Streaming) |
Limits
| Limit | Value |
|---|---|
| JSON request body | 5 MiB (5,242,880 bytes, inclusive), checked before authentication (see the evaluation order for bodies sent without Content-Length) |
| Messages per chat completion request | 200 |
Characters per message (content) | 100,000 (Unicode characters, not bytes) on chat completions and conversation messages |
max_completion_tokens / max_tokens | Values above 8,192 are clamped to 8,192 |
n | Must be 1 |
| Embedding inputs per request | 96, each at most 100,000 characters |
| Embedding dimensions | 1,024 (fixed) |
Conversation metadata | 16 keys; keys up to 64 characters, string values up to 512 characters |
| Files per upload request | 20 |
| Size per uploaded file | 25 MiB (26,214,400 bytes, inclusive) |
| Filename | 255 UTF-8 bytes including the extension; no /, \, control characters, or other non-printable characters |
namespace | ^[a-z0-9][a-z0-9_-]{0,63}$ |
Crawl seed_urls | 1 to 100 |
Crawl max_pages | 1 to 500 (default 50) |
Crawl max_depth | 1 to 10 (default 3) |
Crawl rate_limit_rps | 0.1 to 10 (default 2) |
GET /v1/documents limit | 1 to 1000 (default 100) |
Exceeding the body cap returns 413
request_too_large; every other limit is a
400 validation error naming the field in param.
Per-account request and token limits are covered in
Rate limits.
Strict typing
Request fields are typed as JSON types and are not coerced from strings:
"stream": "yes", "temperature": "0.5", and "max_pages": "10" are each
rejected with 400 and a param naming the field. Send booleans as
true/false, integers as integers, and numbers as numbers. Numbers with a
fractional part are rejected where an integer is expected
("n": 1.5, "max_pages": 10.5).
Required string fields must be non-empty where the endpoint says so:
an empty or whitespace-only content on a system, developer, or user
message, an empty or whitespace-only conversation message content, an
empty stop string, and an empty input array are all 400 (a tool
message may carry null or an empty string as an empty result).
Unknown fields
- In requests, unknown fields are accepted and ignored. This is what lets the OpenAI SDKs send parameters the API does not implement without failing. Misspelled field names are therefore silently ignored too, so check the supported parameters when a setting seems to have no effect.
- In responses, clients must ignore fields they do not recognize. New
fields are added without notice (Versioning); the
citationsfield on assistant messages is one such addition.
Validation errors
A request that fails validation returns 400 with the
error envelope, type: invalid_request_error,
code: null, and param pointing at the offending field in bracket form:
{
"error": {
"message": "A tool message requires tool_call_id.",
"type": "invalid_request_error",
"param": "messages[2].tool_call_id",
"code": null
}
}
Only the first failure is reported. Fix it, resend, and repeat; see Errors for the path forms and the messages you will see.
Evaluation order
Checks run in a fixed order, which tells you which error wins when several apply:
- Malformed
Content-Lengthheader:400(Invalid Content-Length header.,param: null), before routing. - Body size:
413request_too_large, before routing and authentication. For a body sent withoutContent-Length(chunked), the cap is enforced while the body is read, after routing, so an unknown path or an unsupported method is reported first. - Route:
404for an unknown path,405(withAllow) for an unsupported method, before authentication. - JSON syntax:
400(Invalid JSON in request body.,param: null), before authentication. - Authentication:
401invalid_api_key. - Scope:
403insufficient_scope. - Rate limit and quota:
429rate_limit_exceededorinsufficient_quota. - Body validation:
400(validation). - Endpoint checks in the order each guide documents (for chat completions:
n, thenstop, thenresponse_format, then the model lookup, then messages, then tools, then retrieval).
Browser clients
Cross-origin requests are accepted only from eCourtDate-owned origins. Browser-direct use is not a supported scenario: API keys are server-side secrets (Authentication). Route browser traffic through your own backend.
CORS preflight responses (OPTIONS with an Origin and an
Access-Control-Request-Method header) are outside the API contract: they
are answered by the CORS layer itself and carry neither X-Request-ID nor
the error envelope. Every response to an actual API request follows the
rules on this page.