ProductOSDocs

API

Rate limits

Per-endpoint-class rate limits, how to read 429 responses, and how to back off correctly.

ProductOS rate-limits per user (API tokens count against the user who issued them) per endpoint class. Limits are independent, exhausting writes doesn't affect reads.

Limits by class

ClassLimitEndpoints
List reads120 / minuteGET /api/v1/{entity}
Single reads240 / minuteGET /api/v1/{entity}/{id}, GET .../links/{junction}
Writes60 / minutePOST, PATCH, DELETE on entity routes, POST .../restore, link and unlink calls
Bulk30 / minutePOST /api/v1/{entity}/bulk (each call is one hit, whatever its size)
Export6 / minuteGET /api/v1/{entity}/export
AIPer route, 4 to 60 / minute (chat 20, ProductSense 10, embedding 10)POST /api/ai/*, in-app session only
Public widget submission10 / minute per IP, 1,000 / day per keyPOST /api/v1/public/widget/feedback
Feedback API, secret key120 / minute and 10,000 / day per key; read-backs and lists 300 / minute per keyPOST /api/v1/public/feedback, GET /api/v1/public/feedback, GET /api/v1/public/feedback/{id}
Feedback API, publishable key10 / minute per IP, 1,000 / day per keyPOST /api/v1/public/feedback
MCP sign-inRegistration 30 / hour per IP; token exchange and refresh 120 / minute per IP; revocation 60 / minute per IPPOST /api/oauth/register, POST /api/oauth/token, POST /api/oauth/revoke

The same user calling from multiple devices/tokens shares the bucket, limits are per user, not per token. Different users in the same workspace have independent buckets.

How limits work

Each class is a fixed-window bucket. The window opens on your first request and resets one minute later (one hour for MCP client registration, one day for the daily Feedback API caps); it is not aligned to wall-clock minutes. So if your first write lands at 12:00:30 and you make 60 writes in the next second, the 61st waits until 12:01:30.

The 429 response

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests",
    "details": {
      "retryAfterSeconds": 18
    }
  }
}

details.retryAfterSeconds is the number of seconds until the bucket resets; read it from the body. The Feedback API and the MCP sign-in endpoints also send it as a Retry-After header. There are no X-RateLimit-* headers.

Backoff strategy

async function withRetry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn();
    } catch (err) {
      if (err instanceof ApiError && err.code === "rate_limited" && i < attempts - 1) {
        const wait = ((err.details as { retryAfterSeconds?: number } | undefined)?.retryAfterSeconds ?? 1) * 1000;
        await new Promise((r) => setTimeout(r, wait));
        continue;
      }
      throw err;
    }
  }
  throw new Error("unreachable");
}

For bulk operations, stagger your requests instead of relying on retries:

  • Writing 1000 features? Send 50/min for 20 minutes, not 1000 in one burst.
  • Reading 1000 rows? Use limit=200 per request and 5 requests, not 1000 individual GET /api/v1/.../{id} calls.

Bulk endpoints

For large updates and deletes, prefer the bulk endpoint:

POST /api/v1/feedback/bulk
{ "ids": ["FDBK-000042", "FDBK-000043"], "action": "update", "patch": { "status": "planned" } }

action is update or delete (a trash, where the entity has one), and ids takes up to 200 existing rows per call. Each call is one hit against its own 30 / minute bucket, regardless of payload size. Bulk does not create rows: for bulk creates, stagger your POSTs under the writes limit.

What's not rate-limited

  • GET /api/v1/health: uncapped, intended for liveness probes.
  • GET /api/v1/openapi: no per-user limit.
  • Webhook inbound (e.g. /api/integrations/github/webhook), provider-side rate-limited by GitHub/Linear/etc., we don't double-cap.

Upgrading limits

Enterprise customers can request higher limits per class via support. Self-serve limit adjustment isn't possible, limits exist to protect the platform and we want a conversation about the use case before lifting them.

Next

  • Errors: full error code reference including rate_limited.
  • Pagination: how to read large datasets without hitting list-read limits.

Last reviewed 2026-09-22