Developer API
Add accurate handwriting conversion to your own product - math, physics, chemistry, and plain text all supported, with real typeset LaTeX, PDF, and DOCX output. Free to join, pay only for the pages you convert.
Why teams use it instead of building their own OCR
Generic OCR turns handwriting into plain text. Scriveno's pipeline keeps the actual structure - equations stay equations, hand-drawn diagrams and chemical structures are detected and included rather than discarded, and one transcription drives real typeset LaTeX, a compiled PDF, and an editable DOCX with genuine Word equations, not just three separate best-effort exports.
- Async submit / status / download, or a webhook when a job finishes
- HMAC-signed webhook payloads, with an idempotency key to make retries safe
- Output as PDF, DOCX, Markdown, or raw LaTeX from the same request
- Free to join - pay only $0.003 per page converted, no monthly minimum
Quick start
Submit a file, poll for status, then download - the same pipeline the web app uses.
Submit
curl -X POST https://api.scriveno.com/api/v1/convert \ -H "Authorization: Bearer isk_live_YOUR_API_KEY" \ -H "Idempotency-Key: <your-own-uuid>" \ -F "file=@notes.pdf" \ -F "webhookUrl=https://example.com/webhooks/scriveno"
Idempotency-Key is optional but recommended - retry the exact same request after a timeout or network error and you'll get the original job back instead of a duplicate submission.
Check status
curl https://api.scriveno.com/api/v1/convert/{jobId} \
-H "Authorization: Bearer isk_live_YOUR_API_KEY"Download
curl https://api.scriveno.com/api/v1/convert/{jobId}/download/pdf \
-H "Authorization: Bearer isk_live_YOUR_API_KEY" \
-o result.pdfAlso available: docx, markdown, and latex in place of pdf.
List your jobs
curl "https://api.scriveno.com/api/v1/convert?page=1&pageSize=20" \ -H "Authorization: Bearer isk_live_YOUR_API_KEY"
Cancel a job
curl -X POST https://api.scriveno.com/api/v1/convert/{jobId}/cancel \
-H "Authorization: Bearer isk_live_YOUR_API_KEY"Only works while the job is still queued or processing - refunds the entire page reservation, not a pro-rated remainder.
Full interactive docs: https://api.scriveno.com/swagger
Prefer a runnable starting point? Download the .NET quickstart - a single console app (no third-party packages) that does submit, poll, download, list, and cancel. Your API key, input file, formats, and download folder live in appsettings.json; confirm once and it submits, polls with a live status line, and downloads the result on its own.
Prefer Postman? Import the ready-made collection - every request above already set up, just paste your API key into the collection's apiKey variable.
Webhooks
Pass a webhookUrl when you submit and Scriveno POSTs this to it once the job reaches completed or failed - no need to poll if you don't want to.
POST <your webhookUrl>
Content-Type: application/json
X-Scriveno-Signature: sha256=<hex-encoded HMAC-SHA256 of the raw body below>
{
"jobId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "completed",
"totalPages": 3,
"completedPages": 3,
"errorMessage": null,
"downloads": {
"pdf": "https://api.scriveno.com/api/v1/convert/{jobId}/download/pdf",
"docx": "https://api.scriveno.com/api/v1/convert/{jobId}/download/docx",
"markdown": "https://api.scriveno.com/api/v1/convert/{jobId}/download/markdown",
"latex": "https://api.scriveno.com/api/v1/convert/{jobId}/download/latex"
}
}On failure, status is failed, errorMessage is set, and downloads is null instead of the object above.
Verify the signature before trusting a payload - anyone can POST to your endpoint. Compute an HMAC-SHA256 of the exact raw request body using your key's webhook secret (shown once, at key creation time), hex-encode it, and compare to everything after sha256= in the X-Scriveno-Signature header using a constant-time comparison.
// pseudocode
expected = hex(hmac_sha256(webhookSecret, rawRequestBody))
received = header["X-Scriveno-Signature"].removePrefix("sha256=")
if !constantTimeEquals(expected, received): rejectDelivery: up to 2 attempts total (1 retry, 2 seconds later) if your endpoint doesn't return a 2xx or times out after 10 seconds. If both attempts fail, nothing further is retried - poll GET /api/v1/convert/{jobId} as a fallback for a job you haven't seen a webhook for.