Parse a document
Get the whole document back as structured chunks. Start here. Your first call takes about five minutes.
Extract specific fields
Hand us a JSON schema and get back just those fields, with a citation for every value.
Process documents in bulk
Upload thousands of files, submit one batch, poll one endpoint.
Path rename (compatibility). Sync parse is
POST /v1/parse and POST /v1/parse/file. The previous paths POST /v1/extract and POST /v1/extract/file still work and return the same responses; prefer /v1/parse for new integrations.Quickstart
1
Create an API key
Sign up and create a key at hanji.dev/dashboard. The Free plan includes 1,000 credits (one parsed page = 1 credit), enough to parse a few hundred real documents. The key is shown once on creation, so store it somewhere safe.
2
Parse your first document
Pick the path that matches where your document lives. Pass a
url if it’s already reachable over HTTP (an S3 presigned URL, a public doc, a CDN). Upload the file directly if you have the bytes in hand (agent output, local file, webhook payload).- By URL
- By upload
3
Read the response
You’ll get back a That’s it. You’ve parsed your first document. The rest of this page covers the response in detail, the request options, and the operational stuff (billing, limits, errors).
chunks array, the whole document in reading order:Understanding the response
Every chunk is one of three types:text: a short run of text in one style. Not a full paragraph; a paragraph usually splits into several text chunks.table: a table.cellsis the structured representation (0-basedrow/col, withrow_span/col_spanfor merged cells), andpage_contentcarries a markdown rendering so plain-text consumers still get readable output — requesttable_output_format: "html"to get that same table as HTML instead. By default, cellbboxvalues on scanned tables share the whole table’s box; requesttable_output_format: "cell_grid"to get a true box per cell.image: a figure extracted from the page, delivered as a URL or inline base64.
bbox:[x0, y0, x1, y1]in PDF points, so you can highlight the source region on the page.page_no: the 1-based page the chunk came from.confidence:0to1for OCR’d content, scored on the hardest-to-read character of the chunk, so one shaky digit lowers the whole score.nullfor text read from the document’s own text layer, where recognition isn’t a factor. Route low-confidence chunks to review.
Office documents (DOCX / PPTX)
A.docx or .pptx is converted to PDF before parse, and every bbox is in that PDF’s point space. The original Word/PowerPoint file does not paginate the same way, so drawing those boxes on your own render of the .docx will miss. For these inputs the response also includes pdf_rendition_url: an expiring link to the converted PDF. Download it onto your own storage and overlay bbox + page_no exactly as you would for a native PDF. The field is omitted (not null) for PDFs and images — you already have the pages.
Type-specific fields:
cells/n_rows/n_cols: populated on table chunks. Each cell is{ text, row, col, row_span, col_span, bbox, confidence }.image_url/image_mime: populated on image chunks. Some accounts receive the image inline asimage_b64instead.
The whole document as one string
Setinclude_content: true and the response carries one extra field alongside chunks: content — the entire parsed document as a single string, concatenated in reading order. Text flows as paragraphs, tables render as markdown tables in place, and figures are omitted. It is the document’s own text end to end, not reformatted with markdown headings or page markers. Use it when you want to hand the whole document to an LLM in one shot rather than iterate over chunks. The default response is unchanged.
Request options
For the URL route (POST /v1/parse), url is required and everything else is optional. For the upload route (POST /v1/parse/file), file is required and the remaining fields arrive as individual form fields instead of JSON.
Schema extraction (
POST /v1/extract/schema) has its own options: schema, strict, auto_schema, and include_ocr_text (the include_content of that route). See the Schema extraction guide. Batches take the same parsing options as sync, set once per batch (except include_content, which is sync-only); see Batch options.
Supported input formats: PDF, PPTX, DOCX, and images (PNG, JPEG, WebP, TIFF, HEIC/HEIF, and BMP). An image is treated as a one-page document, and the response looks the same as for a PDF. Animated GIF and animated WebP are rejected with 400.
Chunking
Setchunking: "semantic" and the response carries two extra fields alongside the unchanged chunks array: segments and page_dimensions.
Each segment is a group of chunks sized toward chunk_size characters, ready to embed, split at natural boundaries (headings, figures with their captions, page breaks). content is markdown. Tables come as markdown tables; images are not included. A table too large for one segment is split at row boundaries, and every part remains a valid table (table_part records the rows each part covers).
content: the segment’s text, formatted as markdown. Use this field to pass the text to an embedding model.char_count: the number of characters incontent.pages: the 1-based page numbers the segment spans.chunks: the elements that make up the segment. Each includes itschunk_type,page_no, andbbox.source_index: the element’s index in the response’schunksarray, where the full element lives.page_dimensions: the width and height of each page, in the same units asbbox.
Pricing and credits
Billing is in credits, at $3 per 1,000 credits ($0.003 per credit). Each page you process costs the operation’s credit rate:- Parsing (
/v1/parse,/v1/parse/file, and the batch lane): 1 credit per page — $3 per 1,000 pages. - Schema extraction (
/v1/extract/schema): 4 credits per page — $12 per 1,000 pages, all-in. The 4 credits include the parse; it is not billed on top of the parsing rate.
usage object telling you exactly what the request cost:
pages is the source document’s page count, credits is the total charged, and credits_per_page is the rate applied. On requests that aren’t billed, the field is absent, not null.
What counts as a page depends on the input:
Limits and large documents
Two server-side limits apply to every document:- Max 2,000 pages per document. Larger documents fail with
413. - Max 150 MB per document. Larger downloads fail with
413.
chunks arrays; the page_no field lets you offset page numbers across splits. Need support for individual documents beyond 2,000 pages? Email hello@hanji.dev.
For bulk workloads (thousands of documents), don’t loop POST /v1/parse/file. Use the async batch endpoints instead: you upload each file once to a presigned URL, submit the whole set as one batch, and poll for completion. Same response schema, no per-doc HTTP round-trip overhead.
Authentication
Every request needs anX-API-KEY header. Keys are created and revoked from the dashboard. Each key:
- is bound to one customer account
- carries a quota expressed in credits (default 1,000 credits on the Free plan)
- goes down with each request by the document’s page count times the operation’s credit rate
/v1/parse, /v1/parse/file). Email hello@hanji.dev to set up a PHI account.
Data retention
What we keep, lane by lane:
We never train on customer data. Need custom retention terms (shorter windows, customer-managed encryption, dedicated regions)? Email hello@hanji.dev.
Errors
Errors come back as standard HTTP status codes with a JSON body. The short version:4xx means fix the request, 5xx means retry.
Async-batch endpoints (
/v1/files, /v1/batches) additionally return machine-readable error codes in the response body. See Batch errors for the full list, response shapes, and fixes.
Next steps
You’ve made your first call. From here:Extract specific fields
Skip the chunks entirely: define a JSON schema and get back just the fields you care about, each with a citation you can verify.
Go from one document to thousands
The async batch lane: presigned uploads, one submission, one polling loop. Same response schema as sync.