Collections and documents
A collection is a named bucket of documents. Search runs over one collection, over several, or over all of them. Group documents the way your product needs to scope a search, for example one collection for each product line.
Manage collections
Section titled “Manage collections”# Createcurl https://chat.app.urai.dev/api/v1/knowledge/collections \ -H "Authorization: Bearer $URAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Billing policies", "description": "Refunds, plans, invoices" }'
# Listcurl "https://chat.app.urai.dev/api/v1/knowledge/collections?limit=20" \ -H "Authorization: Bearer $URAI_API_KEY"
# Read onecurl https://chat.app.urai.dev/api/v1/knowledge/collections/billing-policies \ -H "Authorization: Bearer $URAI_API_KEY"
# Rename. The slug does not change.curl -X PATCH https://chat.app.urai.dev/api/v1/knowledge/collections/billing-policies \ -H "Authorization: Bearer $URAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Billing and invoicing" }'
# Delete the collection, its documents, and their passagescurl -X DELETE https://chat.app.urai.dev/api/v1/knowledge/collections/billing-policies \ -H "Authorization: Bearer $URAI_API_KEY"name is required on create. A name that is already taken fails with 400 and
the code collection_exists. A name with no letters and no digits, such as
"***", fails with invalid_name, because Urai cannot make a slug from it.
Delete removes everything in the collection and cannot be undone.
The counts lag
Section titled “The counts lag”The documents object on a collection comes from a summary that Urai refreshes
on a timer. A document that finished a moment ago may not be counted yet.
"documents": { "ready": 12, "pending": 1, "failed": 0, "total": 13, "bytes": 8419203 }pending covers pending and processing, because both mean the same thing from
outside: not searchable yet. bytes is the size of the ready documents.
For a current answer, read the document list or one document. Both read live rows.
Push a file
Section titled “Push a file”The body is multipart/form-data. The first part with a filename is the
document. A part named tags adds one tag, and you can repeat it.
curl https://chat.app.urai.dev/api/v1/knowledge/collections/billing-policies/documents \ -H "Authorization: Bearer $URAI_API_KEY" \ -F "file=@billing-policy-2026.pdf" \ -F "tags=policy" \ -F "tags=2026"Urai replies with 202 and a pending document.
{ "id": "3f2a7c19-...", "collection_id": "9b1c8d2e-...", "name": "billing-policy-2026.pdf", "mime_type": "application/pdf", "size_bytes": 184320, "status": "pending", "chunk_count": 0, "tags": ["policy", "2026"], "source": "api", "created_at": "2026-08-15T09:14:02Z", "updated_at": "2026-08-15T09:14:02Z"}Send one file for each request. A second file in the same request fails with
400 and the code too_many_files.
The same file twice
Section titled “The same file twice”Urai compares the bytes. When the identical file is already in that collection
and ready, the reply is 200 with the document that is there, and nothing is
read or charged again. A retry after a network timeout is therefore safe.
The check is per collection. The same file in two collections is two documents, and each one is read and charged.
Push text
Section titled “Push text”Some content never was a file. Send it as Markdown or plain text.
curl https://chat.app.urai.dev/api/v1/knowledge/collections/billing-policies/documents/text \ -H "Authorization: Bearer $URAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Refund window", "content": "# Refunds\n\nAnnual plans may be refunded in full within 30 days...", "tags": ["policy"] }'The reply is 202 and a document, and it goes through the same pipeline as a
file. name and content are both required. Urai adds .md to the name when
it has no such ending, so the format is unambiguous later.
Use this for records from your own database, for pages from a wiki, or for notes your application writes.
Text is never treated as a duplicate. Post the same text twice, and you get two documents, because an edited document that happens to be identical is still something the caller meant to send. Delete the earlier document when you replace it.
Follow a document
Section titled “Follow a document”curl https://chat.app.urai.dev/api/v1/knowledge/documents/3f2a7c19-... \ -H "Authorization: Bearer $URAI_API_KEY"| Status | Meaning |
|---|---|
pending |
Accepted and queued. |
processing |
Urai is reading and indexing it. |
ready |
Terminal. The document answers searches. |
failed |
Terminal. error says what went wrong. |
A ready document carries chunk_count, which is the number of passages search
can return, and indexed_at, which is when it became searchable. page_count
appears for PDF and PowerPoint.
One second between polls is a good interval.
List the documents in a collection
Section titled “List the documents in a collection”curl "https://chat.app.urai.dev/api/v1/knowledge/collections/billing-policies/documents?status=failed&limit=100" \ -H "Authorization: Bearer $URAI_API_KEY"status takes pending, processing, ready, or failed. Any other value
fails with 400 and the code invalid_status, and the message lists what is
valid.
Use the failed filter to find the documents that need attention after a bulk
import.
Read a document back
Section titled “Read a document back”| Path | Returns |
|---|---|
GET /documents/{id}/text |
The extracted Markdown, as text/markdown. |
GET /documents/{id}/content |
The original bytes, with the type they were stored with. |
The Markdown is the same artifact the extraction API returns. You do not pay for a second read to get it.
Ask for the text before the document is ready, and the request fails with 400
and the code not_ready.
Read it again after a change
Section titled “Read it again after a change”POST /documents/{id}/reprocess splits the document into passages again and
indexes them again. Use it after a failure that was not the document’s fault.
curl -X POST https://chat.app.urai.dev/api/v1/knowledge/documents/3f2a7c19-.../reprocess \ -H "Authorization: Bearer $URAI_API_KEY"The default reuses the earlier read of the file, so nothing is charged for reading it a second time.
Add ?reextract=true to read the original bytes again. That is what you want
when a document failed for lack of OCR, or when it was read by an older version
of the reader. It is charged like a new document.
The reply is 202 and the document. Poll it as before. A document that is
already processing fails with 400 and the code already_processing.
Delete a document
Section titled “Delete a document”curl -X DELETE https://chat.app.urai.dev/api/v1/knowledge/documents/3f2a7c19-... \ -H "Authorization: Bearer $URAI_API_KEY"Urai returns 204. The passages leave the index first, then the row and the
stored bytes go. A deleted document stops appearing in search results.