Importing PDFs with OMR
Flat's Optical Music Recognition (OMR) turns a PDF of sheet music into a fully editable Flat score. You can trigger an OMR import directly from our REST API and follow its progress until the score is ready.
Unlike importing a MusicXML or MIDI file — which is synchronous — OMR processing takes some time. The API therefore handles it as an asynchronous task:
- You start the import with
POST /scores. The API replies immediately with a task. - You poll the task until it is done.
- Once done, you retrieve (and optionally export) the newly created score.
In this page:
- Prerequisites
- Step 1 — Start the import
- Step 2 — Poll the task
- Step 3 — Retrieve the imported score
- Error handling
- Usage & billing
- Related
Looking for the end-user OMR feature instead? See the OMR help center article and its changelog.
Prerequisites
- Authentication. A Personal Access Token works nicely to get started quickly. If you are building an OAuth2 app, request the
scoresscope (to create the score) and thetasks.readonlyscope (to poll the task). - File format. Only PDF (
application/pdf) files go through OMR. - Encoding. Binary PDF data must be Base64-encoded and sent in the
dataproperty, withdataEncodingset tobase64. - Opt in to tasks. You must set
supportsTasks: truein the request. Without it, a PDF import is rejected.
Step 1 — Start the import
Create the score with POST /scores, providing the Base64-encoded PDF and supportsTasks: true:
curl -X POST 'https://api.flat.io/v2/scores' \
-H 'Authorization: Bearer <my_api_personal_access_token>' \
-H 'Content-Type: application/json' \
-d '{
"title": "My imported score",
"filename": "my-sheet-music.pdf",
"data": "<base64-encoded-pdf>",
"dataEncoding": "base64",
"supportsTasks": true
}'Because the PDF requires OMR, the API responds with 202 Accepted and a task instead of the score:
{
"id": "5e0a59c0f1e6b8000868e0c1",
"type": "import-omr",
"state": "created",
"progress": {
"percent": 0
},
"creationDate": "2026-06-16T09:24:00.000Z"
}Keep the task id — you will use it to follow the import.
Branch on the HTTP status
MusicXML and MIDI imports are processed synchronously and still return 200 OK with the full score. A PDF import returns 202 Accepted with a task. Have your client branch on the status code: 200 → the score is ready, 202 → follow the task flow below.
Step 2 — Poll the task
Fetch the task with GET /tasks/{task} until it completes:
curl -H 'Authorization: Bearer <my_api_personal_access_token>' \
https://api.flat.io/v2/tasks/5e0a59c0f1e6b8000868e0c1The task moves through the following states:
state | Meaning |
|---|---|
created | The task is queued and waiting to be processed. |
doing | OMR processing is in progress (see progress.percent). |
done | The score has been imported — the score field is set. |
error | Processing failed — see result.error. |
The fields you care about while polling:
| Field | Description |
|---|---|
id | Unique identifier of the task. |
type | import-omr for an OMR import. |
state | Current state of the task (see table above). |
progress.percent | Progression of the task, from 0 to 100. |
score | The unique identifier of the imported score (set once state is done). |
result.error | A human-readable error message when state is error. |
A completed task looks like:
{
"id": "5e0a59c0f1e6b8000868e0c1",
"type": "import-omr",
"state": "done",
"score": "5e0a5b2cf1e6b8000868e0d4",
"revision": "5e0a5b2cf1e6b8000868e0d5",
"progress": {
"percent": 100
},
"creationDate": "2026-06-16T09:24:00.000Z",
"doneDate": "2026-06-16T09:25:12.000Z"
}Poll every few seconds rather than in a tight loop, and respect our rate limits.
Step 3 — Retrieve the imported score
Once the task is done, read the score field for the new score identifier, then fetch the full score details with GET /scores/{score}:
curl -H 'Authorization: Bearer <my_api_personal_access_token>' \
https://api.flat.io/v2/scores/5e0a5b2cf1e6b8000868e0d4From there, the score behaves like any other Flat score. You can also export it to other formats — for example MusicXML, MP3, or MIDI — using the score export endpoints. Exporting follows the same asynchronous task pattern as the import: create the export task with POST /scores/{score}/revisions/{revision}/{format}/task, poll GET /tasks/{task}, then download the result from the task's result.url once it is done.
Error handling
| Status | When it happens |
|---|---|
400 | Bad request — for example a missing supportsTasks: true, or an invalid or password-protected PDF. |
402 | The account is over quota, the OMR feature is not included in the plan, or there are not enough credits. |
In addition, a task may finish in the error state. When that happens, inspect result.error for a description of what went wrong. See the Errors page for the general error format.
Usage & billing
OMR imports consume the credits available on the user's Flat account. If you need a custom implementation, higher volumes, or have specific requirements, reach out to us at developers@flat.io — we're happy to discuss the best setup for your use case.
We are also working on a dedicated import API that will offer more options to control imports. Contact us if you'd like to be kept in the loop.
Related
- API Reference —
createScoreandgetTask - Authentication
- Import API — client-side import of MusicXML and MIDI files from a URL