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 — 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.