---
name: map-music-api
description: Render audio from PAM's factory presets on MAP Audio's servers with the MAP Audio Music API (REST or MCP), with no local install. Use for loops, variations and seed batches, stems, cutdowns, DJ sets, drum hits, MIDI patterns and loudness analysis rendered in the cloud from code. For PAM on the user's own Mac, use the pam-audio-gen skill instead.
---

# MAP Audio Music API

PAM's sound engine as a REST API and an MCP server. Every render is CPU synthesis of a PAM factory preset: no AI
model makes the audio, and the same request with the same seed renders the same audio. Access is invite-only while
the API is in preview. The OpenAPI document is the contract; this skill is the workflow.

- Base URL: `https://api.map.audio`. Contract: `GET https://api.map.audio/v1/openapi.json`.
- Docs: https://map.audio/developers/music-api. Every page is also Markdown (the reference per operation at
  https://map.audio/developers/music-api/reference/<operationId>.md); the index is
  https://map.audio/developers/music-api/llms.txt.
- MCP: `https://api.map.audio/mcp` (Streamable HTTP), with the same credentials. The tools are listed below.
- Every response carries `Map-Version` and `Request-Id`.

## Setup

1. Access. The account holder signs in at https://map.audio/music and requests access. With a map.audio access
   token, `POST /v1/access-requests` sends the same request. Until access is granted, calls answer `403 not_invited`;
   `details.customerId` names the account and `details.accessRequest` the request's state.
2. A key. Once invited, the account holder accepts the prototype terms and creates an API key at
   https://map.audio/music. The key is shown once. Read it from the environment variable `MAP_API_KEY`, and never
   write it into a file, a log or the chat.
3. Every call sends `Authorization: Bearer $MAP_API_KEY` and may pin the version with `Map-Version: 2026-11-01`.

An agent cannot create or revoke keys, or buy a plan or render-minutes: those need the account holder signed in at
https://map.audio/music. If a call answers `terms_required`, show the user the terms and accept them only with
their consent: `POST /v1/account/terms` with `{"accept": "<currentTerms from GET /v1/account>"}`. A leaked key is
revoked at https://map.audio/music; an app connected through OAuth is disconnected at
https://map.audio/account#connected-apps.

Test and live keys (`sk_test_`, `sk_live_`) behave the same today: both render real audio on the same servers and
spend the account's render-minutes, and both see the same jobs, files and webhook endpoints. Only a job's
`livemode`, the idempotency namespace and the provenance licence id (ending `_test`) differ. Confirm the cost with
the user before a large batch, whichever key you hold.

When something fails, quote the response's `Request-Id` (also `error.requestId`) to the user or to
support@map.audio.

## Limits on the free plan

| Limit | Value |
|---|---|
| Render-minutes a month | 30 |
| Charge per render | one render-minute per minute of each rendered file, at least 0.1 each |
| One render | at most 300 s of audio, 22,050 to 48,000 Hz |
| One job | at most 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 budget |
| Rendered files kept | 7 days |

Before authentication, one IP address may send 600 requests a minute. Stems and MIDI files are not charged, and
neither are the calls that render nothing chargeable (preset search, plans, arrangements, analysis, drum hits,
patterns and `state` without `render`). A failed or cancelled job is not charged, except for a preview it already
delivered. Paid plans (Creator, Pro, App) raise the render-minutes and how long files are kept, and Pro and App the jobs at once;
`GET /v1/credits` shows the plan and what is left. The free plan's audio is for evaluating the API: its terms grant
no commercial use (`license.commercialUse` in each provenance record says what applies).

## The core loop

1. Find a preset: `GET /v1/presets?q=disco&tempoMin=110&tempoMax=130`. Use its `ref` (`pam:factory/<slug>`);
   `variations` lists the slots it has, numbered from 1. Never invent a reference.
2. Render: `POST /v1/renders` with `{"source":{"preset":"pam:factory/313-disco","variation":1},"bars":8,"seed":42}`.
   Give `bars` or `seconds`, not both. `format: {"container":"flac"}` or `{"container":"mp3","bitrate":320}` changes
   the encoding (WAV and FLAC are 24-bit). `stems: true` adds up to eight `stem` outputs, one per PAM cell (`cell`
   1 to 8), encoded like the master.
3. Wait: `Prefer: wait=30` (0 to 60) returns the finished job when it ends within 30 s; otherwise the answer is
   `202` with a queued or running job. Then poll `GET /v1/jobs/{id}` no faster than every 2 s, or read
   `GET /v1/jobs/{id}/events` (server-sent events; reconnect with `Last-Event-ID`).
4. Download: `result.outputs[]` holds `role` (`master`, `stem`, `midi`, `preset`, `preview`), `file`, `url`,
   `sha256`, `format` and `durationSeconds`. A `url` works for one hour; `GET /v1/files/{file}/content` redirects to
   a fresh one. `curl -sSL --fail-with-body -o loop.wav "$URL"` saves it. Files are kept 7 days on the free plan
   (`expiresAt` on `GET /v1/files/{id}`); `DELETE /v1/files/{id}` removes one sooner.
5. Keep `result.seed` and `result.provenance`: the seed reproduces the render, and the provenance record (Ed25519,
   keys at `https://api.map.audio/.well-known/jwks.json`) names the engine, the presets, the seed and every file.

Retry a POST that starts a job with `Idempotency-Key: <8 to 128 characters>`: the same key and body return the same
job for 24 hours, even a job that failed; the same key with a different body is `409 idempotency_key_reused`. A new
attempt after a failed job needs a new key. Cancel a job you no longer need with `POST /v1/jobs/{id}/cancel`.

## More than one render in one job

At most one of `variations`, `items` and `quality` per request; `preview` can go with any of them:

- `variations: {"kind":"slots","slots":[1,2,3]}` renders those slots of the preset.
- `variations: {"kind":"seeds","count":4}` renders four seeds derived from `seed` (drawn and returned as
  `result.seed` when you send none).
- `variations: {"kind":"morph","to":{"preset":"…"},"at":[0,0.5,1]}` renders points between two presets.
- `variations: {"kind":"blend","presets":[{"preset":"…","weight":2},{"preset":"…","weight":1}]}` renders a weighted blend.
- `items: [{"name":"intro","variation":1,"bars":4},{"name":"ad","cutdown":{"seconds":30}}]` renders named items;
  a cutdown fits the preset's own variations to a length.
- `quality: "standard"` renders `search.candidates` seeds (2 to 8), measures each and returns the best
  `search.return` that pass the limit gate (no silence, no clipping, loudness near `search.targetLufs`). You pay
  only for the ones returned. `quality: "elite"` is refused (`quality_unavailable`).
- `preview: {"bars":2}` publishes a short render first, as a `job.preview` event, before the rest.

## Recipes

The docs work each of these through with full requests: https://map.audio/developers/music-api#recipes.

- Loop variations for a game or app: one render with `variations` of kind `slots` or `seeds`; each master is a loop.
- Stems for a remix: a render with `stems: true`.
- A 30-second ad cutdown: a render with `items: [{"name":"ad","cutdown":{"seconds":30}}]`, or
  `POST /v1/arrangements` to plan the cut without rendering.
- A DJ set with transitions: `POST /v1/dj/set` with `{"set":{…}}`. Queue items name presets by `presetPath` (a
  preset reference) and `variationIndex` (from 0); `GET /v1/transition-presets` lists the transitions.
- Drum one-shots: `POST /v1/synth/drum` with an `engine` and `param` controls from `GET /v1/drum-engines`.
- A MIDI pattern: `POST /v1/pattern/generate` with a `genre`, `bpm` and `seed`.
- A seed batch, then pick: `variations: {"kind":"seeds","count":8}`, or `quality: "standard"` to keep the best.
- Loudness of a render: `POST /v1/audio/analyze` with `files` naming outputs the API rendered.
- Long jobs: register a webhook endpoint instead of polling.

## Plans and arrangements

- `POST /v1/plans` with `{"prompt":"dusty boom bap 88 bpm"}` ranks the catalogue with fixed rules (no model) and
  returns `candidates` and a `spec`. Render it with `POST /v1/renders {"plan":"plan_…"}`. Send
  `{"planner":"caller","spec":{…}}` to store a spec you wrote.
- `POST /v1/arrangements` fits a cutdown (`{"source":{…},"cutdown":{"seconds":30}}`) or checks a DJ set
  (`{"set":{…}}`) without rendering, and returns the set with planned boundaries.

## Engine commands

`POST /v1/<command>` runs one engine command with the command's options as the JSON body: `/v1/preset/render`,
`/v1/state` (add `"render":true` to render), `/v1/dj/set`, `/v1/synth/drum`, `/v1/pattern/generate` (MIDI),
`/v1/audio/analyze` (on files the API rendered), `/v1/fx`, `/v1/audio/master` and the sample edits
(`/v1/audio/normalize`, `trim`, `gain`, `reverse`, `repitch`). The OpenAPI document lists every field; an unknown
field is refused with its name in `param`. Uploads (`/v1/uploads`, `/v1/project/remix`) answer `uploads_disabled`
until they open.

## Webhooks

`POST /v1/webhook-endpoints` with an API key subscribes an HTTPS URL to `job.succeeded`, `job.failed` and
`job.cancelled` (or `job.preview`, `job.expired`, `webhook.test`, `*`). The `whsec_` secret is shown once.
Deliveries follow Standard Webhooks: check `webhook-signature` (`v1,` and a base64 HMAC-SHA256 of
`<webhook-id>.<webhook-timestamp>.<body>` under the secret's bytes) before trusting the body. A failed delivery is
retried for about two and a half days; three that run out of retries with no success between them disable the
endpoint, and `POST /v1/webhook-endpoints/{id}/rotate-secret` enables it again. Revoking the key that last set an
endpoint's secret disables the endpoint until that rotation.

## MCP tools

Tools that render send `Prefer: wait=45` and return the job; poll `get_job` while it is still running. Every other
operation is REST only.

| Tool | Does |
|---|---|
| `get_account` | the account, its plan, limits and the terms it has accepted |
| `accept_terms` | accept the prototype terms (only with the user's consent) |
| `get_credits` | render-minutes left this month |
| `search_presets` | search the factory presets |
| `get_preset` | one preset and its variations |
| `list_drum_engines` | the drum engines and their controls |
| `list_transition_presets` | DJ transition presets |
| `create_plan` | rank presets for a prompt, or store a spec |
| `get_plan` | a stored plan |
| `create_render` | render a preset or a plan |
| `create_arrangement` | plan a cutdown or check a DJ set |
| `get_job` | a job and its outputs |
| `list_jobs` | the account's jobs |
| `cancel_job` | cancel a job |
| `list_files` | the account's files |
| `get_file` | one file and its expiry |
| `preset_render` | render a preset with the engine command's options |
| `state` | apply state envelopes; with `render`, render them |
| `render_dj_set` | render a DJ set |
| `synth_drum` | render drum hits |
| `pattern_generate` | write a MIDI pattern |
| `audio_analyze` | measure loudness and peaks |
| `audio_normalize` | normalize loudness |
| `audio_trim` | trim a rendered file |
| `audio_gain` | change a rendered file's gain |
| `audio_reverse` | reverse a rendered file |
| `audio_repitch` | repitch a rendered file |
| `fx` | run a PAM cell effect chain over a rendered file |
| `audio_master` | run PAM's master chain over a rendered file |

`project_remix` is listed only once uploads open.

## Errors

Errors are `{"error":{"type","code","message","param","details","retryable","requestId","jobId","docUrl"}}`.
`docUrl` is https://map.audio/developers/music-api/errors#<code>, which explains the code and its fix.

| Code | Status | Means | Do |
|---|---|---|---|
| `invalid_request` | 400 | a field is wrong; `param` names it | fix that field |
| `authentication_failed` | 401 | no key, or a malformed, revoked or unknown one | check `MAP_API_KEY` |
| `not_invited` | 403 | the account has no Music API access yet | request access at https://map.audio/music |
| `terms_required` | 403 | the account has not accepted the prototype terms | ask the user, then accept them |
| `oauth_required` | 403 | keys and billing need the account holder at https://map.audio/music | ask the account holder |
| `insufficient_credits` | 402 | fewer render-minutes left than the job needs (`details.remaining`) | render less, or ask the account holder to buy more |
| `concurrency_limit` | 429 | the plan's jobs at once are all running | wait; honour `Retry-After` |
| `rate_limited` | 429 | too many requests in a minute | back off; honour `Retry-After` |
| `preset_not_found` | 404 | no factory preset with that reference | search `GET /v1/presets` |
| `preset_not_served` | 400 | a pack, MFX or uploaded preset; only factory presets render | pick a factory preset |
| `variation_not_found` | 400 | the preset has no such variation slot | use a slot from its `variations` |
| `render_too_long` | 400 | one render over 300 s, or one job over 600 s of audio | shorten it or split it |
| `idempotency_key_reused` | 409 | the same `Idempotency-Key` with another body | use a new key |
| `file_expired` | 410 | the file's retention ended or it was deleted | render again |
| `uploads_disabled` | 403 | uploads are not open yet | render from presets |
| `version_unsupported` | 400 | `Map-Version` names a version the API does not serve | send the current version |
| `engine_failed` | 500 | the engine failed; nothing was charged | retry with a new `Idempotency-Key` |
| `engine_unavailable` | 503 | no render host took the job; nothing was charged | retry later |
| `queue_unavailable` | 503 | the job could not be queued; nothing was charged | retry later |
| `license_required` | 503 | the render host's licence check failed; nothing was charged | retry later |

Any code marked `retryable` can succeed later; others need a change first. The error catalogue lists every code.

## Rules for agents

- Pick presets from `/v1/presets`; never invent a reference.
- Report the seed and the preset references with every result, so the user can reproduce it.
- Tell the user the render-minutes a job will spend (`estimate.renderMinutes` on the job) before large batches.
- Download what the user wants to keep before its file expires.
- Never print, log or commit the API key.
