Overview
The extraction API reads a document and gives you the text as Markdown. You post a file, you poll for the result, and you get the Markdown back. Nothing is indexed, nothing is searchable, and the result is deleted within a day.
Use it when your own code needs the text of a file. Use the Knowledge API instead when you want the document to stay and to answer searches. The two use the same reader, and they differ in what happens after the read.
Base URL
Section titled “Base URL”https://chat.app.urai.dev/api/v1Every path on this page is relative to that base URL.
Authenticate
Section titled “Authenticate”Send the same API key you use for the Chat Completions API.
Authorization: Bearer sk-urai-...The key sets the organization. An extraction belongs to that organization, and no other key can read it.
An organization admin can limit a key to some products. A key with no product
list reaches everything, and that is what every key made in the app has. A key
that names knowledge alone gets 403 and the code product_not_enabled here,
because extraction and knowledge are sold apart.
Extract a document
Section titled “Extract a document”Post the file as multipart/form-data. Send one file per request.
curl https://chat.app.urai.dev/api/v1/extractions \ -H "Authorization: Bearer $URAI_API_KEY" \ -F "file=@billing-policy.pdf"Urai replies with 202 and a record in the pending state.
{ "id": "8f0a2c31-4d7e-4f0a-9a6e-6b0d2f8c5411", "status": "pending", "name": "billing-policy.pdf", "mime_type": "application/pdf", "size_bytes": 184320, "chunk_count": 0, "created_at": "2026-08-14T18:45:53Z", "expires_at": "2026-08-15T18:45:53Z"}The request does not wait for the reader. A large PDF takes tens of seconds, and no HTTP client should hold a connection open for that.
Poll for the result
Section titled “Poll for the result”Read the record until status is ready or failed. One second between reads
is a good interval.
curl https://chat.app.urai.dev/api/v1/extractions/8f0a2c31-4d7e-4f0a-9a6e-6b0d2f8c5411 \ -H "Authorization: Bearer $URAI_API_KEY"{ "id": "8f0a2c31-4d7e-4f0a-9a6e-6b0d2f8c5411", "status": "ready", "name": "billing-policy.pdf", "mime_type": "application/pdf", "size_bytes": 184320, "page_count": 18, "chunk_count": 96, "markdown": "# Billing Policy 2026\n\nAnnual plans may be refunded...", "created_at": "2026-08-14T18:45:53Z", "expires_at": "2026-08-15T18:45:53Z"}markdown appears only when the status is ready. error appears only when the
status is failed. A field with no value is left out of the reply, so your code
reads the status first.
The whole loop in Python:
import time, requests
BASE = "https://chat.app.urai.dev/api/v1"headers = {"Authorization": f"Bearer {api_key}"}
with open("billing-policy.pdf", "rb") as f: created = requests.post( f"{BASE}/extractions", headers=headers, files={"file": f} ).json()
while created["status"] not in ("ready", "failed"): time.sleep(1) created = requests.get( f"{BASE}/extractions/{created['id']}", headers=headers ).json()
if created["status"] == "failed": raise RuntimeError(created["error"])
print(created["markdown"])What Urai reads
Section titled “What Urai reads”| Format | Extensions | Pages |
|---|---|---|
.pdf |
Real page numbers | |
| Word | .docx |
None |
| PowerPoint | .pptx |
One page for each slide |
| Markdown and plain text | .md, .markdown, .txt, .text |
None |
Urai reads the type from the file extension first, then from the content type.
Clients frequently send application/octet-stream for Office files, so a correct
extension matters more than a correct content type.
Word files and Markdown files have no pages. Word pagination belongs to the
program that prints the file, so page_count is absent for those formats rather
than wrong.
A scanned PDF has no text layer. Urai reads those pages with OCR. When a page
still yields no text, the extraction ends as failed, and error tells you that
the document needs OCR.
Any other file type is rejected with 415 before anything is stored.
The same file twice
Section titled “The same file twice”Urai compares the bytes of the upload. When the identical file was already
extracted and has not expired, the reply is 200 instead of 202 and it carries
the earlier result. Nothing is read again, and nothing is charged again.
Use this to retry safely. A repeated post after a network timeout returns the first result.
Results expire
Section titled “Results expire”Each extraction has an expires_at, 24 hours after it was created. At that time
Urai deletes the record, the original bytes, and the Markdown. The id then
returns 404.
This is an extraction service, not a document store. Save the Markdown in your
own system. To remove a result sooner, call DELETE /extractions/{id}.
What it costs
Section titled “What it costs”Extraction is charged for each page.
- PDF and PowerPoint are charged for the pages the file reports.
- Word and Markdown have no pages, so Urai derives the count from the extracted text at 3,000 characters for one page.
- Every document counts as one page at least.
The charge appears in Usage under the kind document_extraction. A result
that came from the duplicate check is not charged, because nothing was read.
Where extractions do not appear
Section titled “Where extractions do not appear”An extraction is not a knowledge document.
- It does not appear in Knowledge in the app, and it is in no collection you can name.
- The Knowledge API reports its id as absent, even to the key that created it.
search_documentsandread_documentnever reach it.- The
knowledge.collectionsfield of a completion cannot name it.
To make a document searchable, upload it to a collection instead. See Search knowledge collections.