Reference

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.

Response · 403 Forbidden
{
  "error": "insufficient_scope",
  "error_description": "This endpoint requires the read:sleep scope"
}

Status codes and error codes

StatusErrorWhereWhat to do
400invalid_requestOAuth, APIFix the request: a parameter is missing or malformed, or a date range is invalid.
401invalid_clientToken, revokeCheck your client ID and secret. Rotating a secret makes the old one stop working immediately, so deploy the new one.
400invalid_grantTokenThe code or refresh token is no good. For codes, restart the sign-in; for refresh tokens, ask the user to reconnect.
400unsupported_grant_typeTokenUse authorization_code or refresh_token.
400invalid_scopeAuthorize, tokenRequest only scopes enabled for your app.
—access_deniedAuthorize redirectThe user cancelled. Let them try again later; don't loop them back.
401invalid_tokenAPIRefresh the access token and retry once. Sent with WWW-Authenticate: Bearer.
403insufficient_scopeAPIThe user didn't grant this scope. WWW-Authenticate: Bearer error="insufficient_scope", scope="read:sleep" names the one you need.
404not_foundAPIThe resource doesn't exist for this user.
429—EverywhereSlow down. See Handling 429.
5xx—EverywhereSomething went wrong on our side. Retry with backoff.

Rate limits

EndpointLimit (per IP address)
Authorize page (/oauth/authorize)30 per minute
Sign-in on the authorize page10 per minute
Token (/oauth/token) and revoke (/oauth/revoke)60 per minute
Console: create app10 per hour
Console: rotate client secret5 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-After header — it isn't guaranteed.
  • Don't retry 4xx errors other than 429 — 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 res

Data 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 data array 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.