Rate limits
60 requests per minute per API key. Retry-After on 429.
Every API key gets 60 requests per 60-second sliding window.
Across all /v1/* endpoints — sends, reads, remind/withdraw,
signed-PDF download, audit-trail download all count against the
same bucket.
Going over the limit
Returns 429 with a JSON body and a Retry-After header in
seconds:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 23
{
"error": "Rate limit exceeded — retry in 23s",
"code": "rate_limited"
}The Retry-After is the seconds remaining in the current window.
After the window closes the bucket fully resets.
Why a per-key limit?
API keys are workspace-scoped, so a per-key limit is effectively a per-workspace limit. Two keys in the same workspace share the same bucket the moment they hit the same RPC — sustained burst with two keys won't double your throughput.
How the bucket works
Each request acquires a row lock on the api_keys row, increments
rate_count, and resets the window if the previous one elapsed. The
counter is authoritative across all Vercel function instances — you
can't evade it by hitting a different region.
429s do not increment the counter. A backed-off retry can succeed cleanly the moment the window resets.
Recommended client behaviour
async function postRespectingRate(req: () => Promise<Response>) {
for (let attempt = 1; attempt <= 5; attempt++) {
const res = await req()
if (res.status !== 429) return res
const wait = Number(res.headers.get('retry-after')) || 60
await new Promise(r => setTimeout(r, wait * 1000))
}
throw new Error('Rate-limited after 5 retries')
}Idempotency-Key plays nicely with rate limiting. A 429 doesn't "consume" the idempotency key — the request didn't reach our processing logic. Send the same key on the retry; it acquires the lock on first successful pass-through.
If you need higher throughput
Email support@letssign.now with your workspace ID + the burst pattern you need (sustained vs. spike, total daily volume). We'll bump the cap on your key with no SLA negotiation for sane requests; high-volume use cases (>5k/day) typically move to a custom tier.
Future
Per-endpoint differentiated limits (looser on GETs, tighter on POSTs) are on the roadmap. The current single-bucket model is deliberately conservative — we'd rather under-throttle and be predictable.
