Skip to content

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.

Terminal window
# Create
curl 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" }'
# List
curl "https://chat.app.urai.dev/api/v1/knowledge/collections?limit=20" \
-H "Authorization: Bearer $URAI_API_KEY"
# Read one
curl 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 passages
curl -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 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.

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.

Terminal window
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.

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.

Some content never was a file. Send it as Markdown or plain text.

Terminal window
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.

Terminal window
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.

Terminal window
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.

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.

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.

Terminal window
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.

Terminal window
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.