Rate limits
Write-heavy endpoints are rate limited to protect your data and keep the API fast for everyone.
The limit
| Scope | Limit |
|---|---|
| Per API key | 10 requests per minute |
The window is a fixed one-minute bucket (Asia/Bangkok time) that resets at the top of each minute. The limit currently applies to the member import endpoint. Read endpoints are not rate limited, but please be reasonable and page efficiently.
Member import also caps the payload at 1,000 members per request. That is separate from the request-rate limit. See Sync members from your HRIS.
Knowing your remaining quota
Successful import responses include a rate_limit object so you can pace your calls:
{
"status": "success",
"import_id": "imp_1717512730123",
"rate_limit": {
"remaining": 9,
"reset_at": "2026-06-04T14:33:00+07:00"
}
}
When you exceed the limit
You get a RATE_LIMIT_EXCEEDED error with the window details:
{
"error": true,
"errorMsg": "Rate limit exceeded",
"error_code": "RATE_LIMIT_EXCEEDED",
"details": {
"limit": 10,
"window_start": "2026-06-04T14:32:00+07:00",
"reset_at": "2026-06-04T14:33:00+07:00"
}
}
Handling it gracefully
Back off until reset_at, then retry. Batch large jobs into fewer, fuller requests rather than many small ones.
import os, time, requests
from datetime import datetime, timezone
def import_with_retry(payload):
url = "https://api.happily.ai/prod/api/v1/members"
headers = {"x-api-key": os.environ["HAPPILY_API_KEY"]}
while True:
res = requests.post(url, json=payload, headers=headers)
body = res.json()
if body.get("error_code") == "RATE_LIMIT_EXCEEDED":
reset = datetime.fromisoformat(body["details"]["reset_at"])
wait = max(1, (reset - datetime.now(timezone.utc)).total_seconds())
time.sleep(wait)
continue
return body
Import up to 1,000 members in a single call. One full request is far better than 1,000 single-member requests against a 10/minute limit.
Get an API key