{
  "openapi": "3.1.0",
  "info": {
    "title": "Kadence API",
    "version": "1.0.0",
    "description": "Programmatic access to Kadence — manage tasks, track scores, check capacity, and list achievements.\n\nAuthenticate with either:\n- **API key** — `Authorization: Bearer kadence_key_live_<48 hex chars>` (mint at https://app.kadence.life/profile/api-keys)\n- **Supabase JWT** — `Authorization: Bearer <jwt>` from an active web session\n\nEvery response follows the envelope `{ data, meta: { request_id, timestamp } }` on success, or `{ error: { code, message }, meta }` on failure.",
    "contact": {
      "name": "Kadence Security",
      "url": "https://kadence.life/security",
      "email": "security@kadence.life"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://kadence.life/terms"
    }
  },
  "servers": [
    {
      "url": "https://qbtsjotudpvyjpzxeuyd.functions.supabase.co/api-v1",
      "description": "Production"
    }
  ],
  "security": [
    { "bearerApiKey": [] },
    { "bearerJwt": [] }
  ],
  "paths": {
    "/tasks": {
      "get": {
        "summary": "List tasks",
        "operationId": "listTasks",
        "description": "Returns the authenticated user's tasks, newest-first. Cursor-paginated.",
        "tags": ["tasks"],
        "parameters": [
          { "name": "status", "in": "query", "schema": { "$ref": "#/components/schemas/TaskStatus" }, "description": "Filter by status." },
          { "name": "board",  "in": "query", "schema": { "$ref": "#/components/schemas/Board" }, "description": "Filter by board (work/personal)." },
          { "name": "limit",  "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 } },
          { "name": "cursor", "in": "query", "schema": { "type": "string" }, "description": "Opaque pagination cursor from a previous response's meta.pagination.cursor." }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    {
                      "type": "object",
                      "properties": {
                        "data": { "type": "array", "items": { "$ref": "#/components/schemas/Task" } },
                        "meta": {
                          "type": "object",
                          "properties": {
                            "pagination": { "$ref": "#/components/schemas/Pagination" }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "post": {
        "summary": "Create a task",
        "operationId": "createTask",
        "description": "Creates a task on the authenticated user's board. Enforces capacity limits (default 4/hour + 12 work/day + 8 personal/day) before insert. Returns the created row.\n\n**Priority note:** integer 1–4 is accepted, but the DB enum is 3-tier. Current mapping: 1→p0, 2→p1, 3→p1, 4→p2 — values 2 and 3 collapse. Prefer sending 1, 2, or 4 for deterministic mapping, or use the string form directly.",
        "tags": ["tasks"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/TaskCreate" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Task created",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Task" } } }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or capacity exceeded.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "examples": {
                  "capacityExceeded": {
                    "summary": "Capacity cap hit",
                    "value": { "error": { "code": "capacity_exceeded", "message": "Daily personal task cap reached (8/8)", "scope": "day", "current": 8, "limit": 8, "board": "personal" } }
                  },
                  "invalidInput": {
                    "summary": "Invalid field",
                    "value": { "error": { "code": "invalid_input", "message": "title must be 1–100 chars" } }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/tasks/{id}/complete": {
      "post": {
        "summary": "Complete a task",
        "operationId": "completeTask",
        "description": "Marks a task as completed. No body. Idempotent — re-completing a completed task returns 200 without side effects. `task.completed` webhooks fire immediately; server-side XP/streak/achievement re-eval happens on next app open (known behavior).",
        "tags": ["tasks"],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": {
          "200": {
            "description": "Task completed",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Task" } } }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/score": {
      "get": {
        "summary": "Get score",
        "operationId": "getScore",
        "description": "Returns the authenticated user's Kadence score breakdown.",
        "tags": ["scoring"],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Score" } } }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/achievements": {
      "get": {
        "summary": "List unlocked achievements",
        "operationId": "listAchievements",
        "tags": ["scoring"],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Achievement" } } } }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/focus-sessions": {
      "get": {
        "summary": "List focus sessions",
        "operationId": "listFocusSessions",
        "description": "Returns the authenticated user's recent Pomodoro focus sessions, newest first.",
        "tags": ["focus"],
        "parameters": [
          { "name": "limit",  "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 } },
          { "name": "cursor", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/FocusSession" } } } }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/capacity": {
      "get": {
        "summary": "Get capacity report",
        "operationId": "getCapacity",
        "description": "Returns the authenticated user's per-hour task occupancy + per-board day totals + limits + remaining slots for a given date. Used by the Today toolbar chip and by API / MCP clients to avoid triggering a `capacity_exceeded` on POST /tasks. Personal tasks only — team tasks are exempt from the cap.",
        "tags": ["capacity"],
        "parameters": [
          { "name": "date", "in": "query", "schema": { "type": "string", "format": "date" }, "description": "Defaults to today (UTC) if omitted." }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Envelope" },
                    { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/CapacityReport" } } }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Kadence API Key",
        "description": "`kadence_key_live_<48 hex chars>` minted at /profile/api-keys. Scope: the minting user's own data. Cap: 1 personal or 3 per team."
      },
      "bearerJwt": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Supabase session JWT from an active web login."
      }
    },
    "schemas": {
      "Envelope": {
        "type": "object",
        "required": ["data", "meta"],
        "properties": {
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "Meta": {
        "type": "object",
        "required": ["request_id", "timestamp"],
        "properties": {
          "request_id": { "type": "string", "example": "req_NDEo88NTSyj32N6i" },
          "timestamp":  { "type": "string", "format": "date-time" }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "cursor":   { "type": ["string", "null"] },
          "has_more": { "type": "boolean" },
          "limit":    { "type": "integer" }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error", "meta"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code":    { "type": "string", "enum": ["not_authenticated", "forbidden", "not_found", "invalid_input", "rate_limited", "capacity_exceeded", "capacity_check_failed", "internal"] },
              "message": { "type": "string" },
              "scope":   { "type": "string", "enum": ["hour", "day"], "description": "Present on capacity_exceeded." },
              "current": { "type": "integer", "description": "Present on capacity_exceeded." },
              "limit":   { "type": "integer", "description": "Present on capacity_exceeded." },
              "board":   { "type": "string", "enum": ["work", "personal"], "description": "Present on day-scope capacity_exceeded." }
            }
          },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "Task": {
        "type": "object",
        "required": ["id", "title", "board", "priority", "status", "due_date", "created_at", "updated_at"],
        "properties": {
          "id":           { "type": "string", "format": "uuid" },
          "title":        { "type": "string", "minLength": 1, "maxLength": 100 },
          "description":  { "type": ["string", "null"], "maxLength": 1000 },
          "board":        { "$ref": "#/components/schemas/Board" },
          "priority":     { "type": "string", "enum": ["p0", "p1", "p2"] },
          "status":       { "$ref": "#/components/schemas/TaskStatus" },
          "due_date":     { "type": "string", "format": "date" },
          "due_time":     { "type": ["string", "null"], "pattern": "^\\d{2}:\\d{2}(:\\d{2})?$" },
          "tags":         { "type": "array", "items": { "type": "string" } },
          "completed_at": { "type": ["string", "null"], "format": "date-time" },
          "created_at":   { "type": "string", "format": "date-time" },
          "updated_at":   { "type": "string", "format": "date-time" }
        }
      },
      "TaskCreate": {
        "type": "object",
        "required": ["title"],
        "properties": {
          "title":       { "type": "string", "minLength": 1, "maxLength": 100 },
          "description": { "type": "string", "maxLength": 1000 },
          "priority":    { "oneOf": [
            { "type": "integer", "minimum": 1, "maximum": 4, "description": "Note: 2 and 3 both collapse to p1." },
            { "type": "string", "enum": ["p0", "p1", "p2"] }
          ]},
          "board":       { "$ref": "#/components/schemas/Board" },
          "due_date":    { "type": "string", "format": "date" },
          "tags":        { "type": "array", "maxItems": 10, "items": { "type": "string", "minLength": 1, "maxLength": 30 } }
        }
      },
      "TaskStatus": {
        "type": "string",
        "enum": ["pending", "locked", "completed", "abandoned"]
      },
      "Board": {
        "type": "string",
        "enum": ["work", "personal"]
      },
      "Score": {
        "type": "object",
        "required": ["kadence_score", "xp", "level", "streak", "freezes"],
        "properties": {
          "kadence_score": { "type": "integer", "minimum": 0, "maximum": 1000 },
          "xp":            { "type": "integer", "minimum": 0 },
          "level":         { "type": "integer", "minimum": 1 },
          "streak":        { "type": "integer", "minimum": 0 },
          "freezes":       { "type": "integer", "minimum": 0, "maximum": 3 }
        }
      },
      "Achievement": {
        "type": "object",
        "required": ["id", "tier", "earned_at"],
        "properties": {
          "id":        { "type": "string" },
          "tier":      { "type": "string", "enum": ["bronze", "silver", "gold", "diamond"] },
          "earned_at": { "type": "string", "format": "date-time" }
        }
      },
      "FocusSession": {
        "type": "object",
        "required": ["id", "duration_seconds", "created_at"],
        "properties": {
          "id":                { "type": "string", "format": "uuid" },
          "task_id":           { "type": ["string", "null"], "format": "uuid" },
          "duration_seconds":  { "type": "integer" },
          "break_seconds":     { "type": "integer" },
          "paused_seconds":    { "type": "integer" },
          "completed":         { "type": "boolean" },
          "created_at":        { "type": "string", "format": "date-time" }
        }
      },
      "CapacityReport": {
        "type": "object",
        "required": ["date", "hour_slots", "day_totals", "limits", "remaining"],
        "properties": {
          "date": { "type": "string", "format": "date" },
          "hour_slots": {
            "type": "array",
            "minItems": 24,
            "maxItems": 24,
            "items": {
              "type": "object",
              "required": ["hour", "count"],
              "properties": {
                "hour":  { "type": "integer", "minimum": 0, "maximum": 23 },
                "count": { "type": "integer", "minimum": 0 }
              }
            }
          },
          "day_totals": {
            "type": "object",
            "required": ["work", "personal"],
            "properties": {
              "work":     { "type": "integer" },
              "personal": { "type": "integer" }
            }
          },
          "limits": {
            "type": "object",
            "required": ["per_hour", "work_per_day", "personal_per_day"],
            "properties": {
              "per_hour":         { "type": "integer" },
              "work_per_day":     { "type": "integer" },
              "personal_per_day": { "type": "integer" }
            }
          },
          "remaining": {
            "type": "object",
            "required": ["work", "personal"],
            "properties": {
              "work":     { "type": "integer" },
              "personal": { "type": "integer" }
            }
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing, invalid, revoked, or expired credential.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "Resource does not exist or is not owned by the caller.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Too many requests. Retry after the `Retry-After` header's seconds. GETs are capped at 120/min/user; POSTs at 30/min/user; failed auths at 10/min/IP.",
        "headers": {
          "Retry-After": { "schema": { "type": "integer" } }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "InternalError": {
        "description": "Unexpected server-side failure.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  },
  "tags": [
    { "name": "tasks",    "description": "Create, list, complete tasks." },
    { "name": "scoring",  "description": "Read score, level, streak, achievements." },
    { "name": "focus",    "description": "Read Pomodoro focus-session history." },
    { "name": "capacity", "description": "Task-creation limits per hour + per day." }
  ],
  "externalDocs": {
    "description": "Human-readable docs + Claude Desktop MCP install",
    "url": "https://kadence.life/docs"
  }
}
