Rate Limits

SurfaceLimit
API endpoints — ProducerSync, Contracting, Hierarchies, Identity300 requests per minute
Token endpoint200 requests per minute

Limits are counted per credential — that is, per client_id, not per user, per server, or per IP address.

The practical consequence is that everything authenticating with the same client_id draws from one shared budget. If you run your sync across several parallel workers, or across several machines, they are not each allowed 300 requests per minute — together they get 300. Size your concurrency accordingly, and pace against the ratelimit-remaining header rather than assuming a per-worker allowance.

Rate limits are subject to change to ensure fair and high quality usage for all customers.

Response Headers

Every response carries your current status — pace against these rather than probing for the edge.

HeaderMeaning
ratelimit-limitMaximum requests allowed in the current window
ratelimit-remainingRequests still available in the current window
ratelimit-resetWhen the limit resets
retry-afterPresent only on 429 — how long to wait before retrying
ratelimit-limit: 300
ratelimit-remaining: 299
ratelimit-reset: 17

Handling a 429

HTTP/1.1 429 Too Many Requests
{ "message": "API rate limit exceeded" }

Honor retry-after when present. Otherwise back off exponentially, and add randomness so retries from parallel workers don't synchronize into a thundering herd.

import time
import random
import requests

def request_with_backoff(url, headers, max_retries=5):
    """Make a GET request with exponential backoff on 429 responses."""
    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)

        if response.status_code != 429:
            response.raise_for_status()
            return response

        # Honor the retry-after header if present, otherwise use exponential backoff
        retry_after = response.headers.get("retry-after")
        if retry_after:
            wait = float(retry_after)
        else:
            wait = (2 ** attempt) + random.uniform(0, 1)

        if attempt < max_retries - 1:
            time.sleep(wait)

    raise Exception(f"Request failed after {max_retries} attempts due to rate limiting")

Staying Well Inside the Limits

  • Reuse tokens. They're valid for 60 minutes. Fetching a new token per request is the most common way integrations hit the token endpoint limit — see Token Expiration & Reuse.
  • Prefer paged bulk reads over per-record polling.
  • Use updated_since as a high-water mark so each sync run fetches only what changed, rather than re-reading whole collections. See Filter, Search & Sort.