xeve API reference
Base URL: https://api.xeve.io
Every response is JSON. Every request is authenticated, and every query is scoped to the caller by Postgres row-level security rather than by a parameter — there is no user id to pass and no way to ask for anyone else's rows.
Authentication
Two credentials, both presented the same way:
Authorization: Bearer <token>
API tokens (xeve_dt_…) are created by a signed-in human at
xeve.io/dashboard/settings → API Access.
They are scoped, individually revocable, shown once, and never expire unless
you set an expiry. Use one for a script, a tracker, or a local MCP server.
OAuth 2.0 access tokens are obtained by an application on a user's behalf and expire after one hour, with a rotating refresh token. Use this when the person running your software is not the person who owns the data — see OAuth 2.0.
Both are hashed at rest, both carry scopes, and both are enforced by the same code path. A token can never do more than the user granted it.
Scopes
A credential carries an explicit list. A request outside that list is refused
with 403 and the name of the scope it needed, so a client can ask for the
right grant rather than guessing.
| Scope | Grants |
|---|---|
read:activity |
App and coding sessions, devices, goals, and the activity aggregates over them |
read:code |
GitHub activity: commits, pull requests, reviews |
read:health |
Health samples, heart rate, recovery |
read:location |
Check-ins and location history |
read:music |
Listening history |
read:content |
Recordings, AI insights, watch history, calendar |
read:profile |
Profile, timezone, and which integrations are connected — never their credentials |
ingest:app_sessions |
Write application usage |
ingest:coding_sessions |
Write coding sessions |
ingest:health |
Write health samples |
ingest:heartbeats |
Write editor heartbeats |
Read scopes are per domain rather than per table, so granting "my music" does not require reasoning about which tables that is, and a table added later lands inside an existing grant rather than silently outside every one of them. Write scopes are per stream, so a coding-time tracker cannot post health data.
ingest:health and ingest:heartbeats are defined and issuable, and the
ingest API exposes two streams today — application sessions and coding
sessions. A token may carry the other two; nothing accepts them yet.
Endpoints
Ranges are ISO 8601 instants (from, to) unless the description says
calendar days (YYYY-MM-DD). Durations are milliseconds. Timestamps are UTC.
Activity — read:activity
| Method | Path | Returns |
|---|---|---|
| GET | /api/v1/analytics/daily |
Day-by-day totals: tracked, productive, distracted, coding, switches (calendar days) |
| GET | /api/v1/analytics/app-usage |
Time per application, with category and session count |
| GET | /api/v1/analytics/categories |
Time per category |
| GET | /api/v1/analytics/daily-categories |
Category totals per day |
| GET | /api/v1/analytics/coding |
Coding time by project and language |
| GET | /api/v1/analytics/hourly |
One day, hour by hour, by category (date, tz) |
| GET | /api/v1/analytics/hourly-patterns |
Historical day-of-week and hour baseline (weeks) |
| GET | /api/v1/analytics/context-switches |
App-to-app switches and the time before each |
| GET | /api/v1/analytics/focus-streaks |
Uninterrupted stretches of work, and what broke them |
| GET | /api/v1/sessions/apps |
Individual app sessions, newest first (limit) |
| GET | /api/v1/sessions/by-app |
Sessions grouped by application |
| GET | /api/v1/sessions/coding |
Individual coding sessions |
| GET | /api/v1/sessions/daily |
Per-day session rollup |
| GET | /api/v1/devices |
Registered trackers and when each last synced |
| GET | /api/v1/goals |
Goals and progress |
| GET | /api/v1/export |
Everything, as CSV or JSON |
| GET | /api/v1/account/correlation |
Correlation between two metrics over a range |
| GET | /api/v1/account/summaries |
Stored daily summaries |
Code — read:code
| Method | Path | Returns |
|---|---|---|
| GET | /api/v1/github/activity |
The event feed: commits, pull requests, reviews |
| GET | /api/v1/github/activity/counts |
Counts and line changes per event type |
Health — read:health
| Method | Path | Returns |
|---|---|---|
| GET | /api/v1/health/summary |
Per-metric totals, averages, minimum and maximum |
| GET | /api/v1/health/samples |
Raw samples for one metric (metric_type required) |
| GET | /api/v1/health/heart-rate |
Heart rate readings from connected monitors |
| GET | /api/v1/health/recovery |
Whoop recovery, strain and sleep, by calendar day |
Music — read:music
| Method | Path | Returns |
|---|---|---|
| GET | /api/v1/music/history |
Listening history with track, artist and album |
| GET | /api/v1/music/daily |
Listening totals per day |
Location — read:location
| Method | Path | Returns |
|---|---|---|
| GET | /api/v1/locations |
Location history |
| GET | /api/v1/locations/events |
Check-in events with place and status |
Content — read:content
| Method | Path | Returns |
|---|---|---|
| GET | /api/v1/insights |
AI-generated weekly insights |
| GET | /api/v1/recordings |
Voice recordings and transcripts |
| GET | /api/v1/watching/genres |
Watch history by genre |
| GET | /api/v1/calendar/events |
Calendar events from connected providers |
Profile — read:profile
| Method | Path | Returns |
|---|---|---|
| GET | /api/v1/me |
The caller's own profile, timezone and admin flag |
| GET | /api/v1/integrations |
Which integrations are connected. Never their credentials |
| GET | /api/v1/orgs |
Organizations the caller belongs to |
| GET | /api/v1/teams |
Teams the caller belongs to |
| GET | /api/v1/account/subscription |
Plan and billing state |
Ingest — ingest:*
| Method | Path | Writes |
|---|---|---|
| GET | /api/v1/ingest/schema |
The stream contract: endpoints, scopes, idempotency keys, limits. Unauthenticated, so a tracker author can read it before holding a credential |
| POST | /api/v1/ingest/app-sessions |
Application usage |
| POST | /api/v1/ingest/coding-sessions |
Coding sessions |
Ingest answers 200 with a per-event result rather than rejecting a whole
batch: one malformed event must not wedge a client's retry queue forever.
Send an idempotency_key per event, or let the server derive one, so a retry
after a lost response does not double-write.
Unauthenticated
| Method | Path | Returns |
|---|---|---|
| GET | /healthz |
Liveness, database reachability, and the running build |
| GET | /api/v1/capabilities |
Which optional features this deployment has configured |
Errors
| Status | Meaning |
|---|---|
400 |
A malformed parameter. The body names which one |
401 |
No credential, or one that was revoked, expired or never existed |
403 |
A valid credential without the scope this endpoint needs |
404 |
No such route, or a row that is not yours — the two are deliberately indistinguishable |
503 |
The database is unreachable. /healthz says more |
{ "error": "insufficient_scope", "message": "read:health is required" }
Machine-readable description
https://xeve.io/openapi.json — OpenAPI 3.1, including both security schemes and the full scope list.