API Conventions
Base URL, JSON shapes, validation, pagination, rate limits, and errors.
Base URL
https://atlas.sonarhealth.co/v1All public requests use Authorization: Bearer <api key>. Send Content-Type: application/json for POST and PATCH bodies.
Endpoint Inventory
| Method | Path | Success |
|---|---|---|
GET | /v1/providers | 200 provider catalog |
POST | /v1/users | 201 user object |
GET | /v1/users | 200 users and next cursor |
GET | /v1/users/{id} | 200 user object |
PATCH | /v1/users/{id} | 200 user object |
DELETE | /v1/users/{id} | 202 deletion status |
POST | /v1/atlas/roster/invitations | 201 invitation |
GET | /v1/atlas/roster/invitations/{id} | 200 invitation status |
DELETE | /v1/atlas/roster/invitations/{id} | 204 empty body |
PATCH | /v1/atlas/roster/{id} | 200 roster member |
DELETE | /v1/atlas/roster/{id} | 204 empty body |
POST | /v1/users/{id}/devices | 200 authorization URL |
DELETE | /v1/users/{id}/devices/{provider} | 204 empty body |
GET | /v1/users/{id}/daily | 200 dates and per-metric series |
GET | /v1/users/{id}/scores | 200 dates, per-score series, latest |
GET | /v1/users/{id}/workouts | 200 workouts and next cursor |
GET | /v1/users/{id}/workouts/{workout_id} | 200 workout detail |
GET | /v1/users/{id}/sleep | 200 sleep sessions and next cursor |
GET | /v1/users/{id}/timeseries | 200 dense bucket grid |
Download the complete OpenAPI specification.
Response Shapes
Responses are resource-specific and are not wrapped in a universal envelope. For example, user creation returns the user object directly, daily data returns {from_date, to_date, dates, series}, and user listing returns {users, next_cursor}.
Clients should ignore unknown response fields so non-breaking additions do not require a coordinated release. Numeric values carry at most one decimal.
Input Validation
- User and workout user path IDs must be UUIDs.
- Request JSON bodies and query strings reject unknown fields.
- Dates must be real calendar dates in
YYYY-MM-DDformat. - Date ranges are inclusive and can contain at most 366 days; timeseries ranges at most 31 days, or 7 at
1m. - An invalid JSON document returns
400 invalid_json. - A valid JSON document with the wrong shape returns
400 invalid_body.
Dates and Instants
The API uses three time representations. They are intentionally different because an operational event is an absolute instant, while a health day is the day the person experienced.
| Representation | Example | Meaning |
|---|---|---|
| UTC instant | 2026-09-16T08:42:10.000Z | One absolute point in time |
| Local calendar date | 2026-09-16 | A day in the API user’s profile timezone |
| Local wall time | 2026-09-16T07:35:00 | A clock reading in the API user’s profile timezone |
Operational metadata such as created_at, connected_at, last_sync_at, and webhook event at is always an RFC 3339 UTC instant with a trailing Z.
Health-data from_date, to_date, and daily axes are calendar dates in the user’s profile timezone. If to_date is omitted, it defaults to the current date in that timezone, not the current UTC date. If the profile has no timezone, Sonar uses UTC.
Workout and sleep session times, workout heart-rate sample timestamps, and the time-series start value are local wall times without Z or a numeric offset. Interpret them with the timezone saved on the user; appending Z would incorrectly turn them into UTC instants. See Health Data for examples and daylight-saving behavior.
Pagination
Paginated responses return an opaque next_cursor. Pass a non-null value back as the next request’s cursor:
GET /v1/users?limit=50&cursor=WyIyMDI2LTA5LTA5VDA5OjMwOjAwLjAwMFoiLCI3ZTRlOTFhNS0xZTRmLTRmYzItOTAzYy0yNTEwNDZkMmE0ZDMiXQKeep all other filters, date ranges, and the page limit unchanged while following a cursor. next_cursor is null on the final page.
Errors
Handled API errors use one JSON shape:
{
"error": {
"code": "invalid_query",
"message": "The query is not valid."
}
}| Status | Typical codes |
|---|---|
400 | invalid_json, invalid_body, invalid_query, invalid_path, invalid_provider, invalid_redirect_url, invalid_timezone |
401 | unauthenticated, invalid_api_key |
402 | billing_subscription_required, developer_subscription_required, billing_add_trainee_blocked |
403 | forbidden, roster_invitations_disabled |
404 | not_found, user_not_found, workout_not_found |
409 | user_conflict, invite_conflict, live_environment_required, invitation_already_accepted, api_user_lifecycle_managed_by_users_api |
422 | invalid_date, range_inverted, range_too_long, invalid_types, invalid_metric, invalid_cursor |
429 | rate_limited |
500 | Unhandled server error; body may be plain text |
Rate-limit retries
By default, API keys have a rate limit of 600 requests per minute, with a burst capacity of 100 requests. For larger deployments or higher-throughput workloads, contact us to arrange custom rate limits and capacity planning. We’ll work with your team to establish limits suited to your expected usage before launch and as you scale.
A 429 response includes Retry-After in seconds.
Safe Retries
GET requests are safe to retry. Use exponential backoff with jitter for 429 and transient 5xx responses, and respect Retry-After.
POST /users is not declared idempotent, but external_ref is unique. If the response is interrupted, resolve the outcome with GET /users?external_ref=... before attempting another create. Other writes do not accept idempotency keys.
Atlas roster status updates and removals are safe to repeat. Revoking a pending or already inactive roster invitation is also safe to repeat. Creating a roster invitation is not idempotent: another invite for the same email revokes the prior pending invitation and sends a new link.
Versioning
The major API version is part of the path. Additive fields can be introduced within /v1; breaking request or response changes require a new version.
Sonar