ReferenceRate limits

Rate limits

Per-credential and per-tenant request limits, how sliding windows work, and how to handle 429 responses.

Rate limits cap how many API requests you can make in a given time period. This page explains how Zoop's limits work, what the API returns when you exceed them, and how to write code that recovers gracefully.

Zoop enforces rate limits on all external API requests. Limits use a sliding window algorithm: instead of resetting a counter at a fixed clock boundary (say, the top of every minute), the counter looks back over a rolling period.

How sliding windows work

With a fixed window, you could send 120 requests at 12:00:59 and another 120 at 12:01:00 — 240 requests in two seconds — without triggering a limit. A sliding window closes that gap by always counting the last 60 seconds, no matter when the clock ticks.

In practice: if you send 120 requests in one burst, you must wait for the oldest requests to fall outside the 60-second window before you can send more.

Primary API limits

These two limits apply to every request you make through the Zoop external API.

LimiterScopeAlgorithmLimit
Per-credentialYour API key or OAuth tokenSliding window120 requests per minute
Per-tenantAll credentials belonging to your tenant combinedSliding window600 requests per minute

Both limits must pass. A request that clears the per-credential limit can still be rejected if your tenant has reached 600 requests across all integrations in the same window.

The per-tenant limit means that if you run multiple integrations (for example, a Zapier zap and a custom webhook handler) under the same Zoop account, their requests share the 600 requests-per-minute budget.

Handling 429 responses

When you exceed a limit, Zoop returns HTTP 429 Too Many Requests. The response includes a Retry-After header that tells you how many seconds to wait before retrying. The response body is a JSON object with error code -32003:

HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json

{ "jsonrpc": "2.0", "error": { "code": -32003, "message": "Rate limited" }, "id": null }

Always read Retry-After rather than parsing the body — the header is the reliable source of the wait time. The "id": null in the body is expected: a rate-limited request is rejected before it reaches any processing, so there is no request ID to echo back.

The Retry-After value is rounded up to the nearest whole second (minimum 1). It represents how long until the oldest request in your window expires and frees up capacity.

Retry with backoff

Read the Retry-After header and wait at least that long before retrying. Adding a small amount of random jitter prevents multiple parallel clients from hammering the API at the same instant.

async function zoopFetch(url: string, options: RequestInit): Promise<Response> {
  const MAX_RETRIES = 3

  for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
    const res = await fetch(url, options)

    if (res.status !== 429) {
      return res
    }

    if (attempt === MAX_RETRIES - 1) {
      // No more retries — surface the error to the caller.
      return res
    }

    const retryAfter = parseInt(res.headers.get('Retry-After') ?? '5', 10)
    const jitter = Math.random() * 1000 // up to 1 s of extra wait
    await new Promise((resolve) =>
      setTimeout(resolve, retryAfter * 1000 + jitter),
    )
  }

  throw new Error('unreachable')
}

Other limiters

A few specific endpoints have their own tighter limits. You are unlikely to hit these during normal integration work, but they do return 429 responses.

Endpoint / actionAlgorithmLimit
OAuth token endpointSliding window20 requests per minute per IP
Dynamic client registration (DCR) — registering a new OAuth appFixed window10 registrations per hour per IP

See Authentication — OAuth for background on the OAuth and DCR flows.

Behavior when the rate limiter is unavailable

Zoop's rate limiter runs on Redis (an in-memory data store). On the rare occasion Redis is unreachable, Zoop chooses between two behaviors depending on how sensitive the endpoint is:

Fail-closed — the request is rejected with 429 even though the limiter is down. Zoop uses this for unauthenticated or credential-issuance endpoints (the OAuth token endpoint, DCR registration, public PDF rendering) where allowing unlimited requests could enable abuse. The Retry-After in this case is 60 seconds.

Fail-open — the request is allowed through. Zoop uses this for the primary per-credential and per-tenant limiters. Because your requests are already authenticated, blocking them during a Redis outage would break a working integration for no safety benefit.

In short: if Redis goes down briefly, your authenticated API calls continue uninterrupted. You only see 429 errors on the more sensitive endpoints listed above.

Tips for staying within limits

  • Batch where possible. Fetching a list of jobs in one request costs the same 1 request as fetching a single job.
  • Cache responses. If you need the same data in multiple places, fetch once and reuse it.
  • Watch your tenant budget. If you run several integrations, their request counts add up against the shared 600 requests-per-minute ceiling.
  • Implement retry with Retry-After. Do not use a fixed delay — always read the header.

For questions about authentication and how credentials are scoped, see API keys and OAuth.