Skip to main content

Conversations

A conversation is a server-side transcript bound to one bot. You create it once, then post each user message to it; the eCourtDate Chatbot API stores the turn, replays the history to the model, and returns the reply with its citations. The full transcript is retrievable at any time.

POST /v1/conversations
GET /v1/conversations/{conversationId}
POST /v1/conversations/{conversationId}/messages
DELETE /v1/conversations/{conversationId}
Scope: chat

These endpoints are specific to the eCourtDate Chatbot API (they are not part of the OpenAI shape), so call them with plain HTTP rather than the OpenAI SDKs. Conversation ids are UUIDs in canonical hyphenated form, for example a06fb97c-92ba-44ae-ad05-7b1a545a22d5, and are visible only to the account that created them.

Conversations or chat completions?

Use conversations whenUse chat completions when
You want the server to keep the history (a public chat widget, a help desk, a kiosk).Your application already keeps the history or builds each prompt itself.
You want a retrievable transcript for review or support.You need tools, response_format, or per-request sampling parameters.
The bot's configured settings are all you need.You use the OpenAI SDKs or another OpenAI-compatible client.

Generation settings for a conversation (persona, token limit, citations) come from the bot's configuration, and sampling is left to the underlying language model; the messages endpoint accepts only content and stream.

Create a conversation

POST /v1/conversations returns 201 with the new conversation.

FieldTypeDescription
modelstringA bot slug or alias, resolved exactly as on chat completions; the literal "default" binds the conversation to whatever your account's default is right now, provided that default is a real bot (when no bot is enabled and the default is the built-in assistant, nothing is bound and model is stored as null). Omit it, or send null or an empty string (whitespace is trimmed), to store no binding: the conversation then follows the account default, resolved on each message.
metadataobjectUp to 16 keys. Keys are strings of 1 to 64 characters; values are strings of at most 512 characters. Stored with the conversation and returned by create and retrieve. Use it for your own identifiers (a session id, a case number, the channel).
curl -s "https://api.chatbots.ecourtdate.com/v1/conversations" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "court-assistant",
"metadata": { "channel": "web-widget", "session_id": "sess_8f21c4" }
}'
{
"id": "a06fb97c-92ba-44ae-ad05-7b1a545a22d5",
"object": "conversation",
"model": "court-assistant",
"metadata": { "channel": "web-widget", "session_id": "sess_8f21c4" },
"created": 1787360082,
"updated": 1787360082
}
FieldTypeDescription
idstringThe conversation id. Store it; there is no list endpoint.
objectstringAlways conversation.
modelstring or nullThe canonical slug of the bound bot (also when you sent an alias or "default"), or null when the conversation follows the account default (including "default" sent while the built-in assistant is the default).
metadataobjectAs sent; {} when none was sent.
createdintegerUnix time in seconds (UTC) when the conversation was created.
updatedintegerUnix time in seconds (UTC) of the last stored turn; equal to created until the first message.

An empty object {}, an empty body, or no body at all is valid and creates a conversation with model: null that follows the account default. A model that is unknown, disabled, or belongs to another account is rejected at create time with 404 model_not_found; omitting model when several bots are enabled and no default is configured is rejected with 400 and param: "model".

Add a message

POST /v1/conversations/{conversationId}/messages appends a user message, generates the reply, stores both, and returns the reply.

FieldTypeDescription
contentstringRequired. 1 to 100,000 characters (counted as Unicode characters). Must not be empty or whitespace-only (400, param: "content"); otherwise it is not trimmed and is sent to the model as is.
streambooleanDefault false. true returns server-sent events. Must be a JSON boolean.

Unknown fields are ignored.

curl -s "https://api.chatbots.ecourtdate.com/v1/conversations/a06fb97c-92ba-44ae-ad05-7b1a545a22d5/messages" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "What time does jury duty start?" }'
{
"conversation_id": "a06fb97c-92ba-44ae-ad05-7b1a545a22d5",
"message": {
"role": "assistant",
"content": "Jurors report to the jury assembly room on the second floor by 8:30 a.m. [Source 1].",
"citations": [
{
"source_index": 1,
"document_id": "2f6e1c0a-8d3b-4c57-9a41-5b2e8d7f0c13",
"source_filename": "juror-handbook.pdf",
"chunk_index": 0,
"text_preview": "Report to the jury assembly room on the second floor by 8:30 a.m. Parking is validated for jurors in the Fifth Street garage.",
"score": 0.91
}
]
}
}
FieldTypeDescription
conversation_idstringThe conversation the turn was added to.
message.rolestringAlways assistant.
message.contentstringThe reply, with [Source N] markers left in place.
message.citationsarray or nullCitation objects: an array (empty when nothing was cited) when the bot has a knowledge base and citations enabled, null otherwise.

The body carries no usage, model, or finish_reason. Token usage still counts toward your daily quota.

A second message continues the exchange; the server supplies the history:

curl -s "https://api.chatbots.ecourtdate.com/v1/conversations/a06fb97c-92ba-44ae-ad05-7b1a545a22d5/messages" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "Is parking free?" }'
{
"conversation_id": "a06fb97c-92ba-44ae-ad05-7b1a545a22d5",
"message": {
"role": "assistant",
"content": "Parking is validated for jurors in the Fifth Street garage, so you will not pay for the day [Source 1].",
"citations": [
{
"source_index": 1,
"document_id": "2f6e1c0a-8d3b-4c57-9a41-5b2e8d7f0c13",
"source_filename": "juror-handbook.pdf",
"chunk_index": 0,
"text_preview": "Report to the jury assembly room on the second floor by 8:30 a.m. Parking is validated for jurors in the Fifth Street garage.",
"score": 0.89
}
]
}
}

How a turn is processed

  • The call is synchronous: the reply is generated before the 200 is returned, and the user message and the reply are stored together. A GET immediately afterwards shows both.
  • The model receives the stored history plus the new message, with citations stripped from earlier turns. Up to 40 of the most recent messages are sent to the model; the window always opens on a user turn, so it can hold 39. Older messages stay in the transcript but no longer influence replies. Start a new conversation when the topic changes.
  • Knowledge-base retrieval uses only the new message as the query, exactly as on chat completions.
  • The bot is resolved again on every message from the conversation's stored model. If that bot has since been disabled, the message is rejected with 404 model_not_found while GET still returns the transcript. A conversation created without a model follows whatever the account default is at the time of each message; if that default has become ambiguous (several enabled bots and no configured default), the message is rejected with 400 and param: "model".
  • A failed turn is not stored: after a 4xx, 5xx, or a streaming error frame, the transcript is unchanged and the message can simply be resent.

Streamed turns deliver the same reply as delta frames followed by a done frame that carries the final message. See Conversation streams.

Retrieve a conversation

GET /v1/conversations/{conversationId} returns the transcript.

curl -s "https://api.chatbots.ecourtdate.com/v1/conversations/a06fb97c-92ba-44ae-ad05-7b1a545a22d5" \
-H "Authorization: Bearer $API_KEY"
{
"id": "a06fb97c-92ba-44ae-ad05-7b1a545a22d5",
"object": "conversation",
"model": "court-assistant",
"messages": [
{
"role": "user",
"content": "What time does jury duty start?",
"citations": null
},
{
"role": "assistant",
"content": "Jurors report to the jury assembly room on the second floor by 8:30 a.m. [Source 1].",
"citations": [
{
"source_index": 1,
"document_id": "2f6e1c0a-8d3b-4c57-9a41-5b2e8d7f0c13",
"source_filename": "juror-handbook.pdf",
"chunk_index": 0,
"text_preview": "Report to the jury assembly room on the second floor by 8:30 a.m. Parking is validated for jurors in the Fifth Street garage.",
"score": 0.91
}
]
},
{
"role": "user",
"content": "Is parking free?",
"citations": null
},
{
"role": "assistant",
"content": "Parking is validated for jurors in the Fifth Street garage, so you will not pay for the day [Source 1].",
"citations": [
{
"source_index": 1,
"document_id": "2f6e1c0a-8d3b-4c57-9a41-5b2e8d7f0c13",
"source_filename": "juror-handbook.pdf",
"chunk_index": 0,
"text_preview": "Report to the jury assembly room on the second floor by 8:30 a.m. Parking is validated for jurors in the Fifth Street garage.",
"score": 0.89
}
]
}
],
"created": 1787360082,
"updated": 1787360174,
"metadata": { "channel": "web-widget", "session_id": "sess_8f21c4" }
}
FieldTypeDescription
id, object, model, metadataAs on create. model is null for a conversation created without one.
messagesarrayEvery stored message in order, alternating user and assistant. User messages always have citations: null. Per-message timestamps are not exposed.
createdintegerUnix time in seconds (UTC) when the conversation was created. Equal to the created value returned by the create call.
updatedintegerUnix time in seconds (UTC) of the last stored turn. Always >= created; equal to it until the first message.

There is no list endpoint and no way to edit or delete individual messages: keep your own index of conversation ids (for example in metadata of your session store) and treat a transcript as append-only.

Delete a conversation

DELETE /v1/conversations/{conversationId} permanently removes the transcript and returns 204 with an empty body and no Content-Type header.

curl -s -o /dev/null -w "%{http_code}\n" -X DELETE \
"https://api.chatbots.ecourtdate.com/v1/conversations/a06fb97c-92ba-44ae-ad05-7b1a545a22d5" \
-H "Authorization: Bearer $API_KEY"
# 204

Deletion is immediate and irreversible. Afterwards GET, DELETE, and POST .../messages on the id all return 404 not_found, so a repeated delete is not a no-op at the status level; treat a 404 after your own delete as success.

Isolation

Conversations belong to the account whose key created them. An id from another account, a deleted id, a malformed id, and an id that never existed all produce the same 404 not_found body, so a response never reveals whether a conversation exists elsewhere.

{
"error": {
"message": "Conversation not found.",
"type": "invalid_request_error",
"param": null,
"code": "not_found"
}
}

Full example

A minimal help-desk loop in Python with the requests library:

import os
import requests

BASE = "https://api.chatbots.ecourtdate.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['API_KEY']}"}

conversation = requests.post(
f"{BASE}/conversations",
headers=HEADERS,
json={"model": "court-assistant", "metadata": {"channel": "phone-tree", "caller": "anon-4471"}},
)
conversation.raise_for_status()
conversation_id = conversation.json()["id"]

for question in ["What time does jury duty start?", "Is parking free?"]:
reply = requests.post(
f"{BASE}/conversations/{conversation_id}/messages",
headers=HEADERS,
json={"content": question},
)
reply.raise_for_status()
message = reply.json()["message"]
print(question)
print(" ->", message["content"])
for citation in message["citations"] or []:
print(" source:", citation["source_filename"], "chunk", citation["chunk_index"])

transcript = requests.get(f"{BASE}/conversations/{conversation_id}", headers=HEADERS).json()
print(len(transcript["messages"]), "messages stored")

requests.delete(f"{BASE}/conversations/{conversation_id}", headers=HEADERS)

The same in Node with fetch:

const BASE = "https://api.chatbots.ecourtdate.com/v1";
const headers = {
Authorization: `Bearer ${process.env.API_KEY}`,
"Content-Type": "application/json",
};

const created = await fetch(`${BASE}/conversations`, {
method: "POST",
headers,
body: JSON.stringify({ model: "court-assistant", metadata: { channel: "web-widget" } }),
});
const { id } = await created.json();

const reply = await fetch(`${BASE}/conversations/${id}/messages`, {
method: "POST",
headers,
body: JSON.stringify({ content: "What time does jury duty start?" }),
});
const { message } = await reply.json();
console.log(message.content, message.citations);

await fetch(`${BASE}/conversations/${id}`, { method: "DELETE", headers });

Errors

StatuscodeOperationsWhen
400nullcreate, messagesValidation: content missing, empty, whitespace-only, or over 100,000 characters (param: "content"); stream not a boolean (param: "stream"); metadata that is not an object or has more than 16 keys (param: "metadata"), or a key over 64 characters, a non-string value, or a value over 512 characters (param: "metadata.<key>"); model not a string (param: "model"); a body that is not a JSON object (param: null; on create, a missing or empty body counts as {}). Also ambiguous model resolution (param: "model"), on create and on a message to a conversation that follows the account default.
401invalid_api_keyallMissing or invalid key.
403insufficient_scopeallThe key lacks the chat scope.
404not_foundretrieve, messages, deleteUnknown, deleted, malformed, or another account's conversation id.
404model_not_foundcreate, messagesThe model (or the conversation's stored bot) is unknown, disabled, or inaccessible; param: "model".
413request_too_largecreate, messagesBody over 5 MiB.
429rate_limit_exceededallPer-minute request limit reached (retrieve and delete count too), or the underlying language model is rate limited. Honor Retry-After.
429insufficient_quotaallDaily token quota reached.
500nullmessagestype: server_error. Unexpected failure; the turn was not stored. Retry with backoff.
503upstream_unavailablemessagesThe underlying language model or the text embedding service (used for retrieval) failed or timed out; the turn was not stored. Retry after Retry-After.

With stream: true, these statuses apply only before the stream starts; later failures arrive as an in-band error frame (Streaming). All errors use the envelope described in Errors.