OdyDocs

Rate limits

600 requests per minute per key, the RateLimit headers on every response, and how to back off on 429.

Every API key gets 600 requests per minute, shared across /v1/api/* and the MCP endpoint. Requests without a valid key are limited per IP address instead.

#Headers

Every response, success or error, carries:

HeaderMeaning
RateLimit-LimitYour budget for the window (600)
RateLimit-RemainingRequests left in the current window
RateLimit-ResetSeconds until the window resets

When you exceed the limit you get 429 with code resource_exhausted and a Retry-After header:

HTTP
HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 600
RateLimit-Remaining: 0
RateLimit-Reset: 23
Retry-After: 23
Content-Type: application/json

{"error":{"code":"resource_exhausted","message":"Rate limit exceeded (600 requests/minute). Retry after 23s.","requestId":"req_…"}}

The window is a fixed 60 seconds.

#Staying under the limit

  • Watch RateLimit-Remaining and slow down as it approaches zero, rather than reacting to 429s.
  • Use webhooks instead of polling. Subscribe to message.received and Ody pushes new messages to you. If you must poll a shared inbox, every 10–30 seconds is plenty.
  • Request bigger pages (limit up to each endpoint's maximum) when syncing.
  • Back off with jitter on 429 and 5xx; honour Retry-After; cap your retries.
TypeScript
async function withBackoff<T>(fn: () => Promise<T>, attempts = 5): Promise<T> {
  for (let i = 0; ; i++) {
    try {
      return await fn();
    } catch (err: any) {
      const retriable = err?.status === 429 || err?.status >= 500;
      if (!retriable || i >= attempts - 1) throw err;
      const wait = err.retryAfter ? err.retryAfter * 1000 : Math.min(30_000, 2 ** i * 500) * (0.5 + Math.random());
      await new Promise((r) => setTimeout(r, wait));
    }
  }
}

Need a higher limit for a specific integration? Email [email protected] with your use case.

Last updated Wed Oct 14 2026 00:00:00 GMT+0000 (Coordinated Universal Time)Something wrong on this page? Tell us