Rate limits
The hourly limits on calls that send email, open checkouts or book times, and how to handle hitting one.
Most Org API calls aren't limited. Reading events, posts and products, saving contacts, writing group rows and newsletter signups can run as often as your site needs.
The calls that send something or can cost money have an hourly limit per organization. The limits protect you: if your site is attacked by a bot, or a bug loops, it can't send thousands of emails or open thousands of checkouts in your name.
Limits
| Calls | Limit per hour |
|---|---|
POST /v1/uploads | 300 |
POST /v1/emails | 200 |
POST /v1/notifications | 200 |
POST /v1/checkouts and POST /v1/products/checkouts, together | 300 |
POST /v1/bookings | 300 |
How the counting works:
- Per organization. All API keys of one organization share the same counts.
- Per clock hour. Counts reset at the start of every hour, UTC, not an hour after your first call. A burst at 2:55 and another at 3:05 are counted in different hours.
- Every call counts. A call that fails, a retry with the same idempotency key, and a checkout for a basket that turns out to be unavailable all count toward the limit.
- Checkouts share one limit. One-time payments and product checkouts draw from the same 300.
When you hit a limit
The call is refused with a 429 and a body that says how many seconds are left until the counts reset:
{
"_tag": "TooManyRequests",
"retryAfterSeconds": 1260
}The response has no Retry-After header. Read retryAfterSeconds from the body.
Nothing was sent, charged or booked. You can retry after that many seconds.
Handle a 429
What to do depends on who's waiting:
- A visitor is waiting, for example on a checkout or booking form: don't make them wait an hour. Show a friendly message, such as "We're getting a lot of requests right now, please try again in a few minutes", and log it so you notice.
- A background job is sending, for example follow-up emails: pause the queue for
retryAfterSecondsand carry on.
const res = await fetch("https://api.lahuta.org/v1/emails", {
method: "POST",
headers: {
"X-Api-Key": process.env.LAHUTA_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify(email),
})
if (res.status === 429) {
const { retryAfterSeconds } = await res.json()
// Put it back on your own queue and try again after the reset.
await queue.retryLater(email, retryAfterSeconds)
}Keep the same idempotency key when you retry. Then a call that did go through before the limit was hit won't send twice. See Idempotency.
Stay under the limits
- Put a bot check, like a CAPTCHA or a honeypot field, on public forms that trigger emails, checkouts or bookings.
- Don't call
POST /v1/notificationsfor every form field change. Send one notification when the form is submitted. - To email your whole list, use a campaign or the newsletter.
POST /v1/emailsis for one person at a time, like a receipt or a reply to their form.