Getting started
The eCourtDate Chatbot API answers questions with grounded, cited answers drawn from documents you upload. It speaks the OpenAI chat completions dialect, so the official OpenAI SDKs work unmodified. This guide takes you from a freshly activated key to a cited answer in five steps.
Base URL: https://api.chatbots.ecourtdate.com/v1
1. Activate your key
Access requires a paid subscription. When your subscription is active,
eCourtDate issues an API key for your account and activates it; there is no
self-serve key page. The key looks like ecd_sk_ followed by 59 characters
and carries one or both scopes:
| Scope | Needed for |
|---|---|
chat | Chat completions, models, embeddings, conversations (steps 2, 3, and 5) |
ingest | Uploading files, crawling, documents, jobs (step 4) |
Keys are server-side secrets. Store yours in a secrets manager or an environment variable, never in a browser or a mobile app, and send it on every request as a Bearer token:
export API_KEY="ecd_sk_..."
See Authentication for the key lifecycle and rotation guidance.
2. Send your first chat completion
A request needs only messages. Omit model and the request is served by
your account's default bot (when several bots are
enabled and no default is configured, the API answers 400 and asks you to
pass model).
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": "How do I request a continuance for a traffic hearing?" }
]
}'
{
"id": "chatcmpl-8a317b1a95a74321b73b6567",
"object": "chat.completion",
"created": 1787408210,
"model": "court-assistant",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "To request a continuance, file a written motion with the clerk at least five business days before the hearing date and include the case number.",
"refusal": null,
"tool_calls": null,
"citations": []
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 812,
"completion_tokens": 143,
"total_tokens": 955
}
}
model in the response is the canonical slug of the bot that answered,
even when you omitted it or used an alias. Every key of the message is
always present: citations is an empty array here because the bot has a
knowledge base but nothing in it was cited yet (step 5 changes that), and
tool_calls is null because no tools were offered.
The same request with the official OpenAI Python SDK:
from openai import OpenAI
client = OpenAI(
api_key="ecd_sk_...",
base_url="https://api.chatbots.ecourtdate.com/v1",
)
completion = client.chat.completions.create(
model="default",
messages=[
{"role": "user", "content": "How do I request a continuance for a traffic hearing?"}
],
)
print(completion.choices[0].message.content)
And with the official OpenAI Node SDK:
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: "How do I request a continuance for a traffic hearing?" },
],
});
console.log(completion.choices[0].message.content);
The SDKs require a model value; default selects the account default,
exactly as omitting the field does over curl. See
Chat completions for every supported parameter and
Streaming to receive the answer as it is generated.
3. Discover your models
Each bot configured for your account, each alias that points at one, and
default (the account default) are listed as models. Use the ids as the
model value.
curl -s "https://api.chatbots.ecourtdate.com/v1/models" \
-H "Authorization: Bearer $API_KEY"
{
"object": "list",
"data": [
{
"id": "court-assistant",
"object": "model",
"created": 1784812800,
"owned_by": "maple-county-courts"
},
{
"id": "jury-helpdesk",
"object": "model",
"created": 1786022400,
"owned_by": "maple-county-courts"
},
{
"id": "default",
"object": "model",
"created": 1784812800,
"owned_by": "maple-county-courts"
}
]
}
owned_by is your account id, and the default entry carries the default
bot's created. With the SDKs, client.models.list() returns
the same page. An unknown or disabled id returns 404
model_not_found whose message lists the ids
that are available. Bots, aliases, and the default are configured for your
account by eCourtDate; see Models.
4. Upload your first document
The knowledge base is what makes answers grounded. Uploads need a key with
the ingest scope and use multipart/form-data, one file part per file
(PDF, DOCX, XLSX, CSV, TXT, Markdown, HTML, or EML, up to 25 MiB each and
20 per request). The optional namespace field defaults to general; a bot
answers from the namespace eCourtDate configured for it, so upload into that
namespace.
curl -s "https://api.chatbots.ecourtdate.com/v1/ingest/files" \
-H "Authorization: Bearer $API_KEY" \
-F "file=@traffic-division-faq.pdf" \
-F "namespace=general"
{
"job_id": "bfcde661-9eec-4142-a790-ca9a62c8e0f8",
"document_ids": ["17136f01-972d-4956-868e-8159833072e4"],
"status": "processing"
}
The response returns before any text is extracted. Poll the job until its
status is terminal:
curl -s "https://api.chatbots.ecourtdate.com/v1/ingest/jobs/bfcde661-9eec-4142-a790-ca9a62c8e0f8" \
-H "Authorization: Bearer $API_KEY"
{
"job_id": "bfcde661-9eec-4142-a790-ca9a62c8e0f8",
"status": "completed",
"total_documents": 1,
"completed_documents": 1,
"failed_documents": 0,
"errors": []
}
status moves from processing to completed, completed_with_errors, or
failed; errors names each document that could not be indexed and why
(scanned PDFs without a text layer are the usual cause). A short polling loop
in bash:
JOB_ID="bfcde661-9eec-4142-a790-ca9a62c8e0f8"
until [ "$STATUS" = "completed" ] || [ "$STATUS" = "completed_with_errors" ] || [ "$STATUS" = "failed" ]; do
sleep 5
STATUS=$(curl -s "https://api.chatbots.ecourtdate.com/v1/ingest/jobs/$JOB_ID" \
-H "Authorization: Bearer $API_KEY" | jq -r .status)
echo "$STATUS"
done
Instead of polling you can receive an ingest.completed webhook; see
Jobs and webhooks. The OpenAI SDKs do
not cover the knowledge base endpoints; call them with curl or any HTTP
client (SDKs). Details of formats, namespaces, and per-document
errors are in Ingesting files.
5. Ask a grounded question
Once the document is ready, ask a question it answers. The assistant cites
the passages it used with [Source N] markers in the text, and the message
carries a citations array describing each source.
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": "How many days before the hearing must I file a continuance request?" }
]
}'
{
"id": "chatcmpl-fbf6088fdd704a72ac569970",
"object": "chat.completion",
"created": 1787408377,
"model": "court-assistant",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "A written continuance request must be filed at least five business days before the scheduled hearing [Source 1].",
"refusal": null,
"tool_calls": null,
"citations": [
{
"source_index": 1,
"document_id": "17136f01-972d-4956-868e-8159833072e4",
"source_filename": "traffic-division-faq.pdf",
"chunk_index": 3,
"text_preview": "Continuances. A request for a continuance must be made in writing and filed with the clerk no later than five (5) business days before the hearing date.",
"score": 0.93
}
]
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 1048,
"completion_tokens": 41,
"total_tokens": 1089
}
}
Each citation's source_index matches the [Source N] marker in
content, document_id is the document you uploaded, and text_preview is
the first 200 characters of the passage. citations is always present on a
text answer from a bot with a knowledge base (an empty array when nothing was
cited) and is null on a tool-call turn or for a bot that does not cite. The
OpenAI SDKs keep unknown fields, so the array is reachable without any
workaround:
completion = client.chat.completions.create(
model="default",
messages=[{"role": "user", "content": "How many days before the hearing must I file a continuance request?"}],
)
message = completion.choices[0].message
print(message.content)
for citation in (message.model_extra or {}).get("citations") or []:
print(f"[Source {citation['source_index']}] {citation['source_filename']} (chunk {citation['chunk_index']})")
const completion = await client.chat.completions.create({
model: "default",
messages: [{ role: "user", content: "How many days before the hearing must I file a continuance request?" }],
});
const message = completion.choices[0].message;
console.log(message.content);
for (const citation of message.citations ?? []) {
console.log(`[Source ${citation.source_index}] ${citation.source_filename} (chunk ${citation.chunk_index})`);
}
See Citations for every field and for rendering guidance.
Errors you may meet on the way
| Status | Code | Cause |
|---|---|---|
401 | invalid_api_key | Missing, malformed, expired, or revoked key |
403 | insufficient_scope | The key lacks chat (steps 2, 3, 5) or ingest (step 4) |
404 | model_not_found | model is not a bot or alias on your account |
404 | not_found | Unknown job or document id |
400 | validation | A malformed body; param names the field |
400 | validation | model omitted while several bots are enabled and no default is configured; param is model |
429 | rate_limit_exceeded | Too many requests this minute; wait for Retry-After |
Go deeper
| To... | Read |
|---|---|
| Understand keys, scopes, and rotation | Authentication |
| Stream answers token by token | Streaming |
| Keep chat history on the server | Conversations |
| Call your own functions from the model | Tools and structured output |
| Crawl your public website instead of uploading | Crawling websites |
| Get notified when ingestion finishes | Jobs and webhooks |
| Handle every failure mode | Errors and Rate limits |
| Explore every endpoint | API reference |