# MAP Audio Music API

> Part of the MAP Audio Music API docs. Index of every page: https://map.audio/developers/music-api/llms.txt. OpenAPI document: https://map.audio/developers/music-api/openapi.json.

The MAP Audio Music API runs PAM's sound engine on MAP Audio's servers. Send a JSON request and get back audio and MIDI files: loops and variations of PAM's factory presets, stems, cutdowns and DJ mixes, plus drum hits and MIDI patterns. No AI model makes the audio. A preset render returns its seed, and the same request with that seed renders the same audio again.

Base URL: `https://api.map.audio`. Version: `2026-11-01`. Reference: https://map.audio/developers/music-api/reference.md. Errors: https://map.audio/developers/music-api/errors.md.

## Recipes

### Loops and variations for a game level

Render every variation of one preset as a loop of the same length and tempo, so a game or app can switch between them on the bar.

For: Game and app developers who need music that changes with the action. Cost: About 1.125 render-minutes (5 loops of 8 bars at 142 BPM, 13.52 s each; every output is charged its own length, 0.225 render-minutes).

1. Find a preset at your tempo with several variations: `variations` lists each one's slots. (`listPresets`)

   ```sh
   curl -sS "https://api.map.audio/v1/presets?q=jungle&tempoMin=130&tempoMax=150" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01"
   ```

2. One job renders each slot as its own 8-bar loop; FLAC keeps them lossless and smaller than WAV. (`createRender`)

   ```sh
   curl -sS "https://api.map.audio/v1/renders" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01" \
     -H "Prefer: wait=30" \
     -H "Content-Type: application/json" \
     -d '{"source":{"preset":"pam:factory/junglistic"},"bars":8,"variations":{"kind":"slots","slots":[1,2,3,4,5]},"format":{"container":"flac"},"seed":42}'
   ```

3. Poll until `status` is succeeded. Each output's `name` is its slot (slot-1 to slot-5) and its `url` downloads it for an hour. (`getJob`)

   ```sh
   curl -sS "https://api.map.audio/v1/jobs/$JOB_ID" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01"
   ```

### Stems for a remix

Render a groove with one stem per cell beside the master, so each part can be remixed on its own track.

For: Producers and remix apps that want each part of a groove separately. Cost: About 0.529 render-minutes (One 16-bar render at 121 BPM, 31.74 s; stems cost nothing beyond the master).

1. `stems: true` adds one stem per cell, captured after the cell's effects and before the master chain. Keep the seed to render the same parts again. (`createRender`)

   ```sh
   curl -sS "https://api.map.audio/v1/renders" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01" \
     -H "Prefer: wait=30" \
     -H "Content-Type: application/json" \
     -d '{"source":{"preset":"pam:factory/breakbeatrcool","variation":2},"bars":16,"stems":true,"seed":42}'
   ```

2. Stems are the outputs with `role: stem`, each with its `cell`; the full mix has `role: master`. (`getJob`)

   ```sh
   curl -sS "https://api.map.audio/v1/jobs/$JOB_ID" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01"
   ```

3. Download a file again later with a fresh link; outputs are kept 7 days on the free plan. (`getFileContent`)

   ```sh
   curl -sSL --fail-with-body -o render.wav "https://api.map.audio/v1/files/$FILE_ID/content" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01"
   ```

### A 30-second cutdown for an ad

Fit a preset to a spot length as an opening, a main section and an ending over its own variations, so the music ends where the ad does.

For: Video and ad tools that need music cut to an exact length with a real ending. Cost: About 0.515 render-minutes (17 bars at 132 BPM, 30.91 s: a cutdown rounds the length to whole bars).

1. Free: see where each section starts and how long the cutdown really runs before paying for audio. (`createArrangement`)

   ```sh
   curl -sS "https://api.map.audio/v1/arrangements" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01" \
     -H "Content-Type: application/json" \
     -d '{"source":{"preset":"pam:factory/thaw"},"cutdown":{"seconds":30}}'
   ```

2. Render the same fit as an item named spot-30, as 320 kbit/s MP3. (`createRender`)

   ```sh
   curl -sS "https://api.map.audio/v1/renders" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01" \
     -H "Prefer: wait=30" \
     -H "Content-Type: application/json" \
     -d '{"source":{"preset":"pam:factory/thaw"},"items":[{"name":"spot-30","cutdown":{"seconds":30}}],"format":{"container":"mp3","bitrate":320}}'
   ```

3. The output named spot-30 is the cutdown. (`getJob`)

   ```sh
   curl -sS "https://api.map.audio/v1/jobs/$JOB_ID" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01"
   ```

### A DJ mix with transitions

Chain presets into one continuous mix, each change on a DJ-view transition such as an 8-bar blend or a drop.

For: DJ, radio and fitness apps that need continuous mixes without gaps. Cost: About 1.548 render-minutes (48 bars at 124 BPM, 92.90 s).

1. The transitions a set can name: drop, rise, break, blend, sweep and fill. (`listTransitionPresets`)

   ```sh
   curl -sS "https://api.map.audio/v1/transition-presets" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01"
   ```

2. Free: check every preset, variation, length and transition, and see the bar and second each item starts. (`createArrangement`)

   ```sh
   curl -sS "https://api.map.audio/v1/arrangements" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01" \
     -H "Content-Type: application/json" \
     -d '{"set":{"queue":[{"presetPath":"pam:factory/acid-sprinkle","bars":16},{"presetPath":"pam:factory/teknochonic","variationIndex":1,"bars":16,"transition":{"preset":"blend","bars":8}},{"presetPath":"pam:factory/breakbeatrcool","variationIndex":2,"bars":16,"transition":{"preset":"drop"}}],"tempo":{"bpm":124}}}'
   ```

3. Render the checked set as one WAV. (`djSet`)

   ```sh
   curl -sS "https://api.map.audio/v1/dj/set" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01" \
     -H "Prefer: wait=30" \
     -H "Content-Type: application/json" \
     -d '{"set":{"queue":[{"presetPath":"pam:factory/acid-sprinkle","bars":16},{"presetPath":"pam:factory/teknochonic","variationIndex":1,"bars":16,"transition":{"preset":"blend","bars":8}},{"presetPath":"pam:factory/breakbeatrcool","variationIndex":2,"bars":16,"transition":{"preset":"drop"}}],"tempo":{"bpm":124}},"seed":42}'
   ```

4. The result lists each boundary with the bar it landed on. (`getJob`)

   ```sh
   curl -sS "https://api.map.audio/v1/jobs/$JOB_ID" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01"
   ```

### Drum one-shots

Synthesize kicks, 808s and snares from PAM's drum engines, set by named controls in real units.

For: Sample-pack builders and beat apps that need fresh drum hits on demand. Cost: About 0 render-minutes (Free: drum synthesis counts in usage but never in render-minutes).

1. Each engine's controls, with units and ranges. (`listDrumEngines`)

   ```sh
   curl -sS "https://api.map.audio/v1/drum-engines" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01"
   ```

2. A short, clicky kick at 50 Hz. (`synthDrum`)

   ```sh
   curl -sS "https://api.map.audio/v1/synth/drum" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01" \
     -H "Prefer: wait=30" \
     -H "Content-Type: application/json" \
     -d '{"engine":"kick","param":"Tune=50,Decay=0.6,Click=40","duration":1}'
   ```

3. A long 808 tuned to 55 Hz. (`synthDrum`)

   ```sh
   curl -sS "https://api.map.audio/v1/synth/drum" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01" \
     -H "Prefer: wait=30" \
     -H "Content-Type: application/json" \
     -d '{"engine":"808","param":"Tune=55,Decay=1.5","duration":2}'
   ```

4. A bright snare with more noise. (`synthDrum`)

   ```sh
   curl -sS "https://api.map.audio/v1/synth/drum" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01" \
     -H "Prefer: wait=30" \
     -H "Content-Type: application/json" \
     -d '{"engine":"snare","param":"Tune=200,Noise=60","duration":0.5}'
   ```

### A MIDI drum pattern

Generate a seeded drum and bass pattern in a genre from PAM's MIDI corpus, as notes and a .mid file to edit.

For: Music apps and DAW tools that want editable patterns rather than audio. Cost: About 0 render-minutes (Free: no audio is rendered).

1. Four bars of drum and bass at 172 BPM; the same seed gives the same pattern. (`patternGenerate`)

   ```sh
   curl -sS "https://api.map.audio/v1/pattern/generate" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01" \
     -H "Prefer: wait=30" \
     -H "Content-Type: application/json" \
     -d '{"genre":"dnb","bpm":172,"bars":4,"seed":7}'
   ```

2. Download the MIDI file: the output with `role: midi`. (`getFileContent`)

   ```sh
   curl -sSL --fail-with-body -o render.wav "https://api.map.audio/v1/files/$FILE_ID/content" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01"
   ```

### Render several takes and keep the best

Render one preset from several seeds, measure each take, and keep the one you want.

For: Apps that generate candidates and choose by measurement. Cost: About 0.496 render-minutes (4 takes of 4 bars at 129 BPM, 7.44 s each, 0.124 render-minutes each; analysis is free).

1. Four takes, each from a seed derived from 7; the outputs are named seed-1 to seed-4. (`createRender`)

   ```sh
   curl -sS "https://api.map.audio/v1/renders" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01" \
     -H "Prefer: wait=30" \
     -H "Content-Type: application/json" \
     -d '{"source":{"preset":"pam:factory/totes-rando"},"bars":4,"variations":{"kind":"seeds","count":4},"seed":7}'
   ```

2. Measure the takes (up to 16 files a call, free) and pick by loudness, peaks or crest. `quality: standard` on POST /v1/renders does this for you against a loudness target. (`audioAnalyze`)

   ```sh
   curl -sS "https://api.map.audio/v1/audio/analyze" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01" \
     -H "Prefer: wait=30" \
     -H "Content-Type: application/json" \
     -d '{"files":["file_01M6BPRK0M34NBP2XKJ1RMX1TQ","file_01M6BPRMNR7PYGFNA66BX2YJ8M"],"levels":true}'
   ```

### Hear when a long render finishes

Get a signed webhook when a job ends instead of holding a connection open or polling.

For: Back ends that queue long renders. Cost: About 5 render-minutes (300 s of audio, the longest single render).

1. Store the whsec_ secret from the answer: it is shown once, and it signs every delivery. (`createWebhookEndpoint`)

   ```sh
   curl -sS "https://api.map.audio/v1/webhook-endpoints" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01" \
     -H "Content-Type: application/json" \
     -d '{"url":"https://hooks.example.com/map-audio","events":["job.succeeded","job.failed"]}'
   ```

2. Send a test event and check that your server verifies its signature. (`testWebhookEndpoint`)

   ```sh
   curl -sS -X POST "https://api.map.audio/v1/webhook-endpoints/$WEBHOOK_ENDPOINT_ID/test" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01"
   ```

3. Five minutes of Corduroy. `metadata` comes back on the job, and the webhook carries the job. (`createRender`)

   ```sh
   curl -sS "https://api.map.audio/v1/renders" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01" \
     -H "Prefer: wait=30" \
     -H "Content-Type: application/json" \
     -d '{"source":{"preset":"pam:factory/corduroy"},"seconds":300,"seed":11,"metadata":{"order":"A-1042"}}'
   ```

4. When job.succeeded arrives, its `data.object` is the job with fresh download links; reading the job gives the same. (`getJob`)

   ```sh
   curl -sS "https://api.map.audio/v1/jobs/$JOB_ID" \
     -H "Authorization: Bearer $MAP_API_KEY" \
     -H "Map-Version: 2026-11-01"
   ```

## Quickstart

Three steps from an invite to a WAV file. You need a terminal with `curl` and `jq`.

### 1. Get access and a key

Access is invite-only while the API is in preview. Sign in at https://map.audio/music with your map.audio account and request access; we email you when it is ready. Then sign in there again, accept the prototype terms and create an API key. The key is shown once, so put it straight into an environment variable:

```sh
export MAP_API_KEY=sk_test_your_key
```

### 2. Render a loop

`Prefer: wait=30` holds the request open for up to 30 seconds, so a short render comes back finished.

```sh
curl -sS https://api.map.audio/v1/renders \
  -H "Authorization: Bearer $MAP_API_KEY" \
  -H "Map-Version: 2026-11-01" \
  -H "Content-Type: application/json" \
  -H "Prefer: wait=30" \
  -d '{"source":{"preset":"pam:factory/313-disco"},"bars":8,"seed":42}' > job.json
jq '{id, status, seed: .result.seed}' job.json
```

The answer is the job. `succeeded` means the audio is ready. `queued` or `running`, with status `202`, means it is still working; fetch it again every few seconds until it ends:

```sh
JOB_ID=$(jq -r .id job.json)
curl -sS "https://api.map.audio/v1/jobs/$JOB_ID" -H "Authorization: Bearer $MAP_API_KEY" > job.json
```

### 3. Download the audio

Each output carries a signed `url` that works for one hour.

```sh
curl -sSL --fail-with-body -o loop.wav "$(jq -r '.result.outputs[0].url' job.json)"
```

Eight bars of this 120 BPM preset are 16 seconds of audio: about a quarter of a render-minute, out of the 30 the free plan has each month. Keep the seed. The same request with the same seed renders the same audio.

When a call fails, its JSON error names the field at fault (`param`), says whether a retry can succeed (`retryable`) and links to the code's entry in the [error catalogue](https://map.audio/developers/music-api/errors). Quote the `Request-Id` response header when you write to support@map.audio.

## Access and keys

### Request access

The API is invite-only while it is in preview. Sign in at https://map.audio/music with your map.audio account (a new email address creates one), add a line about what you are building and send the request. We email you when your access is ready. An app holding a map.audio access token for the account can send the same request with `POST /v1/access-requests`. Until access is ready, calls answer `403` [`not_invited`](https://map.audio/developers/music-api/errors#not_invited), with the account's customer id and the state of its request in `details`.

### API keys

The account holder creates and revokes keys at https://map.audio/music. A key is shown once, when it is created: keep it in a secret store or an environment variable, never in a web page or a repository. Send it on every call as `Authorization: Bearer $MAP_API_KEY`. An account holds at most 10 active keys.

Revoke a key as soon as it may have leaked; the next call with it is refused. A webhook endpoint whose secret that key last set stops receiving events until you rotate its secret.

### OAuth

An app can act for a map.audio account with an OAuth access token instead of a key: scope `audio`, resource `https://api.map.audio`. Such a token can read the account and render with its render-minutes. Creating or revoking keys and buying plans or render-minutes need the account holder signed in at https://map.audio/music; an API key and other apps are refused. Webhook endpoints take an API key or that sign-in. The account holder can disconnect an app at any time under Connected apps on https://map.audio/account#connected-apps.

### Terms

Every account accepts the prototype terms before its first render. The https://map.audio/music page asks for that when you create your first key. With a key or token, send `POST /v1/account/terms` with `{"accept":"<version>"}`, where the version is `currentTerms` from `GET /v1/account`.

## Test and live keys

A key is either `sk_test_` or `sk_live_` followed by its secret; you pick the mode when you create it. Today the two modes differ only a little. Both render real audio on the same servers, spend the same render-minutes, count against the same limits and see the same jobs, files and webhook endpoints. What differs: idempotency keys are kept apart per mode, each job records its mode in `livemode`, and a render made with a test key carries a test licence id (ending `_test`) in its provenance record.

There is no free sandbox yet. Keep test renders short, and use a test key while you build so that its jobs are marked as tests.

## Limits and plans

A job is charged for the audio it returns: one render-minute per minute of each rendered file, at least 0.1 per file. Stems and MIDI files are not charged, and neither are calls that render nothing chargeable: preset search, plans, arrangements, analysis, drum hits and patterns. A failed or cancelled job is not charged, except for a preview it already delivered.

| Limit | Free plan |
| --- | --- |
| Render-minutes a month | 30 |
| One render | Up to 300 s of audio, at 22,050 to 48,000 Hz |
| One job | Up to 600 s of audio and 10 minutes of running time |
| Jobs at once | 2 |
| Requests | 120 a minute per API key; the OAuth sessions of one account share one such budget |
| Drum hits | Up to 30 s each |
| Rendered files kept | 7 days (uploads 30 days, job and plan records 90 days) |

| Plan | Render-minutes a month | Jobs at once | Files kept | Price |
| --- | --- | --- | --- | --- |
| Free | 30 | 2 | 7 days | Free |
| Creator | 400 | 2 | 30 days | $12 a month |
| Pro | 1,500 | 5 | 90 days | $39 a month |
| App | 20,000 | 20 | 30 days | $299 a month |

Extra render-minutes cost $0.03 each, at least 334 at a time, and stay valid for 365 days; jobs draw on the monthly allowance first. The account holder buys plans and render-minutes signed in at https://map.audio/music, and an API key cannot buy anything. `GET /v1/credits` shows what is left. Audio rendered on the free plan is for evaluating the API: its terms grant no commercial use.

## Jobs and waiting

Every call that runs the engine creates a job. Renders (`POST /v1/renders`) and the longer engine commands answer `202` with the job at once; short commands wait up to 30 seconds for it. Send `Prefer: wait=N`, from 0 to 60 seconds, to choose how long a call holds on: when the job ends in time, the answer is `200` with the finished job. The `Preference-Applied` header says which wait the API used.

A job goes from `queued` to `running` to `succeeded`, `failed` or `cancelled`. It turns `expired` when retention deletes its files; the record and its provenance stay. There are three ways to follow one:

- Poll `GET /v1/jobs/{id}` no faster than every 2 seconds. A `202` answer carries `Retry-After` and the job's `Location`.
- Read `GET /v1/jobs/{id}/events`, a stream of server-sent events: `job.queued`, `job.started`, `job.progress`, `job.preview`, then the outcome. Reconnect with `Last-Event-ID` to resume where you left off; each connection closes after 10 minutes.
- Register a [webhook endpoint](https://map.audio/developers/music-api#webhooks) and receive `job.succeeded`, `job.failed` and `job.cancelled`.

Cancel a job you no longer need with `POST /v1/jobs/{id}/cancel`. A job that has already finished keeps its outcome.

### Retrying safely

Send an `Idempotency-Key` header (8 to 128 letters, digits or `_.:-`) with a `POST` that starts a job. The same key and body return the same job for 24 hours, even one that failed, and the answer carries `Idempotent-Replayed: true`. The same key with a different body is refused with [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused). After a failed job, retry with a new key. The [reference](https://map.audio/developers/music-api/reference) marks each operation that takes the header.

### Results

A finished job's `result.outputs` lists its files, each with its `role` (`master`, `stem`, `midi`, `preset` or `preview`), `file` id, `url`, `sha256`, `format` and `durationSeconds`. With `stems: true`, a render adds up to eight `stem` files, one per PAM cell (`cell` 1 to 8), in the same format as the master. An output's `url` works for one hour; `GET /v1/files/{id}/content` signs a fresh one. Rendered files are kept 7 days on the free plan and longer on paid plans, and `expiresAt` on `GET /v1/files/{id}` says when a file goes. Download what you want to keep before then.

## Webhooks

A webhook endpoint receives an HTTPS `POST` when a job ends, so a long render needs no polling. Create one with an API key: `POST /v1/webhook-endpoints` with its `url` and the `events` it wants. The default is `job.succeeded`, `job.failed` and `job.cancelled`; `job.preview`, `job.expired` and `webhook.test` are also available, and `*` takes them all. The answer holds the endpoint's signing secret, `whsec_` followed by base64, and shows it only once. An account can have 10 endpoints.

Each delivery is a JSON object: `id`, `type`, `createdAt`, `apiVersion` and `data`. For `job.succeeded`, `job.failed`, `job.cancelled` and `job.expired`, `data.object` is the job as it stands when the event is delivered; a `job.preview` carries `data.jobId` and the preview's `data.outputs`; a `webhook.test` carries `data.object`, the endpoint. Deliveries are signed as [Standard Webhooks](https://www.standardwebhooks.com/) describes, so any of its libraries can check them. In Node, with the raw request body as a string:

```js
import { createHmac, timingSafeEqual } from 'node:crypto'

export function verifyWebhook(secret, headers, body) {
  const signed = `${headers['webhook-id']}.${headers['webhook-timestamp']}.${body}`
  const expected = createHmac('sha256', Buffer.from(secret.slice('whsec_'.length), 'base64')).update(signed).digest('base64')
  const fresh = Math.abs(Date.now() / 1000 - Number(headers['webhook-timestamp'])) < 300
  return fresh && headers['webhook-signature'].split(' ').some(entry => {
    const [version, signature] = entry.split(',')
    return version === 'v1' && signature.length === expected.length && timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
  })
}
```

Answer with any `2xx` status within 15 seconds; a redirect counts as a failure. A failed delivery is retried with backoff for about two and a half days. When three deliveries in a row run out of retries, the endpoint is disabled; rotating its secret with `POST /v1/webhook-endpoints/{id}/rotate-secret` enables it again. After a rotation the old secret also signs for 24 hours, so `webhook-signature` can hold two signatures. `POST /v1/webhook-endpoints/{id}/test` sends a signed `webhook.test` event.

## Provenance

Every job that makes audio returns `result.provenance`, a record of what made it, signed by MAP Audio. It names the engine build, the presets and the seed, and every input and output file with its SHA-256. It also states that no AI model made the audio: `aiModels` is empty and `audioByModel` is `false`. Anyone holding a record's id can fetch it from `GET /v1/provenance/{id}` without a key.

The record's `license` says what the audio may be used for. Renders on the free plan are for evaluating the API: the prototype terms grant no commercial use (`commercialUse: false`) and require attribution to MAP Audio where the audio is shown.

The signature is Ed25519 over the record's JSON with its keys sorted and `signature` left out, written as `ed25519:` and the base64url signature. The public keys are at https://api.map.audio/.well-known/jwks.json, where `kid` names the one that signed. In Node:

```js
import { createPublicKey, verify } from 'node:crypto'

const sorted = value => Array.isArray(value) ? value.map(sorted)
  : value && typeof value === 'object' ? Object.fromEntries(Object.keys(value).sort().map(key => [key, sorted(value[key])])) : value

export async function verifyProvenance(record) {
  const { keys } = await (await fetch('https://api.map.audio/.well-known/jwks.json')).json()
  const { signature, ...signed } = record
  const key = createPublicKey({ key: keys.find(entry => entry.kid === record.kid), format: 'jwk' })
  return verify(null, Buffer.from(JSON.stringify(sorted(signed))), key, Buffer.from(signature.slice('ed25519:'.length), 'base64url'))
}
```

## MCP and the skill

### MCP server

The API's operations are also MCP tools, at `https://api.map.audio/mcp` (Streamable HTTP). It takes the same credentials as the REST API: map.audio OAuth, which an MCP client that supports sign-in starts on its own, or an API key as a bearer token. In Claude Code:

```sh
claude mcp add --transport http map-audio https://api.map.audio/mcp
```

With a key instead of a sign-in:

```sh
claude mcp add --transport http map-audio https://api.map.audio/mcp --header "Authorization: Bearer $MAP_API_KEY"
```

A tool that renders waits up to 45 seconds and returns the job; poll it with the job tool when it is still running. The table below lists every tool. Operations missing from it are REST only.

### Skill

The `map-music-api` skill teaches an agent the workflow: finding presets, rendering, waiting, downloading, what renders cost and what each error means. Install it for any agent that reads skills:

```sh
npx skills add https://map.audio --skill map-music-api
```

For claude.ai or Claude Desktop, download [map-music-api.zip](https://map.audio/.well-known/agent-skills/map-music-api.zip) and upload it under Customize > Skills. Without Node, copy the file into the folders Claude Code, Codex and Cursor read:

```sh
for dir in ~/.claude/skills ~/.agents/skills; do
  mkdir -p "$dir/map-music-api"
  curl -fsSL https://map.audio/.well-known/agent-skills/map-music-api/SKILL.md -o "$dir/map-music-api/SKILL.md"
done
```

The skill holds no credentials. It reads the key from `MAP_API_KEY`.

| Tool | Operation | What it does |
| --- | --- | --- |
| `get_account` | [getAccount](https://map.audio/developers/music-api/reference/getAccount.md) | Get the calling account |
| `accept_terms` | [acceptTerms](https://map.audio/developers/music-api/reference/acceptTerms.md) | Accept the prototype terms |
| `get_credits` | [getCredits](https://map.audio/developers/music-api/reference/getCredits.md) | Get the render-minute balance |
| `search_presets` | [listPresets](https://map.audio/developers/music-api/reference/listPresets.md) | Search the factory presets |
| `get_preset` | [getPreset](https://map.audio/developers/music-api/reference/getPreset.md) | Get one preset |
| `list_drum_engines` | [listDrumEngines](https://map.audio/developers/music-api/reference/listDrumEngines.md) | List the drum-synth engines |
| `list_transition_presets` | [listTransitionPresets](https://map.audio/developers/music-api/reference/listTransitionPresets.md) | List the DJ transitions |
| `create_plan` | [createPlan](https://map.audio/developers/music-api/reference/createPlan.md) | Plan a render from a prompt |
| `get_plan` | [getPlan](https://map.audio/developers/music-api/reference/getPlan.md) | Get a plan |
| `create_render` | [createRender](https://map.audio/developers/music-api/reference/createRender.md) | Render presets or a plan to audio |
| `create_arrangement` | [createArrangement](https://map.audio/developers/music-api/reference/createArrangement.md) | Fit a cutdown or check a DJ set |
| `list_jobs` | [listJobs](https://map.audio/developers/music-api/reference/listJobs.md) | List jobs |
| `get_job` | [getJob](https://map.audio/developers/music-api/reference/getJob.md) | Get a job |
| `cancel_job` | [cancelJob](https://map.audio/developers/music-api/reference/cancelJob.md) | Cancel a job |
| `list_files` | [listFiles](https://map.audio/developers/music-api/reference/listFiles.md) | List files |
| `get_file` | [getFile](https://map.audio/developers/music-api/reference/getFile.md) | Get a file |
| `audio_analyze` | [audioAnalyze](https://map.audio/developers/music-api/reference/audioAnalyze.md) | Measure audio loudness and peaks (BS.1770). |
| `audio_normalize` | [audioNormalize](https://map.audio/developers/music-api/reference/audioNormalize.md) | Normalize loudness and verify a true-peak ceiling. |
| `audio_repitch` | [audioRepitch](https://map.audio/developers/music-api/reference/audioRepitch.md) | Render varispeed pitch shifting using the browser repitch core. |
| `audio_trim` | [audioTrim](https://map.audio/developers/music-api/reference/audioTrim.md) | Trim a sample by time using the shared sample-edit core. |
| `audio_gain` | [audioGain](https://map.audio/developers/music-api/reference/audioGain.md) | Apply sample gain using the shared sample-edit core. |
| `audio_reverse` | [audioReverse](https://map.audio/developers/music-api/reference/audioReverse.md) | Reverse a sample using the shared sample-edit core. |
| `preset_render` | [presetRender](https://map.audio/developers/music-api/reference/presetRender.md) | Render a PAM preset to WAV without opening PAM. |
| `project_remix` | [projectRemix](https://map.audio/developers/music-api/reference/projectRemix.md) | Remix an Ableton Live set, Bitwig project, DAWproject or FL Studio project into a PAM preset built from its own samples, MIDI and sections. |
| `fx` | [fx](https://map.audio/developers/music-api/reference/fx.md) | Render a PAM cell effect chain over an audio file. |
| `audio_master` | [audioMaster](https://map.audio/developers/music-api/reference/audioMaster.md) | Render the PAM master effect chain over an audio file. |
| `state` | [state](https://map.audio/developers/music-api/reference/state.md) | Apply state envelopes to Init, a preset or a point between two presets in the headless PAM engine, read the resulting state and optionally render it. |
| `render_dj_set` | [djSet](https://map.audio/developers/music-api/reference/djSet.md) | Render a DJ set of PAM presets to WAV, each boundary on its DJ view transition, without opening PAM. |
| `pattern_generate` | [patternGenerate](https://map.audio/developers/music-api/reference/patternGenerate.md) | Generate seeded genre patterns with the shared MIDI corpus; optionally write MIDI. |
| `synth_drum` | [synthDrum](https://map.audio/developers/music-api/reference/synthDrum.md) | Render procedural drum engines with named controls and timed triggers. |

## Versioning and changelog

The API is versioned by date. The current version is `2026-11-01`. Every JSON response carries it in the `Map-Version` header, as do webhook deliveries (`apiVersion`) and provenance records (`contracts.api`). Send `Map-Version: 2026-11-01` with a request to pin it: a version the API does not serve is refused with [`version_unsupported`](https://map.audio/developers/music-api/errors#version_unsupported).

While the API is in preview, its prototype terms allow changes without notice, and each change is listed here. Within a version, operations, optional fields, enum values, event types and error codes can be added. Write clients that ignore fields they do not know and handle an unknown error `code` by its `type` and `retryable`.

### Changelog

- 2026-11-02, under `2026-11-01`: errors and docs pass. Route and method resolve before auth (404 `route_not_found`, 405 `method_not_allowed` with `Allow`; HEAD is served); a JSON route answers 415 `unsupported_media_type` for another media type; unknown query parameters and `Map-Version` values are refused; non-job POSTs refuse `Idempotency-Key` (`idempotency_key_unsupported`); a wrong terms version is `terms_version_mismatch`; `dj_variation_empty` became `variation_not_found` (400), `pack_license_required` from the API's own check became `preset_not_served`, `quality_unavailable` is 400; hosted MCP tools `list_presets` and `dj_set` were renamed `search_presets` and `render_dj_set`, and `get_account` and `accept_terms` were added; `POST /v1/access-requests` was added; every `docUrl` points at the errors page.
- `2026-11-01`: the first version.

## Downloads

| File | What it is |
| --- | --- |
| [OpenAPI JSON](https://map.audio/developers/music-api/openapi.json) | The contract, with `servers` set to https://api.map.audio. The API serves the same document at https://api.map.audio/v1/openapi.json. |
| [OpenAPI YAML](https://map.audio/developers/music-api/openapi.yaml) | The same document as YAML. |
| [Docs as Markdown](https://map.audio/developers/music-api/llms-full.txt) | This overview, the reference and the error catalogue in one file. |
| [Skill](https://map.audio/.well-known/agent-skills/map-music-api/SKILL.md) | The map-music-api agent skill as one SKILL.md file. |
| [Skill (.zip)](https://map.audio/.well-known/agent-skills/map-music-api.zip) | The same skill in a map-music-api folder, for uploading to Claude. |
