Documents Endpoint
Extract text from PDFs and images, summarize documents, and answer questions about them. Native PDF text extraction is free — no LLM cost.
Point at a PDF or image and get back clean text, a summary, or an answer to your question. Text-based PDFs use native extraction — no LLM call, no token cost. Image inputs and image-based (scanned) PDF pages route through Vision OCR.
Endpoints
Basic Request
// From a URL — text-based PDF, native extraction, no LLM cost
const { text, pageCount, method } = await cencori.documents.extract({
document: { url: 'https://example.com/contract.pdf' },
});
console.log(method); // 'pdf_text' — free
console.log(pageCount, text.length);From a local file (Node.js)
import fs from 'node:fs';
const buf = fs.readFileSync('quarterly-report.pdf');
const { summary } = await cencori.documents.summarize({
document: { base64: buf.toString('base64'), mimeType: 'application/pdf' },
});Answer a question
const { answer } = await cencori.documents.query({
document: { url: 'https://example.com/contract.pdf' },
question: 'What is the termination clause?',
});
// If the answer isn't in the document, the response is
// "Not found in the document." — no invented text.Request Parameters
Response Shapes
/extract
{
text: "The full text of the document...",
pageCount: 12,
kind: "pdf", // or "image"
method: "pdf_text", // or "vision_ocr"
metadata: { info: {...}, pagesWithText: 12 },
// usage + cost are only present when Vision OCR ran
}/summarize
{
summary: "The document outlines a licensing agreement...",
text: "…full extracted text…",
pageCount: 12,
model: "gpt-4o-mini",
provider: "openai",
extractMethod: "pdf_text",
usage: { promptTokens, completionTokens, totalTokens },
cost: { providerCostUsd, cencoriChargeUsd, markupPercentage }
}/query
{
answer: "The termination clause is in Section 8...",
question: "What is the termination clause?",
pageCount: 12,
model: "gpt-4o-mini",
provider: "openai",
extractMethod: "pdf_text",
usage: {...},
cost: {...}
}Supported Inputs
Max size: 25MB per document. Requests over the limit return document_too_large with the exact bytes.
The Free Path
If your PDF has embedded text (most contracts, exports from Word/Google Docs, generated reports), /extract uses pdf-parse and returns the text without ever calling an LLM. cost is omitted from the response and no tokens are billed.
method: "pdf_text" in the response tells you extraction was free. method: "vision_ocr" means Vision was invoked and tokens were counted.
Scanned PDFs
Scanned PDFs (image-only, no embedded text) return a 400:
{
"error": "scanned_pdf_not_yet_supported",
"message": "This PDF appears to be scanned. Rasterize the pages to images and use /api/ai/vision, or split into per-page PNGs and POST via /api/ai/documents/extract as an image.",
"pageCount": 8
}Rasterize the pages to PNGs client-side and either:
- POST each page image to
/api/ai/documents/extractand concatenate the text, or - POST all pages together to
/api/ai/visionwith theimages: [...]multi-image field for a single unified analysis.
Multipart file upload
Both endpoints accept multipart/form-data:
curl -X POST https://cencori.com/api/ai/documents/query \
-H "CENCORI_API_KEY: csk_..." \
-F "file=@contract.pdf" \
-F "question=What is the total contract value?"In other SDKs
Python
from cencori import Cencori
cencori = Cencori()
result = cencori.documents.extract(
document_url="https://example.com/contract.pdf",
)
print(result["text"])
answer = cencori.documents.query(
document_url="https://example.com/contract.pdf",
question="What is the termination clause?",
)
print(answer["answer"])Async variants: a_extract(), a_summarize(), a_query().
Go
result, _ := client.Documents.Extract(context.Background(), &cencori.DocumentParams{
DocumentURL: "https://example.com/contract.pdf",
})
fmt.Println(result.Text)
answer, _ := client.Documents.Query(context.Background(), &cencori.DocumentParams{
DocumentURL: "https://example.com/contract.pdf",
Question: "What is the total contract value?",
})
fmt.Println(answer.Answer)PHP
$result = $cencori->documents->extract([
'document_url' => 'https://example.com/contract.pdf',
]);
echo $result['text'];
$answer = $cencori->documents->query([
'document_url' => 'https://example.com/contract.pdf',
'question' => 'What is the total contract value?',
]);
echo $answer['answer'];Rust
use cencori::documents::DocumentParams;
use cencori::Cencori;
let cencori = Cencori::new(None, None, None)?;
let result = cencori
.documents
.extract(&DocumentParams::from_url("https://example.com/contract.pdf"))?;
println!("{}", result.text);
let answer = cencori
.documents
.query(
&DocumentParams::from_url("https://example.com/contract.pdf")
.with_question("What is the termination clause?"),
)?;
println!("{}", answer.answer);Errors
Where to go next
- Build a Contract Analyzer — end-to-end tutorial
- Vision endpoint — for standalone image analysis or multi-image workflows

