Rate Limits & Retries

Published limits

LimitValue
Standard rate10,000 requests/hour per key
Burst60 requests/second
Webhook deliveriesUnlimited

Higher limits are available on request for Enterprise customers at no additional charge — contact [email protected] if your integration's normal operation approaches the standard ceiling.

Limits are enforced per API key, not per IP — an integration server sitting behind a shared corporate NAT doesn't share its limit with anyone else's key on the same network.

The 429 response

Exceeding the limit returns a 429 with a standard Retry-After header and a matching value in the body, in case a proxy between you and OwlMar strips response headers:

text
429 Too Many Requests
Retry-After: 47

{ "error": "Rate limit exceeded", "code": "RATE_LIMITED", "meta": { "retryAfterMs": 47000 } }

Backoff pattern

Respect Retry-After rather than retrying immediately, and use exponential backoff with jitter as a fallback for any other transient failure (503 UPSTREAM_UNAVAILABLE, network timeouts):

node
async function fetchWithBackoff(url, options, maxAttempts = 5) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const res = await fetch(url, options);
if (res.status !== 429) return res;
const retryAfterHeader = res.headers.get('Retry-After');
const waitMs = retryAfterHeader
? Number(retryAfterHeader) * 1000
: Math.min(2 ** attempt * 1000, 30000) + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, waitMs));
}
throw new Error('Exceeded max retry attempts on 429 RATE_LIMITED');
}
python
import time
import random
import requests
def get_with_backoff(url, headers, max_attempts=5):
for attempt in range(max_attempts):
resp = requests.get(url, headers=headers)
if resp.status_code != 429:
resp.raise_for_status()
return resp.json()
retry_after = resp.headers.get("Retry-After")
wait_s = float(retry_after) if retry_after else min(2 ** attempt, 30) + random.random() * 0.25
time.sleep(wait_s)
raise RuntimeError("exceeded max retries on 429 RATE_LIMITED")
Warning

A tight retry loop with no backoff makes a transient rate limit worse, not better — every immediate retry counts against the same hourly window it just tripped. Always back off.

Note

Combine backoff with an Idempotency-Key on any write you retry — backoff alone protects your rate limit; idempotency protects your data from being created twice.