Skip to main content

Chat completions

POST /v1/chat/completions is the main endpoint of the eCourtDate Chatbot API. It accepts the OpenAI chat completions request shape, answers with a bot configured for your account, and grounds the answer in your knowledge base when the bot has one. Official OpenAI SDKs work unchanged once pointed at the base URL; the only additions are the citations extension on the response and a few account-level limits described below.

POST https://api.chatbots.ecourtdate.com/v1/chat/completions
Scope: chat

A first request

curl -s "https://api.chatbots.ecourtdate.com/v1/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "court-assistant",
"messages": [
{ "role": "user", "content": "What are the Traffic Division hours?" }
]
}'
{
"id": "chatcmpl-c14a12e8a6a64e9283a217e7",
"object": "chat.completion",
"created": 1787360841,
"model": "court-assistant",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "The Traffic Division is open Monday through Friday, 8:00 a.m. to 4:30 p.m., at the Main Street courthouse [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": 2,
"text_preview": "Traffic Division hours: Monday through Friday, 8:00 a.m. to 4:30 p.m. Located on the first floor of the Main Street courthouse.",
"score": 0.93
}
]
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 812,
"completion_tokens": 143,
"total_tokens": 955
}
}

The same call with the official SDKs:

import os
from openai import OpenAI

client = OpenAI(
api_key=os.environ["API_KEY"],
base_url="https://api.chatbots.ecourtdate.com/v1",
)

completion = client.chat.completions.create(
model="court-assistant",
messages=[{"role": "user", "content": "What are the Traffic Division hours?"}],
)
print(completion.choices[0].message.content)
# The citations extension is available on the parsed object's extra fields:
print(completion.choices[0].message.model_extra.get("citations"))
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: "court-assistant",
messages: [{ role: "user", content: "What are the Traffic Division hours?" }],
});
console.log(completion.choices[0].message.content);
console.log(completion.choices[0].message.citations); // extension field

Request

The body is application/json and must be a JSON object. Unknown top-level fields are ignored. Values must be JSON-typed: a boolean is true, not "true", and a number is 0.5, not "0.5".

FieldTypeDescription
messagesarrayRequired. 1 to 200 messages. Each message's text may be at most 100,000 characters.
modelstringA bot slug or alias from GET /v1/models. Omit it, send null, or send "default" to use your account's default model. Surrounding whitespace is trimmed and an empty string is treated as omitted.
streambooleanDefault false. true switches the response to server-sent events.
stream_optionsobject{ "include_usage": true } adds a final usage chunk to a stream. Validated on every request (include_usage must be a JSON boolean) but acted on only when stream is true.
temperaturenumber0 to 2. Values above 1 are treated as 1 by the underlying language model. Omit to use the model's default.
top_pnumber0 to 1. Ignored when temperature is also set, so send one or the other. Omit to use the model's default.
max_completion_tokensintegerUpper bound on generated tokens, at least 1. Values above 8192 are clamped to 8192, not rejected. Omit to use the bot's configured limit.
max_tokensintegerLegacy alias of max_completion_tokens with the same bounds. When both are sent, max_completion_tokens wins.
stopstring or array of stringsSequences at which generation stops. Every string must be non-empty ("" and [""] are rejected with 400); up to 4 sequences are honored.
nintegerMust be 1 (the default). Any other value is rejected with 400.
toolsarrayFunction definitions the model may call. An empty array is the same as omitting the field. See Tools and structured output.
tool_choicestring or object"auto", "none", "required", or a forced function. Validated after the message rules and the tool definitions, even without tools (a malformed value is 400); a well-formed value is ignored when no tools are sent. See Tools and structured output.
response_formatobjecttext, json_object, or json_schema. See Structured output.
userstringAccepted for compatibility and ignored. Must be a string if present.

Accepted and ignored fields

These OpenAI fields are accepted so that existing client code keeps working, but they have no effect: seed, presence_penalty, frequency_penalty, logit_bias, logprobs, top_logprobs, parallel_tool_calls, metadata, store, service_tier, reasoning_effort, modalities, audio, prediction, web_search_options, and the per-message name.

temperature and top_p are the only sampling controls. Bots carry no sampling defaults of their own (only a token limit), so an omitted value is left to the underlying language model, and some models ignore sampling parameters altogether.

Messages

Each message is { "role", "content", ... }. The roles are system, developer, user, assistant, and tool; any other role is rejected with 400.

RolecontentNotes
system, developerstringInstructions from your application. Must not be empty or whitespace-only (400, param: messages[i].content). Layered after the bot's persona; see below.
userstring, or an array of { "type": "text", "text": "..." } partsMust not be empty or whitespace-only. Text parts are joined with a newline. A part whose type is not text (for example image_url) is rejected with 400 and param: messages[i].content[j].type; a text part without a string text with param: messages[i].content[j].text.
assistantstring, or null when tool_calls is presentA previous reply you are replaying. Must not be empty unless it carries tool_calls.
toolstring, an array of text parts, null, or ""The result of a function call (null or "" means an empty result). tool_call_id is required and must answer a tool call from the nearest preceding assistant message. See the tool loop.

The conversation as a whole must satisfy four rules, each enforced with a 400:

  • at least one message other than system or developer is present (param: "messages");
  • the first message that is not system or developer has role user: a leading assistant message is rejected with param: "messages", and a leading tool message by the rule below;
  • the last message has role user or tool (param: "messages");
  • every tool message answers a tool call: its tool_call_id must match an id in the tool_calls of the nearest preceding assistant message, and each call may be answered at most once. Otherwise the request is rejected with param: "messages[i].tool_call_id" (the index of the tool message) and the message tool_call_id does not match a preceding tool call.; a tool message with no assistant tool call before it at all gets the same error.

Consecutive user messages are allowed and are forwarded as sent.

System and developer messages

Every bot has a persona configured by eCourtDate for your account: a name, instructions, tone, and the grounding rules that make it cite your knowledge base. Your system and developer messages do not replace that persona. They are collected from anywhere in messages, concatenated in order of appearance, and appended to the persona as additional instructions from the API caller. Use them for request-specific guidance ("answer in Spanish", "keep it under three sentences"), not to redefine what the bot is.

{
"model": "court-assistant",
"messages": [
{ "role": "system", "content": "Answer in Spanish." },
{ "role": "user", "content": "How do I pay a filing fee online?" }
]
}

Model resolution

model names a bot (by slug) or an alias configured for your account. The response always reports the bot's canonical slug, even when you sent an alias. Lookups are case-sensitive.

You sendResult
An enabled bot slugThat bot.
An aliasThe bot the alias points to; the response model is the bot's slug.
Omitted, null, "", or "default"Your account's default bot. If no default is set and exactly one bot is enabled, that bot is used; if no bot is enabled, the built-in assistant answers. See The default model.
Omitted with several enabled bots and no default400 with param: "model" and code: null. The message asks you to pass model explicitly or to have eCourtDate set a default for your account.
Unknown, disabled, or another account's slug404 model_not_found with param: "model". The message lists the models available to you and ends with Omit the 'model' field to use this account's default. when a default resolves, or Pass one of them. when the default is ambiguous.
{
"error": {
"message": "Model 'traffic-bot' was not found. Available models: court-assistant, jury-helpdesk. Omit the 'model' field to use this account's default.",
"type": "invalid_request_error",
"param": "model",
"code": "model_not_found"
}
}

Branch on code and param, never on the message text. See Models for the list and retrieve endpoints and how bots are configured.

Grounding and retrieval

When the bot has a knowledge base, the API embeds the text of the last user message, retrieves the most relevant passages from the bot's namespace (optionally restricted to specific documents, as configured on the bot), and hands them to the model with the instruction to cite them as [Source N]. Earlier turns are passed through unchanged and are not used as retrieval queries. Retrieval is skipped when the last message is a tool result; it runs again on the next user message.

A bot without a knowledge base answers from the model's general knowledge and its citations field is always null. See Citations for the marker format and the citations array.

Response

A non-streaming call returns 200 with a chat.completion object.

FieldTypeDescription
idstringchatcmpl- followed by 24 lowercase hex characters.
objectstringAlways chat.completion.
createdintegerUnix time in seconds (UTC) when the completion was created.
modelstringThe canonical slug of the bot that answered.
choicesarrayExactly one choice (n is always 1).
choices[0].indexintegerAlways 0.
choices[0].messageobjectThe assistant message, below.
choices[0].logprobsnullAlways null.
choices[0].finish_reasonstringstop, length, content_filter, or tool_calls. See below.
usage.prompt_tokensintegerTokens in the prompt, including the persona and retrieved passages.
usage.completion_tokensintegerTokens generated.
usage.total_tokensintegerprompt_tokens + completion_tokens. Counts toward your daily token quota.

The message object. Every key is always present, null where it does not apply, as in the OpenAI object:

FieldTypeDescription
rolestringAlways assistant.
contentstring or nullThe answer text. "" when the model produced no text; null on a turn that only calls tools.
refusalnullAlways null. A refusal is returned as ordinary content with finish_reason: "content_filter".
tool_callsarray or nullThe functions the model called, or null when it called none. See Tools and structured output.
citationsarray or nulleCourtDate extension. An array (possibly empty) 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. See Citations.

There is no system_fingerprint, service_tier, prompt_tokens_details, or completion_tokens_details.

finish_reason

ValueMeaning
stopThe model finished its answer or hit one of your stop sequences.
lengthGeneration stopped at the token limit (max_completion_tokens or the bot's limit). The answer is truncated.
content_filterThe model declined to answer. Its explanation is in content.
tool_callsThe model wants you to run the functions in message.tool_calls. This value is set whenever tool_calls is non-empty.

Response headers

Every response carries X-Request-ID (echoed from your request when you send one, otherwise minted); quote it when contacting support. Authenticated responses under /v1 also carry RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset for the per-minute request window. See Conventions and Rate limits.

Multi-turn requests

The endpoint is stateless: send the whole exchange every time. Replay prior assistant replies as assistant messages (drop the citations field; it is ignored if present) and end with the new user message.

curl -s "https://api.chatbots.ecourtdate.com/v1/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "court-assistant",
"messages": [
{ "role": "user", "content": "What are the Traffic Division hours?" },
{ "role": "assistant", "content": "The Traffic Division is open Monday through Friday, 8:00 a.m. to 4:30 p.m., at the Main Street courthouse [Source 1]." },
{ "role": "user", "content": "Is it open on Juneteenth?" }
]
}'

Only the last user message ("Is it open on Juneteenth?") is used as the retrieval query, so phrase follow-ups so they stand on their own, or restate the subject. If you would rather have the server keep the history, use Conversations.

Limits

LimitValue
Messages per request1 to 200
Characters per message100,000
Request body5,242,880 bytes (5 MiB), inclusive
Generated tokens per request8192 (max_completion_tokens is clamped)
Choices1

Validation order

Checks run in a fixed order, and only the first failure is reported:

  1. Body size (413), then JSON decoding (400 with param: null for malformed JSON), then authentication (401), scope (403), and rate limits (429). These run before the body is validated.
  2. Schema validation (400, param names the field, for example messages[0].role or temperature; param is null for a body that is not a JSON object).
  3. n, then stop, then response_format, then model lookup (404), then the message rules, then tool definitions, tool_choice, and the bot's tool allowlist, then retrieval and generation.

Requests must be sent with Content-Type: application/json; a JSON body sent as text/plain is rejected with 400.

Errors

StatuscodeWhen
400nullValidation failure. param names the offending field in bracket form (messages[2].tool_call_id, tools[0], stop), or is null for malformed JSON. Also n other than 1, empty stop strings, the message rules above (messages, or messages[i].tool_call_id for a tool message that answers no preceding tool call), and ambiguous model resolution (param: "model").
400tool_not_allowedA tool in tools is not on the bot's allowlist; param is tools[i].
401invalid_api_keyMissing or invalid key. Response carries WWW-Authenticate: Bearer.
403insufficient_scopeThe key lacks the chat scope.
404model_not_foundUnknown, disabled, or inaccessible model; param is model.
413request_too_largeBody over 5 MiB.
429rate_limit_exceededPer-minute request limit reached, or the underlying language model is rate limited. Honor Retry-After.
429insufficient_quotaDaily token quota reached. Retry-After counts down to the next UTC day.
500nulltype: server_error. An unexpected failure. Retry with backoff; quote X-Request-ID to support.
503upstream_unavailableThe underlying language model or the text embedding service (used for retrieval) failed or timed out. Nothing was charged; retry after Retry-After.

All errors use the envelope {"error": {"message", "type", "param", "code"}}; see Errors. With stream: true, these statuses apply only before the stream starts; failures after that arrive in-band, as described in Streaming.