Skip to main content

API Reference

Xenith's public API lets you read and write your own data (intentions, focus sessions, and dimension scores) from your own scripts, tools, or integrations. It's your data; the API just gives you a way to reach it programmatically.

Authentication​

Create an API key in Settings → API access. The raw key is shown once, at creation. Store it somewhere safe, since Xenith only ever stores its hash.

Send it as a bearer token on every request:

curl https://xenith.life/api/v1/intentions \
-H "Authorization: Bearer xnth_live_..."

Requests without a valid key get 401 Unauthorized. You can revoke a key at any time from the same Settings page, and it stops working immediately.

Base URL​

https://xenith.life/api/v1

Rate limits​

Each key is limited to 60 requests per minute, counted in fixed one-minute windows. The limit is enforced atomically, so parallel requests can't exceed it. Every response from an authenticated request, including 429s, carries these headers:

HeaderValue
X-RateLimit-LimitRequests allowed per window (60)
X-RateLimit-RemainingRequests left in the current window (never below 0)
X-RateLimit-ResetUnix time (seconds) when the window resets
Retry-AfterOn 429 only: seconds to wait before retrying

Going over returns 429 with { "error": "rate_limited" }. Wait for Retry-After seconds (or until X-RateLimit-Reset) before retrying. Response bodies also include a remaining field with the same count as X-RateLimit-Remaining.

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1790000040
Retry-After: 23

Errors​

Errors are always a JSON body with an error field:

StatusMeaning
400Invalid request body or query parameters
401Missing or invalid API key
403Your key doesn't have the required scope
429Rate limit exceeded
500Something went wrong on Xenith's end

Intentions​

GET /api/v1/intentions​

Requires scope intentions:read.

Query parameters (all optional):

ParamDescription
scheduled_dateFilter to a single date (YYYY-MM-DD)
completedtrue or false
limitMax results, 1–100 (default 50)
cursorPagination cursor: pass the previous response's next_cursor
curl "https://xenith.life/api/v1/intentions?scheduled_date=2026-07-12" \
-H "Authorization: Bearer xnth_live_..."
{
"data": [ /* intention rows */ ],
"remaining": 59,
"next_cursor": null
}

POST /api/v1/intentions​

Requires scope intentions:write.

Body:

FieldTypeRequired
titlestring (1–200 chars)yes
descriptionstring (max 2000 chars)no
scheduled_datestring (YYYY-MM-DD)no
dimensionstringno
estimated_minutesinteger (1–1440)no
curl -X POST https://xenith.life/api/v1/intentions \
-H "Authorization: Bearer xnth_live_..." \
-H "Content-Type: application/json" \
-d '{"title": "Write the quarterly review", "dimension": "Work"}'

Focus sessions​

GET /api/v1/focus-sessions​

Requires scope focus_sessions:read.

Query parameters (all optional): start, end (ISO timestamps to filter started_at), limit (1–100, default 50), cursor.

POST /api/v1/focus-sessions​

Requires scope focus_sessions:write. Logs a completed (or in-progress) focus session. This is a record of actual timer usage, not a way to schedule a future session, so started_at can't be more than 24 hours in the future.

Body:

FieldTypeRequired
duration_minutesinteger (1–1440)yes
session_typestringno
notesstring (max 2000 chars)no
started_atISO timestampno
completed_atISO timestampno

Dimension scores​

GET /api/v1/dimension-scores​

Requires scope dimension_scores:read. Read-only: scores are recorded through Xenith's own weekly check-in, not writable via the API.

Query parameters (all optional):

ParamDescription
dimensionFilter to one life dimension
history1 to return full history; omitted returns only the latest score per dimension

Scopes​

New API keys currently receive all available scopes:

  • intentions:read, intentions:write
  • focus_sessions:read, focus_sessions:write
  • dimension_scores:read

Per-scope key creation is on the roadmap; for now, every key can do everything listed above.

CORS​

All /api/v1/* endpoints allow cross-origin requests from any origin. Authentication is entirely via the Authorization header (never cookies), so there's no ambient browser credential to protect against.