# Kadence API Reference

> Static markdown mirror of the API documentation. Served at `https://kadence.life/api-docs.md` so AI crawlers and no-JS clients can fetch the content without running the SPA. The canonical human-readable version is at [https://kadence.life/docs](https://kadence.life/docs); the machine-readable schema is [OpenAPI 3.1 at /openapi.json](https://kadence.life/openapi.json).

**Base URL:** `https://qbtsjotudpvyjpzxeuyd.functions.supabase.co/api-v1`

**Authentication** — both schemes use `Authorization: Bearer …` header:

- **API key** — `kadence_key_live_<48 hex chars>`, mint at [/profile/api-keys](https://app.kadence.life/profile/api-keys). Inherits the owning user's permissions. Cap: 1 personal key per user OR 3 per team.
- **Supabase JWT** — session token from a logged-in web session.

## Canonical curl

```bash
curl https://qbtsjotudpvyjpzxeuyd.functions.supabase.co/api-v1/score \
  -H "Authorization: Bearer kadence_key_live_<YOUR_KEY>"
```

## Response envelope

Success:
```json
{
  "data": { /* endpoint-specific payload */ },
  "meta": {
    "request_id": "req_abc123",
    "timestamp": "2026-04-19T12:00:00.000Z"
  }
}
```

Paginated success (adds `meta.pagination`):
```json
{
  "data": [ ... ],
  "meta": {
    "request_id": "req_abc123",
    "timestamp": "2026-04-19T12:00:00.000Z",
    "pagination": { "cursor": "eyJpZCI6MTAwfQ==", "has_more": true, "limit": 25 }
  }
}
```

Error:
```json
{
  "error": { "code": "rate_limited", "message": "Too many requests" },
  "meta": { "request_id": "req_abc123", "timestamp": "..." }
}
```

## Error codes

| Code | HTTP | Meaning |
|---|---|---|
| `not_authenticated` | 401 | Missing / invalid / revoked / expired credential |
| `forbidden` | 403 | Authenticated but not authorized for this resource |
| `not_found` | 404 | Resource does not exist or is not owned by the caller |
| `invalid_input` | 400 | Validation failure (title too long, bad date, etc.) |
| `capacity_exceeded` | 400 | Per-hour or per-day task cap reached. Includes `scope`, `current`, `limit`, `board` fields |
| `rate_limited` | 429 | Throttle hit. Retry after the `Retry-After` header's seconds |
| `capacity_check_failed` | 503 | Upstream capacity service unavailable (fail-closed) |
| `internal` | 500 | Unexpected server-side error |

## Rate limits

- **120 GET / minute / user** (namespace `api-v1-read`)
- **30 POST / minute / user** (namespace `api-v1-write`)
- **10 failed auth / minute / IP** (namespace `api-auth-fail`)
- **60 webhook deliveries / minute / user** (namespace `webhook-dispatch`)

All 429 responses include `Retry-After: 60` header.

## Endpoints

### GET /tasks

List the authenticated user's tasks, newest-first.

**Query parameters:**
- `status` — `pending` | `locked` | `completed` | `abandoned` (optional)
- `board` — `work` | `personal` (optional)
- `limit` — 1–100, default 25
- `cursor` — opaque pagination cursor from a prior response's `meta.pagination.cursor`

**Example:**
```bash
curl "https://qbtsjotudpvyjpzxeuyd.functions.supabase.co/api-v1/tasks?status=pending&limit=5" \
  -H "Authorization: Bearer kadence_key_live_<YOUR_KEY>"
```

### POST /tasks

Create a task on the authenticated user's board. Enforces capacity caps (default 4/hour, 12 work/day, 8 personal/day) before insert.

**Body:**
```json
{
  "title": "string (1-100, required)",
  "description": "string (≤1000, optional)",
  "priority": "1 | 2 | 4 OR 'p0' | 'p1' | 'p2' (optional, default p1)",
  "board": "work | personal (optional, default work)",
  "due_date": "YYYY-MM-DD (optional, default today UTC)",
  "tags": ["optional", "array", "max 10", "each ≤30 chars"]
}
```

**Priority note:** integer 1–4 is accepted, but the DB enum has 3 tiers. Current mapping: `1→p0`, `2→p1`, `3→p1`, `4→p2`. Values 2 and 3 collapse to `p1`. Prefer sending `1`, `2`, or `4` — or use the string form directly.

**Example:**
```bash
curl -X POST https://qbtsjotudpvyjpzxeuyd.functions.supabase.co/api-v1/tasks \
  -H "Authorization: Bearer kadence_key_live_<YOUR_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"title":"Deploy hotfix","priority":1,"board":"work"}'
```

### POST /tasks/:id/complete

Mark a task complete. No body. Idempotent.

**Side effects:**
- DB row set to `status='completed'` + `completed_at=now()`.
- `task.completed` webhook fires immediately via HMAC to every active webhook subscribed to that event.
- XP / streak / achievement evaluation is deferred to the user's next app open (known behavior — documented rather than hidden).

**Example:**
```bash
curl -X POST https://qbtsjotudpvyjpzxeuyd.functions.supabase.co/api-v1/tasks/<id>/complete \
  -H "Authorization: Bearer kadence_key_live_<YOUR_KEY>"
```

### GET /score

Return the user's Kadence score breakdown.

**Response `data`:**
```json
{
  "kadence_score": 0,        // 0-1000
  "xp": 0,
  "level": 1,
  "streak": 2,               // days
  "freezes": 1               // 0-3
}
```

### GET /achievements

List unlocked achievements.

**Response `data`:** Array of `{id, tier: "bronze"|"silver"|"gold"|"diamond", earned_at}`.

### GET /focus-sessions

List recent Pomodoro focus sessions, newest first.

**Query parameters:**
- `limit` — 1–100, default 25
- `cursor` — pagination

**Response `data[i]`:** `{id, task_id, duration_seconds, break_seconds, paused_seconds, completed, created_at}`.

### GET /capacity

Task-creation capacity report for a given date. Used by clients (and MCP) to avoid triggering `capacity_exceeded` on POST /tasks.

**Query parameters:**
- `date` — `YYYY-MM-DD` (optional, default today UTC)

**Response `data`:**
```json
{
  "date": "2026-04-19",
  "hour_slots": [
    { "hour": 0, "count": 0 },
    ...
    { "hour": 23, "count": 0 }
  ],
  "day_totals": { "work": 5, "personal": 1 },
  "limits":     { "per_hour": 4, "work_per_day": 12, "personal_per_day": 8 },
  "remaining":  { "work": 7, "personal": 7 }
}
```

**Only personal tasks** (team tasks exempt from caps). Limits come from the authenticated user's `profiles` row and can be adjusted in Profile → Task Limits.

## Known behaviors

- **Priority mapping is lossy.** See the note on `POST /tasks` above.
- **Server-side scoring is deferred on API-completed tasks.** Task status updates immediately, webhooks fire immediately, but XP / streak / achievements re-eval on next app open.
- **Revoked API keys return 401 on every request.** No cache window. Verify via `GET /score` immediately after revoke.
- **Team tasks are exempt from individual capacity caps** so multiple team members can schedule work in the same hour.

## MCP (Model Context Protocol)

A stdio MCP server ships as `@kadence/mcp` v0.1. Six tools proxy to this API:

- `kadence_add_task` → `POST /tasks`
- `kadence_complete_task` → `POST /tasks/:id/complete` (with title-search fallback)
- `kadence_list_tasks` → `GET /tasks`
- `kadence_get_score` → `GET /score`
- `kadence_list_achievements` → `GET /achievements`
- `kadence_get_capacity` → `GET /capacity`

Install from source: `git clone github.com/app-synerg/Kadence && cd mcp && npm install && npm run build`. Claude Desktop config snippet in [/docs](https://kadence.life/docs) or `mcp/README.md`.

## Related

- [OpenAPI 3.1 spec](https://kadence.life/openapi.json) — machine-readable version of this document.
- [llms.txt](https://kadence.life/llms.txt) — AI-crawler entry point.
- [Security posture](https://kadence.life/security) — RLS, encryption, incident response.
- [Responsible disclosure](https://kadence.life/.well-known/security.txt) — `security@kadence.life`.
