# eCourtDate Chatbot API Documentation > The canonical reference for the eCourtDate Chatbot API > (https://api.chatbots.ecourtdate.com/v1). Grounded, citation-backed answers > for court and agency websites through an OpenAI-compatible API, with a > knowledge base you control: upload files, crawl your public site, and let > the assistant answer from your own content. ## Key resources - [API reference (interactive)](https://docs.chatbots.ecourtdate.com/reference/) - [OpenAPI specification (JSON)](https://docs.chatbots.ecourtdate.com/openapi.json) - [OpenAPI specification (YAML)](https://docs.chatbots.ecourtdate.com/openapi.yaml) - [Guides index](https://docs.chatbots.ecourtdate.com/guides) - [Getting started](https://docs.chatbots.ecourtdate.com/guides/getting-started) - [Authentication](https://docs.chatbots.ecourtdate.com/guides/authentication) - [Conventions](https://docs.chatbots.ecourtdate.com/guides/conventions) - [Chat completions](https://docs.chatbots.ecourtdate.com/guides/chat-completions) - [Streaming](https://docs.chatbots.ecourtdate.com/guides/streaming) - [Citations](https://docs.chatbots.ecourtdate.com/guides/citations) - [Tools and structured output](https://docs.chatbots.ecourtdate.com/guides/tools-and-structured-output) - [Conversations](https://docs.chatbots.ecourtdate.com/guides/conversations) - [Models](https://docs.chatbots.ecourtdate.com/guides/models) - [Embeddings](https://docs.chatbots.ecourtdate.com/guides/embeddings) - [Ingesting files](https://docs.chatbots.ecourtdate.com/guides/ingesting-files) - [Crawling websites](https://docs.chatbots.ecourtdate.com/guides/crawling-websites) - [Documents](https://docs.chatbots.ecourtdate.com/guides/documents) - [Jobs and webhooks](https://docs.chatbots.ecourtdate.com/guides/jobs-and-webhooks) - [Errors](https://docs.chatbots.ecourtdate.com/guides/errors) - [Rate limits](https://docs.chatbots.ecourtdate.com/guides/rate-limits) - [Security](https://docs.chatbots.ecourtdate.com/guides/security) - [Versioning](https://docs.chatbots.ecourtdate.com/guides/versioning) - [SDKs](https://docs.chatbots.ecourtdate.com/guides/sdks) - [Using the API with AI agents](https://docs.chatbots.ecourtdate.com/guides/ai-agents) ## Facts - Base URL: https://api.chatbots.ecourtdate.com/v1 (every endpoint is under /v1; the OpenAI SDK base_url is exactly this value). GET /health, at https://api.chatbots.ecourtdate.com/health, is the only operation outside /v1 and the only one that needs no key; it returns {"status": "ok"}. - Authentication: `Authorization: Bearer $API_KEY` on every /v1 request. Keys look like `ecd_sk_...` and carry scopes: `chat` (chat completions, models, embeddings, conversations) and `ingest` (file ingestion, crawling, documents, jobs). Access requires a paid subscription; keys are issued and activated for your account by eCourtDate (there is no self-serve key page). Keys are server-side secrets; browser-direct calls are not a supported scenario. - OpenAI compatibility: POST /v1/chat/completions, GET /v1/models, GET /v1/models/{modelId}, and POST /v1/embeddings follow the OpenAI request and response shapes, so the official OpenAI Python and Node SDKs work with only base_url and api_key changed. Unknown request fields are accepted and ignored; `n` must be 1. A "model" is a bot (slug) or alias configured for your account; omit it (or send null, "", or "default") for the account default; responses echo the canonical slug; an unknown, disabled, or foreign model is 404 `model_not_found` (param "model", and the message lists the available models). When the account has several bots and no default, omitting `model` is a 400 (param "model"). - Grounded answers with citations: the assistant message always carries a `citations` key (an eCourtDate extension): an array (possibly empty) of {source_index, document_id, source_filename, chunk_index, text_preview, score} naming the knowledge base passages the answer drew on, on a text answer from a bot with a knowledge base and citations enabled; null on a tool-call turn and for bots that do not cite. The text carries matching `[Source N]` markers. Every key of the message is always present: role, content (null on a tool-call turn), refusal (always null), tool_calls (null when none), citations. In the OpenAI Python SDK read `message.model_extra.get("citations")`; in Node the field is on the parsed object. - Streaming: `stream: true` returns text/event-stream; each frame is `data: ` (chat.completion.chunk) and the stream ends with `data: [DONE]`. The first chunk's delta has role "assistant" and an empty content; citations arrive on the finish chunk as delta.citations (an array, possibly empty, when the bot cites; absent otherwise); `[Source N]` markers can straddle deltas. `stream_options.include_usage` adds a final usage chunk with empty `choices` and `usage` (stream_options is validated even when stream is false). Mid-stream failures are in-band `data: {"error": {...}}` frames under HTTP 200, followed by `data: [DONE]`; a frame with type server_error or rate_limit_error is retryable, a frame with type invalid_request_error (the underlying language model rejected the request) is not. Conversation streams send {conversation_id, delta{content}} frames, then one {conversation_id, message, done: true} frame, then `data: [DONE]`; the turn is stored before the done frame is sent. - Tools and structured output: function tools only (names match ^[a-zA-Z0-9_-]{1,64}$); `tool_choice` is auto, none, required, or {type: "function", function: {name}} (any other form is 400, param tool_choice; a well-formed value is ignored when no tools are sent; an empty tools array is the same as omitting it); tool call ids are opaque strings, echoed back in `tool` messages via tool_call_id, and every tool_call_id must match an unanswered call in the nearest preceding assistant message (otherwise 400, param messages[i].tool_call_id); `finish_reason` is `tool_calls` whenever tool_calls is non-empty. A bot's allowlist rejects other tools with 400 `tool_not_allowed` (param `tools[i]`). `response_format` type is text, json_object, or json_schema (json_schema.name and json_schema.schema are required); the JSON modes cannot be combined with tools (400, param response_format). - Embeddings: POST /v1/embeddings returns 1024-dimensional vectors (encoding_format float, the default, or base64); `input` is one string or an array of 1 to 96 strings, each 1 to 100,000 characters (an empty string is 400, param input or input[i]); `dimensions`, if sent, must be the integer 1024; `model` is an echo-only label ("default" when omitted); the extension `input_type` is "search_document" (default) or "search_query". - Server-side conversations (chat scope): POST /v1/conversations (201; optional model and metadata, metadata at most 16 string keys of 1 to 64 characters with string values up to 512 characters; returns {id, object: "conversation", model (canonical slug, or null when following the account default), metadata, created, updated}), POST /v1/conversations/{conversationId}/messages (body {content: 1 to 100,000 characters, stream?}; returns {conversation_id, message{role, content, citations}}), GET /v1/conversations/{conversationId} (the conversation plus messages[], alternating user and assistant), DELETE /v1/conversations/{conversationId} (204, empty body, no Content-Type). A missing or empty create body is treated as {}. Message content must not be empty or whitespace-only. The full history stays retrievable, but the model receives up to 40 of the most recent messages (the window opens on a user turn). An unknown conversation is 404 `not_found`. - Knowledge base (ingest scope): POST /v1/ingest/files (multipart, part name `file` repeated per file, optional `namespace` default "general"; returns {job_id, document_ids, status: "processing"}), GET /v1/ingest/jobs/{jobId}, POST /v1/ingest/crawl (body {namespace, seed_urls, allowed_domains?, max_pages?, max_depth?, rate_limit_rps?, respect_robots_txt?}; seed URLs must be public http(s) URLs; returns {crawl_job_id, status: "processing"}), GET /v1/ingest/crawl/{crawlJobId}, GET /v1/documents (cursor list: limit, after, namespace, status, source_type), GET /v1/documents/{documentId}, DELETE /v1/documents/{documentId} (synchronous and irreversible; returns {id, object: "document.deleted", deleted: true, chunks_deleted}). Job status is processing | completed | completed_with_errors | failed, with live counts and an `errors` array of {document_id, error} (ingest) or {url, error} (crawl; url is "crawl" when the crawl itself failed, and "HTTP " is the error for a page the site answered with an error status) carrying sanitized messages. A crawl has a 10-minute budget; exceeding it ends the job failed with "Crawl timed out.". A document deleted while its job runs is recorded as "Document was deleted before it was processed.". Document status is processing | ready | failed; timestamps are RFC 3339 UTC with millisecond precision rendered as six fractional digits. Namespaces match ^[a-z0-9][a-z0-9_-]{0,63}$. Uploads accept .csv, .docx, .eml, .htm, .html, .md, .pdf, .txt, and .xlsx. Unknown ids are 404 `not_found`. - Limits: JSON request body 5 MiB (5,242,880 bytes), and the multipart body of POST /v1/ingest/files 525,336,576 bytes (20 files of 25 MiB plus 1 MiB of overhead); over either is 413 `request_too_large`. 1 to 200 messages per chat completion; 100,000 characters per message; 1 to 20 files per upload, 25 MiB (26,214,400 bytes) per file, filenames at most 255 bytes with no path separators, control characters, or other non-printable characters; seed_urls 1 to 100; max_pages 1 to 500 (default 50); max_depth 1 to 10 (default 3); rate_limit_rps 0.1 to 10 (default 2); 1 to 96 embedding inputs per request; GET /v1/documents limit 1 to 1000 (default 100); max_completion_tokens above 8192 is clamped to 8192; temperature 0 to 2 (values above 1 are treated as 1 by the underlying language model); top_p 0 to 1 (ignored when temperature is also set); some models ignore sampling parameters, and bots carry no sampling defaults (only a token limit). - Rate limits: per account (shared by all keys), requests per minute plus a daily token quota (reset at midnight UTC; rejected requests still count toward the minute window). Every authenticated /v1 response (not 401, 403, 413, routing errors, or pre-authentication 400s) carries RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset (seconds), absent only when the limiter itself is unavailable; every 429 and 503 carries Retry-After (seconds). - Errors: one envelope on every error, `{"error": {"message", "type", "param", "code"}}`. `type` is authentication_error (401), invalid_request_error (400, 403, 404, 405, 413), rate_limit_error (429), or server_error (500, 503). The nine codes: 400 tool_not_allowed; 401 invalid_api_key (message "Incorrect API key provided.", plus WWW-Authenticate: Bearer); 403 insufficient_scope; 404 model_not_found or not_found; 413 request_too_large; 429 rate_limit_exceeded or insufficient_quota (plus Retry-After); 503 upstream_unavailable (plus Retry-After). `code` is null on 400 validation errors (param in bracket form such as messages[2].tool_call_id, or null when the body is not a JSON object) and on 500. New codes may be added: branch on `code`, then on the status class; never on `message`. - Conventions: snake_case fields, OpenAI-style `object` / `data` list envelopes, UTC epoch seconds on `created` / `updated`, RFC 3339 UTC with a Z designator on `created_at` / `updated_at`, JSON-typed values (strings are not coerced to booleans or numbers), UUID ids for conversations, documents, and jobs, `chatcmpl-` plus 24 hex characters for chat completion ids, and an X-Request-ID header on every response from an API operation (send your own, 1 to 64 characters of [A-Za-z0-9_-], and it is echoed; quote it to support). Malformed Content-Length and invalid JSON are 400 before authentication; CORS preflight responses are outside the contract. - Webhooks: `ingest.completed` and `crawl.completed` are POSTed as JSON, once per job when it reaches completed | completed_with_errors | failed, to the URL configured for your account by eCourtDate. The body mirrors the job status object plus `event` and `namespace`. Headers: X-ECD-Event (the event name), X-ECD-Delivery-Id (a UUID, identical on every retry), and X-ECD-Signature: t=,v1=, where v1 is the hexadecimal HMAC-SHA256 over "." keyed with your account's webhook signing secret (present only when a signing secret is configured; t and v1 are recomputed on every attempt). Verify over the raw bytes, reject stale timestamps, deduplicate on the delivery id, and respond 2xx within 10 seconds; redirects are not followed. Delivery is best-effort: three attempts (immediately, then 30 s and 5 min later), then dropped; poll the job endpoint as the source of truth. - Versioning: /v1 is stable and changes are additive. The deprecation policy gives at least 12 months' notice, signalled with Deprecation and Sunset headers; nothing is deprecated today. - eCourtDate: https://www.ecourtdate.com