Every error has the same shape:
{
"error": {
"code": "not_found",
"message": "not found",
"request_id": "6f1c2a0e-5f0b-4c8a-9a51-0d6f3c1e7b42"
}
}
Build on code. The message is for people and can change. If you write to us about a failed call, send the request_id.
| Status | Code | What it means | What to do |
|---|---|---|---|
| 400 | invalid | The request is wrong: a bad field, a value the app wouldn't accept, or a key reused for a different request. | Read the message, fix the request. |
| 401 | unauthorized | No token, or one that's malformed, unknown, revoked or expired. They all look the same on purpose. | Make a new token in the app. |
| 403 | forbidden_scope | The token doesn't have this call's permission. | Make a token with that permission ticked. |
| 404 | not_found | It doesn't exist, or it isn't yours to see. These are the same answer on purpose. | Check the id. |
| 409 | conflict | The app's rules said no (the nudge limit, one open check-in per goal), or two writes collided. | For a collision, retry with the same Idempotency-Key. |
| 410 | cursor_expired | Events after your cursor have aged out of the 30-day feed. | Read the current state, then continue from ?after=head. |
| 429 | rate_limited | Over the limit. See Rate limits. | Wait for Retry-After seconds. |
| 500, 503 | internal | Something failed on our side. | Retry with the same Idempotency-Key. |
Send a UUID as Idempotency-Key on every write. A retry with the same key never acts twice. Every write's response echoes the key it used. If you don't send one, the API makes one up, and that call can't be retried safely.