A clean API for clean Markdown.
One POST turns a PDF or image into LLM-ready Markdown and a
structured JSON payload. Sync for small docs, async with signed webhooks
for big ones — and zero document-retention on every call, EU-hosted.
API access is included on every paid plan (from €5.89/mo). New accounts get 100 free pages to try HexRead on the web first.
Sync & async
Small docs return Markdown inline (200). Large ones return a job (202) you poll, stream over SSE, or receive by webhook.
Built to script
Idempotency keys, stable error codes with a request_id, RateLimit-* headers, and an OpenAPI
3.0.3 contract you can codegen from.
Zero document retention
Your document streams through memory and is deleted right after conversion — no document backups, never training data.
Base URL & authentication
The API is a plain HTTPS + multipart service. Authenticate every request with a bearer API key.
https://api.hexread.com/v1Authorization: Bearer hr_live_…Mint a key from your account (or POST /v1/keys) — the full
secret is shown exactly once, so store it like a
password. Keys carry least-privilege scopes (convert, jobs:read, usage:read). API keys require a
paid plan; a missing or invalid key returns 401.
Quickstart
Sign in, mint a key, then pick your interface:
# Small docs return Markdown inline (200); large ones return a job (202).
curl -fsS https://api.hexread.com/v1/convert \
-H "Authorization: Bearer $HEXREAD_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F file=@report.pdf \
-F model=auto \
-F formats=md,json | jq -r .markdownnpm i @hexread/ts-clientimport { convertFile, withRetry } from '@hexread/ts-client';
const base = 'https://api.hexread.com/v1';
// backoff retry with a STABLE Idempotency-Key threaded per attempt, so a retry never double-bills;
// returns a sync result or an async job handle
const out = await withRetry((ctx) =>
convertFile(file, { model: 'auto', idempotencyKey: ctx.idempotencyKey }, base),
);
console.log(out.kind === 'sync' ? out.result.markdown : `job ${out.job.job_id}`);curl -fsSL https://hexread.com/install.sh | sh # cosign-verified
hexread login # device grant → OS keychain
hexread convert report.pdf -o report.md # sync/async auto; live progress
hexread batch "invoices/*.pdf" -o out/ --concurrency 4Convert options
fileThe PDF or image to convert (required).modelauto (default — routes by content), mineru-2.5, granite-docling, or paddleocr-vl.formatsComma-separated: md, json. Default md,json.prefersync or async — force the path regardless of document size.webhook · webhook_secretSet delivery=webhook to receive a signed callback when the job finishes.Idempotency-KeyHeader (≤255 chars) — safe retries; a replay returns Idempotency-Replayed: true.
Convert returns clean Markdown plus a JSON payload of per-page Markdown and conversion metadata (model, page count, checksum, timing) — not a raw layout tree.
Endpoints
Convert
- POST
/v1/convertUpload a PDF or image; get Markdown + JSON inline (small docs) or a job handle (large ones).
Jobs
- GET
/v1/jobs/{id}Async job status — metadata only, never document content. - GET
/v1/jobs/{id}/eventsServer-sent progress stream, page by page. - GET
/v1/jobs/{id}/resultRead-once result (also /markdown, /json) — purged the moment you fetch it. - DELETE
/v1/jobs/{id}Cancel a queued or in-flight job.
Keys
- GET
/v1/keysList your API keys (masked — the secret is never returned again). - POST
/v1/keysMint a key — the full secret is shown exactly once. - POST
/v1/keys/{id}/rotateRotate — old key revoked, new secret shown once. - DELETE
/v1/keys/{id}Revoke a key, effective immediately.
Usage & meta
- GET
/v1/usageCurrent-period pages + concurrency meters. No document data. - GET
/v1/versionService version and the selectable model registry. - GET
/v1/openapi.jsonThis API as a machine-readable OpenAPI 3.0.3 document.
The complete, machine-readable contract — every parameter, response, and
error code — is served at GET /v1/openapi.json (OpenAPI 3.0.3). Point your generator at it to build a client in any language.
Errors
Every non-2xx response is the same JSON envelope. Branch on the stable code (never the message), and log the request_id.
{
"type": "validation_error",
"code": "file_too_large",
"message": "The uploaded file exceeds the size limit for your plan.",
"request_id": "req_98fca6c87faace9b"
}Each code maps to a fixed HTTP status and type — e.g. quota_exceeded (402), rate_limited (429), file_too_large (413), unsupported_media_type (415). The request_id is also returned as the X-Request-Id header for support.
Rate limits, quota & headers
- Burst 120
- 500 pages / mo
- 2 concurrent conversions
- Up to 100 MB · 500 pages / doc
- Burst 240
- 3,000 pages / mo
- 3 concurrent conversions
- Up to 100 MB · 1,000 pages / doc
- Burst 480
- 10,000 pages / mo
- 5 concurrent conversions
- Up to 200 MB · 1,500 pages / doc
RateLimit-Limit / -Remaining / -ResetYour window: ceiling, calls left, seconds to reset (IETF draft).RateLimit-PolicyThe active policy, e.g. 120;w=120 (120 requests per 120 s).X-HexRead-Quota-Pages-RemainingWhole pages left in your monthly allowance.Retry-AfterSeconds to wait before retrying, sent on every 429.X-Request-IdEchoes the envelope request_id — quote it in support requests.
On 429, honour Retry-After and back off
exponentially (the SDK's withRetry does this for you). Need
higher limits or a pooled team allowance? Compare plans or talk to us about Custom.
Webhooks
For async jobs, set delivery=webhook with a webhook URL and a webhook_secret on POST /v1/convert.
When the job finishes, HexRead POSTs a signed job.completed (or job.failed) event; fetch the read-once result with its
one-time token.
import { verifyWebhookSignature } from '@hexread/ts-client';
// In your webhook route — verify against the RAW request bytes before trusting it:
const ok = await verifyWebhookSignature(
process.env.HEXREAD_WEBHOOK_SECRET, // your per-request webhook_secret
req.headers['x-hexread-signature'], // "t=<unix>,v1=<hmac-sha256>"
rawBody, // exact bytes received (not re-serialised JSON)
);
if (!ok) return res.status(400).end(); // forged, or timestamp skew > 5 min
const event = JSON.parse(rawBody); // { type: "job.completed", job_id, result_url }Signature header X-HexRead-Signature: t=<unix>,v1=<hmac> is HMAC-SHA256 over <t>.<raw-body> keyed by your
secret. Verify the raw bytes and reject a timestamp skew over 5 minutes to
stop replays.
Versioning & SDKs
Stable /v1
Versioned in the path. Changes are additive-only; anything breaking
ships as /v2. Pin to /v1 — there's no dated
version header to track.
TypeScript SDK
@hexread/ts-client — typed convert, auto-retry, and verifyWebhookSignature. Generated from the same spec,
so it never drifts.
CLI & any language
A cosign-verified Go hexread CLI (install.sh), or generate a client for your stack from /v1/openapi.json.
Build with HexRead.
Start free on the web in seconds, then add a paid plan to drop in the API when you're ready to automate.