Quick start
curl -X POST "https://api.scansing.app/v1/recognitions?response=musicxml" \
-H "Authorization: Bearer YOUR_KEY" \
-F "file=@score.pdf" -o score.musicxml
That is the whole round trip: the request waits while the score is recognised and answers with the MusicXML file. About 10 seconds for one page, a few minutes for a long PDF.
Keys
- Base address:
https://api.scansing.app. Every request exceptGET /healthsendsAuthorization: Bearer YOUR_KEY. - Keys start with
omr_. You get one after payment or with 20 free pages for a new email, and you can make a new one on your account page. A new key replaces the old one. - A recognition is visible only to the account that made it. Someone else's id answers
404, as if it did not exist.
Endpoints
| Request | What it does | Pages |
|---|---|---|
POST /v1/recognitions | Recognise a file sent as multipart/form-data, field file. | Spends |
GET /v1/recognitions/{id} | Status and summary of a recognition. | Free |
GET /v1/recognitions/{id}/musicxml | The MusicXML file. | Free |
GET /v1/recognitions/{id}/files/{name} | One file of the result, by a name from files. | Free |
GET /v1/recognitions/{id}/zip | Every file of the result in one archive. | Free |
GET /v1/recognitions/{id}/describe | Title, composer, key, time, tempo, and per part its range, clefs and lyrics. | Free |
GET /v1/recognitions/{id}/export | MusicXML or MIDI, of some parts, in another key. | Free |
POST /v1/recognitions/{id}/links | A signed link to such an export that opens without a key. | Free |
POST /v1/inspect | The kind and page count of a file, without recognising it. | Free |
GET /v1/usage | Pages used and left this month. | Free |
GET /health | Is the service up. No key. | Free |
Recognising a score
curl -X POST "https://api.scansing.app/v1/recognitions" \
-H "Authorization: Bearer YOUR_KEY" -F "file=@score.pdf"
PDF, PNG, JPEG, HEIC (iPhone photos), TIFF and WebP, up to 30 MB and 40 pages. The type is read from the file's bytes, not its name. Query parameters:
| Parameter | Values |
|---|---|
response | json (default): the summary below. musicxml: the MusicXML file itself. zip: every file of the result. |
pages | Only these pages of a PDF, 3 or 3-5. Page numbers in the result stay the document's. |
mode | merged (default): one MusicXML for the whole file. collection: a songbook split into one MusicXML per piece, with a manifest.json listing them. |
wait | Seconds to wait for the result. 0 answers 202 at once; see below. |
The JSON answer:
{
"id": "rec_3f9c1a0b7d2e4c5a6b8e",
"status": "completed",
"createdAt": "2026-10-01T10:00:00.123Z",
"finishedAt": "2026-10-01T10:00:51.004Z",
"filename": "score.pdf",
"input": {"kind": "pdf", "bytes": 182733},
"processingTimeMs": 50812,
"pages": 4,
"parts": ["Soprano", "Alto", "Tenor", "Bass"],
"measures": 61,
"warnings": [],
"confidence": null,
"musicXmlUrl": "https://api.scansing.app/v1/recognitions/rec_3f9c…/musicxml",
"files": [{"name": "score.musicxml", "url": "…"}, …],
"zipUrl": "https://api.scansing.app/v1/recognitions/rec_3f9c…/zip"
}
status is queued, processing, completed or failed. A failed recognition carries error: {code, message} and spends no pages. confidence is always null for now; the field is there so clients need not change when it is filled in.
Waiting or polling
By default the request stays open until the recognition is done and answers 200. With ?wait=0 it answers 202 at once with status: "processing" and a Location header; poll GET /v1/recognitions/{id} until the status is completed or failed. If a long recognition outlasts the wait, the answer is also 202.
After recognition
These work on the stored result. Nothing is recognised again and no pages are spent.
curl -H "Authorization: Bearer YOUR_KEY" \
"https://api.scansing.app/v1/recognitions/$ID/describe"
curl -H "Authorization: Bearer YOUR_KEY" -OJ \
"https://api.scansing.app/v1/recognitions/$ID/export?part=Alto&key=G"
curl -H "Authorization: Bearer YOUR_KEY" -OJ \
"https://api.scansing.app/v1/recognitions/$ID/export?format=midi&transpose=-2"
curl -H "Authorization: Bearer YOUR_KEY" -X POST \
"https://api.scansing.app/v1/recognitions/$ID/links?format=midi"
format:musicxml(default) ormidi. MIDI has one track per part and the score's tempo; repeats are not unfolded.part: a part's id (P2), name (Alto, any case) or number (2). Several are separated by commas.transpose: semitones, −24 to 24.key: the key to land in, such asG,Bb,F#morE minor. Withkeyalone the nearer direction is taken; with both,transposepicks the octave. Notes are respelled for the new key and chord symbols move too.file: incollectionmode, which piece, e.g.003.musicxml./linksanswers{url, format, expiresAt, filename}. Anyone with the URL can download the file until it expires, at most as long as the result lives;ttl(seconds) shortens it.
Pages and usage
curl -X POST "https://api.scansing.app/v1/inspect" \
-H "Authorization: Bearer YOUR_KEY" -F "file=@songbook.pdf"
{"kind": "pdf", "bytes": 4210331, "pages": 96}
curl -H "Authorization: Bearer YOUR_KEY" "https://api.scansing.app/v1/usage"
{"month": "2026-10", "pagesUsed": 42, "pagesLimit": 300, "pagesLeft": 258, "plan": "starter", …}
- Each recognised page counts once against the month. Failed recognitions do not count. Unused pages do not carry over.
- Free: 20 pages once, for a new email. Starter: 300 pages a month for $9. Pro: 1,500 pages a month for $29. See pricing.
- Results are kept for about an hour, then deleted. Save what you want to keep.
Errors
A request that cannot be served answers an HTTP error with a JSON body {"error": {"code", "message"}}:
| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized | No key, or the key is not valid. |
| 402 | quota_exceeded | No pages left this month. |
| 404 | not_found, link_expired | No such recognition (or it has been deleted), or the link has expired. |
| 409 | not_ready, not_available | The recognition is still running, or it failed. |
| 413 | file_too_large | Over 30 MB. |
| 415 | unsupported_media_type | Not a PDF or a supported image. |
| 422 | bad_request, invalid_input | A parameter is wrong, or the file cannot be read. |
| 429 | too_many_requests | Too many requests; wait the seconds in Retry-After. |
| 503 | queue_full | Busy; retry after Retry-After. |
A recognition that fails has status: "failed" and one of too_many_pages, invalid_input, engine_failed, timeout.
Your data
ScanSing keeps the file and its result only as long as described above and does not use scores to train models. The details are in the Privacy Policy.
Support
Write to hello@scansing.app.

