letssign.now docs

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.

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.