Developers
The Handwrite API
Your documents, sharing and spoken audio, from your own code.
Last updated
Getting started
The Handwrite API lets your own code work with your Handwrite documents: list and sync them, create and edit them, upload original files, share them with other people, and turn them into spoken audio. Requests go to https://handwrites.io/api/v1 and send and receive JSON unless noted.
- Sign in and open Account › API keys.
- Create a key, choose what it may do, and copy it. It's shown once.
- Send it as a bearer token with every request.
curl https://handwrites.io/api/v1/me \
-H "Authorization: Bearer $HANDWRITE_API_KEY"Keys look like hw_ab12cd34ef56_…. Keep them on a server or in a secrets manager. Never put a key in an app, a web page or a public repository. If one leaks, revoke it on the same page; it stops working at once.
Key permissions
Every key acts as your account and can only do what its scopes allow. You can change a key's scopes, or revoke it, at any time. A request without the scope it needs gets 403 insufficient_scope.
documents:read: List, fetch and download documents, including ones shared with you.documents:write: Create documents, edit them and upload their files.documents:delete: Delete documents you own.permissions:read: See who a document is shared with.permissions:write: Share documents and change or remove access.audio:read: Stream and fetch spoken audio (uses your monthly characters).voices:read: List voices and see your character allowance.
Errors and limits
Errors use one shape, with a stable code and a message written for developers:
{ "error": { "code": "not_found", "message": "Document not found." } }400 invalid_request: the body or a parameter is wrong; the message names the field.401 unauthorized: no key, or it's invalid, revoked or expired.403: the key lacks a scope, or you can view a document but not change it.404 not_found: it doesn't exist, or you have no access to it.409 conflictand412 revision_mismatch: someone changed the document first.429:rate_limited(slow down) orquota_exceeded(no characters left); wait forRetry-Afterseconds.
Each key can make bursts of about 120 requests, then 10 a second. Every response has a Request-Id header; include it if you contact support.
Documents
A document has a title, text (plain text or Markdown, up to 4 MB), an optional original file, and a revision that goes up with every change. Send the revision back in If-Match when you edit, so you never overwrite a change made on another device.
GET /documents
Scope: documents:read
Your documents, newest first. Add ?shared=true for documents others shared with you. Page with limit (1–100) and the returned cursor.
To sync, pass ?since= with the time of your last sync. You get every change after it, oldest first, including deleted documents as { "id", "deleted": true } tombstones (kept 30 days).
POST /documents
Scope: documents:write
curl https://handwrites.io/api/v1/documents \
-H "Authorization: Bearer $HANDWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Field notes","content":"The heron came back today.","language":"en"}'GET /documents/:id
Scope: documents:read
The document's details, including your access (owner, editor or viewer).
PATCH /documents/:id
Scope: documents:write
Change any of title, content, contentType and language.
curl -X PATCH https://handwrites.io/api/v1/documents/doc_… \
-H "Authorization: Bearer $HANDWRITE_API_KEY" \
-H "Content-Type: application/json" -H 'If-Match: "3"' \
-d '{"title":"Field notes, week 2"}'DELETE /documents/:id
Scope: documents:delete
Owners only. The text, file, shares and cached audio are removed at once.
GET /documents/:id/content
Scope: documents:read
The text itself. PUT the same path with a text/plain or text/markdown body to replace it (scope documents:write).
GET /export
Scope: documents:read
Every document you own, with its text, as one JSON file.
Uploading files
Attach the original of a document, such as a PDF, a photo of a page or a Word file, up to 50 MB. Files go straight to storage, in three steps:
# 1. Announce the file (scope documents:write)
curl https://handwrites.io/api/v1/documents/doc_…/file -H "Authorization: Bearer $HANDWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"page-1.jpg","type":"image/jpeg","length":482113}'
# -> { "uploadId": "…", "url": "https://…", "headers": { … } }
# 2. PUT the bytes to that URL with those headers (no API key; valid 15 minutes)
curl -X PUT "$URL" -H "Content-Type: image/jpeg" --data-binary @page-1.jpg
# 3. Commit it
curl https://handwrites.io/api/v1/documents/doc_…/file/commit -H "Authorization: Bearer $HANDWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"uploadId":"…","name":"page-1.jpg","type":"image/jpeg","length":482113}'GET /documents/:id/file redirects to a download link that lasts 5 minutes. Add ?redirect=false to get the link as JSON.
Audio
Audio uses Handwrite's natural voices and counts against your account's monthly characters, the same as listening in the app (every account includes 2,000 a month). If a request needs more characters than you have left, it fails before any audio is made.
POST /audio/stream
Scope: audio:read
Streams speech while it's made, for text or a documentId, up to 12,000 characters per request. Handwrite-Next-Offset says where to continue; send it back as offset.
curl https://handwrites.io/api/v1/audio/stream -H "Authorization: Bearer $HANDWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"documentId":"doc_…","voice":"<voice id>","format":"mp3"}' --output part-1.mp3GET /documents/:id/audio?voice=…&format=mp3
Scope: audio:read
The whole document (up to 30,000 characters) as one file. The first request for a version and voice makes it; it's then kept 30 days and free to fetch for everyone with access. Redirects to a download link, or add ?redirect=false.
GET /voices
Scope: voices:read
The voices you can use, filtered by locale or model.
GET /quota
Scope: voices:read
Characters left this period, the limit, your plan and when it resets.
Your data
Documents you create through the API are stored in Handwrite Cloud, encrypted at rest, with titles, file names and sharing emails encrypted again field by field. Deleting your Handwrite account deletes them, their files, your shares and your keys. See the Privacy Policy.