Core concepts
Rate limits
Each API key may make 100 requests per second, counted in fixed one-second windows.
Headers#
Every metered API-key response carries:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed per window (100). |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Reset | Unix time (seconds) when the current window ends and the counter resets. |
Over the limit the API answers 429 with code rate_limited and a Retry-After header in seconds:
HTTP/1.1 429 Too Many Requests
Retry-After: 1
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1792400401
Content-Type: application/json
{"code":"rate_limited","message":"API key rate limit exceeded","detail":"API key rate limit exceeded","request_id":"5c1d9e0a7b3f4e21"}The limit is per key, not per organization: separate keys for separate integrations do not share a budget. Authentication and scope failures are rejected before metering.
Other limits#
| What | Limit |
|---|---|
| Request body | 30 MB (413 payload_too_large) |
POST /documents payload JSON | 64 KB; up to 50 recipients and 200 fields per document |
POST /documents/bulk | 500 CSV rows per call |
Company lookup (GET /lookup/company) | 30 lookups per minute per organization (429, Retry-After: 60) |
Public verification (POST /verify, GET /verify/code/{code}) | 10 requests per 10 minutes per IP |
| Monthly documents, SMS, storage | Plan quotas (402 with codes such as documents_per_month, sms_quota_exceeded) |
Handling 429#
- Wait at least
Retry-Afterseconds, then retry with exponential backoff and jitter (for example 0.5 s, 1 s, 2 s, 4 s, each multiplied by a random factor between 0.5 and 1). - Spread batch jobs out instead of firing them in parallel; watch
X-RateLimit-Remainingand pause when it reaches 0. - For many documents prefer bulk sending (one request for up to 500 drafts) or templates over hundreds of individual uploads.
- Use webhooks instead of polling for status changes.
import os
import random
import time
import httpx
def get_with_backoff(http: httpx.Client, path: str, attempts: int = 5) -> httpx.Response:
for attempt in range(attempts):
resp = http.get(path)
if resp.status_code not in (429, 500, 502, 503):
return resp
retry_after = float(resp.headers.get("Retry-After", 0))
backoff = 0.5 * 2**attempt * (0.5 + random.random() / 2)
time.sleep(max(retry_after, backoff))
return resp
http = httpx.Client(
base_url="https://api.sign.yt/v1",
headers={"Authorization": f"Bearer {os.environ['SIGNYT_API_KEY']}"},
)
print(get_with_backoff(http, "/documents?limit=10").json())async function getWithBackoff(url: string, attempts = 5): Promise<Response> {
let res!: Response;
for (let attempt = 0; attempt < attempts; attempt++) {
res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.SIGNYT_API_KEY}` } });
if (![429, 500, 502, 503].includes(res.status)) return res;
const retryAfterMs = (Number(res.headers.get("Retry-After")) || 0) * 1000;
const backoffMs = 500 * 2 ** attempt * (0.5 + Math.random() / 2);
await new Promise((r) => setTimeout(r, Math.max(retryAfterMs, backoffMs)));
}
return res;
}
console.log(await (await getWithBackoff("https://api.sign.yt/v1/documents?limit=10")).json());The official SDKs already retry GET, PUT and DELETE on 429 and 5xx, honoring Retry-After (capped at 60 seconds).