Documents
A document is one unit of knowledge-base content: an uploaded file
or a crawled page. The documents endpoints let you
inspect what your knowledge base holds, check whether each document is
ready to be cited, and remove content.
All three operations require the ingest scope
and see only the documents of the account the key belongs to.
| Operation | Path |
|---|---|
| List documents | GET /v1/documents |
| Retrieve a document | GET /v1/documents/{documentId} |
| Delete a document | DELETE /v1/documents/{documentId} |
The document object
{
"id": "17136f01-972d-4956-868e-8159833072e4",
"object": "document",
"namespace": "general",
"filename": "traffic-division-faq.pdf",
"source_type": "upload",
"source_url": null,
"status": "ready",
"chunk_count": 18,
"error": null,
"created_at": "2026-08-21T14:03:11.214000Z",
"updated_at": "2026-08-21T14:03:42.908000Z"
}
| Field | Type | Description |
|---|---|---|
id | string | UUID of the document. Returned as document_ids[] by the upload call and as document_id in job errors and citations. |
object | string | Always document. |
namespace | string | The knowledge-base namespace the document belongs to. |
filename | string | For uploads, the sanitized filename (Filenames). For crawled pages, the final page URL. |
source_type | string | upload or crawl. |
source_url | string or null | The final page URL for crawled pages; null for uploads. |
status | string | Processing state; see Status values. |
chunk_count | integer | Number of indexed chunks. Greater than 0 only when status is ready; otherwise 0. |
error | string or null | The sanitized failure reason when status is failed (the same text as the job's errors[] entry); null otherwise. |
created_at | string | RFC 3339 UTC timestamp (Z designator) of when the document was created; millisecond precision rendered with six fractional digits (Timestamps). Never changes, and every document from one upload request shares the same value. |
updated_at | string | RFC 3339 UTC timestamp, same form, of the last status transition. |
Status values
| Status | Meaning |
|---|---|
processing | Created and waiting for, or undergoing, text extraction and indexing. This also covers the time between automatic retries after a transient failure. chunk_count is 0. |
ready | Indexed. chunk_count is greater than 0 and the document can be cited. |
failed | Permanently failed; error says why and chunk_count is 0. Delete it and upload a corrected file or re-crawl. |
A document's status changes only through its job. Poll the job (or wait for its webhook) rather than polling individual documents.
List documents
curl -s "https://api.chatbots.ecourtdate.com/v1/documents?namespace=general&status=ready&limit=2" \
-H "Authorization: Bearer $API_KEY"
{
"object": "list",
"data": [
{
"id": "9a7e5d21-53c4-4b8a-a0e1-2c7f3b4d5e6f",
"object": "document",
"namespace": "general",
"filename": "jury-duty-faq.md",
"source_type": "upload",
"source_url": null,
"status": "ready",
"chunk_count": 5,
"error": null,
"created_at": "2026-08-21T14:03:11.214000Z",
"updated_at": "2026-08-21T14:03:16.030000Z"
},
{
"id": "17136f01-972d-4956-868e-8159833072e4",
"object": "document",
"namespace": "general",
"filename": "traffic-division-faq.pdf",
"source_type": "upload",
"source_url": null,
"status": "ready",
"chunk_count": 18,
"error": null,
"created_at": "2026-08-21T14:03:11.214000Z",
"updated_at": "2026-08-21T14:03:42.908000Z"
}
],
"first_id": "9a7e5d21-53c4-4b8a-a0e1-2c7f3b4d5e6f",
"last_id": "17136f01-972d-4956-868e-8159833072e4",
"has_more": true
}
Documents are sorted by created_at, newest first (ties are broken by id,
descending, so the order is stable across pages). Documents from one upload
request share a created_at, so within that request they are ordered by
id, not by the order the files were sent, as the two documents above show.
Query parameters
| Parameter | Type | Description |
|---|---|---|
limit | integer | Page size, 1 to 1000. Default 100. |
after | string | Cursor: the last_id of the previous page. Returns the documents that follow it in the sort order. Must be the id of one of your documents. |
namespace | string | Only documents in this namespace (exact, case-sensitive match). An empty value is 400. |
status | string | Only documents in this status: processing, ready, or failed. |
source_type | string | Only documents of this origin: upload or crawl. |
Filters combine with AND. A limit outside 1 to 1000 (or not an integer), an
empty after, an after that is not one of your documents, an empty
namespace, or an unknown status or source_type value returns 400
with param naming the parameter:
{
"error": {
"message": "Unknown document id in `after`: '4b1f0c8e-2a77-4c0f-9d3e-6f2a1b9c7d10'.",
"type": "invalid_request_error",
"param": "after",
"code": null
}
}
Response
| Field | Type | Description |
|---|---|---|
object | string | Always list. |
data | object[] | Up to limit document objects. Empty when nothing matches. |
first_id | string or null | id of the first document in data; null when data is empty. |
last_id | string or null | id of the last document in data; null when data is empty. |
has_more | boolean | true when more documents follow the last one in data. |
Paging through every document
Pass the previous page's last_id as after until has_more is false.
The filters must stay the same across pages.
curl -s "https://api.chatbots.ecourtdate.com/v1/documents?namespace=general&limit=100&after=17136f01-972d-4956-868e-8159833072e4" \
-H "Authorization: Bearer $API_KEY"
import os
import requests
API_KEY = os.environ["API_KEY"]
BASE_URL = "https://api.chatbots.ecourtdate.com/v1"
def list_documents(**filters):
params = {"limit": 100, **filters}
while True:
r = requests.get(
f"{BASE_URL}/documents",
headers={"Authorization": f"Bearer {API_KEY}"},
params=params,
)
r.raise_for_status()
page = r.json()
yield from page["data"]
if not page["has_more"]:
return
params["after"] = page["last_id"]
failed = [d for d in list_documents(namespace="general", status="failed")]
for doc in failed:
print(doc["id"], doc["filename"], doc["error"])
Because the list is sorted newest first and paged by id, documents created while you are paging appear at the front and are not included in pages you have already fetched; start over to pick them up.
Retrieve a document
curl -s "https://api.chatbots.ecourtdate.com/v1/documents/17136f01-972d-4956-868e-8159833072e4" \
-H "Authorization: Bearer $API_KEY"
Returns the document object. An unknown id, an id
belonging to another account, or an already-deleted id all return the same
404 not_found:
{
"error": {
"message": "Document not found.",
"type": "invalid_request_error",
"param": null,
"code": "not_found"
}
}
Citations carry the document_id of the chunk they came from, so this
endpoint is the way to turn a citation into
the document's current filename, URL, and status.
Delete a document
curl -s -X DELETE "https://api.chatbots.ecourtdate.com/v1/documents/17136f01-972d-4956-868e-8159833072e4" \
-H "Authorization: Bearer $API_KEY"
{
"id": "17136f01-972d-4956-868e-8159833072e4",
"object": "document.deleted",
"deleted": true,
"chunks_deleted": 18
}
| Field | Type | Description |
|---|---|---|
id | string | The deleted document's id. |
object | string | Always document.deleted. |
deleted | boolean | Always true on a 200. |
chunks_deleted | integer | Number of indexed chunks removed. 0 for a document that was never indexed (processing or failed). |
Deletion is synchronous and irreversible. It removes, in order:
- The indexed chunks, so the document stops being retrieved and cited immediately.
- The stored copy: the uploaded file for an upload, or the archived page HTML for a crawled page.
- The document record, so the id returns
404from then on.
Deleting is not idempotent at the status level: a second DELETE on the
same id returns 404 not_found, which a client can safely treat as
"already gone".
Do not delete a document whose job is still processing. The delete
succeeds (with chunks_deleted: 0), but when the job reaches that document
it is counted as failed and recorded in the job's errors[] with the
message Document was deleted before it was processed. (no document
remains to carry the error). Wait for a terminal
job status first.
There is no bulk delete. To clear a namespace, page through it
and delete each document. Each delete counts against your per-minute request
budget (Rate limits), so honor Retry-After on 429.
def delete_namespace(namespace):
for doc in list(list_documents(namespace=namespace)):
r = requests.delete(
f"{BASE_URL}/documents/{doc['id']}",
headers={"Authorization": f"Bearer {API_KEY}"},
)
if r.status_code == 404:
continue # already deleted
r.raise_for_status()
Errors
| Status | Code | When |
|---|---|---|
400 | null | GET /v1/documents with a limit outside 1 to 1000, an empty or unknown after, an empty namespace, or an unknown status or source_type value; param names the parameter. |
401 | invalid_api_key | Missing or invalid key. |
403 | insufficient_scope | The key lacks the ingest scope. |
404 | not_found | GET or DELETE on an unknown, deleted, or other-account document id. |
429 | rate_limit_exceeded, insufficient_quota | Per-minute request limit or daily token quota reached; honor Retry-After. |
All errors use the standard envelope described in Errors.