> ## Documentation Index
> Fetch the complete documentation index at: https://muveya.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limits

> The request budget of each API key, the headers that report it, and how to back off when you reach it.

Every API key has its own request budget. The budget is counted per key, not per IP address or per server: all the machines that share a key share its budget, and two keys never share one.

## The budget

| Setting | Current value |
| - | - |
| Requests per window | **120** |
| Window length | **60 seconds** |
| Counted per | API key |
| Shared across | `/v1` operations that use the same API key. MCP has separate OAuth authentication. |

The window is fixed. It starts with the first request the key makes after the previous window ended, and it resets 60 seconds later. Every request that passes authentication counts, whatever its result: a `200`, a `400`, a `404` or a `429` all use one unit. Requests keep counting while you are over the limit, but they do not make the window longer.

Requests refused with `401` `api_keys.invalid` or `403` `api_keys.scope_missing` are rejected before the budget is checked: they do not count and they carry no rate-limit headers.

<Note>
  These are the current values. Read the budget from the response headers instead of hard-coding it, so that your integration follows any change.
</Note>

## Headers on every response

Every response to an authenticated request reports the state of the budget in two header families: the widely used `X-RateLimit-*` headers and the IETF draft `RateLimit` headers.

| Header | Example | Meaning |
| - | - | - |
| `X-RateLimit-Limit` | `120` | Requests allowed per window. |
| `X-RateLimit-Remaining` | `117` | Requests left in the current window. `0` when the budget is used up. |
| `X-RateLimit-Reset` | `42` | **Seconds** until the window resets (not a timestamp). At least `1`. |
| `RateLimit-Policy` | `120;w=60` | The limit and the window length in seconds. |
| `RateLimit` | `limit=120, remaining=117, reset=42` | The same snapshot in one header. |

```http theme={null}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117
X-RateLimit-Reset: 42
RateLimit-Policy: 120;w=60
RateLimit: limit=120, remaining=117, reset=42
x-request-id: 8d0e5b8a-2f4c-4e1b-9a7d-0c3b5e6f7a81
```

## When you go over: `429`

The request that goes over the budget is refused with `429 Too Many Requests`, a `Retry-After` header with the number of seconds to wait, the same rate-limit headers, and a problem document with the code `common.too_many_requests`.

```http theme={null}
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json; charset=utf-8
Retry-After: 18
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 18
RateLimit-Policy: 120;w=60
RateLimit: limit=120, remaining=0, reset=18
```

```json theme={null}
{
  "type": "https://docs.muveya.com/errors/common.too_many_requests",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests. Try again later.",
  "instance": "/v1/orders",
  "code": "common.too_many_requests",
  "requestId": "5a1c7e2d-9b3f-4d6a-8e0c-2f4b6d8a0c1e"
}
```

Nothing was read or changed by a refused request. Retrying it after `Retry-After` seconds is safe.

## How to back off

<CodeGroup>
  ```javascript JavaScript theme={null}
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

  async function muveyaGet(url, headers, attempts = 5) {
    for (let attempt = 1; attempt <= attempts; attempt += 1) {
      const response = await fetch(url, { headers });
      if (response.status !== 429) return response;

      const retryAfter = Number(response.headers.get("retry-after") ?? "1");
      await sleep(Math.max(1, retryAfter) * 1000);
    }
    throw new Error("Rate limit: gave up after several attempts");
  }
  ```

  ```python Python theme={null}
  import time
  import requests

  def muveya_get(session: requests.Session, url: str, attempts: int = 5, **kwargs):
      for _ in range(attempts):
          response = session.get(url, timeout=30, **kwargs)
          if response.status_code != 429:
              return response
          retry_after = int(response.headers.get("Retry-After", "1"))
          time.sleep(max(1, retry_after))
      raise RuntimeError("Rate limit: gave up after several attempts")
  ```
</CodeGroup>

Good habits:

* **Wait for `Retry-After`** on every `429`. Retrying sooner only adds refused requests to the same window.
* **Slow down before you hit the wall.** When `X-RateLimit-Remaining` gets close to `0`, wait `X-RateLimit-Reset` seconds before the next burst.
* **Use large pages.** Walk paginated lists with `limit=200` (see [Pagination](/docs/en/api-reference/pagination)).
* **Cache what rarely changes.** Locations, warehouses and categories do not need to be read on every run.
* **Poll exports calmly.** One poll every few seconds is enough while an export is being built (see [Exports](/docs/en/api-reference/exports)).
* **Do not run overlapping jobs** with the same key. Schedule them one after the other.
* **Use one key per integration.** Each key has its own budget, so a busy job does not starve another.

If your integration needs a larger budget, write to [team@muveya.com](mailto:team@muveya.com) with the expected request volume.

## Related pages

* [Errors](/docs/en/api-reference/errors)
* [Webhooks](/docs/en/api-reference/webhooks): polling within the budget
* [Scopes](/docs/en/api-reference/scopes)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.