Rate limits and quotas

Limits apply to your developer account as a whole: all of its keys and applications together. Test and live mode have separate per-second limits, so testing never slows production.

Per second: burst and sustained rate

Each plan gives a burst (requests you can send at once) and a sustained rate (requests per second the burst refills at). For example, the Developer plan allows bursts of 60 and 30 requests per second sustained.

Every authenticated response tells you where you stand:

Header Meaning
X-RateLimit-Limit Your burst size.
X-RateLimit-Remaining Requests left in the current burst.
X-RateLimit-Reset Unix time (seconds) when the burst is full again.

Over the limit, the API answers 429 rate_limited with Retry-After: <seconds>.

Per month: included requests (live mode)

Each plan includes a number of billable requests per calendar month, in Thailand time. The count resets at 00:00 on the 1st.

Header Meaning
X-Quota-Limit Requests included this month.
X-Quota-Remaining Included requests left.
X-Quota-Reset Unix time when the count resets.

On the Free plan, requests stop at the included amount: you get 429 monthly_quota_exceeded, with Retry-After set to the seconds until the 1st. On paid plans, extra requests keep working and are billed at the plan's per-1,000 price on the next monthly invoice.

Billable means a live request that reached an account and that DOOLAE served:

Counts Doesn't count
2xx responses 5xx (DOOLAE's errors)
4xx responses, which still use capacity 429 (rate limited)
401 (no valid key)
Anything in test mode

Other limits

Limit Value Error
LINE sends per landlord account (/v1/notifications, /v1/messages) 60 per minute 429 sending_limit
Test-mode requests per developer account 20,000 per day 429 sandbox_limit
Sandbox size 20 properties, 500 rooms 429 sandbox_limit
Request body 1 MB 413 request_too_large
Page size 100 400 invalid_parameter
Requests with invalid keys from one IP address 30 per minute 429 too_many_failed_attempts

When DOOLAE as a whole is very busy, a request can wait up to 10 seconds for its turn and then get 503 service_unavailable with Retry-After.

Backing off

  • On 429 and 503, wait for Retry-After seconds, then retry.
  • For anything else retryable (network errors, 500), use exponential backoff with jitter: 1 s, 2 s, 4 s, 8 s, … up to a minute.
  • Retry writes with the same Idempotency-Key, so a retry can't do the work twice (see idempotency).
  • Prefer webhooks to polling. If you poll, GET /v1/events?created_after=… is the cheapest way to catch up.
async function call(url, init = {}, attempt = 0) {
  const res = await fetch(url, init);
  if ((res.status === 429 || res.status >= 500) && attempt < 5) {
    const wait = Number(res.headers.get("Retry-After")) || Math.min(60, 2 ** attempt) * (0.5 + Math.random());
    await new Promise((r) => setTimeout(r, wait * 1000));
    return call(url, init, attempt + 1);
  }
  return res;
}