Skip to main content

Chatbots API

The Chatbots API answers questions from an agency-controlled knowledge base and returns the sources it used. It is a standalone eCourtDate product with its own base URL, its own documentation site, and its own API keys.

The API follows the OpenAI wire format, so the official OpenAI SDKs work against it once the base URL and the key are changed.

This page is a high-level orientation. The full endpoint documentation, request and response schemas, and API reference live at docs.chatbots.ecourtdate.com.

What it builds

  • Grounded answers with citations: every answer carries a list of the passages it drew on, with the source document, a text preview, and a relevance score.
  • An agency-controlled knowledge base: upload files or crawl a public website, and the API extracts, chunks, and indexes the text.
  • Drop-in OpenAI compatibility: point an existing OpenAI client at the eCourtDate base URL and chat completions, model listing, and embeddings work unchanged.
  • Streaming replies: set stream to true and the answer arrives as server-sent events while the model generates it.
  • Server-side conversations: let eCourtDate store the transcript instead of replaying the full message history on every request.
  • Tools and structured output: define functions for the model to call, or constrain the answer to a JSON schema.
  • Embeddings: turn text into 1024-dimension vectors using the same embeddings the knowledge base uses for retrieval.
  • Signed webhooks: get a callback when a file upload or a site crawl finishes, instead of polling.

Common uses include self-help chat on a justice agency website, answering routine questions about hours, filing, and procedure, and internal assistants that search agency documents.

Base URL

https://api.chatbots.ecourtdate.com/v1

The service is HTTPS only. Paths are lowercase and use kebab-case. Omit trailing slashes: a trailing slash triggers a 307 redirect, which can strip the request body.

Authentication

The Chatbots API uses an API key sent as a Bearer token in the Authorization header on every request. This is different from the eCourtDate platform API, which issues a client_id and client_secret exchanged for a token, and from the Doc Gen API, which sends a key in an x-api-key header.

Authorization: Bearer ecd_sk_...

Keys are formatted as the prefix ecd_sk_ followed by 59 characters, 66 characters in total.

Create a key in the eCourtDate Console under APIs at console.ecourtdate.com/apis, selecting the Chatbots API when creating the client. Key management is self-service: keys are created, scoped, rotate, and revoke Chatbots API keys directly from that page. See eCourtDate APIs for the shared credential process across all eCourtDate APIs.

The Chatbots API is a paid add-on

The Chatbots API has to be purchased as an add-on, and the eCourtDate help team activates it on the account before it becomes available. Until activation is done, the Chatbots API does not appear as a choice on the APIs page and nu cannot issue a key for it. To buy the add-on or check on activation, open a support ticket in the Console using the Help button in the bottom-right corner.

Scopes

Every key carries one or both scopes. Ask for the narrowest set that lets the integration do its job.

ScopeGrants
chatChat completions, model listing, embeddings, and conversations
ingestFile uploads, website crawling, documents, and job status

A request that needs a scope the key does not carry fails with 403 insufficient_scope.

Handling keys

A Chatbots API key is a server-side secret. Never put one in a web page, a mobile app, or any other client-side code. Put a backend of the agency's own between the users and the API, and store the key in a secrets manager or an environment variable.

Never log the Authorization header. When to identify a key in a support ticket, use the key ID, which is the first 16 characters after the ecd_sk_ prefix, and never the full value.

Use one key per integration so a single compromise does not force a rotation of everything. To rotate a key, issue a replacement with the same scopes on the APIs page in the Console, deploy it everywhere, verify with a test call, then revoke the old key. Revocation can take up to a minute to take effect because keys are cached.

The first request

A single POST /v1/chat/completions call takes a list of messages and returns an answer. Omit model and the account default bot handles the request:

curl -s "https://api.chatbots.ecourtdate.com/v1/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "user", "content": "What are the Traffic Division hours?" }
]
}'

Because the request and response follow the OpenAI shape, the official SDKs work with two settings changed:

import OpenAI from "openai";

const client = new OpenAI({
apiKey: process.env.API_KEY,
baseURL: "https://api.chatbots.ecourtdate.com/v1",
});

const completion = await client.chat.completions.create({
model: "default",
messages: [
{ role: "user", content: "What are the Traffic Division hours?" },
],
});

console.log(completion.choices[0].message.content);

The answer comes back on choices[0].message.content, with [Source N] markers in the text and the matching sources on choices[0].message.citations. For the full walkthrough, see the getting started guide.

Bots and models

The OpenAI model field names a bot: a configured assistant with a persona, a slice of the knowledge base, and its own retrieval settings. eCourtDate manages bot configuration, and changes take effect immediately.

StepEndpoint
List the bots on the accountGET /v1/models
Look up one bot by slug, alias, or defaultGET /v1/models/{modelId}

Each bot has a slug, such as court-assistant, which is what is passed as model. An account can also define aliases that resolve to the same bot. Slugs and aliases are interchangeable in requests, and responses always report the canonical slug.

When model is omitted, null, empty, or set to default, the API picks the configured default bot, or the only enabled bot if there is exactly one, or a built-in assistant if no bots are enabled. If several bots exist and none is marked default, the request is rejected and asks for a bot to be named explicitly.

Citations

When a bot has a knowledge base, the API embeds the user's message, retrieves the passages that match, and gives them to the model with numbered source labels. The model writes [Source N] markers into the answer, and each marker has an entry in the citations array.

FieldMeaning
source_indexThe N in the matching [Source N] marker. 1-based, and unique within an answer
document_idThe document the passage came from, for looking up the full record
source_filenameThe stored filename for an uploaded file, or the page URL for a crawled page
chunk_index0-based position of the passage inside the document, stable for the life of the document
text_previewThe first 200 characters of the passage text
scoreRetrieval relevance, useful for ordering and filtering but not comparable between requests

citations is an empty array when no document matched the question, and null when citations are turned off for the bot or the bot has no knowledge base.

The API checks that each [Source N] marker points at a passage that was actually retrieved. It does not check that the passage supports the sentence, so show text_preview or a link to the source when the answer matters.

Knowledge base

Documents come from two places: uploaded files, and pages the crawler fetches from a public site. Both land in a namespace, a partition of the knowledge base named with lowercase letters, digits, _, and -, up to 64 characters. Uploads default to the general namespace.

StepEndpoint
Upload filesPOST /v1/ingest/files
Check an upload jobGET /v1/ingest/jobs/{jobId}
Start a site crawlPOST /v1/ingest/crawl
Check a crawl jobGET /v1/ingest/crawl/{crawlJobId}
List documentsGET /v1/documents
Retrieve one documentGET /v1/documents/{documentId}
Delete one documentDELETE /v1/documents/{documentId}

Uploads are multipart/form-data with each file in a part named file, plus an optional namespace field. The accepted extensions are .pdf, .docx, .xlsx, .txt, .md, .html, .htm, .csv, and .eml, and the content is checked against the extension. Every file in a request is validated before any of them is stored, so one bad file fails the whole request with a 400 and creates no documents. A scanned PDF with no text layer is accepted as a file but fails during indexing, because there is no readable text to extract.

A crawl takes seed_urls and an optional allowed_domains list, and works breadth-first from the seeds. Seeds must be public HTTP or HTTPS URLs: credentials in the URL, unresolvable hosts, and addresses that resolve to private, loopback, or reserved ranges are rejected. Redirects are followed only while they stay inside the allowed domains. respect_robots_txt defaults to true.

Both endpoints return immediately with a job ID and index in the background. A job ends in exactly one of completed, completed_with_errors, or failed, and never changes after that. A single document that fails does not fail the whole job: it keeps a record with the reason on it. Poll with exponential backoff, from about 2 seconds up to a 30 second ceiling, or use a webhook instead.

Each document reports a status of processing, ready, or failed. Only a ready document is citable, and only a ready document has a chunk_count above zero.

Deleting a document is synchronous and irreversible: it removes the indexed chunks, the stored content, and the record, and returns the number of chunks removed. There is no bulk delete. Re-uploading the same content creates a second document rather than replacing the first, so delete the old one when replacing a file. Wait for a job to finish before deleting anything it touched.

Webhooks

When a webhook is configured, it fires exactly once, when a job reaches a terminal status.

EventFires whenIdentifier in the body
ingest.completedA file upload job reaches a terminal statusjob_id
crawl.completedA crawl job reaches a terminal statuscrawl_job_id

The body mirrors the matching status endpoint, plus event and namespace fields. The event name appears both in the X-ECD-Event header and in the body.

Each delivery is signed with X-ECD-Signature, formatted as t=<unix seconds>,v1=<hex>. The signature is an HMAC-SHA256 over the timestamp, a literal ., and the raw request body, keyed with the webhook secret. Verify it against the raw bytes received rather than a re-serialized copy of the JSON, compare in constant time, and reject a delivery whose timestamp is more than 5 minutes old.

A delivery is attempted up to 3 times: immediately, after 30 seconds, and after 5 minutes. The endpoint has 10 seconds to return a 2xx. Every attempt carries the same X-ECD-Delivery-Id and the same body, with a fresh signature, so deduplicate on the delivery ID. After the third failure the event is dropped and logged. There is no dead-letter queue, so fall back to polling the job endpoints if a delivery matters.

Conversations

Chat completions are stateless: the whole message history is replayed on every call. Conversations move that transcript to the server instead, bound to one bot.

StepEndpoint
Create a conversationPOST /v1/conversations
Send a message and get the replyPOST /v1/conversations/{conversationId}/messages
Read the full transcriptGET /v1/conversations/{conversationId}
Delete the conversationDELETE /v1/conversations/{conversationId}

Sending a message is synchronous: the reply is generated before the 200 returns, and the user message and the reply are stored together. A turn that fails is not stored, so retrying is safe.

The model sees up to the 40 most recent messages, and the window always opens on a user turn. Retrieval uses only the new message as the query, and citations from earlier turns are stripped before the history goes to the model. The bot is resolved from stored configuration on every message, so a configuration change applies to the next turn. A conversation carries up to 16 metadata keys of the agency's own.

Deleting a conversation removes all of its messages immediately and cannot be undone.

Conventions

ConventionDetail
Field namingsnake_case for request and response fields, for example finish_reason. Enum values are lowercase strings, for example completed_with_errors
Object typeEvery resource carries an object field: chat.completion, conversation, document, model, embedding, or list
IdentifiersChat completions are chatcmpl- plus 24 hex characters. Conversations, documents, and jobs are hyphenated UUIDs. Models are a bot slug or alias. Treat every ID as an opaque, case-sensitive string scoped to the account
TimestampsUnix seconds in UTC on OpenAI-shaped objects (created, updated), and RFC 3339 in UTC on documents (created_at, updated_at)
PaginationCursor based on GET /v1/documents. Take limit (1 to 1000, default 100) and after. Responses carry has_more, first_id, and last_id. Pass the previous page's last_id as after until has_more is false
Response headersX-Request-ID on every response, for support. RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset on authenticated requests. Retry-After on 429 and 503
ErrorsThe OpenAI error envelope: an error object with message, type, param, and code. Branch on code and param, never on message
Unknown fieldsUnknown request fields are accepted and ignored, so SDKs that send extra parameters still work. Responses can gain new fields without notice, so unrecognized fields should be ignored
VersioningVersioned path segment (/v1). Retiring anything stable takes an announcement, Deprecation and Sunset headers, and a window of at least 12 months

Errors are grouped by type: authentication_error (401), invalid_request_error (400, 403, 404, 405, 413), rate_limit_error (429), and server_error (500, 503). Validation failures return 400 with a null code and a param naming the field path, such as messages[2].tool_call_id, and only the first failure is reported. Retry on 429, 500, and 503; fix the request on the rest.

Once a response has started streaming, an error arrives in-band as a frame with an error key, followed by data: [DONE]. In-stream errors carry no Retry-After header, so decide from the frame's type and code. Nothing is metered for a failed stream, so resending is safe.

Limits

LimitValue
JSON request body5 MiB
Messages per chat request200
Characters per message100,000
Generated tokens per answer8,192
Files per upload request20
Size per uploaded file25 MiB
Inputs per embeddings request96
Pages per crawl1 to 500, default 50
Crawl depth1 to 10 hops from the seeds, default 3
Crawl request rate0.1 to 10 requests per second, default 2.0
Crawl duration10 minutes, after which the job fails
Messages sent to the model from a conversation40 most recent
Metadata keys per conversation16

Two account-level budgets apply on top of those. A requests-per-minute limit covers every /v1 endpoint equally and resets at the top of each minute; exhausting it returns 429 rate_limit_exceeded with a Retry-After of 1 to 60 seconds. A daily token quota covers chat completions, conversation messages, and embeddings, and resets at UTC midnight; exhausting it returns 429 insufficient_quota and blocks every /v1 operation until the reset. Pace requests against RateLimit-Remaining, and reschedule rather than retry in a loop when the daily quota is gone.

Differences from the OpenAI API

POST /v1/chat/completions, GET /v1/models, GET /v1/models/{modelId}, and POST /v1/embeddings follow the OpenAI request and response shapes, with citations added to assistant messages and input_type added to embeddings. Watch for these differences:

  • n must be 1. The API returns a single choice.
  • seed, presence_penalty, frequency_penalty, logit_bias, logprobs, parallel_tool_calls, store, and service_tier are accepted and ignored.
  • A request cannot combine tools with a response_format other than text.
  • temperature above 1 is treated as 1, and top_p is ignored when temperature is set.
  • Embeddings are always 1024 dimensions. Passing dimensions with any other value is rejected.
  • Conversations, file ingestion, crawling, documents, and jobs are eCourtDate additions with no OpenAI equivalent, so call them with a plain HTTP client.

Next steps