# Errors.

> https://meetkeely.com/developers/errors · Updated 2026-11-06 · Keely, by Metrica Fit LLC

Every error is JSON with the fitting status: `{ "error": "<code>", "message": "<plain words>" }`, sometimes with more. Act on `error`, not on `message`, which may change.

## The errors

| Code | Status | When |
|---|---|---|
| `invalid_request` | 400 | A bad date, range, metric or body; `message` says which |
| `unauthorized` | 401 | No token. Comes with `WWW-Authenticate: Bearer resource_metadata="https://meetkeely.com/.well-known/oauth-protected-resource/api/v1"` |
| `invalid_token` | 401 | The token expired, was revoked (the person disconnected), or isn’t a client’s. The same header, with `error="invalid_token"` |
| `app_not_registered` | 403 | The client isn’t a registered app. Assistants use [the connector](https://meetkeely.com/connect), not the API |
| `app_not_approved` | 403 | The app is awaiting approval, and the person isn’t its developer |
| `app_suspended` | 403 | Keely suspended the app |
| `insufficient_scope` | 403 | The answer needs a scope the person didn’t allow. Comes with `scope`, the one needed, and `scopes`, the ones granted |
| `not_found` | 404 | An unknown path, or a day, workout or entry that doesn’t exist |
| `method_not_allowed` | 405 | With an `Allow` header |
| `journal_off` | 409 | The person hasn’t turned on journal sharing in the Keely app |
| `journal_refused` | 422 | The journal’s rules refused a write. `reason` is one of `unknown_tag`, `bad_tags`, `future`, `too_old`, `bad_window`, `daily_limit`, `bad_ids`, `bad_label`, `bad_category`, `tag_cap`, `not_found`, `not_added_by_an_app` |
| `rate_limited` | 429 | Too many requests. Wait `Retry-After` seconds |
| `unavailable` | 503 | Keely’s backend didn’t answer. Comes with `Retry-After: 5` |

```json 403 insufficient_scope
{
  "error": "insufficient_scope",
  "message": "This person hasn’t allowed your app to read their workouts.",
  "scope": "workouts",
  "scopes": ["nights", "days"]
}
```

## Rate limit

About 60 requests a minute per app and person, best effort: it bounds a runaway loop rather than enforcing a quota. Over it, the API answers `429 rate_limited` with `Retry-After`. A person’s whole year is 366 points per series and at most four reads of 92 days, so an app that keeps a copy current never gets near it.
