The Sonar API is organized around REST. It accepts JSON-encoded request bodies, returns JSON responses, and uses standard HTTP response codes.
Base URL
All API requests should be made to:
https://api.sonarhealth.co/v1
Authentication
Every request must include a Bearer token in the Authorization header:
Authorization: Bearer <token>
See Authentication for details on obtaining API keys and JWTs.
Response Format
All successful responses follow a consistent envelope:
{
"data": { ... },
"meta": {
"page": 1,
"per_page": 20,
"total": 142
}
}
data— the response payload (object or array depending on the endpoint)meta— pagination metadata, included when the response is paginated
Pagination
Offset-Based Pagination
Most endpoints use offset-based pagination with page and per_page query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
int | 1 | Page number (1-indexed) |
per_page |
int | 20 | Results per page (max: 100) |
The meta object in the response tells you the total count and current position:
{
"meta": {
"page": 2,
"per_page": 20,
"total": 142
}
}
Cursor-Based Pagination
The Events API uses cursor-based pagination for reliable sequential processing. Pass your last cursor to resume where you left off:
GET /v1/events?cursor=evt_01HX3K...&limit=100
| Parameter | Type | Default | Description |
|---|---|---|---|
cursor |
string | — | Resume from this position (omit to start from the beginning) |
limit |
int | 100 | Maximum events per page |
The response includes the next cursor and a flag indicating whether more events are available:
{
"events": [
{
"event_id": "evt_01HX4M9ABC",
"event_type": "data.updated",
"metrics": ["resting_heart_rate", "heart_rate_variability", "steps"],
"user_id": "usr_abc123",
"device_id": "dev_garmin_456",
"timestamp": "2026-03-01T14:22:00Z"
}
],
"cursor": "evt_01HX4M9ABC",
"has_more": true
}
Store the returned cursor and pass it on the next call. See Data Delivery for event types and delivery patterns.
Common Query Parameters
These parameters work across multiple endpoints:
| Parameter | Type | Description |
|---|---|---|
unit_system |
string | metric (default) or imperial. When set to imperial, distances return in miles, temperatures in Fahrenheit, and weights in pounds. |
include_sources |
boolean | When true, each data point includes a source object showing which device contributed the value and its original reading. |
Error Handling
Errors return an appropriate HTTP status code and a JSON body:
{
"error": "not_found",
"message": "The requested resource was not found.",
"details": {}
}
The details field is always present — it contains additional context when available, or an empty object otherwise.
| Status Code | Meaning |
|---|---|
| 400 | Bad Request — invalid parameters |
| 401 | Unauthorized — missing or invalid token |
| 403 | Forbidden — insufficient permissions |
| 404 | Not Found — resource doesn't exist |
| 429 | Too Many Requests — rate limit exceeded |
| 500 | Internal Server Error |
Content Types
- Requests — send
Content-Type: application/jsonfor all POST/PUT/PATCH bodies - Responses — all responses return
Content-Type: application/json - Dates — all dates use ISO 8601 format (
2025-01-15for dates,2025-01-15T10:30:00Zfor timestamps)
Versioning
The API version is embedded in the URL path (/v1/). Breaking changes will ship under a new version prefix. Non-breaking additions (new fields, new endpoints) are added to the current version without notice.
Common Questions
What are the rate limits?
The API allows 100 requests per second per API key. Bursts up to 200 requests are allowed for short spikes. If you exceed the limit, the API returns 429 Too Many Requests with a Retry-After header. For high-volume use cases, use webhooks to react to changes instead of polling per-user endpoints.
Are requests idempotent?
All GET requests are safe to retry. POST requests to create resources (users, webhooks) are not idempotent — calling them twice creates two resources. Use reference_id on user creation to detect duplicates in your system. Token generation endpoints (/auth/mobile-token, /auth/user-token) are idempotent in the sense that calling them twice issues two valid tokens, which is harmless.
How should I handle retries?
Use exponential backoff with jitter. Start at 1 second, double on each retry, cap at 30 seconds, and add random jitter to avoid thundering herd. Always respect the Retry-After header on 429 responses. For 500 errors, retry up to 3 times — if it persists, the issue is on Sonar's side.
Can I use the API from the browser?
Yes, using a User Token. User Tokens are scoped to a single user and read-only, making them safe for client-side use. Never use your API Key from browser code.
Sonar