Skip to main content

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.
BucketCovers
uploadPresigned URL requests and direct uploads
submission_writeCreating submissions and retrying assets
submission_readReading submission status, assets, issues, topics, and versions
webhook_writeCreating, updating, and deleting webhook subscriptions
webhook_readListing subscriptions and reading delivery history
sandboxSandbox-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).
{
  "error": "Rate limit exceeded: too many submission writes"
}
HTTP/1.1 429 Too Many Requests
Retry-After: 30
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.

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.
{
  "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.
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.
# 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}]},
    )

# 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},
)

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.

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.
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://support.mediamagic.app — retrying will not help.

Need higher limits?

Contact support at https://support.mediamagic.app with your expected request volume, use case, and timeline.