Source: https://map.audio/developers/music-api.md # 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":""}`, 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. | --- Source: https://map.audio/developers/music-api/reference.md # Music API reference > 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. Every operation of the MAP Audio Music API, grouped as in the OpenAPI document, with its fields, a curl example and the errors it can answer with. Examples read the key from `MAP_API_KEY` and pin the version with `Map-Version`. Their ids have the shape real ones have, such as `job_01M6BPRE186QHP9QPYGDX5NNFP`: put in the ids from your own answers. Every operation is also published on its own as Markdown, linked from its entry. Base URL `https://api.map.audio`. One operation as Markdown: https://map.audio/developers/music-api/reference/.md. ## Account The account behind a credential: plan, render-minutes, usage, terms, access requests, API keys and billing. Keys and billing need a map.audio sign-in on MAP Audio's own pages (https://map.audio/music). ### getAccount `GET https://api.map.audio/v1/account` Get the calling account Who this credential belongs to: the Music API account's status, the terms version it accepted, its tier's limits and this month's render-minutes. Call it first. `currentTerms` is the version POST /v1/account/terms takes (every request that starts work needs it), and `credits.remaining` is what jobs can still draw. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. MCP tool: `get_account`. #### Example ```sh curl -sS "https://api.map.audio/v1/account" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: An active free-tier account ```json { "object": "account", "id": "acct_01M6BPR7CRAWF0WZQZNP3ZD818", "email": "dev@example.com", "status": "active", "termsVersion": "2026-11-01-prototype", "currentTerms": "2026-11-01-prototype", "livemode": false, "limits": { "renderMinutesPerMonth": 30, "maxRenderSeconds": 300, "maxSampleRate": 48000, "concurrentJobs": 2, "requestsPerMinute": 120, "maxDrumSeconds": 30, "outputRetentionDays": 7 }, "credits": { "object": "credits", "period": "2026-11", "unit": "render_minute", "tier": { "id": "free", "name": "Free", "currentPeriodEnd": null, "cancelAtPeriodEnd": false }, "granted": 30, "used": 4.214, "reserved": 0.267, "remaining": 25.519, "grants": [ { "source": "free", "amount": 30, "remaining": 25.519, "expiresAt": "2026-12-01T00:00:00.000Z" } ] } } ``` ##### 403: No Music API access yet ```json { "error": { "type": "entitlement", "code": "not_invited", "message": "This map.audio account (customer 8f14e45f-ceea-467a-9575-62f3b1a6c2d0) has no Music API access yet. Request access at https://map.audio/music, or POST /v1/access-requests with this token.", "param": null, "details": { "customerId": "8f14e45f-ceea-467a-9575-62f3b1a6c2d0", "emailConfirmed": true, "accessRequest": null, "requestAccessUrl": "https://map.audio/music" }, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#not_invited", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The account. - `403`: No Music API access yet - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### acceptTerms `POST https://api.map.audio/v1/account/terms` Accept the prototype terms Accept the terms that every request starting work needs: renders, commands, plans, arrangements, uploads and checkout. Send the version from `currentTerms` in GET /v1/account; an invited account becomes active. The terms are on https://map.audio/music, where you can also accept them signed in. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. MCP tool: `accept_terms`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `accept` | string | yes | The version in `currentTerms`. | #### Example ```sh curl -sS "https://api.map.audio/v1/account/terms" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Content-Type: application/json" \ -d '{"accept":"2026-11-01-prototype"}' ``` ##### Request: Accept the current terms ```json { "accept": "2026-11-01-prototype" } ``` ##### 200: Terms accepted ```json { "object": "account", "id": "acct_01M6BPR7CRAWF0WZQZNP3ZD818", "email": "dev@example.com", "status": "active", "termsVersion": "2026-11-01-prototype", "currentTerms": "2026-11-01-prototype", "livemode": false, "limits": { "renderMinutesPerMonth": 30, "maxRenderSeconds": 300, "maxSampleRate": 48000, "concurrentJobs": 2, "requestsPerMinute": 120, "maxDrumSeconds": 30, "outputRetentionDays": 7 }, "credits": { "object": "credits", "period": "2026-11", "unit": "render_minute", "tier": { "id": "free", "name": "Free", "currentPeriodEnd": null, "cancelAtPeriodEnd": false }, "granted": 30, "used": 4.214, "reserved": 0.267, "remaining": 25.519, "grants": [ { "source": "free", "amount": 30, "remaining": 25.519, "expiresAt": "2026-12-01T00:00:00.000Z" } ] } } ``` ##### 400: Wrong terms version ```json { "error": { "type": "invalid_request", "code": "terms_version_mismatch", "message": "accept must be \"2026-11-01-prototype\", the current terms version; got \"2026-10-01\".", "param": "accept", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#terms_version_mismatch", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The account, now active. - `400`: Wrong terms version - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`terms_version_mismatch`](https://map.audio/developers/music-api/errors#terms_version_mismatch) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`idempotency_key_unsupported`](https://map.audio/developers/music-api/errors#idempotency_key_unsupported) ### createAccessRequest `POST https://api.map.audio/v1/access-requests` Request Music API access Ask MAP Audio for Music API access as the signed-in map.audio account behind the access token. Use it when a call answers 403 not_invited: send the same token, with a line about what you will build and a website if you like. One request per account; sending again updates the fields you send (null clears one), keeps the ones you leave out, and answers 200. MAP Audio emails the account when access is ready, and https://map.audio/music does the same signed in. An API key cannot call this route, and it never answers not_invited. Free. Auth: `Authorization: Bearer` with a map.audio access token; an API key is refused. Not an MCP tool: An MCP session already has Music API access; an account without it requests access signed in at https://map.audio/music. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `useCase` | string or null | no | What you want to build with the API, in a sentence or two. null clears a saved one; leaving it out keeps it. At most 1000 characters. | | `website` | string or null | no | A public https URL for you or your product: a host name, not an IP address, localhost or a URL with credentials. null clears a saved one; leaving it out keeps it. At most 200 characters. Matches `^https://`. | #### Example ```sh curl -sS "https://api.map.audio/v1/access-requests" \ -H "Authorization: Bearer $MAP_ACCESS_TOKEN" \ -H "Map-Version: 2026-11-01" \ -H "Content-Type: application/json" \ -d '{"useCase":"A game that scores every level with new loops from PAM presets.","website":"https://example.com"}' ``` ##### Request: A short note about the project ```json { "useCase": "A game that scores every level with new loops from PAM presets.", "website": "https://example.com" } ``` ##### 200: Updated ```json { "object": "access_request", "id": "areq_01M6BPS09M7WAVD3A2N3JPJE0X", "status": "pending", "email": "dev@example.com", "useCase": "A game that scores every level with new loops from PAM presets.", "website": "https://example.com", "createdAt": "2026-11-01T13:40:12.007Z", "updatedAt": "2026-11-01T14:05:51.630Z" } ``` ##### 201: Created ```json { "object": "access_request", "id": "areq_01M6BPS09M7WAVD3A2N3JPJE0X", "status": "pending", "email": "dev@example.com", "useCase": "A game that scores every level with new loops from PAM presets.", "website": "https://example.com", "createdAt": "2026-11-01T13:40:12.007Z", "updatedAt": "2026-11-01T13:40:12.007Z" } ``` ##### 403: Access requests need a sign-in ```json { "error": { "type": "entitlement", "code": "access_request_needs_sign_in", "message": "An API key cannot request access: every key belongs to an account that has it already. Sign in at https://map.audio/music with the map.audio account that needs access, and request it there.", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#access_request_needs_sign_in", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` ##### 409: Already has access ```json { "error": { "type": "conflict", "code": "already_has_access", "message": "This map.audio account already has Music API access, so there is nothing to request. Call the API with this token, or create API keys at https://map.audio/music.", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#already_has_access", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The request, updated. - `201`: The request, created. - `403`: Access requests need a sign-in - `409`: Already has access - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`access_request_needs_sign_in`](https://map.audio/developers/music-api/errors#access_request_needs_sign_in) - [`already_has_access`](https://map.audio/developers/music-api/errors#already_has_access) - [`customer_erased`](https://map.audio/developers/music-api/errors#customer_erased) - [`rate_limited`](https://map.audio/developers/music-api/errors#rate_limited) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`idempotency_key_unsupported`](https://map.audio/developers/music-api/errors#idempotency_key_unsupported) ### getUsage `GET https://api.map.audio/v1/usage` Get usage by key and meter Meter totals for the account per API key and meter: render-minutes, rendered audio seconds, compute time, and the free commands' own meters. `from` and `to` are UTC days, at most a year apart and at most 90 days back, as usage is kept that long. Requests made with a map.audio sign-in have `keyId: null`. Free; GET /v1/credits shows what is left to spend. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: Per-key meter totals are for dashboards and audits; get_credits shows the render-minutes an agent can still spend. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `from` | query | string | no | First UTC day, YYYY-MM-DD, at most 90 days back. Default: the first of this month. | | `to` | query | string | no | Last UTC day, included. Default: today. | #### Example ```sh curl -sS "https://api.map.audio/v1/usage?from=2026-11-01&to=2026-11-30" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: This month so far ```json { "object": "usage", "from": "2026-11-01", "to": "2026-11-01", "data": [ { "keyId": "key_01M6BPR91W1E1YGMF0P6TAZZNG", "meter": "audioSeconds", "quantity": 252.8 }, { "keyId": "key_01M6BPR91W1E1YGMF0P6TAZZNG", "meter": "computeMs", "quantity": 61408 }, { "keyId": "key_01M6BPR91W1E1YGMF0P6TAZZNG", "meter": "renderMinutes", "quantity": 4.214 } ] } ``` ##### 400: Invalid request ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "from must be a date as YYYY-MM-DD; got \"yesterday\".", "param": "from", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: Usage. - `400`: Invalid request - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### getCredits `GET https://api.map.audio/v1/credits` Get the render-minute balance What jobs can still spend this month: the tier's allowance, render-minutes used, render-minutes running jobs hold, credits bought, and `remaining`, which the next job draws on. A job reserves its estimate when it is admitted and settles at its outputs' real length. Free. Buy render-minutes or a paid tier at https://map.audio/music. Auth: `Authorization: Bearer` with an API key or a map.audio access token. MCP tool: `get_credits`. #### Example ```sh curl -sS "https://api.map.audio/v1/credits" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: A free-tier account part way through the month ```json { "object": "credits", "period": "2026-11", "unit": "render_minute", "tier": { "id": "free", "name": "Free", "currentPeriodEnd": null, "cancelAtPeriodEnd": false }, "granted": 30, "used": 4.214, "reserved": 0.267, "remaining": 25.519, "grants": [ { "source": "free", "amount": 30, "remaining": 25.519, "expiresAt": "2026-12-01T00:00:00.000Z" } ] } ``` ##### 401: Authentication failed ```json { "error": { "type": "authentication", "code": "authentication_failed", "message": "That API key is not valid or was revoked.", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#authentication_failed", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: Credits. - `401`: Authentication failed - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### listApiKeys `GET https://api.map.audio/v1/api-keys` List API keys The account's API keys, newest first, without their secrets; `prefix` shows each one's first characters so you can tell them apart. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: API keys are created, listed and revoked signed in at https://map.audio/music, so a connected app never holds or handles credentials. #### Example ```sh curl -sS "https://api.map.audio/v1/api-keys" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: One key ```json { "object": "list", "data": [ { "object": "api_key", "id": "key_01M6BPR91W1E1YGMF0P6TAZZNG", "name": "Build server", "mode": "test", "prefix": "sk_test_7Hq2", "createdAt": "2026-10-28T09:12:44.031Z", "lastUsedAt": "2026-11-01T14:03:31.000Z", "revokedAt": null } ] } ``` ##### 400: Invalid request ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "\"cursor\" is not a parameter of GET /v1/api-keys; it takes none.", "param": "cursor", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: Keys. - `400`: Invalid request - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### createApiKey `POST https://api.map.audio/v1/api-keys` Create an API key Create an API key; its secret is in this response only, so store it now. Needs a map.audio sign-in on MAP Audio's own pages (https://map.audio/music), not an API key or another app's token, so a leaked key cannot mint more. `mode` picks the `sk_test_` or `sk_live_` prefix: both modes spend the same render-minutes and see the same jobs and files. An account holds at most 10 active keys. Send `acceptTerms` to accept the terms in the same call. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: API keys are created, listed and revoked signed in at https://map.audio/music, so a connected app never holds or handles credentials. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | A label you choose, to tell keys apart. At most 80 characters. | | `mode` | "test" \| "live" | no | `test` or `live`. Both draw on the same render-minutes; the prefix separates Idempotency-Key namespaces and marks test renders' provenance. Default `"test"`. | | `acceptTerms` | string | no | The prototype terms version being accepted, required until the account has accepted it. | #### Example ```sh curl -sS "https://api.map.audio/v1/api-keys" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Content-Type: application/json" \ -d '{"name":"Production server","mode":"live"}' ``` ##### Request: A live key for a server ```json { "name": "Production server", "mode": "live" } ``` ##### Request: A first test key, accepting the terms ```json { "name": "Laptop", "mode": "test", "acceptTerms": "2026-11-01-prototype" } ``` ##### 201: Created ```json { "object": "api_key", "id": "key_01M6BPRAQ0QXZ93APJ3C3FRWXZ", "name": "Production server", "mode": "live", "prefix": "sk_live_Qm4T", "createdAt": "2026-11-01T14:10:05.311Z", "lastUsedAt": null, "revokedAt": null, "secret": "sk_live_Qm4Tz8VbN2rYp6KcW1sXe9JhL3dGa7FuR5o" } ``` ##### 403: Needs the account owner's sign-in ```json { "error": { "type": "entitlement", "code": "oauth_required", "message": "Only the account owner, signed in at https://map.audio/music, can create or revoke API keys. This request used an API key.", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#oauth_required", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` ##### 403: Key limit reached ```json { "error": { "type": "entitlement", "code": "too_many_keys", "message": "An account may hold 10 active keys; revoke one (DELETE /v1/api-keys/{id}) first.", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#too_many_keys", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `201`: The key, with its secret. - `403`: Needs the account owner's sign-in; Key limit reached - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`oauth_required`](https://map.audio/developers/music-api/errors#oauth_required) - [`terms_version_mismatch`](https://map.audio/developers/music-api/errors#terms_version_mismatch) - [`too_many_keys`](https://map.audio/developers/music-api/errors#too_many_keys) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`idempotency_key_unsupported`](https://map.audio/developers/music-api/errors#idempotency_key_unsupported) ### revokeApiKey `DELETE https://api.map.audio/v1/api-keys/{id}` Revoke an API key Revoke a key at once: requests with it answer 401 from then on, and webhook endpoints whose secret it last set are disabled with it. Needs a map.audio sign-in on MAP Audio's own pages (https://map.audio/music), so a leaked key cannot revoke the others. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: API keys are created, listed and revoked signed in at https://map.audio/music, so a connected app never holds or handles credentials. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | An API key id (key_…), from GET /v1/api-keys; not the secret. Matches `^key_[0-9A-HJKMNP-TV-Z]{26}$`. | #### Example ```sh curl -sS -X DELETE "https://api.map.audio/v1/api-keys/key_01M6BPRAQ0QXZ93APJ3C3FRWXZ" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: Revoked ```json { "object": "api_key", "id": "key_01M6BPRAQ0QXZ93APJ3C3FRWXZ", "name": "Production server", "mode": "live", "prefix": "sk_live_Qm4T", "createdAt": "2026-11-01T14:10:05.311Z", "lastUsedAt": null, "revokedAt": "2026-11-01T14:12:40.902Z" } ``` ##### 404: Not found ```json { "error": { "type": "not_found", "code": "not_found", "message": "No API key with that id; GET /v1/api-keys lists this account's keys.", "param": "id", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#not_found", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The key, revoked. - `404`: Not found - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`oauth_required`](https://map.audio/developers/music-api/errors#oauth_required) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### createBillingCheckout `POST https://api.map.audio/v1/billing/checkout` Buy a tier or render-minutes Open a Stripe Checkout page to subscribe to a paid tier or to buy render-minutes; the tier or credits apply once Stripe confirms the payment. Send `tier` or `renderMinutes`, not both. Needs a map.audio sign-in on MAP Audio's own pages, as https://map.audio/music uses; an API key or another app cannot buy. Answers with a URL for a person to open in a browser. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: Spends money on a Stripe page a person completes in a browser; only MAP Audio's own pages may open one. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `tier` | "creator" \| "pro" \| "app" | no | A paid tier to subscribe to. | | `renderMinutes` | integer | no | Whole render-minutes of pay-as-you-go credit to buy. From 334 to 100000. | | `consent` | object | no | The buyer's analytics and advertising choice on map.audio, which the purchase's analytics and ad reports follow. Without it, both count as denied. | | `consent.analytics` | "granted" \| "denied" | yes | | | `consent.advertising` | "granted" \| "denied" | yes | | #### Example ```sh curl -sS "https://api.map.audio/v1/billing/checkout" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Content-Type: application/json" \ -d '{"tier":"pro","consent":{"analytics":"granted","advertising":"denied"}}' ``` ##### Request: Subscribe to Pro ```json { "tier": "pro", "consent": { "analytics": "granted", "advertising": "denied" } } ``` ##### Request: Buy 500 render-minutes ```json { "renderMinutes": 500 } ``` ##### 200: Checkout ```json { "object": "billing_link", "url": "https://checkout.stripe.com/c/pay/cs_live_a1B2c3D4e5F6g7H8i9J0kLmNoPqRsTuVwXyZ" } ``` ##### 409: Already subscribed ```json { "error": { "type": "conflict", "code": "subscription_exists", "message": "This account already subscribes to a plan; change or cancel it from the billing portal (POST /v1/billing/portal).", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#subscription_exists", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: A Stripe Checkout page. - `409`: Already subscribed - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`oauth_required`](https://map.audio/developers/music-api/errors#oauth_required) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`subscription_exists`](https://map.audio/developers/music-api/errors#subscription_exists) - [`billing_rate_limited`](https://map.audio/developers/music-api/errors#billing_rate_limited) - [`billing_unavailable`](https://map.audio/developers/music-api/errors#billing_unavailable) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`idempotency_key_unsupported`](https://map.audio/developers/music-api/errors#idempotency_key_unsupported) ### createBillingPortal `POST https://api.map.audio/v1/billing/portal` Open the billing portal Open the Stripe billing portal, where a subscriber changes plan or card, cancels and reads invoices. Needs a map.audio sign-in on MAP Audio's own pages. It does not wait on the terms, so a subscription can always be cancelled. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: Spends money on a Stripe page a person completes in a browser; only MAP Audio's own pages may open one. #### Example ```sh curl -sS -X POST "https://api.map.audio/v1/billing/portal" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: Portal ```json { "object": "billing_link", "url": "https://billing.stripe.com/p/session/live_YWNjdF8xUHFSc1R1VndYeVo" } ``` ##### 409: No billing account yet ```json { "error": { "type": "conflict", "code": "billing_not_available", "message": "Nothing has been bought on this account yet, so it has no billing portal; buy with POST /v1/billing/checkout.", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#billing_not_available", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The billing portal. - `409`: No billing account yet - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`oauth_required`](https://map.audio/developers/music-api/errors#oauth_required) - [`billing_not_available`](https://map.audio/developers/music-api/errors#billing_not_available) - [`billing_rate_limited`](https://map.audio/developers/music-api/errors#billing_rate_limited) - [`billing_unavailable`](https://map.audio/developers/music-api/errors#billing_unavailable) - [`idempotency_key_unsupported`](https://map.audio/developers/music-api/errors#idempotency_key_unsupported) ## Catalogue The factory presets the engine renders, the drum-synth engines, the DJ transitions, and this document. ### listPresets `GET https://api.map.audio/v1/presets` Search the factory presets Search the factory presets the engine renders, by words in their names, descriptions, categories and tags, by category and by tempo. Each preset's `ref` (pam:factory/) is what renders, plans, arrangements and DJ sets take, and `variations` lists its slots. Free. Next: POST /v1/renders with `source.preset` set to a `ref`. Auth: `Authorization: Bearer` with an API key or a map.audio access token. MCP tool: `search_presets`. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `q` | query | string | no | Words that must each start a word of a preset's name, description, category, subcategory, author or tags. At most 200 characters. | | `category` | query | string | no | A category, matched without regard to case. | | `tempoMin` | query | number | no | Lowest tempo, in BPM. | | `tempoMax` | query | number | no | Highest tempo, in BPM. | | `limit` | query | integer | no | Presets per page. From 1 to 100. Default `25`. | | `cursor` | query | string | no | The `nextCursor` of the previous page. | #### Example ```sh curl -sS "https://api.map.audio/v1/presets?q=disco&category=Drums&tempoMin=118&tempoMax=128" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: Searching for "disco" ```json { "object": "list", "data": [ { "object": "preset", "slug": "313-disco", "ref": "pam:factory/313-disco", "product": "pam", "name": "313 Disco", "author": "TopograFX", "description": "Motor City disco with aquatic funk", "bpm": 120, "timeSignature": [ 4, 4 ], "category": "Drums", "subcategory": "Disco", "tags": [], "variations": [ 1 ], "sha256": "3afbf5dea1003b2d17385955017785a57d74c65f77f97b58cf5d7cec9794ca1c", "hasSong": false } ], "total": 1, "nextCursor": null } ``` ##### 400: Invalid request ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "limit must be a whole number from 1 to 100; got \"500\".", "param": "limit", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: Matching presets. - `400`: Invalid request - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### getPreset `GET https://api.map.audio/v1/presets/{id}` Get one preset One factory preset by its slug (313-disco) or URL-encoded reference: name, tempo, time signature, category, tags and the variation slots it has. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. MCP tool: `get_preset`. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | A factory preset's slug (313-disco) or its URL-encoded reference (pam%3Afactory%2F313-disco). At most 200 characters. | #### Example ```sh curl -sS "https://api.map.audio/v1/presets/313-disco" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: 313 Disco ```json { "object": "preset", "slug": "313-disco", "ref": "pam:factory/313-disco", "product": "pam", "name": "313 Disco", "author": "TopograFX", "description": "Motor City disco with aquatic funk", "bpm": 120, "timeSignature": [ 4, 4 ], "category": "Drums", "subcategory": "Disco", "tags": [], "variations": [ 1 ], "sha256": "3afbf5dea1003b2d17385955017785a57d74c65f77f97b58cf5d7cec9794ca1c", "hasSong": false } ``` ##### 404: No such preset ```json { "error": { "type": "not_found", "code": "preset_not_found", "message": "No factory preset 313-discoo; list them with GET /v1/presets.", "param": "id", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#preset_not_found", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The preset. - `404`: No such preset - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`preset_not_found`](https://map.audio/developers/music-api/errors#preset_not_found) - [`preset_not_served`](https://map.audio/developers/music-api/errors#preset_not_served) ### listDrumEngines `GET https://api.map.audio/v1/drum-engines` List the drum-synth engines The drum-synth engines POST /v1/synth/drum plays, each with its controls: label, unit and range. Set controls by label in that route's `param` (Tune=55,Decay=1.5); percent controls take 0 to 100. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. MCP tool: `list_drum_engines`. #### Example ```sh curl -sS "https://api.map.audio/v1/drum-engines" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: The first two engines ```json { "object": "list", "data": [ { "id": "1", "slug": "kick", "name": "Kick", "controls": [ { "label": "Tune", "unit": "Hz", "min": 30, "max": 160 }, { "label": "P.Depth", "unit": "ratio", "min": 1, "max": 5 }, { "label": "P.Decay", "unit": "s", "min": 0.005, "max": 0.2 }, { "label": "Decay", "unit": "s", "min": 0.03, "max": 3 }, { "label": "Click", "unit": "percent", "min": 0, "max": 100 }, { "label": "Drive", "unit": "percent", "min": 0, "max": 100 }, { "label": "Tone", "unit": "Hz", "min": 150, "max": 16000 }, { "label": "Wave", "unit": "percent", "min": 0, "max": 100 } ] }, { "id": "2", "slug": "snare", "name": "Snare", "controls": [ { "label": "Tune", "unit": "Hz", "min": 120, "max": 400 }, { "label": "Noise", "unit": "percent", "min": 0, "max": 100 }, { "label": "Decay", "unit": "s", "min": 0.03, "max": 0.5 }, { "label": "N.Decay", "unit": "s", "min": 0.03, "max": 0.6 }, { "label": "N.Tone", "unit": "Hz", "min": 800, "max": 8000 }, { "label": "P.Depth", "unit": "ratio", "min": 1, "max": 3 }, { "label": "P.Decay", "unit": "s", "min": 0.005, "max": 0.3 }, { "label": "Drive", "unit": "percent", "min": 0, "max": 100 } ] } ] } ``` ##### 429: Rate limited ```json { "error": { "type": "rate_limit", "code": "rate_limited", "message": "This API key sent more than 120 requests in a minute, the limit for each key; retry after 10 s.", "param": null, "retryable": true, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#rate_limited", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: Engines. - `429`: Rate limited - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### listTransitionPresets `GET https://api.map.audio/v1/transition-presets` List the DJ transitions What a DJ set's transitions can use: the transition presets a render knows, the modes and curves a transition takes, the longest window in bars, and the DJ set schema's definitions. Read it before writing a set for POST /v1/arrangements or POST /v1/dj/set. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. MCP tool: `list_transition_presets`. #### Example ```sh curl -sS "https://api.map.audio/v1/transition-presets" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: Factory transitions (definitions cut to DjTransition's plain fields) ```json { "object": "transition_presets", "presets": [ "drop", "rise", "break", "blend", "sweep", "fill" ], "modes": [ "auto", "param", "mix", "seq", "all", "swap", "chop" ], "curves": [ "linear", "ease", "exp", "log" ], "maxBars": 32, "schema": "https://map.audio/schemas/pam/dj-set.schema.json", "definitions": { "DjTransition": { "type": "object", "additionalProperties": false, "description": "Resolved in order: the preset's spec, spec merged over it one level deep per section, then bars, mode and curve as the DJ view's Length, Mode and Curve rows apply them.", "properties": { "preset": { "type": "string", "minLength": 1, "maxLength": 64, "description": "A transition preset id: drop, rise, break, blend, sweep, fill, or one the user saved." }, "bars": { "type": "number", "minimum": 0, "maximum": 32 }, "mode": { "enum": [ "auto", "param", "mix", "seq", "all", "swap", "chop" ] }, "curve": { "enum": [ "linear", "ease", "exp", "log" ] } } } } } ``` ##### 401: Authentication failed ```json { "error": { "type": "authentication", "code": "authentication_failed", "message": "Send an API key or a map.audio access token as `Authorization: Bearer …`.", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#authentication_failed", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: Transitions. - `401`: Authentication failed - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### getOpenApi `GET https://api.map.audio/v1/openapi.json` Get this OpenAPI document This document, with `servers` set to the origin that served it. Public. Free. Auth: none; this route is public. Not an MCP tool: This document; MCP clients get the same schemas from tools/list. #### Example ```sh curl -sS "https://api.map.audio/v1/openapi.json" \ -H "Map-Version: 2026-11-01" ``` ##### 200: The document (shortened) ```json { "openapi": "3.1.0", "info": { "title": "MAP Audio Music API", "version": "2026-11-01" }, "servers": [ { "url": "https://api.map.audio" } ] } ``` ##### 500: Internal error ```json { "error": { "type": "unavailable", "code": "internal_error", "message": "Something went wrong on our side; nothing was charged.", "param": null, "retryable": true, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#internal_error", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: OpenAPI 3.1. - `500`: Internal error - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. ## Plans Turn a short prompt, or a spec you write, into an editable render plan. Free; no AI model. ### createPlan `POST https://api.map.audio/v1/plans` Plan a render from a prompt Turn a short prompt into an editable render plan, or store one you wrote. The `rules` planner matches the prompt's words and tempo against the catalogue, with no AI model, and returns its ranked candidates and a spec; `caller` stores your own spec once every reference checks out. Plans are free and never change: edit a spec and send it back as a `caller` plan. Next: POST /v1/renders with `plan` set to the plan's id. Auth: `Authorization: Bearer` with an API key or a map.audio access token. MCP tool: `create_plan`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `planner` | "rules" \| "caller" | no | `rules` plans from `prompt` and `constraints`; `caller` stores the `spec` you send. Default `"rules"`. | | `prompt` | string | no | What you want, in a few words: genre, mood, instruments, a tempo such as 128 bpm, or "variations". At most 500 characters. | | `spec` | object | no | | | `spec.product` | "pam" | no | Default `"pam"`. | | `spec.base` | object | yes | A factory preset, and optionally one of its variation slots. | | `spec.base.preset` | string | yes | A factory preset `pam:factory/`, or its bare slug. At most 200 characters. | | `spec.base.variation` | integer | no | A variation slot of the preset, numbered from 1; GET /v1/presets/{id} lists the slots it has. Default: its base state. From 1 to 16. | | `spec.tempo` | number | no | Render at this tempo instead of the preset's, in BPM. From 20 to 400. | | `spec.morph` | object | no | | | `spec.morph.to` | object | yes | A factory preset, and optionally one of its variation slots. | | `spec.morph.at` | number | no | Morph position: 0 is the start, 1 the target; values outside 0..1 extrapolate along the same line. From -1 to 2. Default `0.5`. | | `spec.morph.curve` | "linear" \| "ease" \| "exp" \| "log" | no | How the morph position eases, as a live Glide shapes its window. Default `"ease"`. | | `spec.morph.pattern` | "dissolve" \| "wipe" \| "cells" | no | Stage the cells' patterns at `at` as a Steps transition does; without it the patterns stay the start's below 1. | | `spec.morph.patternSeed` | integer | no | Draws each step of a `dissolve`, so the same seed gives the same steps. From 0 to 4294967295. | | `spec.blend` | array of object | no | 2 to 16 items. | | `spec.blend[].preset` | string | yes | A factory preset `pam:factory/`, or its bare slug. At most 200 characters. | | `spec.blend[].variation` | integer | no | The preset's variation slot to blend; default: its base state. From 1 to 16. | | `spec.blend[].weight` | number | yes | More than 0. | | `spec.envelopes` | array of object | no | State envelopes ({delta, options?}) applied in order before rendering: the same envelopes POST /v1/state takes. 0 to 64 items. | | `spec.arrangement` | object | no | `loop` renders `bars` of the base; `variations` renders each listed slot. | | `spec.arrangement.kind` | "loop" \| "variations" | yes | | | `spec.arrangement.bars` | integer | no | From 1 to 64. | | `spec.arrangement.variations` | array of integer | no | 0 to 16 items. | | `seed` | integer | no | Breaks ties between equally good presets; the same seed and prompt give the same plan. From 0 to 4294967295. Default `0`. | | `constraints` | object or null | no | | | `constraints.tempo` | number | no | From 20 to 400. | | `constraints.category` | string | no | At most 60 characters. | | `constraints.candidates` | integer | no | From 1 to 10. Default `3`. | #### Example ```sh curl -sS "https://api.map.audio/v1/plans" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Content-Type: application/json" \ -d '{"prompt":"dark techno around 125 bpm","constraints":{"candidates":3},"seed":7}' ``` ##### Request: Plan from a prompt ```json { "prompt": "dark techno around 125 bpm", "constraints": { "candidates": 3 }, "seed": 7 } ``` ##### Request: Store your own spec: a morph toward another preset at 128 BPM ```json { "planner": "caller", "spec": { "base": { "preset": "pam:factory/acid-sprinkle", "variation": 1 }, "tempo": 128, "morph": { "to": { "preset": "pam:factory/teknochonic" }, "at": 0.35 }, "arrangement": { "kind": "loop", "bars": 8 } } } ``` ##### 201: A stored caller plan ```json { "object": "plan", "id": "plan_01M6BPRCC4FTKB29RFYD2JDKJQ", "seed": 0, "prompt": null, "planner": { "kind": "caller", "version": "caller-1", "model": null }, "candidates": [], "spec": { "product": "pam", "base": { "preset": "pam:factory/acid-sprinkle", "variation": 1 }, "tempo": 128, "morph": { "to": { "preset": "pam:factory/teknochonic" }, "at": 0.35 }, "arrangement": { "kind": "loop", "bars": 8 } }, "createdAt": "2026-11-01T14:02:10.250Z", "provenance": { "aiModels": [], "presets": [ "pam:factory/acid-sprinkle", "pam:factory/teknochonic" ] } } ``` ##### 400: Invalid request ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "Give a prompt, or constraints with a tempo or category, to plan from.", "param": "prompt", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `201`: The plan. - `400`: Invalid request - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`preset_not_found`](https://map.audio/developers/music-api/errors#preset_not_found) - [`preset_not_served`](https://map.audio/developers/music-api/errors#preset_not_served) - [`variation_not_found`](https://map.audio/developers/music-api/errors#variation_not_found) - [`invalid_envelope`](https://map.audio/developers/music-api/errors#invalid_envelope) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`idempotency_key_unsupported`](https://map.audio/developers/music-api/errors#idempotency_key_unsupported) ### getPlan `GET https://api.map.audio/v1/plans/{id}` Get a plan A stored plan: its spec, the candidates the rules planner ranked, and the presets it names. Plans never change; render one with POST /v1/renders and `plan`. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. MCP tool: `get_plan`. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | A plan id (plan_…), from POST /v1/plans. Matches `^plan_[0-9A-HJKMNP-TV-Z]{26}$`. | #### Example ```sh curl -sS "https://api.map.audio/v1/plans/plan_01M6BPRCC4FTKB29RFYD2JDKJQ" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: A caller plan ```json { "object": "plan", "id": "plan_01M6BPRCC4FTKB29RFYD2JDKJQ", "seed": 0, "prompt": null, "planner": { "kind": "caller", "version": "caller-1", "model": null }, "candidates": [], "spec": { "product": "pam", "base": { "preset": "pam:factory/acid-sprinkle", "variation": 1 }, "tempo": 128, "morph": { "to": { "preset": "pam:factory/teknochonic" }, "at": 0.35 }, "arrangement": { "kind": "loop", "bars": 8 } }, "createdAt": "2026-11-01T14:02:10.250Z", "provenance": { "aiModels": [], "presets": [ "pam:factory/acid-sprinkle", "pam:factory/teknochonic" ] } } ``` ##### 404: Not found ```json { "error": { "type": "not_found", "code": "not_found", "message": "No plan with that id; plans come from POST /v1/plans, which returns the id.", "param": "id", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#not_found", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The plan. - `404`: Not found - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ## Renders Render presets and plans to audio, and fit cutdowns or check DJ sets before rendering. ### createRender `POST https://api.map.audio/v1/renders` Render presets or a plan to audio Render a factory preset or a stored plan as one job, with one output or many: a length in bars or seconds, a variation, stems, a sample rate, WAV, FLAC or MP3, and a seed (the job reports the seed it used; sending it again renders the same audio). `variations` renders several slots, seeds, morph positions or a blend; `items` names outputs of their own lengths or cutdowns; `quality: standard` renders candidate seeds, measures each and keeps the best. Costs render-minutes: each output's length in minutes, at least 0.1 an output, shown in `estimate` before it runs. Answers 202 with the queued job unless `Prefer: wait` covers it; its outputs are in `result.outputs` once it succeeds. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. MCP tool: `create_render`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `source` | object | no | The preset to render; give `source` or `plan`. | | `source.preset` | string | yes | A factory preset `pam:factory/`, or its bare slug. At most 200 characters. | | `source.variation` | integer | no | A variation slot of the preset, numbered from 1; GET /v1/presets/{id} lists the slots it has. Default: its base state. From 1 to 16. | | `plan` | string | no | A stored plan's id (plan_…), instead of `source`. Matches `^plan_`. | | `bars` | integer | no | Length in bars of the preset's time signature, at its tempo (or the plan's). Default 4; give `bars` or `seconds`. From 1 to 64. | | `seconds` | number | no | Length in seconds, instead of `bars`. More than 0, up to 300. | | `tail` | number | no | Seconds of effect tail after the transport stops, added to the length. From 0 to 30. Default `0`. | | `seed` | integer | no | Seed for everything random in the render; 0 draws one, which the job reports, and sending that seed again renders the same audio. From 0 to 4294967295. Default `0`. | | `stems` | boolean | no | Also write one stem per cell, captured after cell effects and before the master chain, encoded like the master. Default `false`. | | `sampleRate` | 22050 \| 32000 \| 44100 \| 48000 | no | Output sample rate in Hz. Default `48000`. | | `format` | object | no | Container and MP3 bitrate for every output; WAV unless you ask. | | `format.container` | "wav" \| "flac" \| "mp3" | no | Default `"wav"`. | | `format.bitrate` | 96 \| 112 \| 128 \| 160 \| 192 \| 224 \| 256 \| 320 | no | MP3 only, kbit/s. At 22050 Hz the most is 160, which is then the default. Default `320`. | | `quality` | "draft" \| "standard" \| "elite" | no | standard renders `search.candidates` seeds, measures each and returns the best that pass the limit gate; elite is refused until a curated archive exists. Default `"draft"`. | | `search` | object | no | Only with quality standard. | | `search.candidates` | integer | no | From 2 to 8. Default `4`. | | `search.return` | integer | no | From 1 to 4. Default `1`. | | `search.targetLufs` | number | no | From -40 to -6. | | `variations` | object, kind slots, seeds, morph, blend | no | Several outputs from one job: `slots` renders variation slots, `seeds` takes from derived seeds, `morph` renders positions between the source and `to`, `blend` renders a weighted blend of presets. | | `variations.slots` | array of integer | no | With kind `slots`. 1 to 16 items. | | `variations.count` | integer | yes | With kind `seeds`. From 2 to 8. | | `variations.to` | object | yes | With kind `morph`. A factory preset, and optionally one of its variation slots. | | `variations.to.preset` | string | yes | With kind `morph`. A factory preset `pam:factory/`, or its bare slug. At most 200 characters. | | `variations.to.variation` | integer | no | With kind `morph`. A variation slot of the preset, numbered from 1; GET /v1/presets/{id} lists the slots it has. Default: its base state. From 1 to 16. | | `variations.at` | array of number | yes | With kind `morph`. 2 to 8 items. | | `variations.curve` | "linear" \| "ease" \| "exp" \| "log" | no | With kind `morph`. | | `variations.pattern` | "dissolve" \| "wipe" \| "cells" | no | With kind `morph`. | | `variations.patternSeed` | integer | no | With kind `morph`. From 0 to 4294967295. | | `variations.presets` | array of object | yes | With kind `blend`. 2 to 16 items. | | `variations.presets[].preset` | string | yes | With kind `blend`. A factory preset `pam:factory/`, or its bare slug. At most 200 characters. | | `variations.presets[].variation` | integer | no | With kind `blend`. The preset's variation slot to blend; default: its base state. From 1 to 16. | | `variations.presets[].weight` | number | yes | With kind `blend`. More than 0. | | `items` | array of object | no | Named outputs of their own lengths: a variation for some bars, or a `cutdown` fitted to seconds across the preset's variations. 1 to 8 items. | | `items[].name` | string | yes | Matches `^[A-Za-z0-9_-]{1,40}$`. | | `items[].variation` | integer | no | From 1 to 16. | | `items[].bars` | integer | no | From 1 to 64. | | `items[].cutdown` | object | no | Fits the preset to this length across its variations. An item with a cutdown takes no variation or bars. | | `items[].cutdown.seconds` | number | yes | From 5 to 300. | | `preview` | object | no | A short render published first, as a `job.preview` event. A job that succeeds pays for its render or its preview, whichever is longer; if it fails or is cancelled, the preview is still charged at its own length. | | `preview.bars` | integer | no | From 1 to 8. Default `4`. | | `metadata` | map of string | no | Up to 20 string pairs stored with the job and returned with it. At most 20 entries. | #### Example ```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/313-disco"},"bars":8,"seed":42}' ``` ##### Request: Eight bars of 313 Disco as WAV, with a fixed seed ```json { "source": { "preset": "pam:factory/313-disco" }, "bars": 8, "seed": 42 } ``` ##### Request: Four of Cal's variation slots in one job, as FLAC ```json { "source": { "preset": "pam:factory/cal" }, "bars": 8, "variations": { "kind": "slots", "slots": [ 1, 2, 3, 4 ] }, "format": { "container": "flac" } } ``` ##### Request: Four takes of Totes Rando, each from its own seed ```json { "source": { "preset": "pam:factory/totes-rando" }, "bars": 4, "variations": { "kind": "seeds", "count": 4 }, "seed": 7 } ``` ##### Request: Sixteen bars of a variation with one stem per cell ```json { "source": { "preset": "pam:factory/breakbeatrcool", "variation": 2 }, "bars": 16, "stems": true, "seed": 42 } ``` ##### Request: Three points on a morph from Acid Sprinkle toward Teknochonic ```json { "source": { "preset": "pam:factory/acid-sprinkle" }, "bars": 8, "variations": { "kind": "morph", "to": { "preset": "pam:factory/teknochonic" }, "at": [ 0.25, 0.5, 0.75 ] } } ``` ##### Request: 30- and 15-second cutdowns for an ad, as 256 kbit/s MP3 ```json { "source": { "preset": "pam:factory/thaw" }, "items": [ { "name": "spot-30", "cutdown": { "seconds": 30 } }, { "name": "spot-15", "cutdown": { "seconds": 15 } } ], "format": { "container": "mp3", "bitrate": 256 } } ``` ##### Request: Render four seeds and keep the one nearest -14 LUFS ```json { "source": { "preset": "pam:factory/junglistic" }, "bars": 8, "quality": "standard", "search": { "candidates": 4, "return": 1, "targetLufs": -14 } } ``` ##### Request: Two minutes, with a 4-bar preview published first ```json { "source": { "preset": "pam:factory/corduroy" }, "seconds": 120, "preview": { "bars": 4 }, "seed": 11 } ``` ##### Request: Render a stored plan ```json { "plan": "plan_01M6BPRCC4FTKB29RFYD2JDKJQ", "format": { "container": "mp3" } } ``` ##### 200: Succeeded: 16 s of 313 Disco ```json { "object": "job", "id": "job_01M6BPRE186QHP9QPYGDX5NNFP", "operation": "renders.create", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 16, "renderMinutes": 0.267 }, "result": { "outputs": [ { "role": "master", "name": null, "cell": null, "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b", "bytes": 4608044, "format": "wav", "sampleRate": 48000, "durationSeconds": 16, "url": "https://api.map.audio/v1/download/file_01M6BPRK0M34NBP2XKJ1RMX1TQ?expires=1793545422&signature=yaoMrgCHcK9T8MaVnCVtXZc4Ym0FWWM8P-0feZDMObk", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": 42, "command": { "ok": true, "wavPath": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sampleRate": 48000, "channels": 2, "durationSeconds": 16, "seed": 42, "stems": [], "stemTap": "post_cell_pre_master", "latencySamples": 0, "missingSamples": [] }, "spec": { "source": { "preset": "pam:factory/313-disco" }, "plan": null, "bars": 8, "seconds": null, "tail": 0, "seed": 42, "stems": false, "sampleRate": 48000, "format": { "container": "wav" }, "quality": "draft", "tempo": 120 }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRE186QHP9QPYGDX5NNFP", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": 42, "presets": [ { "ref": "pam:factory/313-disco", "sha256": "3afbf5dea1003b2d17385955017785a57d74c65f77f97b58cf5d7cec9794ca1c" } ], "packs": [], "userFiles": [], "inputs": [], "outputs": [ { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:jsfkW3eF1r4kChqccp6AKLTvjTK5Re8acrEusqigz7MDUu6uZhsJXVnuQEl6EsErDgVYNXOkos9d0mMyt2CVCg" } }, "error": null, "usage": { "renderMinutes": 0.267, "audioSeconds": 16, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP", "events": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/cancel" } } ``` ##### 202: Queued ```json { "object": "job", "id": "job_01M6BPRE186QHP9QPYGDX5NNFP", "operation": "renders.create", "status": "queued", "livemode": false, "sequence": 1, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": null, "finishedAt": null, "estimate": { "audioSeconds": 16, "renderMinutes": 0.267 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP", "events": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/cancel" } } ``` ##### 400: Render too long ```json { "error": { "type": "invalid_request", "code": "render_too_long", "message": "This render is about 310 s (seconds 300 plus tail 10 s); a render may be at most 300 s.", "param": "seconds", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#render_too_long", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` ##### 402: Not enough render-minutes ```json { "error": { "type": "entitlement", "code": "insufficient_credits", "message": "This job needs 0.267 render-minutes and 0.012 remain this month; make it shorter, or buy render-minutes at https://map.audio/music.", "param": null, "details": { "needed": 0.267, "remaining": 0.012 }, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#insufficient_credits", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` ##### 403: Terms not accepted ```json { "error": { "type": "entitlement", "code": "terms_required", "message": "This account has not accepted the prototype terms (2026-11-01-prototype), which work needs. Read and accept them at https://map.audio/music, or send POST /v1/account/terms with {\"accept\": \"2026-11-01-prototype\"} (over MCP, the accept_terms tool).", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#terms_required", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The finished job, when it ends within `Prefer: wait`. - `202`: The queued job; poll `urls.self`, stream `urls.events`, or wait for a webhook. - `400`: Render too long - `402`: Not enough render-minutes - `403`: Terms not accepted - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`preset_not_found`](https://map.audio/developers/music-api/errors#preset_not_found) - [`preset_not_served`](https://map.audio/developers/music-api/errors#preset_not_served) - [`variation_not_found`](https://map.audio/developers/music-api/errors#variation_not_found) - [`quality_unavailable`](https://map.audio/developers/music-api/errors#quality_unavailable) - [`render_too_long`](https://map.audio/developers/music-api/errors#render_too_long) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`insufficient_credits`](https://map.audio/developers/music-api/errors#insufficient_credits) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) ### createArrangement `POST https://api.map.audio/v1/arrangements` Fit a cutdown or check a DJ set Plan a render without rendering. `cutdown` fits one preset (from `source` or a stored `plan`) to a length in seconds as a DJ set over its variations; `set` checks a DJ set you wrote: references, variations, lengths and transitions. The answer is the set in preset references, its tempo and length, and each item's start in bars and seconds. It costs nothing; render the set with POST /v1/dj/set, or a cutdown as an item of POST /v1/renders. Auth: `Authorization: Bearer` with an API key or a map.audio access token. MCP tool: `create_arrangement`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `source` | object | no | The preset a cutdown fits; give `source` or `plan`. | | `source.preset` | string | yes | A factory preset `pam:factory/`, or its bare slug. At most 200 characters. | | `source.variation` | integer | no | A variation slot of the preset, numbered from 1; GET /v1/presets/{id} lists the slots it has. Default: its base state. From 1 to 16. | | `plan` | string | no | A stored plan whose base preset and tempo a cutdown fits. Matches `^plan_`. | | `cutdown` | object | no | Fit the preset to about this many seconds, as an opening, a main section and an ending over its variations. | | `cutdown.seconds` | number | yes | From 5 to 300. | | `set` | object | no | A DJ set to check: `queue` lists its items in play order, each naming a factory preset (`presetPath`), its variation (`variationIndex`, counted from 0), its length in `bars` and the `transition` into it. GET /v1/transition-presets lists the transitions and the set schema's definitions. | #### Example ```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}}' ``` ##### Request: Fit Thaw to about 30 seconds ```json { "source": { "preset": "pam:factory/thaw" }, "cutdown": { "seconds": 30 } } ``` ##### Request: Check a three-preset set ```json { "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 } } } ``` ##### 200: Thaw fitted to 30.9 seconds ```json { "object": "arrangement", "set": { "queue": [ { "presetPath": "pam:factory/thaw", "variationIndex": 0, "bars": 4 }, { "presetPath": "pam:factory/thaw", "variationIndex": 3, "bars": 9, "transition": { "preset": "blend", "bars": 2 } }, { "presetPath": "pam:factory/thaw", "variationIndex": 5, "bars": 4, "transition": { "preset": "blend", "bars": 2 } } ], "tempo": { "bpm": 132 } }, "seconds": 30.909, "bpm": 132, "bars": 17, "boundaries": [ { "index": 0, "preset": "pam:factory/thaw", "variation": 1, "startBar": 0, "startSeconds": 0, "bars": 4 }, { "index": 1, "preset": "pam:factory/thaw", "variation": 4, "startBar": 4, "startSeconds": 7.273, "bars": 9 }, { "index": 2, "preset": "pam:factory/thaw", "variation": 6, "startBar": 13, "startSeconds": 23.636, "bars": 4 } ] } ``` ##### 400: Unknown transition preset ```json { "error": { "type": "invalid_request", "code": "dj_unknown_transition_preset", "message": "Unknown transition preset swoosh; GET /v1/transition-presets lists the ones a render can use.", "param": "set.queue[1].transition.preset", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#dj_unknown_transition_preset", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The arrangement. - `400`: Unknown transition preset - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`preset_not_found`](https://map.audio/developers/music-api/errors#preset_not_found) - [`preset_not_served`](https://map.audio/developers/music-api/errors#preset_not_served) - [`variation_not_found`](https://map.audio/developers/music-api/errors#variation_not_found) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`dj_invalid_set`](https://map.audio/developers/music-api/errors#dj_invalid_set) - [`dj_offline_unsupported`](https://map.audio/developers/music-api/errors#dj_offline_unsupported) - [`dj_preset_missing`](https://map.audio/developers/music-api/errors#dj_preset_missing) - [`dj_unknown_transition_preset`](https://map.audio/developers/music-api/errors#dj_unknown_transition_preset) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`idempotency_key_unsupported`](https://map.audio/developers/music-api/errors#idempotency_key_unsupported) ## Commands PAM's engine commands as endpoints: audio analysis and editing, preset renders, effects, mastering, headless state, DJ sets, patterns and drum synthesis. Body fields use the same names as the commands' MCP arguments. ### audioAnalyze `POST https://api.map.audio/v1/audio/analyze` Measure audio loudness and peaks (BS.1770). Measure audio this account holds, such as a render's master: loudness (BS.1770), true peak, spectrum, stereo image, tempo, key, sections and defects. Send file ids from a job's `result.outputs`; the measurements come back in `result.command`, under `files` when you send more than one. Run it on a render before you keep or deliver it, or to compare seeds. Free: it counts in GET /v1/usage, never in render-minutes. Answers 200 with the finished job when it ends within `Prefer: wait=N` (30 s unless sent, at most 60), else 202 with the job to poll. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. MCP tool: `audio_analyze`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `files` | array of string | yes | Ids (file_…) of WAV or FLAC audio this account holds, at most 16. 1 to 16 items. | | `peaks` | boolean | no | Include the existing 48-bin RMS envelope. | | `pitch` | boolean | no | Estimate mono pitch and confidence. | | `class` | boolean | no | Classify the mono sample and report measured features. | | `spectrum` | boolean | no | Long-term third-octave spectrum of mid and side, and each band's energy share (sub, low, lowMid, mid, highMid, air), relative to the file's total energy from 20 Hz to 20 kHz. | | `stereo` | boolean | no | Side minus mid and L/R correlation per band, and the mono fold-down's loudness difference. | | `hpss` | boolean | no | Harmonic, percussive and residual energy shares from the Transient card's spectral split (HpssCore, FIRM). | | `sections` | boolean | no | Section boundaries from Foote novelty over third-octave features, with each section's loudness and true peak. | | `transients` | boolean | no | Transient times from the sampler's transient detector. | | `beats` | boolean | no | Tempo in BPM, its confidence and the first downbeat, from the DJ view's beat tracker; a tempo in the file name ("loop 92 BPM.wav") fixes the octave. | | `musicalKey` | boolean | no | Musical key ("A minor") with its Camelot code and confidence, from the DJ view's key estimate. | | `levels` | boolean | no | Level statistics: RMS, PLR (true peak minus integrated), PSR (3 s true peak minus short-term loudness, median and lowest), the loudest momentary and short-term loudness, block crest and level swing. | | `validity` | boolean | no | File defects: non-finite samples with the first frame and channel, subnormals, DC offset, clipped samples, where sound starts and ends, and the level of the last 100 ms. | | `timbre` | boolean | no | Timbre: spectral centroid, spread, skewness, kurtosis and 95% rolloff in Hz, flatness and crest in dB (median and 90th percentile over the frames within 60 dB of the loudest) across 20 Hz-20 kHz and per broad band, MFCCs 1-13 (mean and standard deviation) and each pitch class's share of the power (chroma, C first). | | `compare` | string | no | Compare with a reference file at the same rate, such as a render's input, lined up by `latency`: the null raw, after one best gain and after the best linear filter, the largest and RMS sample difference, the residual per broad band, gain and coherence per third octave, the envelope change per band, and MFCC and chroma distances. The id (file_…) of WAV or FLAC audio this account holds: a render output, or an upload once uploads open. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | | `latency` | integer | no | Frames the file lags the `compare` reference by, such as a render's latencySamples. From -8388608 to 8388608. Default `0`. | | `thd` | number | no | A steady tone's distortion at this frequency in Hz, over the whole file: harmonics in dBc, THD, THD+N, the even harmonics' share and the worst spur. From 1 to 192000. | | `imd` | string | no | A steady twin tone's intermodulation, its two frequencies in Hz as 19000,20000: the difference tone and all products against the pair. | | `window` | number | no | Adds a per-window series of `spectrum`, `stereo` and `hpss` values, this many seconds a window, at most 256 windows. From 0.5 to 3600. | | `metadata` | map of string | no | Up to 20 string pairs stored with the job. At most 20 entries. | #### Example ```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"],"levels":true,"validity":true}' ``` ##### Request: Check a render's loudness and look for defects ```json { "files": [ "file_01M6BPRK0M34NBP2XKJ1RMX1TQ" ], "levels": true, "validity": true } ``` ##### 200: Finished ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "audioAnalyze", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": { "outputs": [], "seed": null, "command": { "ok": true, "path": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "format": "wav", "sampleRate": 48000, "channels": 2, "durationSeconds": 16, "integratedLufs": -9.6, "loudnessRange": 3.8, "truePeakDb": -0.7, "samplePeakDb": -1.1, "levels": { "rmsDbfs": -11.4, "plrDb": 8.9, "psrDb": 7.6, "minPsrDb": 5.2, "maxMomentaryLufs": -6.9, "maxShortTermLufs": -8.3, "crestDb": 10.3, "swingDb": 6.1 }, "validity": { "nonFinite": { "count": 0, "firstFrame": null, "firstChannel": null, "firstSeconds": null }, "subnormals": 0, "dc": [ 0.0001, -0.0001 ], "dcDbfs": -80, "clipping": { "threshold": 0.99997, "samples": 0, "firstSeconds": null }, "firstAudibleSeconds": 0, "lastAudibleSeconds": 15.98, "tailRmsDbfs": -31.2 } }, "provenance": null }, "error": null, "usage": { "renderMinutes": 0, "audioSeconds": 0, "computeMs": 184 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 202: Still running ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "audioAnalyze", "status": "running", "livemode": false, "sequence": 2, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": null, "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 404: Not found ```json { "error": { "type": "not_found", "code": "not_found", "message": "No file with that id; GET /v1/files lists this account's files.", "param": "files[0]", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#not_found", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The finished job: `result.outputs` link each file it wrote and `result.command` is the engine's own result. - `202`: The job, still running when `Prefer: wait` ran out; poll `urls.self`. - `404`: Not found - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`file_not_ready`](https://map.audio/developers/music-api/errors#file_not_ready) - [`file_expired`](https://map.audio/developers/music-api/errors#file_expired) - [`uploads_disabled`](https://map.audio/developers/music-api/errors#uploads_disabled) ### audioNormalize `POST https://api.map.audio/v1/audio/normalize` Normalize loudness and verify a true-peak ceiling. Bring a file to a loudness target under a true-peak ceiling, for a platform's delivery spec (-14 LUFS and -1 dBTP unless you send others). Writes a new WAV and reports the loudness it reached; the source file stays as it was. Free: it counts in GET /v1/usage, never in render-minutes. Answers 200 with the finished job when it ends within `Prefer: wait=N` (30 s unless sent, at most 60), else 202 with the job to poll. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. MCP tool: `audio_normalize`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `file` | string | yes | The id (file_…) of WAV or FLAC audio this account holds: a render output, or an upload once uploads open. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | | `lufs` | number | no | Target integrated LUFS; achieved loudness is reported if the peak ceiling prevents the target. From -60 to 0. Default `-14`. | | `tp` | number | no | Maximum measured true peak in dBTP. From -24 to 0. Default `-1`. | | `metadata` | map of string | no | Up to 20 string pairs stored with the job. At most 20 entries. | #### Example ```sh curl -sS "https://api.map.audio/v1/audio/normalize" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Prefer: wait=30" \ -H "Content-Type: application/json" \ -d '{"file":"file_01M6BPRK0M34NBP2XKJ1RMX1TQ","lufs":-14,"tp":-1}' ``` ##### Request: Normalize a render to -14 LUFS with a -1 dBTP ceiling ```json { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "lufs": -14, "tp": -1 } ``` ##### 200: Finished ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "audioNormalize", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": { "outputs": [ { "role": "master", "name": null, "cell": null, "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4", "bytes": 4608044, "format": "wav", "sampleRate": 48000, "durationSeconds": 16, "url": "https://api.map.audio/v1/download/file_01M6BPRMNR7PYGFNA66BX2YJ8M?expires=1793545422&signature=GQ2GkmPjqXuxxBSSM4NKBCY7OjU7w3YSeZBrCoxxtbY", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": null, "command": { "ok": true, "wavPath": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sampleRate": 48000, "channels": 2, "durationSeconds": 16, "integratedLufs": -14, "truePeakDb": -1.4, "targetReached": true }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": null, "presets": [], "packs": [], "userFiles": [], "inputs": [ { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b" } ], "outputs": [ { "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:LEVZHpOHj8BMGZgJh-_Y7SmKbqDlBDR8LqY-XelU6GYK8lntZqg3rRK4b3BH8cy25bK0iWzOXFXmkiGHEYue7A" } }, "error": null, "usage": { "renderMinutes": 0, "audioSeconds": 16, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 202: Still running ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "audioNormalize", "status": "running", "livemode": false, "sequence": 2, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": null, "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 410: File no longer stored ```json { "error": { "type": "gone", "code": "file_expired", "message": "File file_01M6BPRK0M34NBP2XKJ1RMX1TQ expired at 2026-11-08T14:03:41.877Z; render it again.", "param": "file", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#file_expired", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The finished job: `result.outputs` link each file it wrote and `result.command` is the engine's own result. - `202`: The job, still running when `Prefer: wait` ran out; poll `urls.self`. - `410`: File no longer stored - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`file_not_ready`](https://map.audio/developers/music-api/errors#file_not_ready) - [`file_expired`](https://map.audio/developers/music-api/errors#file_expired) - [`uploads_disabled`](https://map.audio/developers/music-api/errors#uploads_disabled) ### audioRepitch `POST https://api.map.audio/v1/audio/repitch` Render varispeed pitch shifting using the browser repitch core. Shift a file's pitch by varispeed, as a turntable does: tempo and length move with the pitch, so 12 semitones down plays at half speed. Writes a new WAV. Free: it counts in GET /v1/usage, never in render-minutes. Answers 200 with the finished job when it ends within `Prefer: wait=N` (30 s unless sent, at most 60), else 202 with the job to poll. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. MCP tool: `audio_repitch`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `file` | string | yes | The id (file_…) of WAV or FLAC audio this account holds: a render output, or an upload once uploads open. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | | `semitones` | number | no | Varispeed pitch shift; duration changes. From -24 to 24. Default `0`. | | `cents` | number | no | Additional pitch shift in cents. From -100 to 100. Default `0`. | | `metadata` | map of string | no | Up to 20 string pairs stored with the job. At most 20 entries. | #### Example ```sh curl -sS "https://api.map.audio/v1/audio/repitch" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Prefer: wait=30" \ -H "Content-Type: application/json" \ -d '{"file":"file_01M6BPRK0M34NBP2XKJ1RMX1TQ","semitones":-2}' ``` ##### Request: Drop a loop two semitones ```json { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "semitones": -2 } ``` ##### 200: Finished ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "audioRepitch", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": { "outputs": [ { "role": "master", "name": null, "cell": null, "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4", "bytes": 5172236, "format": "wav", "sampleRate": 48000, "durationSeconds": 17.959, "url": "https://api.map.audio/v1/download/file_01M6BPRMNR7PYGFNA66BX2YJ8M?expires=1793545422&signature=GQ2GkmPjqXuxxBSSM4NKBCY7OjU7w3YSeZBrCoxxtbY", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": null, "command": { "ok": true, "wavPath": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sampleRate": 48000, "channels": 2, "durationSeconds": 17.959 }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": null, "presets": [], "packs": [], "userFiles": [], "inputs": [ { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b" } ], "outputs": [ { "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:LEVZHpOHj8BMGZgJh-_Y7SmKbqDlBDR8LqY-XelU6GYK8lntZqg3rRK4b3BH8cy25bK0iWzOXFXmkiGHEYue7A" } }, "error": null, "usage": { "renderMinutes": 0, "audioSeconds": 17.959, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 202: Still running ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "audioRepitch", "status": "running", "livemode": false, "sequence": 2, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": null, "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 400: Invalid request ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "semitones must be at most 24; got 30.", "param": "semitones", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The finished job: `result.outputs` link each file it wrote and `result.command` is the engine's own result. - `202`: The job, still running when `Prefer: wait` ran out; poll `urls.self`. - `400`: Invalid request - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`file_not_ready`](https://map.audio/developers/music-api/errors#file_not_ready) - [`file_expired`](https://map.audio/developers/music-api/errors#file_expired) - [`uploads_disabled`](https://map.audio/developers/music-api/errors#uploads_disabled) ### audioTrim `POST https://api.map.audio/v1/audio/trim` Trim a sample by time using the shared sample-edit core. Cut a file to a time range in seconds, such as a loop's first bars or a hit without its lead-in. Writes a new WAV. Free: it counts in GET /v1/usage, never in render-minutes. Answers 200 with the finished job when it ends within `Prefer: wait=N` (30 s unless sent, at most 60), else 202 with the job to poll. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. MCP tool: `audio_trim`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `file` | string | yes | The id (file_…) of WAV or FLAC audio this account holds: a render output, or an upload once uploads open. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | | `start` | number | no | Trim start in seconds. From 0 to 86400. Default `0`. | | `end` | number | no | Trim end in seconds; absent uses file end. From 0 to 86400. | | `metadata` | map of string | no | Up to 20 string pairs stored with the job. At most 20 entries. | #### Example ```sh curl -sS "https://api.map.audio/v1/audio/trim" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Prefer: wait=30" \ -H "Content-Type: application/json" \ -d '{"file":"file_01M6BPRK0M34NBP2XKJ1RMX1TQ","start":0,"end":8}' ``` ##### Request: Keep the first 8 seconds ```json { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "start": 0, "end": 8 } ``` ##### 200: Finished ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "audioTrim", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": { "outputs": [ { "role": "master", "name": null, "cell": null, "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4", "bytes": 2304044, "format": "wav", "sampleRate": 48000, "durationSeconds": 8, "url": "https://api.map.audio/v1/download/file_01M6BPRMNR7PYGFNA66BX2YJ8M?expires=1793545422&signature=GQ2GkmPjqXuxxBSSM4NKBCY7OjU7w3YSeZBrCoxxtbY", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": null, "command": { "ok": true, "wavPath": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sampleRate": 48000, "channels": 2, "durationSeconds": 8 }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": null, "presets": [], "packs": [], "userFiles": [], "inputs": [ { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b" } ], "outputs": [ { "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:LEVZHpOHj8BMGZgJh-_Y7SmKbqDlBDR8LqY-XelU6GYK8lntZqg3rRK4b3BH8cy25bK0iWzOXFXmkiGHEYue7A" } }, "error": null, "usage": { "renderMinutes": 0, "audioSeconds": 8, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 202: Still running ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "audioTrim", "status": "running", "livemode": false, "sequence": 2, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": null, "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 400: Invalid request ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "end (2 s) must be after start (4 s).", "param": "end", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The finished job: `result.outputs` link each file it wrote and `result.command` is the engine's own result. - `202`: The job, still running when `Prefer: wait` ran out; poll `urls.self`. - `400`: Invalid request - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`file_not_ready`](https://map.audio/developers/music-api/errors#file_not_ready) - [`file_expired`](https://map.audio/developers/music-api/errors#file_expired) - [`uploads_disabled`](https://map.audio/developers/music-api/errors#uploads_disabled) ### audioGain `POST https://api.map.audio/v1/audio/gain` Apply sample gain using the shared sample-edit core. Raise or lower a file's level by a number of decibels; samples past full scale clip. For a loudness target, POST /v1/audio/normalize measures and sets the level for you. Free: it counts in GET /v1/usage, never in render-minutes. Answers 200 with the finished job when it ends within `Prefer: wait=N` (30 s unless sent, at most 60), else 202 with the job to poll. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. MCP tool: `audio_gain`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `file` | string | yes | The id (file_…) of WAV or FLAC audio this account holds: a render output, or an upload once uploads open. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | | `db` | number | yes | Gain in decibels; samples exceeding full scale clip in the output WAV. From -96 to 48. | | `metadata` | map of string | no | Up to 20 string pairs stored with the job. At most 20 entries. | #### Example ```sh curl -sS "https://api.map.audio/v1/audio/gain" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Prefer: wait=30" \ -H "Content-Type: application/json" \ -d '{"file":"file_01M6BPRK0M34NBP2XKJ1RMX1TQ","db":-3}' ``` ##### Request: Turn a render down 3 dB ```json { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "db": -3 } ``` ##### 200: Finished ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "audioGain", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": { "outputs": [ { "role": "master", "name": null, "cell": null, "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4", "bytes": 4608044, "format": "wav", "sampleRate": 48000, "durationSeconds": 16, "url": "https://api.map.audio/v1/download/file_01M6BPRMNR7PYGFNA66BX2YJ8M?expires=1793545422&signature=GQ2GkmPjqXuxxBSSM4NKBCY7OjU7w3YSeZBrCoxxtbY", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": null, "command": { "ok": true, "wavPath": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sampleRate": 48000, "channels": 2, "durationSeconds": 16 }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": null, "presets": [], "packs": [], "userFiles": [], "inputs": [ { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b" } ], "outputs": [ { "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:LEVZHpOHj8BMGZgJh-_Y7SmKbqDlBDR8LqY-XelU6GYK8lntZqg3rRK4b3BH8cy25bK0iWzOXFXmkiGHEYue7A" } }, "error": null, "usage": { "renderMinutes": 0, "audioSeconds": 16, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 202: Still running ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "audioGain", "status": "running", "livemode": false, "sequence": 2, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": null, "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 400: Invalid request ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "db is required.", "param": "db", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The finished job: `result.outputs` link each file it wrote and `result.command` is the engine's own result. - `202`: The job, still running when `Prefer: wait` ran out; poll `urls.self`. - `400`: Invalid request - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`file_not_ready`](https://map.audio/developers/music-api/errors#file_not_ready) - [`file_expired`](https://map.audio/developers/music-api/errors#file_expired) - [`uploads_disabled`](https://map.audio/developers/music-api/errors#uploads_disabled) ### audioReverse `POST https://api.map.audio/v1/audio/reverse` Reverse a sample using the shared sample-edit core. Play a file backwards, for reverse cymbals, risers and transitions. Writes a new WAV. Free: it counts in GET /v1/usage, never in render-minutes. Answers 200 with the finished job when it ends within `Prefer: wait=N` (30 s unless sent, at most 60), else 202 with the job to poll. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. MCP tool: `audio_reverse`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `file` | string | yes | The id (file_…) of WAV or FLAC audio this account holds: a render output, or an upload once uploads open. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | | `metadata` | map of string | no | Up to 20 string pairs stored with the job. At most 20 entries. | #### Example ```sh curl -sS "https://api.map.audio/v1/audio/reverse" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Prefer: wait=30" \ -H "Content-Type: application/json" \ -d '{"file":"file_01M6BPRK0M34NBP2XKJ1RMX1TQ"}' ``` ##### Request: Reverse a render ```json { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ" } ``` ##### 200: Finished ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "audioReverse", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": { "outputs": [ { "role": "master", "name": null, "cell": null, "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4", "bytes": 4608044, "format": "wav", "sampleRate": 48000, "durationSeconds": 16, "url": "https://api.map.audio/v1/download/file_01M6BPRMNR7PYGFNA66BX2YJ8M?expires=1793545422&signature=GQ2GkmPjqXuxxBSSM4NKBCY7OjU7w3YSeZBrCoxxtbY", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": null, "command": { "ok": true, "wavPath": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sampleRate": 48000, "channels": 2, "durationSeconds": 16 }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": null, "presets": [], "packs": [], "userFiles": [], "inputs": [ { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b" } ], "outputs": [ { "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:LEVZHpOHj8BMGZgJh-_Y7SmKbqDlBDR8LqY-XelU6GYK8lntZqg3rRK4b3BH8cy25bK0iWzOXFXmkiGHEYue7A" } }, "error": null, "usage": { "renderMinutes": 0, "audioSeconds": 16, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 202: Still running ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "audioReverse", "status": "running", "livemode": false, "sequence": 2, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": null, "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 404: Not found ```json { "error": { "type": "not_found", "code": "not_found", "message": "No file with that id; GET /v1/files lists this account's files.", "param": "file", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#not_found", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The finished job: `result.outputs` link each file it wrote and `result.command` is the engine's own result. - `202`: The job, still running when `Prefer: wait` ran out; poll `urls.self`. - `404`: Not found - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`file_not_ready`](https://map.audio/developers/music-api/errors#file_not_ready) - [`file_expired`](https://map.audio/developers/music-api/errors#file_expired) - [`uploads_disabled`](https://map.audio/developers/music-api/errors#uploads_disabled) ### presetRender `POST https://api.map.audio/v1/preset/render` Render a PAM preset to WAV without opening PAM. The engine's own render command: one factory preset to one WAV, with optional stems and an input file that the preset's External cells play. POST /v1/renders is the render API for everything else (FLAC and MP3, variation and seed batches, cutdowns, quality search, previews); use this route for an input file or the exact `pam preset render` options. It costs render-minutes for the audio it renders: its length in minutes, at least 0.1 a render. Answers 202 with the queued job: poll `GET /v1/jobs/{id}`, stream `GET /v1/jobs/{id}/events`, or send `Prefer: wait=N` (at most 60 s) to get the finished job in this response. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. MCP tool: `preset_render`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `preset` | string | yes | A factory preset reference, pam:factory/ or the bare slug; GET /v1/presets lists them. At most 200 characters. | | `seconds` | number | no | Render length in seconds; mutually exclusive with `bars`. Defaults to the input duration for fx and audio master. From 0.001 to 300. | | `bars` | integer | no | Length in bars (1..64): pattern generation uses four beats and defaults to 1; preset rendering uses the preset time signature and defaults to 4. From 1 to 64. | | `sampleRate` | integer | no | Output sample rate. From 22050 to 48000. Default `48000`. | | `tail` | number | no | Additional effect tail in seconds after transport stops. From 0 to 30. Default `0`. | | `seed` | integer | no | Reproducible seed; zero draws a random seed. From 0 to 4294967295. Default `0`. | | `variation` | integer | no | Preset variation, numbered from 1. From 1 to 16. | | `stems` | boolean | no | Write each cell stem alongside the master in one render pass. | | `input` | string | no | Audio played into the engine's input, through the PAM cells whose source is External. Without `seconds` or `bars` the render lasts as long as the input, plus `tail`. The id (file_…) of WAV or FLAC audio this account holds: a render output, or an upload once uploads open. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | | `metadata` | map of string | no | Up to 20 string pairs stored with the job. At most 20 entries. | #### Example ```sh curl -sS "https://api.map.audio/v1/preset/render" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Prefer: wait=30" \ -H "Content-Type: application/json" \ -d '{"preset":"pam:factory/313-disco","bars":8,"seed":42}' ``` ##### Request: Render 8 bars of 313 Disco with a fixed seed ```json { "preset": "pam:factory/313-disco", "bars": 8, "seed": 42 } ``` ##### 200: Finished ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "presetRender", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 16, "renderMinutes": 0.267 }, "result": { "outputs": [ { "role": "master", "name": null, "cell": null, "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4", "bytes": 4608044, "format": "wav", "sampleRate": 48000, "durationSeconds": 16, "url": "https://api.map.audio/v1/download/file_01M6BPRMNR7PYGFNA66BX2YJ8M?expires=1793545422&signature=GQ2GkmPjqXuxxBSSM4NKBCY7OjU7w3YSeZBrCoxxtbY", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": 42, "command": { "ok": true, "wavPath": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sampleRate": 48000, "durationSeconds": 16, "seed": 42, "stems": [], "latencySamples": 0 }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": 42, "presets": [ { "ref": "pam:factory/313-disco", "sha256": "3afbf5dea1003b2d17385955017785a57d74c65f77f97b58cf5d7cec9794ca1c" } ], "packs": [], "userFiles": [], "inputs": [], "outputs": [ { "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:LEVZHpOHj8BMGZgJh-_Y7SmKbqDlBDR8LqY-XelU6GYK8lntZqg3rRK4b3BH8cy25bK0iWzOXFXmkiGHEYue7A" } }, "error": null, "usage": { "renderMinutes": 0.267, "audioSeconds": 16, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 202: Queued ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "presetRender", "status": "queued", "livemode": false, "sequence": 1, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": null, "finishedAt": null, "estimate": { "audioSeconds": 16, "renderMinutes": 0.267 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 402: Not enough render-minutes ```json { "error": { "type": "entitlement", "code": "insufficient_credits", "message": "This job needs 0.267 render-minutes and 0.012 remain this month; make it shorter, or buy render-minutes at https://map.audio/music.", "param": null, "details": { "needed": 0.267, "remaining": 0.012 }, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#insufficient_credits", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The finished job: `result.outputs` link each file it wrote and `result.command` is the engine's own result. - `202`: The queued job; poll `urls.self` or stream `urls.events`. - `402`: Not enough render-minutes - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) - [`insufficient_credits`](https://map.audio/developers/music-api/errors#insufficient_credits) - [`render_too_long`](https://map.audio/developers/music-api/errors#render_too_long) - [`preset_not_found`](https://map.audio/developers/music-api/errors#preset_not_found) - [`preset_not_served`](https://map.audio/developers/music-api/errors#preset_not_served) - [`variation_not_found`](https://map.audio/developers/music-api/errors#variation_not_found) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`file_not_ready`](https://map.audio/developers/music-api/errors#file_not_ready) - [`file_expired`](https://map.audio/developers/music-api/errors#file_expired) - [`uploads_disabled`](https://map.audio/developers/music-api/errors#uploads_disabled) ### projectRemix `POST https://api.map.audio/v1/project/remix` Remix an Ableton Live set, Bitwig project, DAWproject or FL Studio project into a PAM preset built from its own samples, MIDI and sections. Turn an uploaded DAW project (Ableton Live, Bitwig, DAWproject or FL Studio) into a PAM preset built from its own samples, MIDI and sections, returned as a .zip that holds the preset and its samples. Free: it counts in GET /v1/usage, never in render-minutes. Answers 202 with the queued job: poll `GET /v1/jobs/{id}`, stream `GET /v1/jobs/{id}/events`, or send `Prefer: wait=N` (at most 60 s) to get the finished job in this response. Needs uploads, which stay off until the decoder and project-reader fuzzers have run; until then it answers 403 uploads_disabled. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. MCP tool: `project_remix`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `project` | string | yes | The id (file_…) of an uploaded DAW project or zipped project folder. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | | `seed` | integer | no | Seed for the remix variations; absent or zero derives one from the project, so the same project remixes the same way as a drop into PAM. From 0 to 4294967295. Default `0`. | | `name` | string | no | Preset name; absent uses the project file name followed by Remix. | | `faithful` | boolean | no | Keep only the project's own sections; skip PAM's remix variations and song tail. | | `metadata` | map of string | no | Up to 20 string pairs stored with the job. At most 20 entries. | #### Example ```sh curl -sS "https://api.map.audio/v1/project/remix" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Prefer: wait=30" \ -H "Content-Type: application/json" \ -d '{"project":"file_01M6BPRR00A11F2KEPGNR7V3Q9","seed":7}' ``` ##### Request: Remix an uploaded Live set with a fixed seed ```json { "project": "file_01M6BPRR00A11F2KEPGNR7V3Q9", "seed": 7 } ``` ##### 200: Finished ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "projectRemix", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": { "outputs": [ { "role": "preset", "name": null, "cell": null, "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4", "bytes": 18734, "format": "zip", "sampleRate": null, "durationSeconds": null, "url": "https://api.map.audio/v1/download/file_01M6BPRMNR7PYGFNA66BX2YJ8M?expires=1793545422&signature=GQ2GkmPjqXuxxBSSM4NKBCY7OjU7w3YSeZBrCoxxtbY", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": 7, "command": { "ok": true, "presetPath": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "bundled": true, "presetName": "Night Drive Remix", "bpm": 124, "timeSignature": "4/4", "seed": 7, "variations": [ "Intro", "Groove", "Break", "Drop" ], "missingSampleCount": 0, "warnings": [] }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": 7, "presets": [], "packs": [], "userFiles": [], "inputs": [ { "file": "file_01M6BPRR00A11F2KEPGNR7V3Q9", "sha256": "e20c37e5e2607bde5dfd78c1fe0b7fd000e0209a38b9e4eb963d14cfe7191b16" } ], "outputs": [ { "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:LEVZHpOHj8BMGZgJh-_Y7SmKbqDlBDR8LqY-XelU6GYK8lntZqg3rRK4b3BH8cy25bK0iWzOXFXmkiGHEYue7A" } }, "error": null, "usage": { "renderMinutes": 0, "audioSeconds": 0, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 202: Queued ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "projectRemix", "status": "queued", "livemode": false, "sequence": 1, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": null, "finishedAt": null, "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 403: Uploads not open yet ```json { "error": { "type": "entitlement", "code": "uploads_disabled", "message": "POST /v1/project/remix works on uploaded files, and uploads are not open yet. Nothing was charged.", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#uploads_disabled", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The finished job: `result.outputs` link each file it wrote and `result.command` is the engine's own result. - `202`: The queued job; poll `urls.self` or stream `urls.events`. - `403`: Uploads not open yet - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`file_not_ready`](https://map.audio/developers/music-api/errors#file_not_ready) - [`file_expired`](https://map.audio/developers/music-api/errors#file_expired) - [`uploads_disabled`](https://map.audio/developers/music-api/errors#uploads_disabled) ### fx `POST https://api.map.audio/v1/fx` Render a PAM cell effect chain over an audio file. Run a file through PAM's cell effects: one cell's chain (delay, flanger, crusher, EQ and the rest of the cell rack), set as PAM parameters in `chain`. Without bars or seconds the render lasts as long as the file, plus `tail`. It costs render-minutes for the audio it renders: its length in minutes, at least 0.1 a render. Answers 202 with the queued job: poll `GET /v1/jobs/{id}`, stream `GET /v1/jobs/{id}/events`, or send `Prefer: wait=N` (at most 60 s) to get the finished job in this response. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. MCP tool: `fx`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `file` | string | yes | The id (file_…) of WAV or FLAC audio this account holds: a render output, or an upload once uploads open. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | | `seconds` | number | no | Render length in seconds; mutually exclusive with `bars`. Defaults to the input duration for fx and audio master. From 0.001 to 300. | | `bars` | integer | no | Length in bars (1..64): pattern generation uses four beats and defaults to 1; preset rendering uses the preset time signature and defaults to 4. From 1 to 64. | | `sampleRate` | integer | no | Output sample rate. From 22050 to 48000. Default `48000`. | | `tail` | number | no | Additional effect tail in seconds after transport stops. From 0 to 30. Default `0`. | | `seed` | integer | no | Reproducible seed; zero draws a random seed. From 0 to 4294967295. Default `0`. | | `chain` | object | yes | A PAM state fragment as a JSON object: for fx, cell1_* settings, cell1_effectOrder and cells.1.effectConfig, or the same as {params, effectConfig, effectOrder} without the cell1_ prefix; master* settings for audio master. | | `metadata` | map of string | no | Up to 20 string pairs stored with the job. At most 20 entries. | #### Example ```sh curl -sS "https://api.map.audio/v1/fx" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Prefer: wait=30" \ -H "Content-Type: application/json" \ -d '{"file":"file_01M6BPRK0M34NBP2XKJ1RMX1TQ","chain":{"params":{"delayMix":30,"delayFeedback":45},"effectConfig":{"delayEnabled":true},"effectOrder":["delay"]},"tail":2}' ``` ##### Request: Put a render through a feedback delay with a 2-second tail ```json { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "chain": { "params": { "delayMix": 30, "delayFeedback": 45 }, "effectConfig": { "delayEnabled": true }, "effectOrder": [ "delay" ] }, "tail": 2 } ``` ##### 200: Finished ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "fx", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 18, "renderMinutes": 0.3 }, "result": { "outputs": [ { "role": "master", "name": null, "cell": null, "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4", "bytes": 5184044, "format": "wav", "sampleRate": 48000, "durationSeconds": 18, "url": "https://api.map.audio/v1/download/file_01M6BPRMNR7PYGFNA66BX2YJ8M?expires=1793545422&signature=GQ2GkmPjqXuxxBSSM4NKBCY7OjU7w3YSeZBrCoxxtbY", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": 1795302311, "command": { "ok": true, "wavPath": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sampleRate": 48000, "durationSeconds": 18, "seed": 1795302311, "stems": [], "latencySamples": 0 }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": 1795302311, "presets": [], "packs": [], "userFiles": [], "inputs": [ { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b" } ], "outputs": [ { "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:LEVZHpOHj8BMGZgJh-_Y7SmKbqDlBDR8LqY-XelU6GYK8lntZqg3rRK4b3BH8cy25bK0iWzOXFXmkiGHEYue7A" } }, "error": null, "usage": { "renderMinutes": 0.3, "audioSeconds": 18, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 202: Queued ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "fx", "status": "queued", "livemode": false, "sequence": 1, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": null, "finishedAt": null, "estimate": { "audioSeconds": 18, "renderMinutes": 0.3 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 400: Invalid request ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "chain must be a JSON object; got a string.", "param": "chain", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The finished job: `result.outputs` link each file it wrote and `result.command` is the engine's own result. - `202`: The queued job; poll `urls.self` or stream `urls.events`. - `400`: Invalid request - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) - [`insufficient_credits`](https://map.audio/developers/music-api/errors#insufficient_credits) - [`render_too_long`](https://map.audio/developers/music-api/errors#render_too_long) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`file_not_ready`](https://map.audio/developers/music-api/errors#file_not_ready) - [`file_expired`](https://map.audio/developers/music-api/errors#file_expired) - [`uploads_disabled`](https://map.audio/developers/music-api/errors#uploads_disabled) ### audioMaster `POST https://api.map.audio/v1/audio/master` Render the PAM master effect chain over an audio file. Run a file through PAM's master chain (EQ, compression, tape, clipping, limiting and the other master stages), set as master* parameters in `chain`. For loudness alone, POST /v1/audio/normalize is free. It costs render-minutes for the audio it renders: its length in minutes, at least 0.1 a render. Answers 202 with the queued job: poll `GET /v1/jobs/{id}`, stream `GET /v1/jobs/{id}/events`, or send `Prefer: wait=N` (at most 60 s) to get the finished job in this response. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. MCP tool: `audio_master`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `file` | string | yes | The id (file_…) of WAV or FLAC audio this account holds: a render output, or an upload once uploads open. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | | `seconds` | number | no | Render length in seconds; mutually exclusive with `bars`. Defaults to the input duration for fx and audio master. From 0.001 to 300. | | `bars` | integer | no | Length in bars (1..64): pattern generation uses four beats and defaults to 1; preset rendering uses the preset time signature and defaults to 4. From 1 to 64. | | `sampleRate` | integer | no | Output sample rate. From 22050 to 48000. Default `48000`. | | `tail` | number | no | Additional effect tail in seconds after transport stops. From 0 to 30. Default `0`. | | `seed` | integer | no | Reproducible seed; zero draws a random seed. From 0 to 4294967295. Default `0`. | | `chain` | object | yes | A PAM state fragment as a JSON object: for fx, cell1_* settings, cell1_effectOrder and cells.1.effectConfig, or the same as {params, effectConfig, effectOrder} without the cell1_ prefix; master* settings for audio master. | | `metadata` | map of string | no | Up to 20 string pairs stored with the job. At most 20 entries. | #### Example ```sh curl -sS "https://api.map.audio/v1/audio/master" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Prefer: wait=30" \ -H "Content-Type: application/json" \ -d '{"file":"file_01M6BPRK0M34NBP2XKJ1RMX1TQ","chain":{"masterEqEnabled":1,"masterEqTilt":-1.5,"masterCompressorEnabled":1,"masterCompThreshold":-18,"masterCompRatio":2,"masterTapeEnabled":1,"masterTapeDrive":0.35}}' ``` ##### Request: Master a render with a gentle tilt, glue compression and tape ```json { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "chain": { "masterEqEnabled": 1, "masterEqTilt": -1.5, "masterCompressorEnabled": 1, "masterCompThreshold": -18, "masterCompRatio": 2, "masterTapeEnabled": 1, "masterTapeDrive": 0.35 } } ``` ##### 200: Finished ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "audioMaster", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 16, "renderMinutes": 0.267 }, "result": { "outputs": [ { "role": "master", "name": null, "cell": null, "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4", "bytes": 4608044, "format": "wav", "sampleRate": 48000, "durationSeconds": 16, "url": "https://api.map.audio/v1/download/file_01M6BPRMNR7PYGFNA66BX2YJ8M?expires=1793545422&signature=GQ2GkmPjqXuxxBSSM4NKBCY7OjU7w3YSeZBrCoxxtbY", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": 2290871365, "command": { "ok": true, "wavPath": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sampleRate": 48000, "durationSeconds": 16, "seed": 2290871365, "stems": [], "latencySamples": 0 }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": 2290871365, "presets": [], "packs": [], "userFiles": [], "inputs": [ { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b" } ], "outputs": [ { "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:LEVZHpOHj8BMGZgJh-_Y7SmKbqDlBDR8LqY-XelU6GYK8lntZqg3rRK4b3BH8cy25bK0iWzOXFXmkiGHEYue7A" } }, "error": null, "usage": { "renderMinutes": 0.267, "audioSeconds": 16, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 202: Queued ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "audioMaster", "status": "queued", "livemode": false, "sequence": 1, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": null, "finishedAt": null, "estimate": { "audioSeconds": 16, "renderMinutes": 0.267 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 409: File not ready ```json { "error": { "type": "conflict", "code": "file_not_ready", "message": "File file_01M6BPRK0M34NBP2XKJ1RMX1TQ is still being written by job_01M6BPRE186QHP9QPYGDX5NNFP; retry when that job succeeds.", "param": "file", "retryable": true, "jobId": "job_01M6BPRE186QHP9QPYGDX5NNFP", "docUrl": "https://map.audio/developers/music-api/errors#file_not_ready", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The finished job: `result.outputs` link each file it wrote and `result.command` is the engine's own result. - `202`: The queued job; poll `urls.self` or stream `urls.events`. - `409`: File not ready - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) - [`insufficient_credits`](https://map.audio/developers/music-api/errors#insufficient_credits) - [`render_too_long`](https://map.audio/developers/music-api/errors#render_too_long) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`file_not_ready`](https://map.audio/developers/music-api/errors#file_not_ready) - [`file_expired`](https://map.audio/developers/music-api/errors#file_expired) - [`uploads_disabled`](https://map.audio/developers/music-api/errors#uploads_disabled) ### state `POST https://api.map.audio/v1/state` 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. Run PAM's engine headless on a preset's state: apply envelopes (parameter changes), morph toward another preset or variation, or blend presets, then read the resulting state or render it. Without `render` it is free and answers at once; with `render: true` it renders like POST /v1/renders and also returns the session as a .preset file. Free without render; with render: true it costs render-minutes for the audio it renders. Answers 200 with the finished job when it ends within `Prefer: wait=N` (30 s unless sent, at most 60), else 202 with the job to poll; with render: true it waits only as long as `Prefer: wait` asks. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. MCP tool: `state`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `preset` | string | no | A factory preset reference, pam:factory/ or the bare slug; GET /v1/presets lists them. At most 200 characters. | | `envelopes` | object or array of any | no | State envelopes ({delta, options?}, or {directNodeOps, options?} without a delta) applied in order, as a JSON array or object. | | `keys` | string | no | Top-level state keys to return, as a comma list or a JSON array; absent keys are listed in missingKeys. | | `tempo` | number | no | Transport tempo in beats per minute before the envelopes apply. From 20 to 999. | | `playing` | boolean | no | Start the transport before the envelopes apply. | | `variation` | integer | no | Preset variation, numbered from 1. From 1 to 16. | | `morphTo` | string | no | Preset to morph toward, from the preset argument or Init, at the position `at` gives. Below 1 the session stays the start's and only its parameters move; from 1 it is this preset's. A factory preset reference, pam:factory/ or the bare slug; GET /v1/presets lists them. At most 200 characters. | | `toVariation` | integer | no | Variation, numbered from 1, the morph moves toward: of the `morphTo` preset, or of the preset argument without `morphTo`. From 1 to 16. | | `at` | number | no | Morph position: 0 is the start, 1 the target. Values outside 0..1 extrapolate along the same line, clamped to each parameter's range. | | `curve` | "linear" \| "ease" \| "exp" \| "log" | no | Shape applied to `at` between 0 and 1, as a live Glide shapes its window. Default `"ease"`. | | `discrete` | string | no | When parameters a Glide switches instead of moving (modes, selectors, timing) take the target's value: hold until 1, switch once `at` is above 0, or a threshold between 0 and 1. Default `"hold"`. | | `pattern` | "dissolve" \| "wipe" \| "cells" | no | Stage the cells' patterns at `at` as a Steps transition does: dissolve fades the start's steps out and the target's in through their probability, wipe sweeps a frontier through each pattern, cells hands patterns over cell by cell. Without it the patterns stay the start's below 1. | | `patternCurve` | "linear" \| "ease" \| "exp" \| "log" | no | Shape applied to `at` for `pattern`, as a live transition shapes its pattern stages. Default `"linear"`. | | `patternSeed` | integer | no | Decide each step a `pattern` dissolve scaled: a start-only step stays with probability 1 - p and a target-only step arrives with p, drawn from this seed, so the cells hold a concrete pattern. The same seed gives the same steps. From 0 to 4294967295. | | `relations` | "off" \| "stepped" | no | A live Glide's relations: stepped moves a drum synth's pitch through its semitones instead of switching it at the anchor. Default `"off"`. | | `stagger` | "off" \| "cell" \| "category" | no | A live Glide's cascade: parameters start moving group by group, by cell or by category, over the `staggerSpread` fraction of the window. Default `"off"`. | | `staggerSpread` | number | no | Fraction of the window a `stagger` cascade spreads its groups' starts over; 0 staggers nothing, as in a live transition. From 0 to 0.9. Default `0`. | | `staggerOrder` | "forward" \| "reverse" | no | Order of a `stagger` cascade: forward starts the first cell or category first. Default `"forward"`. | | `liveCap` | boolean | no | Move only the 1,024 largest moves, as a live Glide does when more parameters differ; the others switch as `discrete` says. | | `blend` | array of object | no | Presets to blend instead of a start and a target, each with a positive weight; weights are relative. 2 to 16 items. | | `blend[].preset` | string | yes | A factory preset `pam:factory/`, or its bare slug. At most 200 characters. | | `blend[].variation` | integer | no | The preset's variation slot to blend; default: its base state. From 1 to 16. | | `blend[].weight` | number | yes | More than 0. | | `render` | boolean | no | Render the session the envelopes leave to WAV, as preset render renders a preset. | | `seconds` | number | no | Render length in seconds; mutually exclusive with `bars`. Defaults to the input duration for fx and audio master. From 0.001 to 300. | | `bars` | integer | no | Length in bars (1..64): pattern generation uses four beats and defaults to 1; preset rendering uses the preset time signature and defaults to 4. From 1 to 64. | | `sampleRate` | integer | no | Output sample rate. From 22050 to 48000. Default `48000`. | | `tail` | number | no | Additional effect tail in seconds after transport stops. From 0 to 30. Default `0`. | | `seed` | integer | no | Reproducible seed; zero draws a random seed. From 0 to 4294967295. Default `0`. | | `stems` | boolean | no | Write each cell stem alongside the master in one render pass. | | `input` | string | no | Audio played into the engine's input, through the PAM cells whose source is External. Without `seconds` or `bars` the render lasts as long as the input, plus `tail`. The id (file_…) of WAV or FLAC audio this account holds: a render output, or an upload once uploads open. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | | `saveSlot` | integer | no | Variation slot, numbered from 1, that holds the saved state; the other slots keep the session's bank. Defaults to the slot the session is on. From 1 to 8. | | `meta` | object | no | Metadata for the preset a render saves, as a JSON object: name, author, description, category, tags and the other browser fields. | | `metadata` | map of string | no | Up to 20 string pairs stored with the job. At most 20 entries. | #### Example ```sh curl -sS "https://api.map.audio/v1/state" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Prefer: wait=30" \ -H "Content-Type: application/json" \ -d '{"preset":"pam:factory/acid-sprinkle","variation":1,"toVariation":3,"at":0.5,"render":true,"bars":8,"seed":7}' ``` ##### Request: Render 8 bars halfway between two variations of Acid Sprinkle ```json { "preset": "pam:factory/acid-sprinkle", "variation": 1, "toVariation": 3, "at": 0.5, "render": true, "bars": 8, "seed": 7 } ``` ##### 200: Finished ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "state", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 15.484, "renderMinutes": 0.258 }, "result": { "outputs": [ { "role": "master", "name": null, "cell": null, "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4", "bytes": 4459436, "format": "wav", "sampleRate": 48000, "durationSeconds": 15.484, "url": "https://api.map.audio/v1/download/file_01M6BPRMNR7PYGFNA66BX2YJ8M?expires=1793545422&signature=GQ2GkmPjqXuxxBSSM4NKBCY7OjU7w3YSeZBrCoxxtbY", "expiresAt": "2026-11-01T15:03:42.000Z" }, { "role": "preset", "name": null, "cell": null, "file": "file_01M6BPRPAWG14FNFS6AKGVPEHH", "sha256": "da659a796033a280992f3e665c5eb95ebfe7a08b753aa574d84cf70db61b8b29", "bytes": 18734, "format": "preset", "sampleRate": null, "durationSeconds": null, "url": "https://api.map.audio/v1/download/file_01M6BPRPAWG14FNFS6AKGVPEHH?expires=1793545422&signature=oRSM1YDQ_hhkALQN7N4rgA-xKU4SFZlZGkf6rO28LlI", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": 7, "command": { "ok": true, "source": { "kind": "preset", "path": "pam:factory/acid-sprinkle", "variation": 1 }, "applied": 0, "render": { "wavPath": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sampleRate": 48000, "channels": 2, "durationSeconds": 15.484, "seed": 7, "stems": [] }, "saved": { "presetPath": "file_01M6BPRPAWG14FNFS6AKGVPEHH", "name": "session", "variation": 1, "product": "pam" }, "ms": 1840 }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": 7, "presets": [ { "ref": "pam:factory/acid-sprinkle", "variation": 1, "sha256": "eee63255e1350ec8f03f098656befcc3c1c1c23c0bd7933f93fee84071f9279d" } ], "packs": [], "userFiles": [], "inputs": [], "outputs": [ { "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4" }, { "file": "file_01M6BPRPAWG14FNFS6AKGVPEHH", "sha256": "da659a796033a280992f3e665c5eb95ebfe7a08b753aa574d84cf70db61b8b29" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:LEVZHpOHj8BMGZgJh-_Y7SmKbqDlBDR8LqY-XelU6GYK8lntZqg3rRK4b3BH8cy25bK0iWzOXFXmkiGHEYue7A" } }, "error": null, "usage": { "renderMinutes": 0.258, "audioSeconds": 15.484, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 202: Still running ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "state", "status": "running", "livemode": false, "sequence": 2, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": null, "estimate": { "audioSeconds": 15.484, "renderMinutes": 0.258 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 400: Invalid request ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "bars needs render: true.", "param": "bars", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The finished job: `result.outputs` link each file it wrote and `result.command` is the engine's own result. - `202`: The job, still running when `Prefer: wait` ran out; poll `urls.self`. - `400`: Invalid request - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) - [`insufficient_credits`](https://map.audio/developers/music-api/errors#insufficient_credits) - [`render_too_long`](https://map.audio/developers/music-api/errors#render_too_long) - [`preset_not_found`](https://map.audio/developers/music-api/errors#preset_not_found) - [`preset_not_served`](https://map.audio/developers/music-api/errors#preset_not_served) - [`variation_not_found`](https://map.audio/developers/music-api/errors#variation_not_found) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`file_not_ready`](https://map.audio/developers/music-api/errors#file_not_ready) - [`file_expired`](https://map.audio/developers/music-api/errors#file_expired) - [`uploads_disabled`](https://map.audio/developers/music-api/errors#uploads_disabled) - [`invalid_envelope`](https://map.audio/developers/music-api/errors#invalid_envelope) ### djSet `POST https://api.map.audio/v1/dj/set` Render a DJ set of PAM presets to WAV, each boundary on its DJ view transition, without opening PAM. Render a DJ set: factory presets in order, each boundary on its DJ-view transition (blend, drop, rise, break, sweep or fill), as one WAV. Check the set first with POST /v1/arrangements, which costs nothing and returns the boundaries; GET /v1/transition-presets lists the transitions. It costs render-minutes for the audio it renders: its length in minutes, at least 0.1 a render. Answers 202 with the queued job: poll `GET /v1/jobs/{id}`, stream `GET /v1/jobs/{id}/events`, or send `Prefer: wait=N` (at most 60 s) to get the finished job in this response. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. MCP tool: `render_dj_set`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `set` | object | yes | The set to render. `queue` lists its items in play order: `queue[0]` plays first, and each item names a factory preset (`presetPath`, such as pam:factory/313-disco), its variation (`variationIndex`, counted from 0), its length in `bars` and the `transition` into it. `stepBars`, `launchBars`, `defaultTransition` and `tempo` set the defaults; without `tempo.bpm` the set runs at its first preset's tempo, and `auto: false` is refused because a render steps through the set itself. POST /v1/arrangements checks a set without rendering it; GET /v1/transition-presets lists the transition presets, modes and the set schema's definitions. | | `sampleRate` | integer | no | Output sample rate. From 22050 to 48000. Default `48000`. | | `tail` | number | no | Additional effect tail in seconds after transport stops. From 0 to 30. Default `0`. | | `seed` | integer | no | Reproducible seed; zero draws a random seed. From 0 to 4294967295. Default `0`. | | `stems` | boolean | no | Write each cell stem alongside the master in one render pass. | | `metadata` | map of string | no | Up to 20 string pairs stored with the job. At most 20 entries. | #### Example ```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}' ``` ##### Request: Three presets at 124 BPM: an 8-bar blend, then a drop ```json { "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 } ``` ##### 200: Finished ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "djSet", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 92.903, "renderMinutes": 1.548 }, "result": { "outputs": [ { "role": "master", "name": null, "cell": null, "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4", "bytes": 26756108, "format": "wav", "sampleRate": 48000, "durationSeconds": 92.903, "url": "https://api.map.audio/v1/download/file_01M6BPRMNR7PYGFNA66BX2YJ8M?expires=1793545422&signature=GQ2GkmPjqXuxxBSSM4NKBCY7OjU7w3YSeZBrCoxxtbY", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": 42, "command": { "ok": true, "wavPath": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sampleRate": 48000, "durationSeconds": 92.903, "seed": 42, "stems": [], "boundaries": [ { "index": 1, "presetPath": "pam:factory/teknochonic", "variationIndex": 1, "anchorBar": 16, "windowBars": 8, "mode": "mix", "locked": false }, { "index": 2, "presetPath": "pam:factory/breakbeatrcool", "variationIndex": 2, "anchorBar": 32, "windowBars": 0, "mode": "auto", "locked": false } ], "bpm": 124, "timeSig": [ 4, 4 ], "bars": 48 }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": 42, "presets": [ { "ref": "pam:factory/acid-sprinkle", "sha256": "eee63255e1350ec8f03f098656befcc3c1c1c23c0bd7933f93fee84071f9279d" }, { "ref": "pam:factory/teknochonic", "variation": 2, "sha256": "a2e6645b2108343284008cd4ef4b8356ab1f4c47dbd9da63a785d893b31a9266" }, { "ref": "pam:factory/breakbeatrcool", "variation": 3, "sha256": "92916c116f9e0a17fd8217a67a98b57063efc8554de0e66b8398053dda7e3296" } ], "packs": [], "userFiles": [], "inputs": [], "outputs": [ { "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:LEVZHpOHj8BMGZgJh-_Y7SmKbqDlBDR8LqY-XelU6GYK8lntZqg3rRK4b3BH8cy25bK0iWzOXFXmkiGHEYue7A" } }, "error": null, "usage": { "renderMinutes": 1.548, "audioSeconds": 92.903, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 202: Queued ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "djSet", "status": "queued", "livemode": false, "sequence": 1, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": null, "finishedAt": null, "estimate": { "audioSeconds": 92.903, "renderMinutes": 1.548 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 400: Set cannot render as sent ```json { "error": { "type": "invalid_request", "code": "dj_offline_unsupported", "message": "set.auto false waits for a hand to advance the set, and a render steps through it on its own; leave out auto or send true.", "param": "set.auto", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#dj_offline_unsupported", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The finished job: `result.outputs` link each file it wrote and `result.command` is the engine's own result. - `202`: The queued job; poll `urls.self` or stream `urls.events`. - `400`: Set cannot render as sent - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) - [`insufficient_credits`](https://map.audio/developers/music-api/errors#insufficient_credits) - [`render_too_long`](https://map.audio/developers/music-api/errors#render_too_long) - [`preset_not_found`](https://map.audio/developers/music-api/errors#preset_not_found) - [`preset_not_served`](https://map.audio/developers/music-api/errors#preset_not_served) - [`variation_not_found`](https://map.audio/developers/music-api/errors#variation_not_found) - [`dj_invalid_set`](https://map.audio/developers/music-api/errors#dj_invalid_set) - [`dj_offline_unsupported`](https://map.audio/developers/music-api/errors#dj_offline_unsupported) - [`dj_preset_missing`](https://map.audio/developers/music-api/errors#dj_preset_missing) - [`dj_unknown_transition_preset`](https://map.audio/developers/music-api/errors#dj_unknown_transition_preset) ### patternGenerate `POST https://api.map.audio/v1/pattern/generate` Generate seeded genre patterns with the shared MIDI corpus; optionally write MIDI. Generate seeded drum and bass patterns in a genre from PAM's MIDI corpus, returned as notes per cell and a MIDI file. It renders no audio, so it is free; render presets to audio with POST /v1/renders. Free: it counts in GET /v1/usage, never in render-minutes. Answers 200 with the finished job when it ends within `Prefer: wait=N` (30 s unless sent, at most 60), else 202 with the job to poll. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. MCP tool: `pattern_generate`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `genre` | "trap" \| "drill" \| "boombap" \| "dnb" \| "house" \| "techno" \| "idm" | no | Pattern genre. Default `"trap"`. | | `seed` | integer | no | Reproducible seed; zero draws a random seed. From 0 to 4294967295. Default `0`. | | `bpm` | number | no | MIDI tempo in beats per minute. From 20 to 400. Default `120`. | | `bars` | integer | no | Length in bars (1..64): pattern generation uses four beats and defaults to 1; preset rendering uses the preset time signature and defaults to 4. From 1 to 64. | | `cells` | string | no | Comma-separated PAM cell IDs (1..12); factory roles are used. Default `"1,2,3,6"`. | | `density` | number | no | Pattern density; absent uses genre default. From 0 to 1. | | `syncopation` | number | no | Pattern syncopation; absent uses genre default. From 0 to 1. | | `coherence` | number | no | Cross-cell pattern coherence. From 0 to 1. Default `0.5`. | | `metadata` | map of string | no | Up to 20 string pairs stored with the job. At most 20 entries. | #### Example ```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":"house","bpm":124,"bars":4,"seed":42}' ``` ##### Request: Four bars of house at 124 BPM ```json { "genre": "house", "bpm": 124, "bars": 4, "seed": 42 } ``` ##### 200: Finished ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "patternGenerate", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": { "outputs": [ { "role": "midi", "name": null, "cell": null, "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4", "bytes": 1214, "format": "mid", "sampleRate": null, "durationSeconds": null, "url": "https://api.map.audio/v1/download/file_01M6BPRMNR7PYGFNA66BX2YJ8M?expires=1793545422&signature=GQ2GkmPjqXuxxBSSM4NKBCY7OjU7w3YSeZBrCoxxtbY", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": 42, "command": { "ok": true, "seed": 42, "genre": "house", "bpm": 124, "lengthBeats": 16, "roles": { "1": "kick", "2": "snare", "3": "hihat", "6": "bass" }, "corpus": true, "midiPath": "file_01M6BPRMNR7PYGFNA66BX2YJ8M" }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": 42, "presets": [], "packs": [], "userFiles": [], "inputs": [], "outputs": [ { "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:LEVZHpOHj8BMGZgJh-_Y7SmKbqDlBDR8LqY-XelU6GYK8lntZqg3rRK4b3BH8cy25bK0iWzOXFXmkiGHEYue7A" } }, "error": null, "usage": { "renderMinutes": 0, "audioSeconds": 0, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 202: Still running ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "patternGenerate", "status": "running", "livemode": false, "sequence": 2, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": null, "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 400: Invalid request ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "genre must be one of trap, drill, boombap, dnb, house, techno or idm; got \"polka\".", "param": "genre", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The finished job: `result.outputs` link each file it wrote and `result.command` is the engine's own result. - `202`: The job, still running when `Prefer: wait` ran out; poll `urls.self`. - `400`: Invalid request - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) ### synthDrum `POST https://api.map.audio/v1/synth/drum` Render procedural drum engines with named controls and timed triggers. Synthesize drum hits with one of PAM's drum-synth engines (kick, snare, hats, 808 and more), set by named controls in real units, with trigger times for a pattern of hits. It is free; GET /v1/drum-engines lists the engines and their controls. Free: it counts in GET /v1/usage, never in render-minutes. Answers 200 with the finished job when it ends within `Prefer: wait=N` (30 s unless sent, at most 60), else 202 with the job to poll. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. MCP tool: `synth_drum`. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `engine` | "1" \| "kick" \| "2" \| "snare" \| "3" \| "hihat" \| "4" \| "clap" \| "5" \| "tom" \| "6" \| "fm" \| "7" \| "cymbal" \| "8" \| "cowbell" \| "9" \| "808" \| "10" \| "shaker" \| "11" \| "rim" \| "12" \| "conga" \| "13" \| "bell" \| "14" \| "pluck" \| "15" \| "zap" \| "16" \| "wave" \| "17" \| "chaos" \| "18" \| "stab" \| "19" \| "acoustic-kick" \| "20" \| "acoustic-snare" \| "21" \| "acoustic-cymbal" \| "22" \| "acoustic-hi-hat" | no | Drum engine name or id; GET /v1/drum-engines lists them with their controls. Default `"kick"`. | | `param` | string | no | Comma-separated name=value controls in registry units (Hz, seconds, ratios; pct controls use 0..100). Kick: Tune=30..160 note, P.Depth=1..5 ratio, P.Decay=0.005..0.2 time, Decay=0.03..3 time, Click=0..100 percent, Drive=0..100 percent, Tone=150..16000 freq, Wave=0..100 percent; Snare: Tune=120..400 freq, Noise=0..100 percent, Decay=0.03..0.5 time, N.Decay=0.03..0.6 time, N.Tone=800..8000 freq, P.Depth=1..3 ratio, P.Decay=0.005..0.3 time, Drive=0..100 percent; HiHat: Tune=160..640 freq, Decay=0.02..1 time, Tone=2000..12000 freq, Metal=0..100 percent, Noise=0..100 percent; Clap: Freq=400..3500 freq, Spread=0.004..0.02 time, Decay=0.05..2 time, Reso=1..12 num, Drive=0..100 percent; Tom: Tune=80..400 note, P.Depth=1..3 ratio, Decay=0.05..2.5 time, Tone=120..12000 freq, Noise=0..100 percent, Drive=0..100 percent, P.Decay=0.01..0.8 time; FM: Tune=50..1000 note, Ratio=0.5..8 ratio, FM Amt=0..24 num, Decay=0.03..3 time, FM Decay=0..100 percent, F.Back=0..100 percent, Drive=0..100 percent; Cymbal: Tune=140..560 freq, Decay=0.2..4 time, Tone=3000..14000 freq, Metal=0..100 percent, Sizzle=0..100 percent; Cowbell: Tune=300..900 note, Decay=0.08..0.8 time, Ratio=1.2..1.8 ratio, Tone=700..9000 freq, Drive=0..100 percent; 808: Tune=27.5..220 note, P.Depth=0..36 st, P.Time=0.005..0.12 time, Decay=0.1..8 time, Saturate=0..100 percent, Click=0..100 percent, Tone=80..6000 freq; Shaker: Tune=2000..12000 freq, Color=0..100 percent, Onset=0.0005..0.05 time, Decay=0.02..0.6 time, Rattle=0..0.012 time, Tone=0..100 percent; Rim: Tune=200..2500 note, Ring=0..100 percent, Decay=0.015..0.15 time, Partial=1..2.7 ratio, Snap=0..100 percent, Drive=0..100 percent; Conga: Tune=75..700 note, Slap=1..2.5 ratio, Decay=0.08..0.7 time, Partial=1.4..2 ratio, Tone=300..9000 freq, Skin=0..100 percent, Drive=0..100 percent; Bell: Tune=200..1800 note, Ratio=1.4..7 ratio, FM Amt=0..10 num, Decay=0.2..4 time, Inharm=0..100 percent, Strike=0..100 percent, Tone=800..18000 freq; Pluck: Tune=60..1200 note, Sustain=0..100 percent, Damp=0..100 percent, Excite=0..100 percent, Bright=300..12000 freq, Drive=0..100 percent, Decay=0.3..6 time; Zap: Tune=40..2000 note, Sweep=-6..6 num, S.Time=0.02..1 time, Decay=0.03..1.5 time, Timbre=0..100 percent, Noise=0..100 percent, Drive=0..100 percent, Tone=200..14000 freq; Wave: Tune=40..2000 note, Position=0..100 percent, P.Env=1..4 ratio, Decay=0.03..1.5 time, Bright=300..14000 freq, Fold=0..100 percent, Drive=0..100 percent, Draw=0..100 percent; Chaos: Tune=40..6000 note, Chaos=0..100 percent, Decay=0.03..1.2 time, Tone=300..12000 freq, Drive=0..100 percent, Noise=0..100 percent; Stab: Tune=35..1000 note, Shape=0..100 percent, Width=0..100 percent, Chord=0..6 num, Detune=0..100 percent, Decay=0.05..3 time, Tone=200..16000 freq, Drive=0..100 percent; Acoustic Kick: Tune=35..120 note, Punch=1..4 ratio, Decay=0.12..1.8 time, Beater=0..100 percent, Shell=0..100 percent, Damp=0..100 percent, Tone=200..9000 freq, Drive=0..100 percent; Acoustic Snare: Tune=110..440 note, Depth=0..100 percent, Decay=0.08..1.2 time, Snap=0..100 percent, Wires=0..100 percent, Buzz=0.03..0.9 time, Tone=400..12000 freq, Drive=0..100 percent; Acoustic Cymbal: Size=180..900 freq, Strike=0..100 percent, Decay=0.4..8 time, Bell=0..100 percent, Sizzle=0..100 percent, Tone=200..6000 freq, Spread=0..100 percent, Drive=0..100 percent; Acoustic Hi-Hat: Size=260..1100 freq, Open=0..100 percent, Decay=0.08..4.5 time, Strike=0..100 percent, Chatter=0..100 percent, Sizzle=0..100 percent, Tone=1200..16000 freq, Drive=0..100 percent | | `duration` | number | no | Duration in seconds. From 0.05 to 30. Default `1`. | | `velocity` | number | no | Drum trigger velocity. From 0 to 1. Default `1`. | | `triggers` | string | no | Comma-separated trigger times in seconds. Default `"0"`. | | `sampleRate` | integer | no | Output sample rate. From 22050 to 48000. Default `48000`. | | `metadata` | map of string | no | Up to 20 string pairs stored with the job. At most 20 entries. | #### Example ```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}' ``` ##### Request: A two-second 808 tuned to 55 Hz ```json { "engine": "808", "param": "Tune=55,Decay=1.5", "duration": 2 } ``` ##### 200: Finished ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "synthDrum", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 2, "renderMinutes": 0 }, "result": { "outputs": [ { "role": "master", "name": null, "cell": null, "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4", "bytes": 576044, "format": "wav", "sampleRate": 48000, "durationSeconds": 2, "url": "https://api.map.audio/v1/download/file_01M6BPRMNR7PYGFNA66BX2YJ8M?expires=1793545422&signature=GQ2GkmPjqXuxxBSSM4NKBCY7OjU7w3YSeZBrCoxxtbY", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": null, "command": { "ok": true, "wavPath": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sampleRate": 48000, "channels": 2, "durationSeconds": 2, "engine": "808" }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": null, "presets": [], "packs": [], "userFiles": [], "inputs": [], "outputs": [ { "file": "file_01M6BPRMNR7PYGFNA66BX2YJ8M", "sha256": "13d41d922519699fc14a7587130eceee7d0f149414350000578de1564866a0d4" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:LEVZHpOHj8BMGZgJh-_Y7SmKbqDlBDR8LqY-XelU6GYK8lntZqg3rRK4b3BH8cy25bK0iWzOXFXmkiGHEYue7A" } }, "error": null, "usage": { "renderMinutes": 0, "audioSeconds": 2, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 202: Still running ```json { "object": "job", "id": "job_01M6BPRFPCH9EBPY7B6JKM7CE1", "operation": "synthDrum", "status": "running", "livemode": false, "sequence": 2, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": null, "estimate": { "audioSeconds": 2, "renderMinutes": 0 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1", "events": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRFPCH9EBPY7B6JKM7CE1/cancel" } } ``` ##### 400: Invalid request ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "duration must be at most 30; got 45.", "param": "duration", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The finished job: `result.outputs` link each file it wrote and `result.command` is the engine's own result. - `202`: The job, still running when `Prefer: wait` ran out; poll `urls.self`. - `400`: Invalid request - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) ## Jobs Every engine call is a job: read it, wait for it, stream its events or cancel it. ### listJobs `GET https://api.map.audio/v1/jobs` List jobs The account's jobs, newest first, optionally filtered by `status` and `operation`, with fresh download links on the outputs. Jobs from test and live keys are listed together. Job records are kept 90 days. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. MCP tool: `list_jobs`. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `status` | query | "queued" \| "running" \| "succeeded" \| "failed" \| "cancelled" \| "expired" | no | Only jobs in this status. | | `operation` | query | string | no | Only jobs of this operation: `renders.create`, `uploads.complete`, or a command's operationId such as `presetRender`. | | `limit` | query | integer | no | Jobs per page. From 1 to 100. Default `25`. | | `cursor` | query | string | no | The `nextCursor` of the previous page. | #### Example ```sh curl -sS "https://api.map.audio/v1/jobs?operation=renders.create" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: The latest job ```json { "object": "list", "data": [ { "object": "job", "id": "job_01M6BPRE186QHP9QPYGDX5NNFP", "operation": "renders.create", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 16, "renderMinutes": 0.267 }, "result": { "outputs": [ { "role": "master", "name": null, "cell": null, "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b", "bytes": 4608044, "format": "wav", "sampleRate": 48000, "durationSeconds": 16, "url": "https://api.map.audio/v1/download/file_01M6BPRK0M34NBP2XKJ1RMX1TQ?expires=1793545422&signature=yaoMrgCHcK9T8MaVnCVtXZc4Ym0FWWM8P-0feZDMObk", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": 42, "command": { "ok": true, "wavPath": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sampleRate": 48000, "channels": 2, "durationSeconds": 16, "seed": 42, "stems": [], "stemTap": "post_cell_pre_master", "latencySamples": 0, "missingSamples": [] }, "spec": { "source": { "preset": "pam:factory/313-disco" }, "plan": null, "bars": 8, "seconds": null, "tail": 0, "seed": 42, "stems": false, "sampleRate": 48000, "format": { "container": "wav" }, "quality": "draft", "tempo": 120 }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRE186QHP9QPYGDX5NNFP", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": 42, "presets": [ { "ref": "pam:factory/313-disco", "sha256": "3afbf5dea1003b2d17385955017785a57d74c65f77f97b58cf5d7cec9794ca1c" } ], "packs": [], "userFiles": [], "inputs": [], "outputs": [ { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:jsfkW3eF1r4kChqccp6AKLTvjTK5Re8acrEusqigz7MDUu6uZhsJXVnuQEl6EsErDgVYNXOkos9d0mMyt2CVCg" } }, "error": null, "usage": { "renderMinutes": 0.267, "audioSeconds": 16, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP", "events": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/cancel" } } ], "nextCursor": null } ``` ##### 400: Invalid request ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "status must be one of queued, running, succeeded, failed, cancelled or expired; got \"done\".", "param": "status", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: Jobs. - `400`: Invalid request - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### getJob `GET https://api.map.audio/v1/jobs/{id}` Get a job A job as it stands: status, progress and estimate, and once it ends, its outputs with download links valid for an hour, the seed, the engine's own result, the provenance record and the render-minutes it was charged. Poll it after a 202, or read it when a webhook says the job ended. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. MCP tool: `get_job`. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | A job id (job_…), from the response that created the job or GET /v1/jobs. Matches `^job_[0-9A-HJKMNP-TV-Z]{26}$`. | #### Example ```sh curl -sS "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: Succeeded: 16 s of 313 Disco ```json { "object": "job", "id": "job_01M6BPRE186QHP9QPYGDX5NNFP", "operation": "renders.create", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 16, "renderMinutes": 0.267 }, "result": { "outputs": [ { "role": "master", "name": null, "cell": null, "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b", "bytes": 4608044, "format": "wav", "sampleRate": 48000, "durationSeconds": 16, "url": "https://api.map.audio/v1/download/file_01M6BPRK0M34NBP2XKJ1RMX1TQ?expires=1793545422&signature=yaoMrgCHcK9T8MaVnCVtXZc4Ym0FWWM8P-0feZDMObk", "expiresAt": "2026-11-01T15:03:42.000Z" } ], "seed": 42, "command": { "ok": true, "wavPath": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sampleRate": 48000, "channels": 2, "durationSeconds": 16, "seed": 42, "stems": [], "stemTap": "post_cell_pre_master", "latencySamples": 0, "missingSamples": [] }, "spec": { "source": { "preset": "pam:factory/313-disco" }, "plan": null, "bars": 8, "seconds": null, "tail": 0, "seed": 42, "stems": false, "sampleRate": 48000, "format": { "container": "wav" }, "quality": "draft", "tempo": 120 }, "provenance": { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRE186QHP9QPYGDX5NNFP", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": 42, "presets": [ { "ref": "pam:factory/313-disco", "sha256": "3afbf5dea1003b2d17385955017785a57d74c65f77f97b58cf5d7cec9794ca1c" } ], "packs": [], "userFiles": [], "inputs": [], "outputs": [ { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:jsfkW3eF1r4kChqccp6AKLTvjTK5Re8acrEusqigz7MDUu6uZhsJXVnuQEl6EsErDgVYNXOkos9d0mMyt2CVCg" } }, "error": null, "usage": { "renderMinutes": 0.267, "audioSeconds": 16, "computeMs": 6273 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP", "events": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/cancel" } } ``` ##### 404: Not found ```json { "error": { "type": "not_found", "code": "not_found", "message": "No job with that id; GET /v1/jobs lists this account's jobs.", "param": "id", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#not_found", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The job. - `404`: Not found - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### cancelJob `POST https://api.map.audio/v1/jobs/{id}/cancel` Cancel a job Stop a queued or running job. A queued job ends `cancelled` at once and a running one when its engine stops; nothing is charged unless the job already published a preview. A job that already ended keeps its outcome. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. MCP tool: `cancel_job`. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | A job id (job_…), from the response that created the job or GET /v1/jobs. Matches `^job_[0-9A-HJKMNP-TV-Z]{26}$`. | #### Example ```sh curl -sS -X POST "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/cancel" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: Cancelled while queued ```json { "object": "job", "id": "job_01M6BPRE186QHP9QPYGDX5NNFP", "operation": "renders.create", "status": "cancelled", "livemode": false, "sequence": 2, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": null, "finishedAt": "2026-11-01T14:03:32.204Z", "estimate": { "audioSeconds": 16, "renderMinutes": 0.267 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP", "events": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/cancel" } } ``` ##### 404: Not found ```json { "error": { "type": "not_found", "code": "not_found", "message": "No job with that id; GET /v1/jobs lists this account's jobs.", "param": "id", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#not_found", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The job. - `404`: Not found - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`idempotency_key_unsupported`](https://map.audio/developers/music-api/errors#idempotency_key_unsupported) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### streamJobEvents `GET https://api.map.audio/v1/jobs/{id}/events` Stream a job's events Server-sent events for one job: job.queued, job.started, job.progress, job.preview and job.requeued, then job.succeeded, job.failed, job.cancelled or job.expired. Each event's `data` is JSON with `jobId` and `createdAt`; job.preview carries the preview's outputs, and job.failed the error. Reconnect with `Last-Event-ID` (or `after`) to miss nothing; a stream closes after 10 minutes or once the job has ended. A browser's EventSource cannot send Authorization, so read it with fetch. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: A server-sent event stream; MCP tools answer once, so poll get_job instead. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | A job id (job_…), from the response that created the job or GET /v1/jobs. Matches `^job_[0-9A-HJKMNP-TV-Z]{26}$`. | | `after` | query | integer | no | Resume after this event id, as the Last-Event-ID header does. At least 0. | | `Last-Event-ID` | header | string | no | The last event id received; the stream resumes after it. Matches `^\d{1,9}$`. | #### Example ```sh curl -sS "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/events?after=2" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 404: Not found ```json { "error": { "type": "not_found", "code": "not_found", "message": "No job with that id; GET /v1/jobs lists this account's jobs.", "param": "id", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#not_found", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The event stream. - `404`: Not found - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ## Files Render outputs and uploads, with signed download links. Uploads stay off until the decoder and project-reader fuzzers have run. ### listFiles `GET https://api.map.audio/v1/files` List files Render outputs and uploads the account holds, newest first: kind, status, size, SHA-256 and the job that wrote each. Outputs are kept 7 days on the free tier and longer on paid tiers. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. MCP tool: `list_files`. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `limit` | query | integer | no | Files per page. From 1 to 100. Default `25`. | | `cursor` | query | string | no | The `nextCursor` of the previous page. | #### Example ```sh curl -sS "https://api.map.audio/v1/files" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: One output ```json { "object": "list", "data": [ { "object": "file", "id": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "kind": "output", "status": "ready", "name": "master.wav", "contentType": "audio/wav", "bytes": 4608044, "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b", "jobId": "job_01M6BPRE186QHP9QPYGDX5NNFP", "role": "master", "createdAt": "2026-11-01T14:03:41.877Z", "expiresAt": "2026-11-08T14:03:41.877Z" } ], "nextCursor": null } ``` ##### 400: Invalid request ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "The cursor is not one this API issued; pass the nextCursor of the page before, unchanged.", "param": "cursor", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: Files. - `400`: Invalid request - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### getFile `GET https://api.map.audio/v1/files/{id}` Get a file One file's metadata: kind, status, name, content type, size, SHA-256, the job that wrote it and when retention deletes it. For its bytes, GET /v1/files/{id}/content. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. MCP tool: `get_file`. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | A file id (file_…), from a job's `result.outputs` or GET /v1/files. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | #### Example ```sh curl -sS "https://api.map.audio/v1/files/file_01M6BPRK0M34NBP2XKJ1RMX1TQ" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: A render's master ```json { "object": "file", "id": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "kind": "output", "status": "ready", "name": "master.wav", "contentType": "audio/wav", "bytes": 4608044, "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b", "jobId": "job_01M6BPRE186QHP9QPYGDX5NNFP", "role": "master", "createdAt": "2026-11-01T14:03:41.877Z", "expiresAt": "2026-11-08T14:03:41.877Z" } ``` ##### 404: Not found ```json { "error": { "type": "not_found", "code": "not_found", "message": "No file with that id; GET /v1/files lists this account's files.", "param": "id", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#not_found", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The file. - `404`: Not found - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### deleteFile `DELETE https://api.map.audio/v1/files/{id}` Delete a file Delete a file's bytes now, before retention would; its record stays with status `deleted`, and an output leaves its job's outputs. A file whose job is still running cannot be deleted yet. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: Deleting a render cannot be undone, so it stays with your own code: DELETE /v1/files/{id} with an API key. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | A file id (file_…), from a job's `result.outputs` or GET /v1/files. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | #### Example ```sh curl -sS -X DELETE "https://api.map.audio/v1/files/file_01M6BPRK0M34NBP2XKJ1RMX1TQ" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: Deleted ```json { "object": "file", "id": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "kind": "output", "status": "deleted", "name": "master.wav", "contentType": "audio/wav", "bytes": 4608044, "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b", "jobId": "job_01M6BPRE186QHP9QPYGDX5NNFP", "role": "master", "createdAt": "2026-11-01T14:03:41.877Z", "expiresAt": "2026-11-08T14:03:41.877Z" } ``` ##### 409: File not ready ```json { "error": { "type": "conflict", "code": "file_not_ready", "message": "File file_01M6BPRK0M34NBP2XKJ1RMX1TQ is still being written by job_01M6BPRE186QHP9QPYGDX5NNFP; retry when that job succeeds.", "param": "id", "retryable": true, "jobId": "job_01M6BPRE186QHP9QPYGDX5NNFP", "docUrl": "https://map.audio/developers/music-api/errors#file_not_ready", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The file, deleted. - `409`: File not ready - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`file_not_ready`](https://map.audio/developers/music-api/errors#file_not_ready) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### getFileContent `GET https://api.map.audio/v1/files/{id}/content` Download a file Redirects (302) to a signed link for the file's bytes, valid for one hour; the link needs no Authorization header. Job outputs already carry such a link in `url`; use this route for a fresh one. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: Answers with a redirect to raw bytes, which a tool result cannot carry; get_job lists each output with a signed download url. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | A file id (file_…), from a job's `result.outputs` or GET /v1/files. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | #### Example ```sh curl -sSL --fail-with-body -o render.wav "https://api.map.audio/v1/files/file_01M6BPRK0M34NBP2XKJ1RMX1TQ/content" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 410: File no longer stored ```json { "error": { "type": "gone", "code": "file_expired", "message": "File file_01M6BPRK0M34NBP2XKJ1RMX1TQ expired at 2026-11-08T14:03:41.877Z; render it again.", "param": "id", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#file_expired", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `302`: Redirect to the signed download link. - `410`: File no longer stored - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`file_not_ready`](https://map.audio/developers/music-api/errors#file_not_ready) - [`file_expired`](https://map.audio/developers/music-api/errors#file_expired) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### createUpload `POST https://api.map.audio/v1/uploads` Start an upload Start a multipart upload of an audio file or a DAW project, declared by kind, name, size and SHA-256. Send the parts with PUT /v1/uploads/{id}/parts/{part}, then POST /v1/uploads/{id}/complete. Uploads are off until the decoder and project-reader fuzzers have run, so today this answers 403 uploads_disabled. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: Uploads send raw bytes in parts, which a JSON tool call cannot carry; uploads are also off until the decoder fuzzers have run. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | "audio" \| "sample" \| "project" | yes | `sample` is an alias of `audio`. Preset and project-folder uploads are not served yet. | | `name` | string | yes | At most 255 characters. | | `bytes` | integer | yes | At least 1. | | `sha256` | string | yes | Matches `^[0-9a-f]{64}$`. | #### Example ```sh curl -sS "https://api.map.audio/v1/uploads" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Content-Type: application/json" \ -d '{"kind":"audio","name":"vocal-take.wav","bytes":23040044,"sha256":"3b8f1c6e2d9a4b7e0f5c1a8d6e3b9f2c4a7d0e1b5c8f3a6d9e2b4c7f0a1d3e5b"}' ``` ##### Request: A 24-bit WAV ```json { "kind": "audio", "name": "vocal-take.wav", "bytes": 23040044, "sha256": "3b8f1c6e2d9a4b7e0f5c1a8d6e3b9f2c4a7d0e1b5c8f3a6d9e2b4c7f0a1d3e5b" } ``` ##### 201: Two parts to send ```json { "object": "upload", "id": "file_01M6BPRR00A11F2KEPGNR7V3Q9", "file": "file_01M6BPRR00A11F2KEPGNR7V3Q9", "kind": "audio", "status": "uploading", "partBytes": 16777216, "parts": 2, "expiresAt": "2026-11-02T14:20:00.000Z" } ``` ##### 403: Uploads not open yet ```json { "error": { "type": "entitlement", "code": "uploads_disabled", "message": "File uploads are not open yet; render from factory presets (GET /v1/presets) and pass render outputs as files meanwhile.", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#uploads_disabled", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `201`: The upload. - `403`: Uploads not open yet - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`uploads_disabled`](https://map.audio/developers/music-api/errors#uploads_disabled) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`idempotency_key_unsupported`](https://map.audio/developers/music-api/errors#idempotency_key_unsupported) ### putUploadPart `PUT https://api.map.audio/v1/uploads/{id}/parts/{part}` Send one upload part Send one part of an upload as raw bytes with its exact Content-Length: every part but the last is `partBytes` long (16 MiB), the last holds the rest. Sending a part again replaces it. Uploads are off for now, so this answers 403 uploads_disabled. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: Uploads send raw bytes in parts, which a JSON tool call cannot carry; uploads are also off until the decoder fuzzers have run. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | The upload's file id (file_…), from POST /v1/uploads. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | | `part` | path | integer | yes | The part number, from 1. From 1 to 10000. | #### Example ```sh curl -sS -X PUT "https://api.map.audio/v1/uploads/file_01M6BPRR00A11F2KEPGNR7V3Q9/parts/1" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Content-Type: application/octet-stream" \ --data-binary @part.bin ``` ##### 200: Part 1 stored ```json { "object": "upload_part", "upload": "file_01M6BPRR00A11F2KEPGNR7V3Q9", "part": 1, "bytes": 16777216 } ``` ##### 403: Uploads not open yet ```json { "error": { "type": "entitlement", "code": "uploads_disabled", "message": "File uploads are not open yet; render from factory presets (GET /v1/presets) and pass render outputs as files meanwhile.", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#uploads_disabled", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The part, stored. - `403`: Uploads not open yet - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`uploads_disabled`](https://map.audio/developers/music-api/errors#uploads_disabled) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`upload_closed`](https://map.audio/developers/music-api/errors#upload_closed) - [`upload_expired`](https://map.audio/developers/music-api/errors#upload_expired) ### completeUpload `POST https://api.map.audio/v1/uploads/{id}/complete` Finish an upload Close the upload and start its probe job: the render host checks the bytes against the declared SHA-256 and rewrites audio as a canonical WAV, after which the file is `ready` for commands. Answers with the probe job; completing again retries a probe that never started. Uploads are off for now, so this answers 403 uploads_disabled. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Send `Prefer: wait=N` (0 to 60) to receive the finished job when it ends within N seconds; otherwise the answer is `202` with the job. Send `Idempotency-Key` to retry safely: the same key and body return the same result for 24 hours. Not an MCP tool: Uploads send raw bytes in parts, which a JSON tool call cannot carry; uploads are also off until the decoder fuzzers have run. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | The upload's file id (file_…), from POST /v1/uploads. Matches `^file_[0-9A-HJKMNP-TV-Z]{26}$`. | #### Example ```sh curl -sS -X POST "https://api.map.audio/v1/uploads/file_01M6BPRR00A11F2KEPGNR7V3Q9/complete" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" \ -H "Prefer: wait=30" ``` ##### 200: Probe finished ```json { "object": "job", "id": "job_01M6BPRE186QHP9QPYGDX5NNFP", "operation": "uploads.complete", "status": "succeeded", "livemode": false, "sequence": 3, "progress": 1, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": "2026-11-01T14:03:33.104Z", "finishedAt": "2026-11-01T14:03:41.877Z", "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": { "outputs": [], "seed": null, "command": { "probe": { "ok": true, "durationSeconds": 120, "sampleRate": 48000, "channels": 2 } }, "provenance": null }, "error": null, "usage": { "renderMinutes": 0, "audioSeconds": 0, "computeMs": 2210 }, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP", "events": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/cancel" } } ``` ##### 202: Probe queued ```json { "object": "job", "id": "job_01M6BPRE186QHP9QPYGDX5NNFP", "operation": "uploads.complete", "status": "queued", "livemode": false, "sequence": 1, "progress": 0, "queuePosition": null, "createdAt": "2026-11-01T14:03:31.512Z", "startedAt": null, "finishedAt": null, "estimate": { "audioSeconds": 0, "renderMinutes": 0 }, "result": null, "error": null, "usage": null, "metadata": {}, "urls": { "self": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP", "events": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/events", "cancel": "https://api.map.audio/v1/jobs/job_01M6BPRE186QHP9QPYGDX5NNFP/cancel" } } ``` ##### 403: Uploads not open yet ```json { "error": { "type": "entitlement", "code": "uploads_disabled", "message": "File uploads are not open yet; render from factory presets (GET /v1/presets) and pass render outputs as files meanwhile.", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#uploads_disabled", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The probe job, finished. - `202`: The probe job, queued. - `403`: Uploads not open yet - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`uploads_disabled`](https://map.audio/developers/music-api/errors#uploads_disabled) - [`terms_required`](https://map.audio/developers/music-api/errors#terms_required) - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`upload_closed`](https://map.audio/developers/music-api/errors#upload_closed) - [`upload_expired`](https://map.audio/developers/music-api/errors#upload_expired) - [`upload_incomplete`](https://map.audio/developers/music-api/errors#upload_incomplete) - [`upload_processing`](https://map.audio/developers/music-api/errors#upload_processing) - [`checksum_mismatch`](https://map.audio/developers/music-api/errors#checksum_mismatch) - [`concurrency_limit`](https://map.audio/developers/music-api/errors#concurrency_limit) - [`idempotency_key_reused`](https://map.audio/developers/music-api/errors#idempotency_key_reused) - [`idempotency_in_flight`](https://map.audio/developers/music-api/errors#idempotency_in_flight) - [`queue_unavailable`](https://map.audio/developers/music-api/errors#queue_unavailable) ## Webhooks Signed Standard Webhooks deliveries of job events to your HTTPS endpoints. ### listWebhookEndpoints `GET https://api.map.audio/v1/webhook-endpoints` List webhook endpoints The account's webhook endpoints, newest first, without their secrets. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: Webhook endpoints hold signing secrets for your own server; set them up with an API key (POST /v1/webhook-endpoints) or with the account holder's map.audio sign-in. #### Example ```sh curl -sS "https://api.map.audio/v1/webhook-endpoints" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: One endpoint ```json { "object": "list", "data": [ { "object": "webhook_endpoint", "id": "whep_01M6BPRWZCT8J4PSWZ8KG7EXWV", "url": "https://hooks.example.com/map-audio", "events": [ "job.succeeded", "job.failed" ], "description": "Render notifications", "status": "enabled", "createdAt": "2026-11-01T13:58:02.418Z" } ] } ``` ##### 400: Invalid request ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "\"limit\" is not a parameter of GET /v1/webhook-endpoints; it takes none.", "param": "limit", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: Endpoints. - `400`: Invalid request - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### createWebhookEndpoint `POST https://api.map.audio/v1/webhook-endpoints` Create a webhook endpoint Subscribe a public HTTPS URL to job events: job.succeeded, job.failed and job.cancelled unless you list others, or `*` for every event. Each delivery is a signed Standard Webhooks POST (webhook-id, webhook-timestamp and webhook-signature headers: HMAC-SHA256 under the whsec_ secret, which is in this response only), retried with backoff for about 63 hours. An endpoint is disabled after three deliveries in a row run out of retries. Needs an API key or a map.audio sign-in on MAP Audio's own pages; an account has at most 10 endpoints. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: Webhook endpoints hold signing secrets for your own server; set them up with an API key (POST /v1/webhook-endpoints) or with the account holder's map.audio sign-in. #### Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | yes | A public HTTPS URL with a host name; deliveries are POSTs to it. Matches `^https://`. | | `events` | array of "job.preview" \| "job.succeeded" \| "job.failed" \| "job.cancelled" \| "job.expired" \| "webhook.test" or "*" | no | Event types to receive; `*` receives every event. | | `description` | string | no | At most 200 characters. | #### Example ```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"],"description":"Render notifications"}' ``` ##### Request: Hear when renders end ```json { "url": "https://hooks.example.com/map-audio", "events": [ "job.succeeded", "job.failed" ], "description": "Render notifications" } ``` ##### 201: Created ```json { "object": "webhook_endpoint", "id": "whep_01M6BPRWZCT8J4PSWZ8KG7EXWV", "url": "https://hooks.example.com/map-audio", "events": [ "job.succeeded", "job.failed" ], "description": "Render notifications", "status": "enabled", "createdAt": "2026-11-01T13:58:02.418Z", "secret": "whsec_Zm9ydHkgdHdvIGJ5dGVzIG9mIGV4YW1wbGUgc2VjcmV0IGtleSE=" } ``` ##### 400: Invalid request ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "url must use https; got http.", "param": "url", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` ##### 403: Webhook endpoint limit reached ```json { "error": { "type": "entitlement", "code": "too_many_endpoints", "message": "An account may have 10 webhook endpoints; delete one with DELETE /v1/webhook-endpoints/{id} first (GET /v1/webhook-endpoints lists them).", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#too_many_endpoints", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `201`: The endpoint, with its secret. - `400`: Invalid request - `403`: Webhook endpoint limit reached - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`oauth_required`](https://map.audio/developers/music-api/errors#oauth_required) - [`too_many_endpoints`](https://map.audio/developers/music-api/errors#too_many_endpoints) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) - [`unsupported_media_type`](https://map.audio/developers/music-api/errors#unsupported_media_type) - [`payload_too_large`](https://map.audio/developers/music-api/errors#payload_too_large) - [`idempotency_key_unsupported`](https://map.audio/developers/music-api/errors#idempotency_key_unsupported) ### deleteWebhookEndpoint `DELETE https://api.map.audio/v1/webhook-endpoints/{id}` Delete a webhook endpoint Delete an endpoint; deliveries to it stop. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: Webhook endpoints hold signing secrets for your own server; set them up with an API key (POST /v1/webhook-endpoints) or with the account holder's map.audio sign-in. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | A webhook endpoint id (whep_…), from GET /v1/webhook-endpoints. Matches `^whep_[0-9A-HJKMNP-TV-Z]{26}$`. | #### Example ```sh curl -sS -X DELETE "https://api.map.audio/v1/webhook-endpoints/whep_01M6BPRWZCT8J4PSWZ8KG7EXWV" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: Deleted ```json { "object": "webhook_endpoint", "id": "whep_01M6BPRWZCT8J4PSWZ8KG7EXWV", "url": "https://hooks.example.com/map-audio", "events": [ "job.succeeded", "job.failed" ], "description": "Render notifications", "status": "enabled", "createdAt": "2026-11-01T13:58:02.418Z", "deleted": true } ``` ##### 404: Not found ```json { "error": { "type": "not_found", "code": "not_found", "message": "No webhook endpoint with that id; GET /v1/webhook-endpoints lists this account's endpoints.", "param": "id", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#not_found", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The endpoint, deleted. - `404`: Not found - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### rotateWebhookSecret `POST https://api.map.audio/v1/webhook-endpoints/{id}/rotate-secret` Rotate a webhook secret Issue a new whsec_ secret, in this response only. For 24 hours each delivery carries signatures under both the old and the new secret, so your server can switch without dropping one; rotating a disabled endpoint also enables it again. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: Webhook endpoints hold signing secrets for your own server; set them up with an API key (POST /v1/webhook-endpoints) or with the account holder's map.audio sign-in. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | A webhook endpoint id (whep_…), from GET /v1/webhook-endpoints. Matches `^whep_[0-9A-HJKMNP-TV-Z]{26}$`. | #### Example ```sh curl -sS -X POST "https://api.map.audio/v1/webhook-endpoints/whep_01M6BPRWZCT8J4PSWZ8KG7EXWV/rotate-secret" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 200: Rotated ```json { "object": "webhook_endpoint", "id": "whep_01M6BPRWZCT8J4PSWZ8KG7EXWV", "url": "https://hooks.example.com/map-audio", "events": [ "job.succeeded", "job.failed" ], "description": "Render notifications", "status": "enabled", "createdAt": "2026-11-01T13:58:02.418Z", "secret": "whsec_bmV3IHNlY3JldCBmb3IgdGhlIG5leHQgMjQgaG91cnMgb2Ygc2lnbmluZw==" } ``` ##### 409: Secret rotation in progress ```json { "error": { "type": "conflict", "code": "rotation_in_progress", "message": "The previous secret stays valid until 2026-11-02T14:20:00.000Z; rotate again after that, or delete the endpoint and create a new one.", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#rotation_in_progress", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The endpoint, with its new secret. - `409`: Secret rotation in progress - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`oauth_required`](https://map.audio/developers/music-api/errors#oauth_required) - [`rotation_in_progress`](https://map.audio/developers/music-api/errors#rotation_in_progress) - [`idempotency_key_unsupported`](https://map.audio/developers/music-api/errors#idempotency_key_unsupported) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### testWebhookEndpoint `POST https://api.map.audio/v1/webhook-endpoints/{id}/test` Send a test event Queue a signed `webhook.test` event to the endpoint, to check that your server receives deliveries and verifies their signatures. Free. Auth: `Authorization: Bearer` with an API key or a map.audio access token. Not an MCP tool: Webhook endpoints hold signing secrets for your own server; set them up with an API key (POST /v1/webhook-endpoints) or with the account holder's map.audio sign-in. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | A webhook endpoint id (whep_…), from GET /v1/webhook-endpoints. Matches `^whep_[0-9A-HJKMNP-TV-Z]{26}$`. | #### Example ```sh curl -sS -X POST "https://api.map.audio/v1/webhook-endpoints/whep_01M6BPRWZCT8J4PSWZ8KG7EXWV/test" \ -H "Authorization: Bearer $MAP_API_KEY" \ -H "Map-Version: 2026-11-01" ``` ##### 202: Queued ```json { "id": "evt_01M6BPRYMG75T6HXQ8E61EARAX", "type": "webhook.test", "createdAt": "2026-11-01T14:15:09.664Z", "apiVersion": "2026-11-01", "data": { "object": { "endpoint": "whep_01M6BPRWZCT8J4PSWZ8KG7EXWV" } } } ``` ##### 409: Webhook endpoint disabled ```json { "error": { "type": "conflict", "code": "endpoint_disabled", "message": "This endpoint is disabled, after repeated failed deliveries or because the key that set its secret was revoked; rotate its secret (POST /v1/webhook-endpoints/{id}/rotate-secret) to enable it again.", "param": null, "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#endpoint_disabled", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `202`: The queued event. - `409`: Webhook endpoint disabled - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`endpoint_disabled`](https://map.audio/developers/music-api/errors#endpoint_disabled) - [`idempotency_key_unsupported`](https://map.audio/developers/music-api/errors#idempotency_key_unsupported) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ## Provenance Signed records of what made each render, and the public keys that verify them. ### getProvenance `GET https://api.map.audio/v1/provenance/{id}` Get a provenance record A render's signed provenance record: the engine image, seed, presets, inputs and outputs with their SHA-256, the licence the render was made under, and that no AI model made the audio. Public, so anyone holding a record id can check it: verify `signature` (Ed25519 over the record's JSON with sorted keys and without `signature`) with the key in GET /.well-known/jwks.json whose `kid` matches. Free. Auth: none; this route is public. Not an MCP tool: Public and unauthenticated: read it with a plain GET. get_job already returns each render's provenance record. #### Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | A provenance record id (prov_…), from a job's `result.provenance.id`. Matches `^prov_[0-9A-HJKMNP-TV-Z]{26}$`. | #### Example ```sh curl -sS "https://api.map.audio/v1/provenance/prov_01M6BPRVA8ZC7F7J737SQ0EPN2" \ -H "Map-Version: 2026-11-01" ``` ##### 200: A render of 313 Disco ```json { "object": "provenance", "id": "prov_01M6BPRVA8ZC7F7J737SQ0EPN2", "jobId": "job_01M6BPRE186QHP9QPYGDX5NNFP", "createdAt": "2026-11-01T14:03:41.877Z", "engine": { "product": "pam", "version": null, "runtime": "native-linux-x86_64", "image": "registry.cloudflare.com/c7c2e61448617e81ced650f444fb6787/pam-render@sha256:5c5ffe3fabc103e277280fbd65a08988d31361f00fe7857e36f603d67bcec42d" }, "seed": 42, "presets": [ { "ref": "pam:factory/313-disco", "sha256": "3afbf5dea1003b2d17385955017785a57d74c65f77f97b58cf5d7cec9794ca1c" } ], "packs": [], "userFiles": [], "inputs": [], "outputs": [ { "file": "file_01M6BPRK0M34NBP2XKJ1RMX1TQ", "sha256": "5a3485b7a3221be5f7ead6ab953cbc198988a7246158215b32bb937acbd3852b" } ], "planner": null, "contracts": { "api": "2026-11-01", "pam.cli": 1 }, "aiModels": [], "audioByModel": false, "license": { "id": "lic_api_prototype_test", "commercialUse": false, "attribution": true }, "kid": "6c2f0a9d4b7e1358", "signature": "ed25519:jsfkW3eF1r4kChqccp6AKLTvjTK5Re8acrEusqigz7MDUu6uZhsJXVnuQEl6EsErDgVYNXOkos9d0mMyt2CVCg" } ``` ##### 404: Not found ```json { "error": { "type": "not_found", "code": "not_found", "message": "No provenance record with that id; a job's result.provenance.id names its record.", "param": "id", "retryable": false, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#not_found", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: The record. - `404`: Not found - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. #### Errors - [`not_found`](https://map.audio/developers/music-api/errors#not_found) - [`rate_limited`](https://map.audio/developers/music-api/errors#rate_limited) - [`invalid_request`](https://map.audio/developers/music-api/errors#invalid_request) ### getJwks `GET https://api.map.audio/.well-known/jwks.json` Get the provenance signing keys The public keys (Ed25519, as JWKs) that sign provenance records; pick the one whose `kid` matches a record's. Public. Free. Auth: none; this route is public. Not an MCP tool: Public keys for checking provenance; fetch them with a plain GET. #### Example ```sh curl -sS "https://api.map.audio/.well-known/jwks.json" \ -H "Map-Version: 2026-11-01" ``` ##### 200: One signing key ```json { "keys": [ { "kty": "OKP", "crv": "Ed25519", "x": "r7VbRk8sQ1mN4pT6yW2cE9hJ3uL5aF0dG7iK1oZ2qXs", "kid": "6c2f0a9d4b7e1358", "use": "sig", "alg": "EdDSA" } ] } ``` ##### 500: Internal error ```json { "error": { "type": "unavailable", "code": "internal_error", "message": "Something went wrong on our side; nothing was charged.", "param": null, "retryable": true, "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#internal_error", "requestId": "9a3f7c2e5b1d8f40-LHR" } } ``` #### Responses - `200`: JWKS. - `500`: Internal error - `default`: Any other error: `error.code` names it, `error.param` the field at fault, and `error.docUrl` its entry in the error catalogue. --- Source: https://map.audio/developers/music-api/errors.md # Music API errors > 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. Every error the Music API answers with has the same JSON shape and a stable code. This page lists each code, when it happens and what to do next. The body is `{"error":{"type","code","message","param","details","retryable","requestId","jobId","docUrl"}}`. `code` names an entry below, and `docUrl` links to it. `param` names the field at fault in the body's own notation, such as `items[0].name`. `retryable` says whether the same request can succeed later: honour `Retry-After` when it is sent, and change the request when it is not retryable. Quote `requestId`, which is also the `Request-Id` header, when you report a problem to support@map.audio. New codes can appear within a version; handle one you do not know by its `type` and `retryable`. | Code | Status | Type | Retryable | | --- | --- | --- | --- | | [`invalid_request`](#invalid_request) | 400 | invalid_request | no | | [`not_invited`](#not_invited) | 403 | entitlement | no | | [`authentication_failed`](#authentication_failed) | 401 | authentication | no | | [`insufficient_scope`](#insufficient_scope) | 403 | entitlement | no | | [`auth_unavailable`](#auth_unavailable) | 503 | unavailable | yes | | [`account_suspended`](#account_suspended) | 403 | entitlement | no | | [`customer_erased`](#customer_erased) | 409 | conflict | no | | [`already_has_access`](#already_has_access) | 409 | conflict | no | | [`access_request_needs_sign_in`](#access_request_needs_sign_in) | 403 | entitlement | no | | [`rate_limited`](#rate_limited) | 429 | rate_limit | yes | | [`route_not_found`](#route_not_found) | 404 | not_found | no | | [`method_not_allowed`](#method_not_allowed) | 405 | invalid_request | no | | [`unsupported_media_type`](#unsupported_media_type) | 415 | invalid_request | no | | [`version_unsupported`](#version_unsupported) | 400 | invalid_request | no | | [`payload_too_large`](#payload_too_large) | 413 | too_large | no | | [`not_found`](#not_found) | 404 | not_found | no | | [`idempotency_key_unsupported`](#idempotency_key_unsupported) | 400 | invalid_request | no | | [`idempotency_key_reused`](#idempotency_key_reused) | 409 | conflict | no | | [`idempotency_in_flight`](#idempotency_in_flight) | 409 | conflict | yes | | [`oauth_required`](#oauth_required) | 403 | entitlement | no | | [`terms_required`](#terms_required) | 403 | entitlement | no | | [`terms_version_mismatch`](#terms_version_mismatch) | 400 | invalid_request | no | | [`too_many_keys`](#too_many_keys) | 403 | entitlement | no | | [`too_many_endpoints`](#too_many_endpoints) | 403 | entitlement | no | | [`rotation_in_progress`](#rotation_in_progress) | 409 | conflict | no | | [`endpoint_disabled`](#endpoint_disabled) | 409 | conflict | no | | [`billing_unavailable`](#billing_unavailable) | 503 | unavailable | no | | [`subscription_exists`](#subscription_exists) | 409 | conflict | no | | [`billing_not_available`](#billing_not_available) | 409 | conflict | no | | [`billing_rate_limited`](#billing_rate_limited) | 429 | rate_limit | yes | | [`insufficient_credits`](#insufficient_credits) | 402 | entitlement | no | | [`concurrency_limit`](#concurrency_limit) | 429 | rate_limit | yes | | [`queue_unavailable`](#queue_unavailable) | 503 | unavailable | yes | | [`internal_error`](#internal_error) | 500 | unavailable | yes | | [`preset_not_found`](#preset_not_found) | 404 | not_found | no | | [`preset_not_served`](#preset_not_served) | 400 | invalid_request | no | | [`variation_not_found`](#variation_not_found) | 400 | invalid_request | no | | [`quality_unavailable`](#quality_unavailable) | 400 | invalid_request | no | | [`render_too_long`](#render_too_long) | 400 | invalid_request | no | | [`file_not_ready`](#file_not_ready) | 409 | conflict | yes | | [`file_expired`](#file_expired) | 410 | gone | no | | [`download_link_invalid`](#download_link_invalid) | 403 | authentication | no | | [`uploads_disabled`](#uploads_disabled) | 403 | entitlement | no | | [`upload_closed`](#upload_closed) | 409 | conflict | no | | [`upload_expired`](#upload_expired) | 410 | gone | no | | [`upload_incomplete`](#upload_incomplete) | 409 | conflict | no | | [`upload_processing`](#upload_processing) | 409 | conflict | yes | | [`checksum_mismatch`](#checksum_mismatch) | 400 | invalid_request | no | | [`dj_invalid_set`](#dj_invalid_set) | 400 | invalid_request | no | | [`dj_offline_unsupported`](#dj_offline_unsupported) | 400 | invalid_request | no | | [`dj_preset_missing`](#dj_preset_missing) | 400 | invalid_request | no | | [`dj_unknown_transition_preset`](#dj_unknown_transition_preset) | 400 | invalid_request | no | | [`invalid_envelope`](#invalid_envelope) | 400 | invalid_request | no | | [`invalid_batch_line`](#invalid_batch_line) | 400 | invalid_request | no | | [`unknown_op`](#unknown_op) | 400 | invalid_request | no | | [`invalid_project`](#invalid_project) | 400 | invalid_request | no | | [`unsupported_project`](#unsupported_project) | 415 | invalid_request | no | | [`project_too_large`](#project_too_large) | 413 | too_large | no | | [`pack_license_required`](#pack_license_required) | 403 | entitlement | no | | [`license_required`](#license_required) | 503 | unavailable | yes | | [`resource_exhausted`](#resource_exhausted) | 503 | unavailable | yes | | [`engine_busy`](#engine_busy) | 503 | unavailable | yes | | [`render_busy`](#render_busy) | 503 | unavailable | yes | | [`engine_unavailable`](#engine_unavailable) | 503 | unavailable | yes | | [`timeout`](#timeout) | 504 | engine | no | | [`engine_failed`](#engine_failed) | 500 | engine | yes | | [`encode_failed`](#encode_failed) | 500 | engine | no | | [`no_candidate_passed`](#no_candidate_passed) | 422 | engine | no | ## invalid_request Invalid request. Status 400, type `invalid_request`, not retryable. When: A field, query parameter or header is missing, has the wrong type or is out of range. `param` names it, in the body's own notation (items[0].name). Fix: Change the field `param` names as the message says, then send the request again. ```json { "error": { "type": "invalid_request", "code": "invalid_request", "message": "Invalid request", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_request" } } ``` ## not_invited No Music API access yet. Status 403, type `entitlement`, not retryable. When: The map.audio account behind the access token has no Music API account. `details.customerId` names it, `details.emailConfirmed` says whether this sign-in proved its email address, `details.accessRequest` shows any request already sent and `details.requestAccessUrl` is where to ask. Fix: Request access at https://map.audio/music (or POST /v1/access-requests with the same token). If `details.emailConfirmed` is false and MAP Audio invited your address, sign in again and confirm it with the emailed code. If an invite went to another address, sign in with that address. ```json { "error": { "type": "entitlement", "code": "not_invited", "message": "No Music API access yet", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#not_invited" } } ``` ## authentication_failed Authentication failed. Status 401, type `authentication`, not retryable. When: The request has no `Authorization: Bearer` header, or its API key or map.audio access token is malformed, quoted, unknown, revoked or issued for another API. The `WWW-Authenticate` header points OAuth clients at the resource metadata. Fix: Send `Authorization: Bearer` followed by an API key (sk_test_ or sk_live_ and 40 letters and digits) or a current map.audio access token, with nothing around it. The message says which part was wrong. ```json { "error": { "type": "authentication", "code": "authentication_failed", "message": "Authentication failed", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#authentication_failed" } } ``` ## insufficient_scope Token lacks the audio scope. Status 403, type `entitlement`, not retryable. When: The map.audio access token was granted without the `audio` scope. Fix: Sign in again and ask for the `audio` scope; GET /.well-known/oauth-protected-resource lists it. ```json { "error": { "type": "entitlement", "code": "insufficient_scope", "message": "Token lacks the audio scope", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#insufficient_scope" } } ``` ## auth_unavailable Sign-in check unavailable. Status 503, type `unavailable`, retryable. When: The map.audio sign-in service could not check the access token just now. API keys do not depend on this check. Fix: Retry in a few seconds with the same token; signing in again does not help. ```json { "error": { "type": "unavailable", "code": "auth_unavailable", "message": "Sign-in check unavailable", "param": null, "retryable": true, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#auth_unavailable" } } ``` ## account_suspended Account suspended. Status 403, type `entitlement`, not retryable. When: The Music API account behind this API key or sign-in is suspended: MAP Audio suspended it, or the map.audio customer was erased. Fix: Email support@map.audio and quote the response's Request-Id. ```json { "error": { "type": "entitlement", "code": "account_suspended", "message": "Account suspended", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#account_suspended" } } ``` ## customer_erased Customer erased. Status 409, type `conflict`, not retryable. When: The map.audio customer behind this sign-in was erased, so the Music API cannot open an account or take an access request for them. Fix: This cannot be undone. Email support@map.audio if you think it is a mistake. ```json { "error": { "type": "conflict", "code": "customer_erased", "message": "Customer erased", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#customer_erased" } } ``` ## already_has_access Already has access. Status 409, type `conflict`, not retryable. When: POST /v1/access-requests was sent with the access token of a map.audio account that already has Music API access. Fix: Nothing to request: call the API with the same token, or create API keys at https://map.audio/music. ```json { "error": { "type": "conflict", "code": "already_has_access", "message": "Already has access", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#already_has_access" } } ``` ## access_request_needs_sign_in Access requests need a sign-in. Status 403, type `entitlement`, not retryable. When: POST /v1/access-requests was sent with an API key. A key always belongs to an account that has access, so a request needs the map.audio access token of the account that wants it. Fix: Sign in at https://map.audio/music with the map.audio account that needs access and request it there, or send that account's access token. ```json { "error": { "type": "entitlement", "code": "access_request_needs_sign_in", "message": "Access requests need a sign-in", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#access_request_needs_sign_in" } } ``` ## rate_limited Rate limited. Status 429, type `rate_limit`, retryable. When: More requests arrived within a minute than a limit allows. The message names the limit and what it counts: one API key, the map.audio sign-ins of one account, the access requests of one account, or one network address. Fix: Wait the seconds in `Retry-After`, then send again. To follow a job, use `Prefer: wait`, the job's events stream or a webhook rather than polling. ```json { "error": { "type": "rate_limit", "code": "rate_limited", "message": "Rate limited", "param": null, "retryable": true, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#rate_limited" } } ``` ## route_not_found No such route. Status 404, type `not_found`, not retryable. When: No route has this path, whatever the method. The message repeats the method and path, and suggests a route when one is a typo away. A trailing slash is ignored. Fix: Check the path against the reference at https://map.audio/developers/music-api/reference or GET /v1/openapi.json. ```json { "error": { "type": "not_found", "code": "route_not_found", "message": "No such route", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#route_not_found" } } ``` ## method_not_allowed Method not allowed. Status 405, type `invalid_request`, not retryable. When: The path exists, but not with this method. The `Allow` header lists the methods it takes; HEAD is answered wherever GET is. Fix: Send one of the methods in `Allow`. The message names where a related list lives, such as GET /v1/jobs for the jobs a POST route starts. ```json { "error": { "type": "invalid_request", "code": "method_not_allowed", "message": "Method not allowed", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#method_not_allowed" } } ``` ## unsupported_media_type Unsupported media type. Status 415, type `invalid_request`, not retryable. When: A route that takes a JSON body got one without `Content-Type: application/json`, with another media type, or compressed (`Content-Encoding`). `param` names the header and the message names what was sent. Fix: Send the body uncompressed, as JSON, with `Content-Type: application/json`. Routes that take no body (cancel, rotate-secret, test, billing portal) need neither. ```json { "error": { "type": "invalid_request", "code": "unsupported_media_type", "message": "Unsupported media type", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#unsupported_media_type" } } ``` ## version_unsupported API version not served. Status 400, type `invalid_request`, not retryable. When: The `Map-Version` request header names a version this API does not serve. Every response carries the version it answered with in `Map-Version`. Fix: Send `Map-Version: 2026-11-01`, or leave the header out to get the current version. ```json { "error": { "type": "invalid_request", "code": "version_unsupported", "message": "API version not served", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#version_unsupported" } } ``` ## payload_too_large Body too large. Status 413, type `too_large`, not retryable. When: The body is over the route's limit (256 KiB, or 2 MiB on the command routes), an upload declares more bytes than its kind allows, or one JSON field is over 1 MiB. `param` names the field when one is to blame. Fix: Send less: shorter envelopes or chains, fewer items per request. ```json { "error": { "type": "too_large", "code": "payload_too_large", "message": "Body too large", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#payload_too_large" } } ``` ## not_found Not found. Status 404, type `not_found`, not retryable. When: No job, file, plan, API key, webhook endpoint or provenance record with that id is visible to this account. `param` names the id (`id` for a path id). Another account's ids answer exactly as missing ones do. Fix: Check the id against the list route the message names, such as GET /v1/jobs or GET /v1/files; job and plan ids also get their expected shape in the message. ```json { "error": { "type": "not_found", "code": "not_found", "message": "Not found", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#not_found" } } ``` ## idempotency_key_unsupported Idempotency-Key not taken here. Status 400, type `invalid_request`, not retryable. When: An `Idempotency-Key` header was sent to a route that does not replay requests. Only routes that start a job take one: POST /v1/renders, the command routes and POST /v1/uploads/{id}/complete. Fix: Leave the header out on this route. A route that creates something (a key, a webhook endpoint) creates another on a retry, so list first to see whether the first attempt landed. ```json { "error": { "type": "invalid_request", "code": "idempotency_key_unsupported", "message": "Idempotency-Key not taken here", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#idempotency_key_unsupported" } } ``` ## idempotency_key_reused Idempotency-Key reused. Status 409, type `conflict`, not retryable. When: This `Idempotency-Key` was used in the last 24 hours with a different route or body. Fix: Use a new key for a new request. To replay the first answer, send the first request again unchanged. ```json { "error": { "type": "conflict", "code": "idempotency_key_reused", "message": "Idempotency-Key reused", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#idempotency_key_reused" } } ``` ## idempotency_in_flight Same request in flight. Status 409, type `conflict`, retryable. When: A request with this `Idempotency-Key` is still being accepted. Fix: Retry the same request, with the same key, after a few seconds. ```json { "error": { "type": "conflict", "code": "idempotency_in_flight", "message": "Same request in flight", "param": null, "retryable": true, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#idempotency_in_flight" } } ``` ## oauth_required Needs the account owner's sign-in. Status 403, type `entitlement`, not retryable. When: Creating or revoking API keys, buying a plan or render-minutes and opening the billing portal need the account owner signed in at https://map.audio/music. Webhook endpoints take an API key or that sign-in. This request used an API key or another app's sign-in. Fix: Do it at https://map.audio/music, signed in as the account owner. ```json { "error": { "type": "entitlement", "code": "oauth_required", "message": "Needs the account owner's sign-in", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#oauth_required" } } ``` ## terms_required Terms not accepted. Status 403, type `entitlement`, not retryable. When: The account has not accepted the current prototype terms, which every route that starts work needs. Creating an API key without `acceptTerms` on such an account answers this too. Fix: Read and accept the terms at https://map.audio/music, or send POST /v1/account/terms with {"accept": "2026-11-01-prototype"}, or call the MCP tool accept_terms. GET /v1/account shows `termsVersion` and `currentTerms`. ```json { "error": { "type": "entitlement", "code": "terms_required", "message": "Terms not accepted", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#terms_required" } } ``` ## terms_version_mismatch Wrong terms version. Status 400, type `invalid_request`, not retryable. When: `accept` (POST /v1/account/terms) or `acceptTerms` (POST /v1/api-keys) names a terms version other than the current one. Fix: Send the version GET /v1/account returns as `currentTerms`, today "2026-11-01-prototype". ```json { "error": { "type": "invalid_request", "code": "terms_version_mismatch", "message": "Wrong terms version", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#terms_version_mismatch" } } ``` ## too_many_keys Key limit reached. Status 403, type `entitlement`, not retryable. When: The account already holds 10 active API keys. Fix: Revoke one at https://map.audio/music (or DELETE /v1/api-keys/{id} signed in there), then create the new key. ```json { "error": { "type": "entitlement", "code": "too_many_keys", "message": "Key limit reached", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#too_many_keys" } } ``` ## too_many_endpoints Webhook endpoint limit reached. Status 403, type `entitlement`, not retryable. When: The account already has 10 webhook endpoints. Fix: Delete one with DELETE /v1/webhook-endpoints/{id} (GET /v1/webhook-endpoints lists them), then create the new one. ```json { "error": { "type": "entitlement", "code": "too_many_endpoints", "message": "Webhook endpoint limit reached", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#too_many_endpoints" } } ``` ## rotation_in_progress Secret rotation in progress. Status 409, type `conflict`, not retryable. When: The endpoint's previous secret is still valid from the last rotation, or another rotation changed the secret at the same moment. Fix: Rotate again once the time in the message has passed, or delete the endpoint and create a new one. ```json { "error": { "type": "conflict", "code": "rotation_in_progress", "message": "Secret rotation in progress", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#rotation_in_progress" } } ``` ## endpoint_disabled Webhook endpoint disabled. Status 409, type `conflict`, not retryable. When: The endpoint was disabled after repeated failed deliveries, or because the API key that set its secret was revoked. Fix: Rotate its secret with POST /v1/webhook-endpoints/{id}/rotate-secret, which enables it again. ```json { "error": { "type": "conflict", "code": "endpoint_disabled", "message": "Webhook endpoint disabled", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#endpoint_disabled" } } ``` ## billing_unavailable Billing unavailable. Status 503, type `unavailable`, not retryable. When: Plans and render-minutes are not on sale on this stage or not yet, or billing could not be reached. The response's `retryable` is true only in the last case. Fix: When `retryable` is true, try again shortly; otherwise buy on the production API at https://map.audio/music. ```json { "error": { "type": "unavailable", "code": "billing_unavailable", "message": "Billing unavailable", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#billing_unavailable" } } ``` ## subscription_exists Already subscribed. Status 409, type `conflict`, not retryable. When: The account already subscribes to a plan. Fix: Change or cancel the plan from the billing portal (POST /v1/billing/portal, signed in at https://map.audio/music). ```json { "error": { "type": "conflict", "code": "subscription_exists", "message": "Already subscribed", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#subscription_exists" } } ``` ## billing_not_available No billing account yet. Status 409, type `conflict`, not retryable. When: Nothing has been bought on this account, so there is no billing portal to open. Fix: Buy a plan or render-minutes first (POST /v1/billing/checkout). ```json { "error": { "type": "conflict", "code": "billing_not_available", "message": "No billing account yet", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#billing_not_available" } } ``` ## billing_rate_limited Too many billing requests. Status 429, type `rate_limit`, retryable. When: Too many checkout or billing portal links were requested for this account in the last hour. Fix: Wait the seconds in `Retry-After`; open the link already returned meanwhile. ```json { "error": { "type": "rate_limit", "code": "billing_rate_limited", "message": "Too many billing requests", "param": null, "retryable": true, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#billing_rate_limited" } } ``` ## insufficient_credits Not enough render-minutes. Status 402, type `entitlement`, not retryable. When: The job needs more render-minutes than the account has left this month. `details.needed` and `details.remaining` give both. Fix: Make the job shorter, wait for next month's allowance, or buy render-minutes or a plan at https://map.audio/music. ```json { "error": { "type": "entitlement", "code": "insufficient_credits", "message": "Not enough render-minutes", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#insufficient_credits" } } ``` ## concurrency_limit Too many jobs running. Status 429, type `rate_limit`, retryable. When: The account's plan runs this many jobs at once (2 on the free plan) and all of them are busy. Fix: Wait the seconds in `Retry-After`, or for a running job to end (`Prefer: wait`, its events or a webhook), then send again. ```json { "error": { "type": "rate_limit", "code": "concurrency_limit", "message": "Too many jobs running", "param": null, "retryable": true, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#concurrency_limit" } } ``` ## queue_unavailable Job not queued. Status 503, type `unavailable`, retryable. When: The job was admitted but could not be queued. Nothing was charged. Fix: Send the same request again; with the same `Idempotency-Key` it starts the job afresh. ```json { "error": { "type": "unavailable", "code": "queue_unavailable", "message": "Job not queued", "param": null, "retryable": true, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#queue_unavailable" } } ``` ## internal_error Internal error. Status 500, type `unavailable`, retryable. When: Something failed on the API's side. Nothing was charged. Fix: Retry shortly. If it keeps failing, email support@map.audio with the response's Request-Id. ```json { "error": { "type": "unavailable", "code": "internal_error", "message": "Internal error", "param": null, "retryable": true, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#internal_error" } } ``` ## preset_not_found No such preset. Status 404, type `not_found`, not retryable. When: No factory preset has this slug or reference. `param` names the field. Fix: Search with GET /v1/presets?q= and send the `ref` (pam:factory/) it returns. ```json { "error": { "type": "not_found", "code": "preset_not_found", "message": "No such preset", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#preset_not_found" } } ``` ## preset_not_served Preset not served. Status 400, type `invalid_request`, not retryable. When: The reference names a pack preset, an MFX preset or an uploaded preset. The API renders factory presets only, and no plan changes that. Fix: Pick a factory preset: GET /v1/presets lists them. ```json { "error": { "type": "invalid_request", "code": "preset_not_served", "message": "Preset not served", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#preset_not_served" } } ``` ## variation_not_found Variation not in the preset. Status 400, type `invalid_request`, not retryable. When: The preset has no variation in that slot. The message counts as the field does (`variation` from 1, a DJ set's `variationIndex` from 0) and lists the slots the preset has. Fix: Use a slot from the message; GET /v1/presets/{id} lists a preset's `variations`. ```json { "error": { "type": "invalid_request", "code": "variation_not_found", "message": "Variation not in the preset", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#variation_not_found" } } ``` ## quality_unavailable Quality not available. Status 400, type `invalid_request`, not retryable. When: `quality` is elite, which waits for a curated archive of rated presets and no plan unlocks yet. Fix: Send draft or standard. ```json { "error": { "type": "invalid_request", "code": "quality_unavailable", "message": "Quality not available", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#quality_unavailable" } } ``` ## render_too_long Render too long. Status 400, type `invalid_request`, not retryable. When: One render would be over 300 s (its length plus tail), or one job over 600 s of audio in all. The message gives the computed length and the fields that set it; `param` names the one to change. Fix: Lower the field `param` names (bars, seconds, tail, the count of seeds or items), or split the work into more jobs. ```json { "error": { "type": "invalid_request", "code": "render_too_long", "message": "Render too long", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#render_too_long" } } ``` ## file_not_ready File not ready. Status 409, type `conflict`, retryable. When: The file is still being written by its job (`jobId` names it, `Retry-After` says when to look again), or it is an upload that has not been completed or checked yet. Fix: Retry once the job named in `jobId` succeeds, or complete the upload first. ```json { "error": { "type": "conflict", "code": "file_not_ready", "message": "File not ready", "param": null, "retryable": true, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#file_not_ready" } } ``` ## file_expired File no longer stored. Status 410, type `gone`, not retryable. When: The file expired (outputs are kept 7 days on the free plan) or was deleted, or the account behind a download link was suspended. Fix: Render it again. GET /v1/files/{id} shows a file's `expiresAt`. ```json { "error": { "type": "gone", "code": "file_expired", "message": "File no longer stored", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#file_expired" } } ``` ## download_link_invalid Download link invalid. Status 403, type `authentication`, not retryable. When: The signed download link expired (links last an hour) or was altered. Fix: Ask for a fresh link: GET /v1/jobs/{id} or GET /v1/files/{id}/content. ```json { "error": { "type": "authentication", "code": "download_link_invalid", "message": "Download link invalid", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#download_link_invalid" } } ``` ## uploads_disabled Uploads not open yet. Status 403, type `entitlement`, not retryable. When: The route needs uploaded files (uploads, project remix, or a command naming an upload), and uploads are not open yet. No plan opens them. Fix: Render from factory presets (GET /v1/presets), and pass render outputs as files, meanwhile. ```json { "error": { "type": "entitlement", "code": "uploads_disabled", "message": "Uploads not open yet", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#uploads_disabled" } } ``` ## upload_closed Upload closed. Status 409, type `conflict`, not retryable. When: The upload was already completed, deleted or rejected. Fix: Start a new upload with POST /v1/uploads. ```json { "error": { "type": "conflict", "code": "upload_closed", "message": "Upload closed", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#upload_closed" } } ``` ## upload_expired Upload window passed. Status 410, type `gone`, not retryable. When: The upload was not completed within 24 hours of being created. Fix: Start a new upload with POST /v1/uploads. ```json { "error": { "type": "gone", "code": "upload_expired", "message": "Upload window passed", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#upload_expired" } } ``` ## upload_incomplete Upload parts missing. Status 409, type `conflict`, not retryable. When: POST /v1/uploads/{id}/complete was sent before every part arrived with its exact size. Fix: PUT the missing parts (the message says how many arrived), then complete again. ```json { "error": { "type": "conflict", "code": "upload_incomplete", "message": "Upload parts missing", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#upload_incomplete" } } ``` ## upload_processing Upload check starting. Status 409, type `conflict`, retryable. When: Another completion of this upload is starting its check job. Fix: Retry after the seconds in `Retry-After`. ```json { "error": { "type": "conflict", "code": "upload_processing", "message": "Upload check starting", "param": null, "retryable": true, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#upload_processing" } } ``` ## checksum_mismatch Upload does not match its checksum. Status 400, type `invalid_request`, not retryable. When: A job found that an uploaded file does not match the SHA-256 its upload declared. It arrives in the failed job's `error`. Fix: Upload the file again with its correct `sha256`. ```json { "error": { "type": "invalid_request", "code": "checksum_mismatch", "message": "Upload does not match its checksum", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#checksum_mismatch" } } ``` ## dj_invalid_set Invalid DJ set. Status 400, type `invalid_request`, not retryable. When: The DJ set does not fit its schema (https://map.audio/schemas/pam/dj-set.schema.json). `param` names the field, such as set.queue[0].bars. Fix: Change that field. POST /v1/arrangements checks a set and shows its timing without rendering. ```json { "error": { "type": "invalid_request", "code": "dj_invalid_set", "message": "Invalid DJ set", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#dj_invalid_set" } } ``` ## dj_offline_unsupported Set cannot render as sent. Status 400, type `invalid_request`, not retryable. When: The set sets `auto` to false. A render steps through the queue on its own. Fix: Leave `auto` out, or send true. ```json { "error": { "type": "invalid_request", "code": "dj_offline_unsupported", "message": "Set cannot render as sent", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#dj_offline_unsupported" } } ``` ## dj_preset_missing DJ set preset not found. Status 400, type `invalid_request`, not retryable. When: A preset in the set's queue is not a factory preset. `param` names the item. Fix: Use references from GET /v1/presets, such as pam:factory/313-disco. ```json { "error": { "type": "invalid_request", "code": "dj_preset_missing", "message": "DJ set preset not found", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#dj_preset_missing" } } ``` ## dj_unknown_transition_preset Unknown transition preset. Status 400, type `invalid_request`, not retryable. When: A transition names a preset a render cannot use; renders hold only the factory transition presets. Fix: Pick one from GET /v1/transition-presets, or describe the transition with `bars` and `spec`. ```json { "error": { "type": "invalid_request", "code": "dj_unknown_transition_preset", "message": "Unknown transition preset", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#dj_unknown_transition_preset" } } ``` ## invalid_envelope Invalid state envelope. Status 400, type `invalid_request`, not retryable. When: An item of `envelopes` is not an object with a `delta` object, or with a `directNodeOps` array and no delta, or its `options` is not an object. Fix: Send each envelope as {"delta": {...}} or {"directNodeOps": [...]}, with an optional "options" object. ```json { "error": { "type": "invalid_request", "code": "invalid_envelope", "message": "Invalid state envelope", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_envelope" } } ``` ## invalid_batch_line Invalid batch line. Status 400, type `invalid_request`, not retryable. When: The engine refused a line of a state batch. Fix: Change the line the message names. ```json { "error": { "type": "invalid_request", "code": "invalid_batch_line", "message": "Invalid batch line", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_batch_line" } } ``` ## unknown_op Unknown operation. Status 400, type `invalid_request`, not retryable. When: The engine was asked for an operation it does not know. Fix: Check the request against the route's reference. ```json { "error": { "type": "invalid_request", "code": "unknown_op", "message": "Unknown operation", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#unknown_op" } } ``` ## invalid_project Invalid project. Status 400, type `invalid_request`, not retryable. When: The uploaded project could not be read. Fix: Export the project again and upload it. ```json { "error": { "type": "invalid_request", "code": "invalid_project", "message": "Invalid project", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#invalid_project" } } ``` ## unsupported_project Project format not supported. Status 415, type `invalid_request`, not retryable. When: The uploaded project is in a format the engine does not read. Fix: Upload a supported project format; the remix route's reference lists them. ```json { "error": { "type": "invalid_request", "code": "unsupported_project", "message": "Project format not supported", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#unsupported_project" } } ``` ## project_too_large Project too large. Status 413, type `too_large`, not retryable. When: The uploaded project is over the engine's size limit. Fix: Upload a smaller project. ```json { "error": { "type": "too_large", "code": "project_too_large", "message": "Project too large", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#project_too_large" } } ``` ## pack_license_required Pack licence required. Status 403, type `entitlement`, not retryable. When: The render host refused a preset that needs a pack licence. The API sends only factory presets, so this means a factory preset referenced pack content. Fix: Pick another factory preset, and email support@map.audio with the Request-Id. ```json { "error": { "type": "entitlement", "code": "pack_license_required", "message": "Pack licence required", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#pack_license_required" } } ``` ## license_required Render host licence check failed. Status 503, type `unavailable`, retryable. When: The render host's licence check failed. Nothing was charged. Fix: Retry the job. ```json { "error": { "type": "unavailable", "code": "license_required", "message": "Render host licence check failed", "param": null, "retryable": true, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#license_required" } } ``` ## resource_exhausted Render host out of resources. Status 503, type `unavailable`, retryable. When: The render host ran out of memory or another resource. Nothing was charged. Fix: Retry the job, or make it shorter. ```json { "error": { "type": "unavailable", "code": "resource_exhausted", "message": "Render host out of resources", "param": null, "retryable": true, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#resource_exhausted" } } ``` ## engine_busy Engine busy. Status 503, type `unavailable`, retryable. When: The engine was busy with other work. Nothing was charged. Fix: Retry the job. ```json { "error": { "type": "unavailable", "code": "engine_busy", "message": "Engine busy", "param": null, "retryable": true, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#engine_busy" } } ``` ## render_busy Renderer busy. Status 503, type `unavailable`, retryable. When: The renderer was busy with other work. Nothing was charged. Fix: Retry the job. ```json { "error": { "type": "unavailable", "code": "render_busy", "message": "Renderer busy", "param": null, "retryable": true, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#render_busy" } } ``` ## engine_unavailable No render host. Status 503, type `unavailable`, retryable. When: No render host took the job in time, or one failed three times. Nothing was charged beyond a preview already delivered. Fix: Send the job again; with a new `Idempotency-Key` it starts afresh. ```json { "error": { "type": "unavailable", "code": "engine_unavailable", "message": "No render host", "param": null, "retryable": true, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#engine_unavailable" } } ``` ## timeout Job deadline passed. Status 504, type `engine`, not retryable. When: The job ran past its deadline and was stopped. Nothing was charged. Fix: Make the job shorter or split it. ```json { "error": { "type": "engine", "code": "timeout", "message": "Job deadline passed", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#timeout" } } ``` ## engine_failed Engine failed. Status 500, type `engine`, retryable. When: The engine failed, or did not write an output it should have. Nothing was charged. Fix: Retry the job; if it fails again, email support@map.audio with the job id. ```json { "error": { "type": "engine", "code": "engine_failed", "message": "Engine failed", "param": null, "retryable": true, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#engine_failed" } } ``` ## encode_failed Encoding failed. Status 500, type `engine`, not retryable. When: A rendered WAV could not be encoded to the requested format. Nothing was charged. Fix: Render again with format wav, and email support@map.audio with the job id. ```json { "error": { "type": "engine", "code": "encode_failed", "message": "Encoding failed", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#encode_failed" } } ``` ## no_candidate_passed No candidate passed. Status 422, type `engine`, not retryable. When: A quality: standard search rendered every candidate and none passed the limit gate (silence, clipping, loudness). Nothing was charged. Fix: Try another seed or preset, or another `search.targetLufs`. ```json { "error": { "type": "engine", "code": "no_candidate_passed", "message": "No candidate passed", "param": null, "retryable": false, "requestId": "8e1f2a3b4c5d6e7f-LHR", "jobId": null, "docUrl": "https://map.audio/developers/music-api/errors#no_candidate_passed" } } ```