Security
Court and agency content is public by design, but the systems that serve it are not. This page describes the security properties of the eCourtDate Chatbot API and the practices integrations should follow. It states what the API does; it does not make compliance claims.
Transport and keys
- The API is served over HTTPS at
https://api.chatbots.ecourtdate.com. - Every
/v1request is authenticated with a Bearer API key issued and activated for your account by eCourtDate (Authentication). - Keys are server-side secrets. Never place one in a web page, a mobile app, or any client you do not control; route browser traffic through your own backend. Cross-origin requests are accepted only from eCourtDate-owned origins, so a key in a browser would not work from your domain in any case (server-side only).
- Scope each key to what its integration does (
chatfor answering,ingestfor maintaining the knowledge base), keep one key per integration, store keys in a secrets manager, and rotate them on a schedule (Rotation). - Do not log the
Authorizationheader. When you need to reference a key, use its id (ecd_sk_plus the next 16 characters), never the full value.
Per-account isolation
Every resource is bound to the account the API key belongs to. Your account is derived from the key on every request; nothing in a body, form, or query string can redirect a request at another account.
- Bots, aliases, conversations, documents, ingest jobs, and crawl jobs are scoped to your account. A bot slug configured on two accounts resolves to each account's own bot.
- An id that belongs to another account behaves exactly like an unknown
id:
404not_foundfor conversations, documents, and jobs, and404model_not_foundfor models. The API never confirms that a resource exists on some other account. - Knowledge base retrieval searches only your account's documents, within the namespace the bot is configured for.
- Rate limits and the daily token quota are per account (Rate limits).
What the service stores
| Data | Stored | Notes |
|---|---|---|
| Uploaded files | Yes, the original bytes, under your account | Identified by a SHA-256 hash and the sanitized filename |
| Extracted text | Yes, split into chunks | What retrieval searches and what citations quote (text_preview is the first 200 characters of a chunk) |
| Text embeddings of chunks | Yes | One vector per chunk, used for semantic search |
| Document metadata | Yes | Filename, namespace, source type, source URL, status, chunk count, timestamps |
| Crawled pages | Yes | The extracted text as chunks and embeddings, plus an archived copy of each page's raw HTML, removed with the document |
| Ingest and crawl jobs | Yes | Status, counts, and sanitized per-document or per-page errors |
| Conversations | Yes, until deleted | Every user and assistant message, each assistant message's citations, the bound model, metadata, and timestamps |
| Stateless chat completions | No message content | Only a usage record per request: request id, endpoint, method, status, key id, bot slug, token counts, and latency |
| Embeddings requests | No input content | A usage record with the token count |
| Webhook configuration | Yes | The URL and the signing secret; the secret is never returned by any endpoint |
Usage records are what support reads when you quote an
X-Request-ID. They do not contain message text.
A few behaviors follow from this model:
- Retrieved context and tool results are treated as data, not instructions. When a request offers tools and retrieval returns content, the model is explicitly instructed not to act on instructions found inside retrieved passages or tool output.
- Error responses never include detail from the underlying language model
or embedding service; a failure there is reported as a generic
server_errororupstream_unavailable. - Uploaded filenames are checked before anything is stored: a name with a
/,\, or control character, or longer than 255 UTF-8 bytes, is rejected with400. Leading dots and surrounding whitespace are stripped from the stored name, so it may differ slightly from the one you sent (Ingesting files).
Deletion
Deletion is synchronous and irreversible.
Documents. DELETE /v1/documents/{documentId} removes the document's
chunks and their embeddings first (so it stops answering immediately), then
the stored object (the uploaded original, or the archived raw HTML of a
crawled page), then the record, and returns
{"id": ..., "object": "document.deleted", "deleted": true, "chunks_deleted": N}.
A document deleted while its ingest job is still processing is removed,
and the job then records it as failed; wait for a terminal job status before
deleting (Documents).
Conversations. DELETE /v1/conversations/{conversationId} removes the
conversation and every message in it and returns 204. A second DELETE,
or any later GET, returns 404 not_found.
Re-uploads are not deduplicated. Uploading the same file twice, or crawling the same site twice, creates a second document with the same content. Delete the old document first, or use a separate namespace, when you replace content.
To have every document, conversation, and stored file for your account removed, contact eCourtDate.
Crawler restrictions
Website crawling (POST /v1/ingest/crawl) is restricted to public content
and to the scope you declare:
- Public hosts only. Every seed URL must use
httporhttps, carry no embedded credentials, and resolve by DNS at submission time to public addresses only. Private, loopback, link-local, multicast, and reserved addresses are rejected with400andparam: seed_urls[i], and the whole request fails if any seed fails. The same rule is applied to every link the crawler discovers and to every redirect it follows: an off-scope or non-public destination is discarded. - Allowed domains.
allowed_domainsis an exact host match. An explicit port is part of the match, except that the scheme's default port (:443forhttps,:80forhttp) counts as no port at all. When the list is empty, the hosts of the seed URLs are the allowlist. The crawler never leaves it. - robots.txt is honored when
respect_robots_txtistrue, which is the default. - Bounded.
max_pages(1 to 500),max_depth(1 to 10), andrate_limit_rps(0.1 to 10 requests per second) cap how much and how fast a crawl fetches.
See Crawling websites for the request fields.
Webhooks
Job completion webhooks are delivered to a URL configured for your account
by eCourtDate and signed with a secret configured at the same time. Verify
the X-ECD-Signature header over the raw request body before trusting a
delivery, and deduplicate on the delivery id; see
Jobs and webhooks for the scheme and
verification code. A webhook URL must be a public host, like a crawl seed;
a URL that resolves to a private address receives no deliveries. If no
secret is configured, deliveries are unsigned: ask eCourtDate to set one
before relying on webhooks.
Request ids for support
Every response from an API operation carries an X-Request-ID header
(CORS preflight responses are outside the API contract and carry none). It
is recorded with the request's usage entry and, for ingestion and crawls,
carried into the background job's logs. When you contact eCourtDate about a
specific request, include:
- the
X-Request-IDvalue (or the value you sent, if it was echoed); - the endpoint, the HTTP status, and the
codefrom the error body; - the key id (
ecd_sk_plus the next 16 characters), never the full key.
Send your own correlation id in X-Request-ID on every call so the value
is already in your logs (Conventions).
Reporting
To report a suspected vulnerability or a suspected key compromise, contact eCourtDate through your support channel. For a compromised key, ask for it to be revoked immediately and for a replacement with the same scopes (Key lifecycle).