> ## Documentation Index
> Fetch the complete documentation index at: https://docs.audiopod.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Audio to MIDI

> Convert audio — or stems you already separated — into MIDI using AudioPod's audio-to-MIDI engine.

## Overview

AudioPod's audio-to-MIDI engine transcribes bass, vocals, and piano in a
recording into note-level MIDI — bass is the strongest instrument. Guitar is
supported too but is opt-in/experimental. Use it **standalone** on a full mix
(we split it into stems first, then transcribe each requested stem) or as an
**add-on** to a stem-separation job you already ran (transcribe-only, no
re-split).

<Note>
  **This is a starting-point transcription, not a note-perfect one.** Bass is
  the strongest instrument; expect to tidy timing and note lengths by ear in
  your DAW. Dynamics (velocity) are approximate. Drums aren't transcribed
  yet — a requested `drums` (or `other`) stem is accepted but comes back in
  `skipped_stems`. Guitar is opt-in and experimental — it over-detects on busy
  mixes, so it isn't requested by default. Detected tempo is surfaced as
  `detected_tempo_bpm`.
</Note>

### Key Features

* **Standalone or add-on**: convert a raw file/URL, or transcribe stems from
  an existing [stem-separation](/api-reference/stem-splitter) job
* **Per-stem + multitrack output**: individual `.mid` per stem, plus a merged
  multitrack `.mid` with tempo metadata
* **Tempo detection**: auto-detected via beat tracking, or pass an explicit
  `tempo_bpm` override
* **Presigned downloads**: direct URLs for every output file

## Installation

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    pip install audiopod
    ```
  </Tab>

  <Tab title="Node.js">
    ```bash theme={null}
    npm install audiopod
    ```
  </Tab>
</Tabs>

## Authentication

Get your API key from the [API Keys Dashboard](https://www.audiopod.ai/dashboard/account/api-keys).

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from audiopod import AudioPod

    client = AudioPod(api_key="ap_your_api_key")
    # Or set AUDIOPOD_API_KEY environment variable
    ```
  </Tab>

  <Tab title="Node.js">
    ```typescript theme={null}
    import AudioPod from 'audiopod';

    const client = new AudioPod({ apiKey: 'ap_your_api_key' });
    // Or set AUDIOPOD_API_KEY environment variable
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    export AUDIOPOD_API_KEY="ap_your_api_key"
    # Use -H "X-API-Key: $AUDIOPOD_API_KEY" in requests
    ```
  </Tab>
</Tabs>

***

## Convert From a File or URL

Standalone conversion splits the mix into stems, then transcribes each
requested stem. Provide either `file` or `url` — not both. `stems` defaults
to `["bass", "vocals", "piano"]` — the strongest instruments — when omitted.
`guitar` is supported but opt-in/experimental (it over-detects on busy
mixes), so pass it explicitly if you want it. `drums` and `other` are
accepted but are never transcribed — they always come back in
`skipped_stems`.

<Tabs>
  <Tab title="cURL (file)">
    ```bash theme={null}
    curl -X POST "https://api.audiopod.ai/api/v1/midi/convert" \
      -H "X-API-Key: $AUDIOPOD_API_KEY" \
      -F "file=@/path/to/song.mp3"
    ```
  </Tab>

  <Tab title="cURL (URL, with guitar opt-in)">
    ```bash theme={null}
    curl -X POST "https://api.audiopod.ai/api/v1/midi/convert" \
      -H "X-API-Key: $AUDIOPOD_API_KEY" \
      -F "url=https://youtube.com/watch?v=VIDEO_ID" \
      -F 'stems=["bass","vocals","piano","guitar"]'
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from audiopod import AudioPod

    client = AudioPod(api_key="ap_your_api_key")

    # From a local file (or pass url="https://.../song.mp3")
    # Default stems: bass, vocals, piano
    job = client.midi.convert(file="/path/to/song.mp3")

    print(f"Multitrack: {job['merged_midi_url']}")
    for stem, url in (job["midi_urls"] or {}).items():
        print(f"  {stem}: {url}")
    ```
  </Tab>

  <Tab title="Node.js">
    ```typescript theme={null}
    import AudioPod from 'audiopod';

    const client = new AudioPod({ apiKey: 'ap_your_api_key' });

    // From a local file (or pass { url: 'https://.../song.mp3' })
    // Default stems: bass, vocals, piano
    const job = await client.midi.transcribe({ file: '/path/to/song.mp3' });

    console.log(`Multitrack: ${job.merged_midi_url}`);
    for (const [stem, url] of Object.entries(job.midi_urls ?? {})) {
      console.log(`  ${stem}: ${url}`);
    }
    ```
  </Tab>
</Tabs>

### Options

Tunables forwarded to the audio-to-MIDI engine, passed as `options` (a JSON
object):

| Field             | Type                     | Default              | Description                            |
| ----------------- | ------------------------ | -------------------- | -------------------------------------- |
| `onset_threshold` | float (0.05–0.95)        | `0.5`                | Note-onset detection sensitivity       |
| `frame_threshold` | float (0.05–0.95)        | `0.3`                | Frame-level pitch confidence threshold |
| `min_note_len_ms` | float (10–2000)          | `128.0`              | Minimum note duration in milliseconds  |
| `tempo_bpm`       | float (30–300) or `null` | `null` (auto-detect) | Override the detected tempo            |

## Convert From an Existing Stem-Separation Job

If you already ran [stem separation](/api-reference/stem-splitter), transcribe
its output directly — no re-split, so it bills the add-on rate only. `stems`
must be a subset of the stems the source job produced, and defaults to all
of them when omitted — `drums`/`other` stems still come back in
`skipped_stems` (never transcribed), and `guitar` remains
opt-in/experimental.

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST "https://api.audiopod.ai/api/v1/midi/convert/from-stems/5512" \
      -H "X-API-Key: $AUDIOPOD_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"stems": ["vocals", "bass"]}'
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    stem_job = client.stems.separate(file="/path/to/song.mp3", mode="six")

    job = client.midi.convert_from_stem_job(
        stem_job_id=stem_job["id"],
        stems=["vocals", "bass"],
    )
    print(job["merged_midi_url"])
    ```
  </Tab>

  <Tab title="Node.js">
    ```typescript theme={null}
    const stemJob = await client.stems.separate({ file: '/path/to/song.mp3', mode: 'six' });

    const job = await client.midi.convertFromStemJob(stemJob.id, {
      stems: ['vocals', 'bass'],
    });
    console.log(job.merged_midi_url);
    ```
  </Tab>
</Tabs>

<Info>
  If the source stem job's output has expired under your plan's [retention
  window](https://www.audiopod.ai/pricing), this returns `410 Gone` — re-run
  stem separation first.
</Info>

***

## Job Status & Stages

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X GET "https://api.audiopod.ai/api/v1/midi/status/JOB_ID" \
      -H "X-API-Key: $AUDIOPOD_API_KEY"
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    status = client.midi.get_job(job_id)
    print(f"Status: {status['status']}, stage: {status['stage']}")
    ```
  </Tab>

  <Tab title="Node.js">
    ```typescript theme={null}
    const status = await client.midi.status(jobId);
    console.log(`Status: ${status.status}, stage: ${status.stage}`);
    ```
  </Tab>
</Tabs>

`stage` tracks pipeline progress for a `PROCESSING` job:

| Stage        | Description                                             |
| ------------ | ------------------------------------------------------- |
| `split`      | Separating the mix into stems (standalone mode only)    |
| `transcribe` | Running the audio-to-MIDI engine on each requested stem |
| `merge`      | Building the merged multitrack `.mid`                   |

### Other Job Endpoints

```bash theme={null}
# List jobs
curl -X GET "https://api.audiopod.ai/api/v1/midi/jobs?skip=0&limit=50" \
  -H "X-API-Key: $AUDIOPOD_API_KEY"

# Delete a job (purges its outputs)
curl -X DELETE "https://api.audiopod.ai/api/v1/midi/jobs/JOB_ID" \
  -H "X-API-Key: $AUDIOPOD_API_KEY"
```

***

## Output Format

**Completed Job Response:**

```json theme={null}
{
  "id": 5512,
  "status": "COMPLETED",
  "progress": 100,
  "stage": "merge",
  "mode": "standalone",
  "source_job_id": null,
  "stems_requested": ["bass", "vocals", "piano"],
  "midi_urls": {
    "bass": "https://media.audiopod.ai/...",
    "vocals": "https://media.audiopod.ai/...",
    "piano": "https://media.audiopod.ai/..."
  },
  "merged_midi_url": "https://media.audiopod.ai/...",
  "stem_urls": {
    "bass": "https://media.audiopod.ai/...",
    "vocals": "https://media.audiopod.ai/..."
  },
  "detected_tempo_bpm": 118.0,
  "skipped_stems": [],
  "failed_stems": null,
  "duration": 214,
  "original_filename": "song.mp3",
  "created_at": "2026-07-24T12:39:37.379848Z",
  "completed_at": "2026-07-24T12:41:02.104213Z"
}
```

* **`midi_urls`** — presigned download URL per transcribed stem's `.mid` file
* **`merged_midi_url`** — a single multitrack `.mid` with all transcribed
  stems as separate tracks plus tempo metadata
* **`stem_urls`** — the separated stem *audio* (standalone mode only, so you
  can preview what was transcribed)
* **`skipped_stems`** — stems requested but not transcribed (always `drums`
  and `other`, if requested — they're not part of the transcribable set)
* **`failed_stems`** — `{stem: error_message}` for any stem the engine
  couldn't transcribe; the job still completes if at least one stem succeeded

All download URLs are presigned and expire — call the status endpoint again
to mint fresh ones.

***

## Status Codes

| Status       | Description                                             |
| ------------ | ------------------------------------------------------- |
| `PENDING`    | Job queued, waiting for worker                          |
| `PROCESSING` | Splitting and/or transcribing — see `stage`             |
| `COMPLETED`  | At least one stem transcribed — download URLs available |
| `FAILED`     | No stem could be transcribed — check `error_message`    |

***

## Pricing

See [audiopod.ai/pricing](https://www.audiopod.ai/pricing) for current rates,
or check your estimated cost before converting via the [API Wallet](/account/api-wallet).

***

## Next Steps

<Columns cols={2}>
  <Card title="Stem Separation" icon="waveform" href="/api-reference/stem-splitter">
    Separate a mix into stems before (or instead of) converting to MIDI.
  </Card>

  <Card title="API Wallet" icon="wallet" href="/account/api-wallet">
    Manage billing and view usage history.
  </Card>
</Columns>
