Skip to main content

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 /v1 request 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 (chat for answering, ingest for 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 Authorization header. 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: 404 not_found for conversations, documents, and jobs, and 404 model_not_found for 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

DataStoredNotes
Uploaded filesYes, the original bytes, under your accountIdentified by a SHA-256 hash and the sanitized filename
Extracted textYes, split into chunksWhat retrieval searches and what citations quote (text_preview is the first 200 characters of a chunk)
Text embeddings of chunksYesOne vector per chunk, used for semantic search
Document metadataYesFilename, namespace, source type, source URL, status, chunk count, timestamps
Crawled pagesYesThe extracted text as chunks and embeddings, plus an archived copy of each page's raw HTML, removed with the document
Ingest and crawl jobsYesStatus, counts, and sanitized per-document or per-page errors
ConversationsYes, until deletedEvery user and assistant message, each assistant message's citations, the bound model, metadata, and timestamps
Stateless chat completionsNo message contentOnly a usage record per request: request id, endpoint, method, status, key id, bot slug, token counts, and latency
Embeddings requestsNo input contentA usage record with the token count
Webhook configurationYesThe 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_error or upstream_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 with 400. 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 http or https, 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 with 400 and param: 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_domains is an exact host match. An explicit port is part of the match, except that the scheme's default port (:443 for https, :80 for http) 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_txt is true, which is the default.
  • Bounded. max_pages (1 to 500), max_depth (1 to 10), and rate_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-ID value (or the value you sent, if it was echoed);
  • the endpoint, the HTTP status, and the code from 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).