Skip to content

Errors and limits

Rate limits

The two request budgets of a key, the 429 answer, and how to stay within them.

Every API key has two request budgets a minute. This guide covers the budgets, the answer to a call over them, and how to stay within them.

Two budgets#

The budgets are counted in fixed one-minute windows:

CallsLimit per key
Reading venues, menus, tables, orders, bookable times and reservations; pricing a basketAbout 120 a minute
Placing an order; taking or cancelling a reservation; sending a test webhookAbout 30 a minute
  • The two budgets are separate: reading the menu does not use up the orders and reservations a key may send.
  • A retried order or reservation counts as a call.
  • All the calls from one IP address also share a ceiling of about 500 a minute with everything else on that address.

Why the limits are approximate#

Cibusy runs on several servers, and each counts the calls it receives for itself. Under load a key may therefore get somewhat more than 120 or 30 calls through in a minute. Plan for the figures above rather than for what may get through, and treat a 429 as the signal to slow down.

The 429 answer#

A call over the budget is not queued. It is answered 429 RATE_LIMIT_EXCEEDED with a Retry-After: 60 header, and the next window opens within the minute.

JSON
{
  "success": false,
  "userMessage": "Too many requests for this API key. Wait a minute and try again.",
  "developerMessage": "Rate limit exceeded for policy. Please retry after 60 seconds.",
  "errorCode": "RATE_LIMIT_EXCEEDED",
  "retryAfter": 60,
  "timestamp": "2026-10-01T09:30:00.123Z",
  "traceId": "0HNF3N1G9B0TO:00000003"
}
successboolean

Always false.

userMessagestring

A sentence for the person using your site, in the language of the Accept-Language header.

developerMessagestring

For you and your logs, in English. Its wording may change; do not parse it.

errorCodestring

Always RATE_LIMIT_EXCEEDED.

retryAfterinteger

Seconds to wait. The same as the Retry-After header.

timestampstring · date-time

When the answer was written, in UTC.

traceIdstring

Identifies the request in Cibusy's logs. Quote it when you write to support.

Wait the number of seconds in the Retry-After header (or in retryAfter), then try again. Trying again without waiting only brings the next 429.

JavaScript
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function callCibusy(url, options = {}) {
  for (let attempt = 1; ; attempt++) {
    const response = await fetch(url, {
      ...options,
      headers: { ...options.headers, 'X-Api-Key': process.env.CIBUSY_API_KEY },
    });
    if (response.status !== 429 || attempt === 3) return response;

    const seconds = Number(response.headers.get('Retry-After')) || 60;
    await wait(seconds * 1000);
  }
}

Waiting is right in a background job. While answering a guest's request, rather than keep them waiting for a minute, tell them to try again shortly: that is what userMessage is written for.

Staying within the budget#

  • Keep a copy of the menu. Do not read the menu for every visitor; check your copy now and then with If-None-Match. Reading the menu shows how.
  • Follow orders and reservations with webhooks. Rather than asking about the open ones over and over, use a webhook, and read only an order or a reservation you are unsure about.
  • Do not re-read what does not change. Tables and a venue's details change seldom. Reading the venue's ordering state when the order screen opens is enough; the answer to placing an order says whether it was accepted anyway.
  • Give each integration a key of its own. The budget is per key: if your website and an ordering kiosk use separate keys, one cannot use up the other's budget.