On this page
telo · Personal API · Pro

Your Apple Health data, over REST.

Read your own synced health history from dashboards, scripts and agents. Six read-only endpoints, included with an active paid telo Pro subscription.

Full parameter and response schemas (OpenAPI 3.1)

Make your first request

  1. Subscribe to Pro in the telo iPhone app under Account → Plan, then sync your history.
  2. Open Account on the web dashboard and create a key for your project. Save it now: the full secret is shown only once.
  3. Set TELO_API_KEY in your server environment or local shell, then run the request below.
curl --fail-with-body 'https://telo.health/api/v1/list_metrics' \
  -H "Authorization: Bearer $TELO_API_KEY"

Authentication

Send Authorization: Bearer <key> on every request. Each key reads only its own account. Store it on your server or local machine, never in browser code, a public repository or a URL. Cookies and iPhone tokens do not authenticate this API.

Create up to five keys and revoke each project independently in Account. Revocation stays available after Pro expires. A paid Pro entitlement is required even for founder accounts; refunds and subscription changes follow the existing billing cache, up to 15 minutes, with webhook invalidation.

Manage API keys

Endpoints

Base URL: https://telo.health/api/v1. Every endpoint uses GET and returns the same JSON payload as its MCP read tool, without the MCP envelope. Dates use YYYY-MM-DD. Lists use commas; booleans use true or false. Unknown or repeated parameters are rejected.

GET /data_status

Check coverage, import progress and the last sync before interpreting missing records.

Required
None
Optional
None
curl --fail-with-body 'https://telo.health/api/v1/data_status' \
  -H "Authorization: Bearer $TELO_API_KEY"

GET /list_metrics

Discover the metric names, units and date ranges available in your account.

Required
None
Optional
None
curl --fail-with-body 'https://telo.health/api/v1/list_metrics' \
  -H "Authorization: Bearer $TELO_API_KEY"

GET /query_daily

Read daily values or aggregate by week or month. Missing buckets are absent, not zero.

Required
metrics, from, to
Optional
granularity=day|week|month
curl --fail-with-body 'https://telo.health/api/v1/query_daily?metrics=steps&from=2026-11-01&to=2026-11-08&granularity=day' \
  -H "Authorization: Bearer $TELO_API_KEY"

GET /sessions

List sleep nights or workouts. Workout filters and weekly grouping require kind=workout.

Required
kind=sleep|workout, from, to
Optional
activity, granularity=session|week, max_hr
curl --fail-with-body 'https://telo.health/api/v1/sessions?kind=workout&from=2026-11-01&to=2026-11-08' \
  -H "Authorization: Bearer $TELO_API_KEY"

GET /workout_detail

Read a workout summary, events and available series. Use a UUID returned by sessions.

Required
uuid
Optional
metrics, offset, event_offset, limit, max_hr, wall_tolerance_s
curl --fail-with-body 'https://telo.health/api/v1/workout_detail?uuid=WORKOUT_UUID&limit=500' \
  -H "Authorization: Bearer $TELO_API_KEY"

GET /derived_metrics

Read duration-based training load and personal HRV/resting-heart-rate baselines. Walking is excluded by default.

Required
from, to
Optional
baseline_days=30..60 (60), include_walking=false, include_trimp=false, max_hr
curl --fail-with-body 'https://telo.health/api/v1/derived_metrics?from=2026-11-01&to=2026-11-08&include_trimp=true' \
  -H "Authorization: Bearer $TELO_API_KEY"

Code examples

The examples read the catalog only. Your API key belongs in the environment, not in the source file. HTTP failures must be handled before using the response.

Python

import json
import os
from urllib.request import Request, urlopen

request = Request(
    "https://telo.health/api/v1/list_metrics",
    headers={"Authorization": "Bearer " + os.environ["TELO_API_KEY"]},
)
# urlopen raises HTTPError for non-success responses.
with urlopen(request, timeout=30) as response:
    catalog = json.load(response)
print(catalog)

JavaScript (Node.js)

// Node.js, server-side only.
const response = await fetch("https://telo.health/api/v1/list_metrics", {
  headers: { Authorization: `Bearer ${process.env.TELO_API_KEY}` },
  signal: AbortSignal.timeout(30_000),
});
if (!response.ok) throw new Error(`telo HTTP ${response.status}`);
const catalog = await response.json();
console.log(catalog);

Reading the response

Illustrative response for query_daily, using fictional data:

{
  "rows": [
    {
      "period": "2026-11-01",
      "metric": "steps",
      "unit": "count",
      "value": 8200,
      "aggregation": "sum",
      "days_present": 1,
      "days_expected": 1,
      "period_start": "2026-11-01",
      "period_end": "2026-11-01",
      "daily_min": 8200,
      "daily_max": 8200
    }
  ],
  "truncated": false
}

days_present counts days with stored values; days_expected describes the requested calendar coverage. Missing data and null are not zero. Preserve units and the local offset on timestamps. The API reads what the iPhone has synced; calling it does not trigger a sync. Workout summaries are permanent, while raw series are retained for 30 days. GPS is not included.

Limits and pagination

  • 60 requests per minute per account, shared by all its keys. On 429, wait the number of seconds in Retry-After.
  • At most 3,660 days per date query, or 366 for derived_metrics. query_daily accepts up to 256 comma-separated metric names.
  • Date-based reads return up to 500 rows. If truncated is true, discard the partial response and query fewer metrics or smaller non-overlapping ranges aligned to complete weekly/monthly buckets. Do not treat a partial average as a complete period.
  • workout_detail uses separate next_offset and next_event_offset values. Follow both independently until both are null. Defaults: offset=0, event_offset=0, limit=360; limit is at most 500. An empty metrics= returns metadata and events without series.

Errors

400
Invalid parameters, dates or range. Check the OpenAPI reference.
401
Missing, invalid or revoked personal key. Create a new key if necessary.
403
A paid Pro subscription is not active. Check Account → Plan on iPhone.
429
Rate limit reached. Wait Retry-After seconds before retrying.
500
Internal error. Retry later; do not interpret the response as empty health data.
503
Subscription verification is unavailable. Retry later.