# Rate limits and errors
URL: https://musicapi.com/docs/get-started/rate-limits-and-errors.md

> The rate limit of a partner account, the error shape, the internal error codes, and what to do on a 412 or a 429.

Product: MusicAPI Core and MusicAPI Managed Transfers. Section: Get started.

## Rate limit

Each partner account has one rate limit for all its keys and credentials together.

- **300 requests per minute** by default.
- One **fixed 60-second window**. The first request starts the window, and the count resets 60 seconds later.
- Over the limit, the API answers `429 Too Many Requests` with `Retry-After: 60`. The header gives the window length in seconds, not the time left in the window.
- The limit counts calls to `/public/*` and `/api/*`. The hosted sign-in pages and webhooks do not count.
- Need more? [Contact us](https://schedule.musicapi.com/). We can raise the limit of your account or remove it.

A docs sandbox session has its own limit: 30 calls per minute.

```json
{
  "message": "MusicAPI rate limit. You have exceeded the rate limit for this endpoint or server is currently unable to handle the request. Please wait and retry after the indicated time."
}
```

### Handle a 429

```js
async function callMusicApi(url, init, attempt = 0) {
  const response = await fetch(url, init);
  if (response.status !== 429 || attempt >= 3) {
    return response;
  }
  const waitSeconds = Number(response.headers.get("retry-after") ?? "60");
  await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000));
  return callMusicApi(url, init, attempt + 1);
}
```

```python
import time, requests

def call_music_api(method, url, attempt=0, **kwargs):
    response = requests.request(method, url, **kwargs)
    if response.status_code != 429 or attempt >= 3:
        return response
    time.sleep(int(response.headers.get("Retry-After", "60")))
    return call_music_api(method, url, attempt + 1, **kwargs)
```

Spread bulk work over time instead of sending it in bursts. A music service can also refuse a call because of its own limits. Retry such a call later, with a growing pause.

## The error shape

Every error answer is JSON.

```json
{
  "internalCode": 14,
  "message": "Needs reauthentication",
  "sentryId": "b6f1c2d4e5a64f0f9a1b2c3d4e5f6a7b"
}
```

| Field          | What it is                                                                        |
| -------------- | --------------------------------------------------------------------------------- |
| `message`      | A short text. Show it in your logs, not to your users.                            |
| `internalCode` | A number for the cases worth branching on (table below). Not every error has one. |
| `details`      | More detail, when there is any.                                                   |
| `sentryId`     | Our error id. Send it to us when you report a problem.                            |

A body or query that fails validation answers `400` with the field that is wrong:

```json
{
  "type": "body",
  "message": "\"itemIds\" must contain less than or equal to 20 items",
  "details": [{ "message": "\"itemIds\" must contain less than or equal to 20 items", "path": ["itemIds"] }]
}
```

## Status codes

| Status | Meaning                                                                                 | What to do                                                                                                                               |
| ------ | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | The request is not valid.                                                               | Fix the request. Do not retry it unchanged.                                                                                              |
| `401`  | No credential, or a credential this route does not read.                                | Check the `Authorization` header and the [route table](https://musicapi.com/docs/get-started/authentication.md#which-route-takes-which). |
| `403`  | The credential is valid but not allowed here, or an evaluation account reached its cap. | Use a Client Secret where the route needs one.                                                                                           |
| `404`  | The connected user, item or transfer does not exist in your account.                    | Check the id. Another account's id also answers `404`.                                                                                   |
| `412`  | The user must sign in again.                                                            | Send the user through the hosted sign-in.                                                                                                |
| `418`  | The service does not support this operation.                                            | Check [Services and features](https://musicapi.com/docs/get-started/services-and-features.md).                                           |
| `422`  | The item is already in the collection.                                                  | Treat it as done.                                                                                                                        |
| `429`  | Rate limit.                                                                             | Wait `Retry-After` seconds.                                                                                                              |
| `5xx`  | Our error, or the music service failed.                                                 | Retry later with a growing pause.                                                                                                        |

## Internal error codes

| `internalCode` | Name                                                                                                          | Status |
| -------------- | ------------------------------------------------------------------------------------------------------------- | ------ |
| 12             | `InvalidReturnUrl`: the `returnUrl` is not one of your Accepted Return URLs                                   | 400    |
| 14             | `IntegrationUserNotAuthenticated`: the stored sign-in no longer works                                         | 412    |
| 15             | `NotSupportedMethod`: the service has no such operation                                                       | 418    |
| 18             | `ItemAlreadyExistsInCollection`                                                                               | 422    |
| 19             | `CollectionDoesNotExists`: the playlist or album is gone, or cannot be read                                   | 404    |
| 20             | `ManualLoginRequired`: the user must sign in on the service's own site first                                  | 403    |
| 21             | `ActionInMusicServiceRequired`: the user must do something in the service first, for example accept new terms | 403    |

## The user must sign in again (412)

A `412` with `internalCode: 14` means the music service refused the stored sign-in, and MusicAPI could not renew it. The user changed their password, removed your app, or the refresh token expired.

```json
{
  "internalCode": 14,
  "message": "Needs reauthentication"
}
```

What to do:

1. Mark the connection as broken in your app.
2. Show the user a "Reconnect" button that opens the [hosted sign-in](https://musicapi.com/docs/connect-users/start-sign-in.md) for the same service.
3. When the same music account signs in again, it keeps its `integrationUserUUID`. Your stored id stays valid.

Before it answers `412`, MusicAPI already tried to renew the tokens once. Do not retry the call in a loop.
