Using the API
The Kombine apps all talk to one REST API. With a personal access token your own tools can use it too. Everything here works with curl and jq.
- Base URL:
https://api.kombine.dev/v1 - Auth: send the token as
Authorization: Bearer kmb_pat_… - Format: JSON in, JSON out. Times are ISO 8601 (
2026-10-23T09:00:00Z). - Full reference: the OpenAPI spec lists every endpoint and field. Paste it into any OpenAPI viewer, or hand it to your AI agent.
Set up your shell once:
export KOMBINE_TOKEN='kmb_pat_…'export API='https://api.kombine.dev/v1'k() { curl -sS -H "Authorization: Bearer $KOMBINE_TOKEN" -H 'Content-Type: application/json' "$@"; }The examples below use that k helper.
Needs tasks: Read to list, Read & write to change.
List tasks. Results come in pages under data, with total, limit (default 200, max 500) and offset:
k "$API/tasks?limit=500" | jq '.data[] | {title, status, priority, due_date}'There’s no server-side filter for status or date, so filter with jq. Open tasks due today or earlier:
today=$(date +%F)k "$API/tasks?limit=500" | jq -r --arg d "$today" ' .data[] | select(.status != "DONE" and .is_archived == false and .due_date != null and .due_date[0:10] <= $d) | "\(.priority // "-")\t\(.title)"'Add a task. Your tool picks the task’s id (any UUID), the same way the apps do. That makes retries safe: sending the same id twice can’t create two tasks.
k -X POST "$API/tasks" -d "$(jq -n --arg id "$(uuidgen | tr A-Z a-z)" '{ id: $id, title: "Renew passport", priority: "HIGH", due_date: "2026-11-01T00:00:00Z", due_date_has_time: false, tags: ["errands"]}')"priority:LOW,MEDIUMorHIGH(leave it out for none)status:TODO(default),IN_PROGRESSorDONEdue_date_has_time: falsemeans “due that day”, with no time
Change a task. PUT changes only the fields you send. Mark one done:
k -X PUT "$API/tasks/$TASK_ID" -d '{"status":"DONE"}'Completing a task over the API counts the same as in the apps: it’s logged for your stats, and a repeating task moves to its next date.
Focus sessions
Section titled “Focus sessions”Needs sessions: Read & write. Log a session you did somewhere else (a meeting, a run, time tracked in another app):
k -X POST "$API/sessions" -d "$(jq -n --arg id "$(uuidgen | tr A-Z a-z)" '{ id: $id, mode: "POMODORO", started_at: "2026-10-23T07:00:00Z", ended_at: "2026-10-23T07:25:00Z", duration_ms: 1500000, note: "Pairing on the importer"}')"mode is POMODORO, FLOW or ADAPTIVE. Add task_id or activity_id to tag it. List your activities with k "$API/activities" | jq '.data[] | {id, name}'.
Needs timer: Read. See what’s running on any of your devices:
k "$API/timer/state" | jq '{status, mode, is_break, started_at, elapsed_ms, task_id}'An empty response (204) means no timer is running.
Starting or stopping the timer from a script isn’t recommended. A session is only recorded by the app that ran the timer, so a timer started over the API leaves no session behind. To record time, log a session instead (see Focus sessions above).
Statistics
Section titled “Statistics”Needs stats: Read.
k "$API/stats/all-time" | jq '{totalFocusSessions, hours: ((.totalFocusSeconds // 0) / 3600 | floor), longestStreakLength}'k "$API/daily-snapshots?from=2026-10-01&to=2026-10-31" | jq '.snapshots[] | {date, sessions_completed, achieved}'All-time stats are worked out in the background. On a brand-new account, stats/all-time answers 404 (“No stats available yet”) until they’ve been calculated for the first time.
Errors
Section titled “Errors”Errors are JSON with an error_code you can match on, for example:
{ "title": "Forbidden", "status": 403, "detail": "Personal access token does not permit this route", "error_code": 1014 }| Status | error_code |
Meaning | What to do |
|---|---|---|---|
| 400 | 5001, 5002 | A field is missing or invalid; errors says which |
Fix the request |
| 401 | 1013 | Token expired | Create a new token |
| 401 | 1011 | Token revoked | Create a new token |
| 401 | 1004 | Token not recognised | Check you copied it whole |
| 403 | 1014 | The token’s permissions don’t cover this | Create a token with the right permissions |
| 409 | 4003 | That id is already taken |
Safe to ignore on a retry |
| 409 | 4010 | That external_ref is already used |
See Workflows |
| 429 | 6001 | Too many requests | Wait Retry-After seconds |
GET /v1/error-codes lists them all (no token needed).
Limits
Section titled “Limits”Each token gets 60 requests a minute, with short bursts of up to 20. Past that you get 429 with a Retry-After header. Other tokens and your apps aren’t affected.