REST API

ScanSing API
reference

Send a PDF or a photo of printed sheet music and get MusicXML back, the format MuseScore, Sibelius, Finale and Dorico open. The same recognition can then tell you what is in the score, give you one part, transpose it or make a MIDI file. Everything is one HTTPS address and an API key.

Using Claude, ChatGPT or another assistant? The MCP connector calls this API for you.

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
Base address
https://api.scansing.app

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 except GET /health sends Authorization: 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

RequestWhat it doesPages
POST /v1/recognitionsRecognise 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}/musicxmlThe MusicXML file.Free
GET /v1/recognitions/{id}/files/{name}One file of the result, by a name from files.Free
GET /v1/recognitions/{id}/zipEvery file of the result in one archive.Free
GET /v1/recognitions/{id}/describeTitle, composer, key, time, tempo, and per part its range, clefs and lyrics.Free
GET /v1/recognitions/{id}/exportMusicXML or MIDI, of some parts, in another key.Free
POST /v1/recognitions/{id}/linksA signed link to such an export that opens without a key.Free
POST /v1/inspectThe kind and page count of a file, without recognising it.Free
GET /v1/usagePages used and left this month.Free
GET /healthIs 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:

ParameterValues
responsejson (default): the summary below. musicxml: the MusicXML file itself. zip: every file of the result.
pagesOnly these pages of a PDF, 3 or 3-5. Page numbers in the result stay the document's.
modemerged (default): one MusicXML for the whole file. collection: a songbook split into one MusicXML per piece, with a manifest.json listing them.
waitSeconds 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) or midi. 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 as G, Bb, F#m or E minor. With key alone the nearer direction is taken; with both, transpose picks the octave. Notes are respelled for the new key and chord symbols move too.
  • file: in collection mode, which piece, e.g. 003.musicxml.
  • /links answers {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"}}:

StatusCodeMeaning
401unauthorizedNo key, or the key is not valid.
402quota_exceededNo pages left this month.
404not_found, link_expiredNo such recognition (or it has been deleted), or the link has expired.
409not_ready, not_availableThe recognition is still running, or it failed.
413file_too_largeOver 30 MB.
415unsupported_media_typeNot a PDF or a supported image.
422bad_request, invalid_inputA parameter is wrong, or the file cannot be read.
429too_many_requestsToo many requests; wait the seconds in Retry-After.
503queue_fullBusy; 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.

Questions, or more pages?

Write to us and we will answer.