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

# Rate limiting

> Understand the tier-based rate limit model and how to handle 429 responses

## Overview

The Integrations API enforces rate limits to keep the service stable and usage fair. Limits are tier-based and resolved per API key: the limit that applies to a request depends on the plan associated with your key and on which capability bucket the request falls into.

Exact request-per-minute numbers are configured per tier and are not published here. To see the limits that apply to your key, check the control plane or contact support.

## Capability buckets

Each request is counted against one of six buckets. Read operations, write operations, uploads, and sandbox calls are limited independently, so heavy polling does not consume your submission-write budget.

| Bucket             | Covers                                                          |
| ------------------ | --------------------------------------------------------------- |
| `upload`           | Presigned upload URL requests                                   |
| `submission_write` | Creating submissions and retrying assets                        |
| `submission_read`  | Reading submission status, assets, issues, topics, and versions |
| `webhook_write`    | Creating, updating, and deleting webhook subscriptions          |
| `webhook_read`     | Listing subscriptions and reading delivery history              |
| `sandbox`          | Sandbox-only endpoints such as firing synthetic events          |

## When you exceed a limit

Exceeding a bucket limit returns a 429 with a single `error` string and a `Retry-After` header (in seconds).

```json theme={null}
{
  "error": "Rate limit exceeded: too many submission writes"
}
```

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 30
```

<Warning>
  The API does not emit `X-RateLimit-Limit`, `X-RateLimit-Remaining`, or `X-RateLimit-Reset` headers. The only rate-limit header is `Retry-After`, returned on 429 responses. Do not write code that reads `X-RateLimit-*` headers.
</Warning>

## When your plan lacks API access

If your tier is not authorized for the Integrations API at all, requests return a 402 instead of a 429. This is a plan-gating response, not a transient rate limit.

```json theme={null}
{
  "error_code": "tier_not_authorized_for_integrations_api",
  "message": "Your plan does not include access to the Integrations API.",
  "bucket": "submission_write"
}
```

The `bucket` field names the capability that was gated. To enable API access, upgrade your plan in the control plane or contact support. Retrying a 402 will not succeed.

## Best practices

### Respect Retry-After

When you receive a 429, wait for the duration in the `Retry-After` header before retrying, then apply exponential backoff with jitter for any further attempts.

```python theme={null}
import random
import time

import requests


def request_with_backoff(method: str, url: str, headers: dict, max_retries: int = 5, **kwargs):
    """Retry on 429/500/502 with Retry-After and exponential backoff plus jitter."""
    delay = 1.0

    for attempt in range(max_retries):
        response = requests.request(method, url, headers=headers, **kwargs)

        if response.status_code not in (429, 500, 502):
            return response

        if response.status_code == 429 and response.headers.get("Retry-After"):
            wait = float(response.headers["Retry-After"])
        else:
            wait = delay + random.uniform(0, delay)  # exponential backoff with jitter

        print(f"Got {response.status_code}, waiting {wait:.1f}s before retry...")
        time.sleep(wait)
        delay *= 2

    raise RuntimeError("Exceeded max retries")
```

### Batch assets into one submission

Submit all assets for a job in a single request rather than one submission per asset. This consumes a single `submission_write` instead of many.

```python theme={null}
# Less efficient: one submission per file.
for blob_path in blob_paths:
    requests.post(
        "https://mm-midmarket-integrations-api-preview.azurewebsites.net/api/integrations/submissions",
        headers={"X-API-Key": api_key},
        json={"assets": [{"blobPath": blob_path}], "sidekickIds": ["<sidekick-id>"]},
    )

# Preferred: one submission for all files.
assets = [{"blobPath": blob_path} for blob_path in blob_paths]
requests.post(
    "https://mm-midmarket-integrations-api-preview.azurewebsites.net/api/integrations/submissions",
    headers={"X-API-Key": api_key},
    json={"assets": assets, "sidekickIds": ["<sidekick-id>"]},
)
```

### Prefer webhooks over tight polling

Polling submission status in a tight loop consumes your `submission_read` budget quickly. Subscribe to webhooks so you are notified when processing completes, and poll only as a fallback. See [Webhook subscriptions](/guides/webhook-subscriptions).

### Poll with backoff

When you do poll, read status from `GET /submissions/{id}` (there is no `/status` suffix), space your requests out, and stop once the top-level `status` reaches a terminal state of `complete` or `failed`.

```python theme={null}
import time

import requests

API_BASE = "https://mm-midmarket-integrations-api-preview.azurewebsites.net"


def poll_until_done(submission_id: str, api_key: str) -> dict:
    """Poll a submission with a widening interval until it reaches a terminal state."""
    start = time.time()

    while True:
        elapsed = time.time() - start
        interval = 2 if elapsed < 30 else (10 if elapsed < 120 else 30)

        response = requests.get(
            f"{API_BASE}/api/integrations/submissions/{submission_id}",
            headers={"X-API-Key": api_key},
        )
        submission = response.json()

        if submission["status"] in ("complete", "failed"):
            return submission

        time.sleep(interval)
```

### Queue and space out requests

If you generate bursts of work, place requests on a queue and release them at a steady rate rather than firing them all at once. This keeps you under your bucket limits and reduces the number of 429 responses you have to recover from.

## Troubleshooting

### Hitting limits unexpectedly

* Confirm you are batching assets into single submissions where possible
* Replace tight polling loops with webhooks
* Space requests out instead of issuing them concurrently
* Check your tier's limits in the control plane

### Getting 429 responses

* Wait for the `Retry-After` duration before retrying
* Apply exponential backoff with jitter for repeated retries
* Remember that read, write, upload, and sandbox buckets are counted separately

### Getting a 402 instead of a 429

A 402 with `error_code: tier_not_authorized_for_integrations_api` means your plan does not include API access. Upgrade in the control plane or contact support at [https://mediamagic-verify.support.site](https://mediamagic-verify.support.site) — retrying will not help.

## Need higher limits?

Contact support at [https://mediamagic-verify.support.site](https://mediamagic-verify.support.site) with your expected request volume, use case, and timeline.
