Rate limits
Limits protect every company on the platform. They are generous; a well-behaved integration rarely meets them.
Request limits
| Limit | Value |
|---|---|
| Requests per API key | 3,000 per minute |
| Requests per client IP address (all keys, and unauthenticated requests) | 6,000 per minute |
Replaying one webhook delivery (…/deliveries/{delivery_id}/replay), per API key | 60 per minute |
Replaying failed deliveries (…/deliveries/replay_failed), per API key | 5 per minute and 30 per hour, up to 1,000 deliveries per call |
The two limits apply independently: many keys behind one IP share the IP budget. Successful responses do not carry rate-limit headers; you only see the limit when you hit it.
When you hit one
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 3000
X-RateLimit-Subject: user:apikey:0199c38d-7a8b-7c9d-98f9-142536475827
Content-Type: application/problem+json
{
"type": "https://officehero.io/errors/rate-limited",
"title": "Too many requests",
"status": 429,
"detail": "Too many requests — try again in 60 seconds",
"retry_after_s": 60,
"code": "rate_limited"
}
The replay limits answer differently: X-RateLimit-Bucket names the limit
(integrations-replay-minute, integrations-replay-failed-minute or
integrations-replay-failed-hour) instead of X-RateLimit-Subject, and
Retry-After counts down to the end of that limit's window, so it can be anything up to 60 seconds
(3,600 for the hourly limit).
Wait Retry-After seconds before the next request. A small helper that also backs off on 5xx:
async function withRetry(send, attempts = 5) {
for (let i = 0; ; i++) {
const res = await send();
if (res.status !== 429 && res.status < 500) return res;
if (i === attempts - 1) return res;
const retryAfter = Number(res.headers.get('Retry-After'));
const wait = Number.isFinite(retryAfter) && retryAfter > 0
? retryAfter * 1000
: Math.min(30_000, 500 * 2 ** i) * (0.8 + Math.random() * 0.4);
await new Promise((r) => setTimeout(r, wait));
}
} Other limits
| What | Limit |
|---|---|
Page size (limit) | default 50, maximum 100 |
Webhook endpoints per company (active + paused; resuming or pausing one switched off after failures counts) | 5 |
| Idempotency window | 24 hours; responses up to 256 KiB are replayed |
| Webhook response time | 10 s to connect, 15 s in total |
| Webhook attempts | 8, retried after 1 min, 5 min, 15 min, 1 h, 3 h, 6 h, 12 h |
| Webhook signature age you should accept | 300 s |
| Automatic endpoint disabling | 50 failed attempts in a row, 3 × 410 Gone within 24 hours, or every delivery of the last 7 days failed — see Retries |
Need more? Tell support what you are building.