Errors & rate limits
How the Arkaayu API reports problems, how much you can call it, and how to build an integration that behaves well when things go wrong.
Error format
OAuth and data API errors return top-level JSON with a machine-readable error code and a human-readable error_description (RFC 6749 style). Branch on error; show or log error_description, but don't parse it — the wording may change.
{
"error": "insufficient_scope",
"error_description": "This endpoint requires the read:sleep scope"
}Status codes and error codes
| Status | Error | Where | What to do |
|---|---|---|---|
| 400 | invalid_request | OAuth, API | Fix the request: a parameter is missing or malformed, or a date range is invalid. |
| 401 | invalid_client | Token, revoke | Check your client ID and secret. Rotating a secret makes the old one stop working immediately, so deploy the new one. |
| 400 | invalid_grant | Token | The code or refresh token is no good. For codes, restart the sign-in; for refresh tokens, ask the user to reconnect. |
| 400 | unsupported_grant_type | Token | Use authorization_code or refresh_token. |
| 400 | invalid_scope | Authorize, token | Request only scopes enabled for your app. |
| — | access_denied | Authorize redirect | The user cancelled. Let them try again later; don't loop them back. |
| 401 | invalid_token | API | Refresh the access token and retry once. Sent with WWW-Authenticate: Bearer. |
| 403 | insufficient_scope | API | The user didn't grant this scope. WWW-Authenticate: Bearer error="insufficient_scope", scope="read:sleep" names the one you need. |
| 404 | not_found | API | The resource doesn't exist for this user. |
| 429 | — | Everywhere | Slow down. See Handling 429. |
| 5xx | — | Everywhere | Something went wrong on our side. Retry with backoff. |
Rate limits
| Endpoint | Limit (per IP address) |
|---|---|
Authorize page (/oauth/authorize) | 30 per minute |
| Sign-in on the authorize page | 10 per minute |
Token (/oauth/token) and revoke (/oauth/revoke) | 60 per minute |
| Console: create app | 10 per hour |
| Console: rotate client secret | 5 per hour |
All limits are counted per client IP address. When you go over one you get 429 Too Many Requests. The data endpoints (/api/*) have no documented per-route limit, but please poll politely — at most about once an hour per user (see Data freshness). Limits may change, so handle 429 on every endpoint.
Handling 429
- Back off exponentially with jitter, starting at about 30 seconds: wait ~30 s, then ~60 s, ~120 s… and give up after a few tries (try again on your next scheduled poll).
- Don't rely on a
Retry-Afterheader — it isn't guaranteed. - Don't retry
4xxerrors other than429— they won't succeed unchanged. - Refresh tokens before they expire, from one place, rather than in a burst whenever requests start failing.
Node.js
async function arkaayuGet(url, token, attempt = 0) {
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
if ((res.status === 429 || res.status >= 500) && attempt < 4) {
// ~30 s, 60 s, 120 s, ... with jitter
const wait = 30000 * 2 ** attempt * (0.75 + Math.random() / 2);
await new Promise((r) => setTimeout(r, wait));
return arkaayuGet(url, token, attempt + 1);
}
return res;
}Python
import random, time, requests
def arkaayu_get(url, token, params=None, attempts=4):
for attempt in range(attempts):
res = requests.get(url, params=params,
headers={"Authorization": f"Bearer {token}"}, timeout=10)
if res.status_code != 429 and res.status_code < 500:
return res
# ~30 s, 60 s, 120 s, ... with jitter
time.sleep(30 * 2 ** attempt * (0.75 + random.random() / 2))
return resData freshness and polling
Readings reach Arkaayu when the user's watch syncs with the Arkaayu app on their phone, and the phone is online. Depending on how the user wears and charges their watch, today's data may arrive in minutes or only the next morning — and a row for a past day can still change after a late sync.
- Poll politely. Fetch each user's data at most about once an hour, and less often for users who haven't opened your app recently.
- Re-fetch a short overlap. Ask for the last 2–3 days each time and upsert by date or ID, so late syncs are picked up.
- Back-fill once. When a user first connects, fetch history in windows (up to 400 days each), then switch to incremental polling.
- Treat empty as "not yet", not "zero". An empty
dataarray usually means the watch hasn't synced.
There are no webhooks yet — polling is the only way to get new data. If push notifications would make a big difference to your app, tell us at developers@arkaayu.com.
Console errors
The developer console endpoints (/developer/*, sign-in) use a different shape from the OAuth and data API: FastAPI's {"detail": "…"}, where detail is a message or, for validation errors, a list of problems. The console shows these messages directly. Common ones: app limit reached, an app name that includes "Arkaayu" or "Healaxy", a non-https URL, or a missing field when you submit for review.
Questions or something unclear? Write to developers@arkaayu.com.