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

CallsLimit per hour
POST /v1/uploads300
POST /v1/emails200
POST /v1/notifications200
POST /v1/checkouts and POST /v1/products/checkouts, together300
POST /v1/bookings300

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 retryAfterSeconds and 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/notifications for 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/emails is for one person at a time, like a receipt or a reply to their form.

On this page