Learn about API rate limits and how to work with them.
Ironclad CLM Limits
These API limits are set on a per-company basis. Each bucket below covers a set of HTTP methods and endpoint paths that share a single quota — for example, GET /public/api/v1/workflows and GET /public/api/v1/workflows/{id} both fall under the same 400-requests-per-minute quota (other than the sub-resource buckets listed separately).
Limits stated in requests per minute (RPM) unless otherwise stated.
Public API Buckets (base path /public/api/v1)
/public/api/v1)| Bucket | Limit (RPM) | Methods | Path (* as a wildcard) |
|---|---|---|---|
pub:workflows:read | 400 | GET, HEAD | /public/api/v1/workflows* |
pub:workflows:write | 40 | POST, PUT, PATCH, DELETE | /public/api/v1/workflows* |
pub:workflows:sign-status | 50 | POST, PATCH | /public/api/v1/workflows/{id}/sign-status* |
pub:workflows:signatures | 50 | POST, PATCH | /public/api/v1/workflows/{id}/signatures* |
pub:records:read | 600 | GET, HEAD | /public/api/v1/records* |
pub:records:write | 200 | POST, PUT, PATCH, DELETE | /public/api/v1/records* |
pub:records:export | 10 | GET, HEAD | /public/api/v1/records/export* |
pub:entities:read | 600 | GET, HEAD | /public/api/v1/entities* |
pub:entities:write | 200 | POST, PUT, PATCH, DELETE | /public/api/v1/entities* |
pub:obligations:read | 600 | GET, HEAD | /public/api/v1/obligations* |
pub:obligations:write | 200 | POST, PUT, PATCH, DELETE | /public/api/v1/obligations* |
pub:exports:create | 20 | POST | /public/api/v1/exports |
pub:exports:download | 20 | GET, HEAD | /public/api/v1/exports/{id}/download |
pub:exports:status | 600 | GET, HEAD | /public/api/v1/exports/{id} |
pub:webhooks | 600 | ANY | /public/api/v1/webhooks* |
pub:attribute-source:write | 40 | POST, PUT, PATCH, DELETE | /public/api/v1/attribute-source* |
pub:signature-requests | 40 | ANY | /public/api/v1/signature-requests* |
pub:other | 800 | ANY | /public/api/v1* (any other Public API endpoint) |
SCIM API Buckets (base path /scim/v2)
/scim/v2)| Bucket | Limit (RPM) | Methods | Path (* as a wildcard) |
|---|---|---|---|
scim:users:read-write | 600 | GET, HEAD, PUT, POST | /scim/v2/users* |
scim:users:write | 50 | PATCH, DELETE | /scim/v2/users* |
scim:groups:read | 200 | GET, HEAD | /scim/v2/groups* |
scim:groups:write | 50 | POST, PUT, PATCH, DELETE | /scim/v2/groups* |
scim:other | 800 | ANY | /scim/v2* (any other SCIM endpoint) |
MCP Buckets (requests made via the Ironclad MCP server)
| Bucket | Limit (RPM) | Methods | Path (* as a wildcard) |
|---|---|---|---|
mcp:other | 80 | ANY | /mcp* |
In addition to the per-bucket limits above, Ironclad enforces a company-wide cap of 4,500 requests per minute across all API requests combined — including SCIM — regardless of bucket.
Rate limit response headers
Every response from an endpoint that enforces a rate limit includes the following headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | The maximum number of requests allowed for the matched bucket in the current window. |
X-RateLimit-Remaining | The number of requests you have left in the current window. |
X-RateLimit-Reset | The time (as a Unix epoch timestamp, in seconds) when the current window resets. |
If a request is rejected for exceeding the limit (429 Too Many Requests), the response also includes:
| Header | Description |
|---|---|
Retry-After | The number of seconds to wait before retrying the request. |
Strategies for Handling API Rate Limits
If your integration is hitting rate limits (HTTP 429 responses) against Ironclad's Public API or SCIM API, here are four approaches. We'd recommend combining at least the first two, with caching and webhooks layered on top wherever applicable to your integration's use case.
1. Spread requests out over time
If your integration performs bulk operations (e.g., syncing many users, or scanning many workflows), avoid firing all requests as fast as possible in a tight burst. Add pacing/delay between calls so your request rate stays comfortably under the limit, and spread bulk jobs out over a longer window rather than compressing them into a minute or two. Keep in mind that rate limits are often shared across all integrations your organization runs against the same API, so leaving some headroom below the limit (rather than targeting it exactly) helps avoid contention with other traffic.
2. Handle 429 responses with retries and backoff
Even with good pacing, your integration should treat a 429 as an expected, recoverable condition — not a failure to surface to an end user or drop silently. In increasing order of robustness:
- Simplest: On a 429, wait a fixed interval (e.g., 30-60 seconds) and retry.
- Better: Check the response for a
Retry-Afterheader (or equivalent rate-limit header, if provided) and wait exactly that long before retrying, rather than guessing. - Most robust: Use exponential backoff — if a retry also gets a 429, wait progressively longer before the next attempt (with a reasonable maximum), rather than retrying at a fixed interval indefinitely.
Whichever approach you choose, make sure retries are actually implemented end-to-end — a request that fails with a 429 should still eventually succeed, not be lost.
3. Cache data that doesn't change often
Before re-fetching data, consider whether your integration already has a recent, still-valid copy of it. Reference data, configuration/schema information, and any records you've already retrieved and haven't been told have changed are good candidates for a local cache with a reasonable TTL. This avoids repeatedly requesting the same data on every run, cutting overall request volume without changing your integration's logic. When a webhook (see below) tells you something changed, invalidate the relevant cache entry rather than waiting for the TTL to expire, so you're not working from stale data.
4. Use webhooks instead of polling
The most effective way to stay well under rate limits is to avoid unnecessary requests in the first place. If your integration is polling an endpoint on a schedule to detect changes (e.g., "has anything changed since last time I checked?"), check whether Ironclad offers a webhook for that event instead. Webhooks push a notification to your integration only when something actually changes, so you can replace routine polling (and any "full re-scan" logic that re-fetches everything on a schedule) with targeted, on-demand requests triggered only by relevant events. See the full list of available webhooks here. This typically reduces request volume dramatically compared to polling, and eliminates the periodic bursts that come from scanning an entire dataset on a fixed schedule.
SummaryPace your requests, retry gracefully on 429s (ideally with backoff), cache what you don't need to re-fetch, and replace polling with webhooks wherever one is available for the data you need.
Other limits
Notwithstanding the limits specified in this document, the system might still limit requests if it detects an unusual spike in requests from the account, for example, in a denial of service attack. Please see our API Terms of Use for more information.

