Skip to content

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.

https://chat.app.urai.dev/api/v1

Every path on this page is relative to that base URL.

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.

Post the file as multipart/form-data. Send one file per request.

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

Read the record until status is ready or failed. One second between reads is a good interval.

Terminal window
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"])
Format Extensions Pages
PDF .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.

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.

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}.

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.

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_documents and read_document never reach it.
  • The knowledge.collections field of a completion cannot name it.

To make a document searchable, upload it to a collection instead. See Search knowledge collections.