Rate limits

The hourly limits on calls that send email or texts, start workflows, open checkouts or book times, and how to handle hitting one.

Most Org API calls aren't limited. Reading events, posts and products, looking up and 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. Starting a workflow counts, because its steps send emails and texts. The limits protect you: if your site is attacked by a bot, or a bug loops, it can't send thousands of emails or texts, or open thousands of checkouts, in your name.

Limits

CallsLimit per hour
POST /v1/uploads300
POST /v1/emails200
POST /v1/texts200
POST /v1/notifications200
POST /v1/checkouts and POST /v1/products/checkouts, together300
POST /v1/bookings300
POST /v1/workflows/{workflowId}/start300

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, a workflow start for someone who's already going through it, 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 send emails or texts, start workflows, open checkouts or make bookings.
  • Don't call POST /v1/notifications for every form field change. Send one notification when the form is submitted.
  • To email or text your whole list, use a campaign or the newsletter. POST /v1/emails and POST /v1/texts are for one person at a time, like a receipt or a reply to their form.

On this page