# 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/<operationId>.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/<slug>) 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/<slug>`, 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/<slug>`, 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/<slug>`, 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/<slug>`, 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/<slug>`, 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/<slug>`, 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/<slug> 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/<slug> 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/<slug> 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/<slug>`, 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.
