API Conventions

Base URL, JSON shapes, validation, pagination, rate limits, and errors.

7 min read Updated Sep 16, 2026

Base URL

http
https://atlas.sonarhealth.co/v1

All public requests use Authorization: Bearer <api key>. Send Content-Type: application/json for POST and PATCH bodies.

Endpoint Inventory

MethodPathSuccess
GET/v1/providers200 provider catalog
POST/v1/users201 user object
GET/v1/users200 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/invitations201 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}/devices200 authorization URL
DELETE/v1/users/{id}/devices/{provider}204 empty body
GET/v1/users/{id}/daily200 dates and per-metric series
GET/v1/users/{id}/scores200 dates, per-score series, latest
GET/v1/users/{id}/workouts200 workouts and next cursor
GET/v1/users/{id}/workouts/{workout_id}200 workout detail
GET/v1/users/{id}/sleep200 sleep sessions and next cursor
GET/v1/users/{id}/timeseries200 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-DD format.
  • 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.

RepresentationExampleMeaning
UTC instant2026-09-16T08:42:10.000ZOne absolute point in time
Local calendar date2026-09-16A day in the API user’s profile timezone
Local wall time2026-09-16T07:35:00A 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:

http
GET /v1/users?limit=50&cursor=WyIyMDI2LTA5LTA5VDA5OjMwOjAwLjAwMFoiLCI3ZTRlOTFhNS0xZTRmLTRmYzItOTAzYy0yNTEwNDZkMmE0ZDMiXQ

Keep 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:

json
{
  "error": {
    "code": "invalid_query",
    "message": "The query is not valid."
  }
}
StatusTypical codes
400invalid_json, invalid_body, invalid_query, invalid_path, invalid_provider, invalid_redirect_url, invalid_timezone
401unauthenticated, invalid_api_key
402billing_subscription_required, developer_subscription_required, billing_add_trainee_blocked
403forbidden, roster_invitations_disabled
404not_found, user_not_found, workout_not_found
409user_conflict, invite_conflict, live_environment_required, invitation_already_accepted, api_user_lifecycle_managed_by_users_api
422invalid_date, range_inverted, range_too_long, invalid_types, invalid_metric, invalid_cursor
429rate_limited
500Unhandled 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.