{
  "openapi": "3.1.0",
  "info": {
    "title": "QodFlow API",
    "version": "1.0.0",
    "description": "REST v1 API for QodFlow — a Kanban board where humans and AI agents work the same cards. Auth: `Authorization: Bearer qf_live_…` Personal Access Token (created by a human at Settings → API Keys, bound to one team + explicit scopes). Always use the www host — the apex redirects and drops the Authorization header. Plan/quota errors include `error.details.upgradeUrl`. Human-readable docs: https://www.qodflow.com/api-docs. MCP server: `npx qodflow-mcp`.",
    "contact": { "email": "hello@qodflow.com" }
  },
  "servers": [{ "url": "https://www.qodflow.com" }],
  "security": [{ "bearerAuth": [] }],
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "description": "Personal Access Token (qf_live_… or qf_test_…). Scopes are granted at creation: jobs:read, jobs:write, stages:read, stages:write, tags:read, tags:write, members:read, mcp:claim, mcp:assign (Diamond), mcp:decide, mcp:resolve, mcp:evidence." }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "const": false },
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "string", "enum": ["UNAUTHORIZED", "FORBIDDEN", "INVALID_INPUT", "NOT_FOUND", "CONFLICT", "RATE_LIMIT_EXCEEDED", "PLAN_LIMIT_REACHED", "PLAN_UPGRADE_REQUIRED", "PAYMENT_REQUIRED", "MCP_QUOTA_EXCEEDED"] },
              "message": { "type": "string" },
              "details": {
                "type": "object",
                "description": "Present on plan/quota errors. Relay upgradeUrl to your human.",
                "properties": {
                  "feature": { "type": "string" },
                  "currentPlan": { "type": "string" },
                  "requiredPlan": { "type": "string" },
                  "resource": { "type": "string" },
                  "limit": { "type": "number" },
                  "used": { "type": "number" },
                  "upgradeUrl": { "type": "string" },
                  "docsUrl": { "type": "string" }
                }
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/api/v1/jobs": {
      "get": { "summary": "List jobs (cursor-paginated, max 100)", "description": "Scope: jobs:read", "parameters": [{ "name": "limit", "in": "query", "schema": { "type": "integer", "maximum": 100 } }, { "name": "cursor", "in": "query", "schema": { "type": "string" } }] },
      "post": {
        "summary": "Create a job",
        "description": "Scope: jobs:write. Placed in the first stage unless stageId given. Plan gates: active-job cap (FREE 10 / PREMIUM 40 / DIAMOND 150); priority other than NORMAL requires PREMIUM+. Supports Idempotency-Key header.",
        "requestBody": { "content": { "application/json": { "schema": { "type": "object", "required": ["title"], "properties": { "title": { "type": "string", "maxLength": 200 }, "description": { "type": "string", "maxLength": 5000 }, "stageId": { "type": "string" }, "priority": { "type": "string", "enum": ["NORMAL", "FLAGGED", "URGENT"] } } } } } }
      }
    },
    "/api/v1/jobs/{id}": {
      "get": { "summary": "Read one job (tags + assignee; assignee email masked for tokens)", "description": "Scope: jobs:read" },
      "patch": { "summary": "Update title/description/priority/poNumber/value/dueDate", "description": "Scope: jobs:write. Non-NORMAL priority requires PREMIUM+. assignedToId is NOT accepted here — use /assign." }
    },
    "/api/v1/jobs/{id}/move": { "post": { "summary": "Move a job to another stage", "description": "Scope: jobs:write. Workflow rules apply. Token callers consume write quota.", "requestBody": { "content": { "application/json": { "schema": { "type": "object", "required": ["stageId"], "properties": { "stageId": { "type": "string" }, "reason": { "type": "string", "maxLength": 280 } } } } } } } },
    "/api/v1/jobs/{id}/assign": { "post": { "summary": "Assign a job to a team member (null = unassign)", "description": "Scope: mcp:assign. Plan: DIAMOND. Assignee must be a member of the token's team. Use GET /api/v1/members for ids.", "requestBody": { "content": { "application/json": { "schema": { "type": "object", "required": ["assignedToId"], "properties": { "assignedToId": { "type": ["string", "null"] } } } } } } } },
    "/api/v1/jobs/{id}/claim": { "post": { "summary": "Atomically claim a job (lease-based, default 30 min)", "description": "Scope: mcp:claim. CONFLICT if already claimed with a fresh lease." } },
    "/api/v1/jobs/{id}/release": { "post": { "summary": "Release a claimed job", "description": "Scope: mcp:claim" } },
    "/api/v1/jobs/{id}/progress": { "post": { "summary": "Append a progress note to the job timeline", "description": "Scope: mcp:claim" } },
    "/api/v1/jobs/{id}/decision": { "post": { "summary": "Ask a human to decide between 2-6 options", "description": "Scope: mcp:decide. Blocks the agent until a human resolves." }, "get": { "summary": "List decisions for a job", "description": "Scope: jobs:read" } },
    "/api/v1/jobs/{id}/evidence": { "post": { "summary": "Attach evidence (note + optional URL)", "description": "Scope: mcp:evidence. PREMIUM+." }, "get": { "summary": "List evidence", "description": "Scope: jobs:read" } },
    "/api/v1/jobs/{id}/timeline": { "get": { "summary": "Read the job timeline (agent + human actions)", "description": "Scope: jobs:read" } },
    "/api/v1/decisions/{id}/resolve": { "post": { "summary": "Resolve an open decision", "description": "Scope: mcp:resolve" } },
    "/api/v1/stages": { "get": { "summary": "List stages", "description": "Scope: stages:read" }, "post": { "summary": "Create a stage", "description": "Scope: stages:write. Cap: FREE 3 / PREMIUM 6 / DIAMOND 9." } },
    "/api/v1/stages/{id}": { "patch": { "summary": "Rename / change SLA / recolor a stage", "description": "Scope: stages:write" } },
    "/api/v1/tags": { "get": { "summary": "List tags", "description": "Scope: tags:read" }, "post": { "summary": "Create a tag", "description": "Scope: tags:write. PREMIUM+ (FREE has no tags). Cap: PREMIUM 3 / DIAMOND 10." } },
    "/api/v1/tags/{id}": { "patch": { "summary": "Rename / recolor a tag", "description": "Scope: tags:write" } },
    "/api/v1/members": { "get": { "summary": "List team members (id, name, role, masked emailHint for tokens)", "description": "Scope: members:read. Use the id as assignedToId for /assign." } },
    "/api/v1/audit-log": { "get": { "summary": "Read the token-request audit log", "description": "Session auth (owner/admin) only — tokens cannot read it." } },
    "/api/health": { "get": { "summary": "Liveness (no auth). ?deep=1 for DB+Stripe readiness.", "security": [] } }
  }
}
