Skip to content

Rate limits

Per-key request limits by plan, the rate-limit headers and how to back off when you receive a 429.

On this page

Limits by plan

Limits apply per API key, per minute, and depend on the plan of the account that owns the key.

Rate limits by plan
PlanRequests per minute, per key
Free60
Pro300
Business600

A few endpoints have their own, lower limits per account, because each call emails people:

Endpoint limits
EndpointLimit
POST /v1/envelopes/{id}/remind6 per minute
POST /v1/envelopes/{id}/recipients/{rid}/resend10 per minute
POST /v1/webhooks/{id}/test10 per minute

Requests without a valid key are limited to 30 per minute per IP address.

Rate-limit headers

Every response tells you where you stand:

Rate-limit headers
HeaderMeaning
X-RateLimit-LimitRequests allowed per minute for this key.
X-RateLimit-RemainingRequests left in the current minute.
Retry-AfterOn 429 only: seconds to wait before retrying.

Handling 429 responses

Over the limit, the API answers 429 with error type rate_limit_error and code rate_limited. Wait for Retry-After seconds, then retry. Use exponential backoff with jitter when there is no header, as for 5xx errors:

429 response headers
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-Request-Id: 0f6c8f5e-2b1d-4c3a-9e7f-5a8b1c2d3e4f
Retry with backoff
async function signkoi(path, options = {}, attempt = 0) {
    const res = await fetch(`https://signkoi.com/api/v1${path}`, {
        ...options,
        headers: { Authorization: `Bearer ${process.env.SIGNKOI_API_KEY}`, ...options.headers },
    });

    if ((res.status === 429 || res.status >= 500) && attempt < 5) {
        const retryAfter = Number(res.headers.get('Retry-After'));
        const delay = retryAfter > 0 ? retryAfter * 1000 : Math.min(30_000, 500 * 2 ** attempt) + Math.random() * 250;
        await new Promise((resolve) => setTimeout(resolve, delay));
        return signkoi(path, options, attempt + 1); // reuse the same Idempotency-Key header
    }

    return res;
}

Use webhooks, not polling

Polling every envelope is the quickest way to hit the limit. Subscribe to webhooks and fetch an envelope only when it changes.