Sixty writes and 120 MCP requests a minute in SAGARIS
Three limits apply to the public API, each counted per key and per minute. They are deliberately loose: they exist to stop a loop running away, not to meter your usage. What your plan actually meters is a separate thing, and it answers with a different status.
The limits
- 60 per minute
- Write operations, POST /contacts/query and GET /linkedin/actions on /api/v1 share this budget. Assertion delegates to the existing create/update routes; a concurrent-create retry can consume a second token.
- 120 per minute
- JSON-RPC requests to the MCP endpoint, counted separately from the write limit.
- 120 per minute, reads
- The three Social checklist reads (GET /social/checklist/items, /due and /items/{id}/context), on their own counter, because an assistant can call them in a loop and each one does more than one query.
- No limit
- GET /contacts, GET /contacts/{id} and GET /sequences do not pass through a limiter. POST /contacts/query does.
Every counter is keyed on the API key, not on the workspace, the IP or the user. Two keys in one workspace therefore have two independent budgets. That is a consequence of how the counter is keyed rather than a licence to shard around the limit: if you need materially more throughput than this, the limit is the wrong thing to work around and we would rather hear about the use case.
What a limited request looks like
You get a 429 carrying the standard error envelope with the code rate_limited, and a Retry-After header giving whole seconds to wait. Retry-After is never less than one second, so it is always safe to sleep for the value you are given.
Retry-After: 12{ "data": null, "error": "Rate limit exceeded.", "code": "rate_limited" }The API does not send X-RateLimit-Limit, X-RateLimit-Remaining or X-RateLimit-Reset. There is no way to read your remaining budget before you spend it, so a client that needs to stay under the limit should pace itself rather than watch a counter. Retry-After on a 429 is the only rate-limit signal on this surface.
Treat the limit as approximately enforced rather than exact. It is a protective control against runaway loops, not a security boundary or a billing meter, and it is implemented accordingly. Do not build a client that depends on getting exactly sixty writes through in a given minute.
The other 429, which is not a rate limit
Creating a contact can also return 429 for an entirely different reason: the workspace has reached the contact allowance on its plan. This is a plan cap, not a throughput limit, and waiting will not clear it.
Read the code to tell them apart. A throughput limit answers with code rate_limited and a Retry-After header. A plan cap answers with a code beginning quota_exceeded, carries no Retry-After, and includes a quota block describing the allowance you have hit. Retrying a plan cap on a backoff loop will simply fail until somebody changes the plan.
There is a third refusal in the same family and it is a 402 rather than a 429: a workspace that is read-only because a trial expired, a financed term elapsed, or an account went delinquent. That one refuses every write except adding to the suppression list and POST /linkedin/actions. A client handling billing states should branch on 402 and quota_exceeded separately from rate_limited, because only the last of the three is worth retrying.
Staying under them
- Honour Retry-After rather than retrying immediately. A tight retry loop against a limiter spends your next window on requests that are refused.
- Send an Idempotency-Key on writes. Then a retry after a 429, or after a timeout where you never saw the response, cannot create a duplicate record.
- Most reads cost you nothing against the write limit, so reconciling what already exists before writing is free: GET /contacts, GET /contacts/{id} and GET /sequences are not limited at all, and the Social checklist reads have their own counter. POST /contacts/query and GET /linkedin/actions do spend the write budget.
- Back off exponentially on repeated 429s, and stop retrying entirely once the code is not rate_limited.
Limits on the delivery side
These limits are about requests you make to us. Requests we make to you, when a webhook fires, have their own bounds: each POST is given 10 seconds, and a delivery is attempted six times before it is dead-lettered. Those are described on the Webhooks page.
Behaviour on this page is read from
- src/lib/public-api-auth.ts
- src/lib/rate-limit.ts
- src/lib/linkedin/action-api.ts
- src/lib/mcp/server.ts
- src/app/api/v1/contacts/route.ts
- src/app/api/v1/sequences/route.ts
- src/app/api/v1/social/checklist/items/route.ts
- src/lib/billing/api-write-gate.ts
- src/lib/billing/usage-quota-guard.ts
- src/lib/webhooks/webhook-delivery.ts
Was this page helpful?
In this topic
Developers and integrations
- SAGARIS documentationGuides, concepts and API reference for the revenue engine.
- SDKsNo official SDK, and how to generate one from the OpenAPI spec.
- HubSpot integrationHubSpot reads are live; write-back is a dry run.
- Salesforce integrationSalesforce reads are live; write-back is switched off in production.