{
  "openapi": "3.1.0",
  "info": {
    "title": "Pathmode API",
    "version": "1.0.0",
    "description": "REST API for the Pathmode Intent Engineering platform. Connect AI agents and tooling to your Intent Layer — intents, evidence, workspace strategy, and verification. Authenticate with a pm_live_ API key as a Bearer token. See https://pathmode.io/developers for the full guide.",
    "contact": {
      "name": "Pathmode",
      "url": "https://pathmode.io/developers"
    }
  },
  "servers": [
    {
      "url": "https://pathmode.io",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "intents",
      "description": "Create, read, update, and transition intent specs through their lifecycle, plus AI grading and agent-ready prompts."
    },
    {
      "name": "notes",
      "description": "Record and read the technical decisions made while implementing an intent."
    },
    {
      "name": "outcomes",
      "description": "Track measured values against an intent’s outcomes after shipping. Session-authenticated (dashboard) endpoints."
    },
    {
      "name": "evidence",
      "description": "Create and search evidence items, and link them to intents for traceability from user problem to shipped solution."
    },
    {
      "name": "analyze-codebase",
      "description": "Stream an AI analysis of the bound repository to synthesize Implementation Context for an intent. Beta, session-authenticated."
    },
    {
      "name": "workspace",
      "description": "Read workspace strategy, products, and the constitution rules enforced during verification."
    },
    {
      "name": "export",
      "description": "Export workspace and intent context as CLAUDE.md, AGENTS.md, .cursorrules, an intent spec, or an outcome rubric."
    },
    {
      "name": "api-keys",
      "description": "List, create, and revoke API keys. Session-authenticated (dashboard) endpoints — not callable with a pm_live_ key."
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key as a Bearer token, prefixed with pm_live_. Created in Settings → API Keys or via POST /api-keys."
      },
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "sb-access-token",
        "description": "Logged-in Supabase session cookie. Used by dashboard/admin endpoints that do not accept API keys."
      }
    }
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/api/v1/intents": {
      "get": {
        "operationId": "get_intents",
        "tags": [
          "intents"
        ],
        "summary": "List all intents for the authenticated workspace, optionally filtered by status, ordered by updated_at descending.",
        "x-stability": "stable",
        "x-scope": "read",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "none",
        "description": "List path uses INTENT_LIST_SELECT which does NOT join edge cases or evidence: edgeCases and evidenceIds always come back as empty arrays [] here (populate them via the detail endpoint GET /api/v1/intents/:id). relations IS populated on the list path. Rows that fail apiIntentSchema.parse are silently skipped (logged), so count may be less than the raw row count. Default order: updated_at descending. No rate limiting on this GET.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Optional exact-match filter on intent status (e.g. draft, validated, approved, shipped, verified). Passed straight to an .eq('status', ...) filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required ONLY when authenticating via session cookie (no pm_live_ Bearer). Identifies which workspace the cookie session targets; ignored for API-key auth (key carries its own workspace).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK — returns { intents, count }",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "intents": [
                    {
                      "id": "3f2a9c1e-8b4d-4e2a-9c1e-7a6b5c4d3e2f",
                      "workspaceId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                      "productId": "7d8e9f0a-1b2c-4d3e-9f8a-6b5c4d3e2f1a",
                      "stageName": null,
                      "status": "draft",
                      "version": 1,
                      "title": "Speed up onboarding",
                      "objective": "Cut time-to-first-value for new workspaces under 10 minutes.",
                      "problemSeverity": "high",
                      "outcomes": [
                        {
                          "id": "c4d3e2f1-a0b9-4c8d-7e6f-5a4b3c2d1e0f",
                          "text": "New user reaches first intent in under 10 min",
                          "priority": "must"
                        }
                      ],
                      "healthMetrics": [
                        "activation_rate"
                      ],
                      "scope": null,
                      "strategicAlignment": null,
                      "alignmentNotes": null,
                      "context": {},
                      "constraints": [
                        "No new third-party signup steps"
                      ],
                      "verification": {},
                      "externalLinks": [],
                      "evidenceAnchors": {},
                      "implementationContext": null,
                      "createdAt": "2026-06-01T12:00:00.000Z",
                      "updatedAt": "2026-06-05T09:30:00.000Z",
                      "shippedAt": null,
                      "verifiedAt": null,
                      "edgeCases": [],
                      "evidenceIds": [],
                      "relations": [
                        {
                          "targetId": "9e8d7c6b-5a4f-4e3d-2c1b-0a9f8e7d6c5b",
                          "type": "depends_on"
                        }
                      ]
                    }
                  ],
                  "count": 1
                }
              }
            }
          },
          "400": {
            "description": "Cookie-session auth only: workspace_id query param missing",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid/expired API key, or no Bearer key and no valid session cookie",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Authenticated but lacks read scope (or, for cookie auth, not a member of the workspace)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "DB error fetching intents or unexpected internal error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      },
      "post": {
        "operationId": "post_intents",
        "tags": [
          "intents"
        ],
        "summary": "Create a new intent spec (status defaults to draft) under a product in the authenticated workspace.",
        "x-stability": "stable",
        "x-scope": "write",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "intents 200/h",
        "description": "Rate-limited by the 'intents' bucket (200/h sliding window, keyed user:<userId> or ip:<ip>). Billing-gated: when the workspace intent quota is reached, returns 403 with body { error: 'plan_limit_reached', metric: 'intents', current, limit, plan }. New intent always starts at status 'draft', version 1. Fires a Slack 'intent_status_changed' notification and an 'intent_created' analytics event (both fire-and-forget). Response is built from a re-fetch with relations (INTENT_WITH_RELATIONS_SELECT) so edgeCases are populated; evidenceIds empty on a fresh create.",
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required ONLY for cookie-session auth; identifies the target workspace. Ignored for API-key auth.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "description": "Non-empty after trim. Stored as user_goal."
                  },
                  "objective": {
                    "type": "string",
                    "description": "Non-empty after trim."
                  },
                  "productId": {
                    "type": "string",
                    "description": "Non-empty after trim. Must reference a product in this workspace or returns 404."
                  },
                  "outcomes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Strings or structured objects; strings must be non-empty after trim. Normalized to StructuredOutcome[] at the boundary."
                  },
                  "constraints": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Coerced to trimmed non-empty strings."
                  },
                  "healthMetrics": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Coerced to trimmed non-empty strings."
                  },
                  "edgeCases": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Each field non-empty after trim. Synced via replaceIntentEdgeCasesRpc; on failure the just-created intent is deleted and 500 returned."
                  },
                  "verification": {
                    "type": "object",
                    "description": "Verification plan object; defaults to {} if omitted."
                  },
                  "problemSeverity": {
                    "type": "string",
                    "enum": [
                      "low",
                      "medium",
                      "high",
                      "critical"
                    ],
                    "description": "Optional severity enum."
                  },
                  "scope": {
                    "type": "string",
                    "description": "Optional scope object; may be null."
                  }
                },
                "required": [
                  "title",
                  "objective",
                  "productId"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created — returns the full mapped intent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "3f2a9c1e-8b4d-4e2a-9c1e-7a6b5c4d3e2f",
                  "workspaceId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                  "productId": "7d8e9f0a-1b2c-4d3e-9f8a-6b5c4d3e2f1a",
                  "stageName": null,
                  "status": "draft",
                  "version": 1,
                  "title": "Speed up onboarding",
                  "objective": "Cut time-to-first-value for new workspaces under 10 minutes.",
                  "problemSeverity": "high",
                  "outcomes": [
                    {
                      "id": "c4d3e2f1-a0b9-4c8d-7e6f-5a4b3c2d1e0f",
                      "text": "New user reaches first intent in under 10 min",
                      "priority": "must"
                    }
                  ],
                  "healthMetrics": [
                    "activation_rate"
                  ],
                  "scope": null,
                  "strategicAlignment": null,
                  "alignmentNotes": null,
                  "context": {},
                  "constraints": [
                    "No new third-party signup steps"
                  ],
                  "verification": {},
                  "externalLinks": [],
                  "evidenceAnchors": {},
                  "implementationContext": null,
                  "createdAt": "2026-06-07T10:15:00.000Z",
                  "updatedAt": "2026-06-07T10:15:00.000Z",
                  "shippedAt": null,
                  "verifiedAt": null,
                  "edgeCases": [
                    {
                      "id": "b2c1d0e9-f8a7-4b6c-5d4e-3f2a1b0c9d8e",
                      "scenario": "User has no products yet",
                      "expectedBehavior": "Show empty-state prompt"
                    }
                  ],
                  "evidenceIds": [],
                  "relations": []
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body (zod) — returns { error, details } with flattened zod errors; OR cookie-session auth missing workspace_id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid/expired API key, or no Bearer key and no valid session cookie",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Lacks write scope; OR plan_limit_reached (billing intents quota exceeded); OR cookie auth not a member of the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Product not found in this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (intents bucket, 200/h) — { error } with Retry-After header",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Insert error, edge-case sync failure (intent rolled back/deleted), or unexpected internal error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/api/v1/intents/{id}": {
      "get": {
        "operationId": "get_intents_id",
        "tags": [
          "intents"
        ],
        "summary": "Get a single intent with edge cases, linked evidence ids, and relations.",
        "x-stability": "stable",
        "x-scope": "read",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "none",
        "description": "Detail path uses INTENT_WITH_RELATIONS_SELECT, so edgeCases, evidenceIds (from intent_spec_evidence.friction_id), and relations are all fully populated here (unlike the list endpoint). Scoped to the authenticated workspace via .eq('workspace_id', ...). No rate limiting on this GET.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id path parameter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required ONLY for cookie-session auth; identifies the target workspace. Ignored for API-key auth.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK — returns the full mapped intent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "3f2a9c1e-8b4d-4e2a-9c1e-7a6b5c4d3e2f",
                  "workspaceId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                  "productId": "7d8e9f0a-1b2c-4d3e-9f8a-6b5c4d3e2f1a",
                  "stageName": null,
                  "status": "shipped",
                  "version": 3,
                  "title": "Speed up onboarding",
                  "objective": "Cut time-to-first-value for new workspaces under 10 minutes.",
                  "problemSeverity": "high",
                  "outcomes": [
                    {
                      "id": "c4d3e2f1-a0b9-4c8d-7e6f-5a4b3c2d1e0f",
                      "text": "New user reaches first intent in under 10 min",
                      "priority": "must"
                    }
                  ],
                  "healthMetrics": [
                    "activation_rate"
                  ],
                  "scope": {
                    "inScope": [
                      "Signup flow"
                    ],
                    "outOfScope": [
                      "Billing"
                    ]
                  },
                  "strategicAlignment": null,
                  "alignmentNotes": null,
                  "context": {},
                  "constraints": [
                    "No new third-party signup steps"
                  ],
                  "verification": {
                    "manualChecks": [
                      "New user can create an intent in under 10 min"
                    ]
                  },
                  "externalLinks": [],
                  "evidenceAnchors": {
                    "outcome:0": [
                      "d3e2f1a0-b9c8-4d7e-6f5a-4b3c2d1e0f9a"
                    ]
                  },
                  "implementationContext": null,
                  "createdAt": "2026-06-01T12:00:00.000Z",
                  "updatedAt": "2026-06-06T14:20:00.000Z",
                  "shippedAt": "2026-06-06T14:20:00.000Z",
                  "verifiedAt": null,
                  "edgeCases": [
                    {
                      "id": "b2c1d0e9-f8a7-4b6c-5d4e-3f2a1b0c9d8e",
                      "scenario": "User has no products yet",
                      "expectedBehavior": "Show empty-state prompt"
                    }
                  ],
                  "evidenceIds": [
                    "d3e2f1a0-b9c8-4d7e-6f5a-4b3c2d1e0f9a"
                  ],
                  "relations": [
                    {
                      "targetId": "9e8d7c6b-5a4f-4e3d-2c1b-0a9f8e7d6c5b",
                      "type": "depends_on"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Cookie-session auth only: workspace_id query param missing",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid/expired API key, or no Bearer key and no valid session cookie",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Lacks read scope; OR cookie auth not a member of the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Intent not found (Supabase PGRST116) within this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "DB error or unexpected internal error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      },
      "patch": {
        "operationId": "patch_intents_id",
        "tags": [
          "intents"
        ],
        "summary": "Update intent content fields (title, objective, outcomes, constraints, etc.). Does NOT change status — use the /status endpoint.",
        "x-stability": "stable",
        "x-scope": "write",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "none",
        "description": "Content-only update; status/shipped_at/verified_at are NOT touched here. Always sets updated_at. Requires at least one updatable field (otherwise 400). When edgeCases is provided, the prior intent state is snapshotted first so a failed edge-case sync can roll back. Response is re-fetched with INTENT_WITH_RELATIONS_SELECT so edgeCases/evidenceIds/relations are fully populated. No rate limiting on this PATCH.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id path parameter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required ONLY for cookie-session auth; identifies the target workspace. Ignored for API-key auth.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "description": "Non-empty after trim if provided. Stored as user_goal."
                  },
                  "objective": {
                    "type": "string",
                    "description": "Non-empty after trim if provided."
                  },
                  "outcomes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Replaces both outcomes_v2 and the flat outcomes text list."
                  },
                  "constraints": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Replaces constraints (coerced to trimmed non-empty strings)."
                  },
                  "healthMetrics": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Replaces health_metrics (coerced to trimmed non-empty strings)."
                  },
                  "edgeCases": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Replace-all semantics via replaceIntentEdgeCasesRpc; on failure the intent is rolled back to its prior snapshot and 500 returned."
                  },
                  "verification": {
                    "type": "object",
                    "description": "Replaces verification plan."
                  },
                  "problemSeverity": {
                    "type": "string",
                    "enum": [
                      "low",
                      "medium",
                      "high",
                      "critical"
                    ],
                    "description": "Nullable severity enum."
                  },
                  "evidenceAnchors": {
                    "type": "string",
                    "description": "Map of anchor key (e.g. 'objective', 'outcome:0', 'edgeCase:<uuid>') to evidence id arrays."
                  },
                  "scope": {
                    "type": "string",
                    "description": "Nullable scope object."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — returns the full updated intent (re-fetched with relations)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "3f2a9c1e-8b4d-4e2a-9c1e-7a6b5c4d3e2f",
                  "workspaceId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                  "productId": "7d8e9f0a-1b2c-4d3e-9f8a-6b5c4d3e2f1a",
                  "stageName": null,
                  "status": "draft",
                  "version": 1,
                  "title": "Speed up onboarding (v2)",
                  "objective": "Cut time-to-first-value for new workspaces under 8 minutes.",
                  "problemSeverity": "critical",
                  "outcomes": [
                    {
                      "id": "c4d3e2f1-a0b9-4c8d-7e6f-5a4b3c2d1e0f",
                      "text": "New user reaches first intent in under 8 min",
                      "priority": "must"
                    }
                  ],
                  "healthMetrics": [
                    "activation_rate",
                    "time_to_first_intent"
                  ],
                  "scope": null,
                  "strategicAlignment": null,
                  "alignmentNotes": null,
                  "context": {},
                  "constraints": [
                    "No new third-party signup steps"
                  ],
                  "verification": {
                    "manualChecks": [
                      "New user creates an intent under 8 min"
                    ]
                  },
                  "externalLinks": [],
                  "evidenceAnchors": {
                    "objective": [
                      "d3e2f1a0-b9c8-4d7e-6f5a-4b3c2d1e0f9a"
                    ]
                  },
                  "implementationContext": null,
                  "createdAt": "2026-06-01T12:00:00.000Z",
                  "updatedAt": "2026-06-07T11:00:00.000Z",
                  "shippedAt": null,
                  "verifiedAt": null,
                  "edgeCases": [
                    {
                      "id": "b2c1d0e9-f8a7-4b6c-5d4e-3f2a1b0c9d8e",
                      "scenario": "User has no products yet",
                      "expectedBehavior": "Show empty-state prompt"
                    }
                  ],
                  "evidenceIds": [
                    "d3e2f1a0-b9c8-4d7e-6f5a-4b3c2d1e0f9a"
                  ],
                  "relations": []
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body (zod) — { error, details }; OR no updatable field provided ('At least one field must be provided to update'); OR cookie-session auth missing workspace_id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid/expired API key, or no Bearer key and no valid session cookie",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Lacks write scope; OR cookie auth not a member of the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Intent not found (PGRST116) within this workspace (on snapshot or update)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "DB error, edge-case sync failure (intent rolled back to prior snapshot), or unexpected internal error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/api/v1/intents/{id}/status": {
      "patch": {
        "operationId": "patch_intents_id_status",
        "tags": [
          "intents"
        ],
        "summary": "Update an intent's status (draft/validated/approved/shipped/verified), managing shipped_at/verified_at timestamps and returning a verification checklist for shipped/verified transitions.",
        "x-stability": "stable",
        "x-scope": "write",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "none",
        "description": "Base response is always { id, status, updatedAt, shippedAt, verifiedAt }. ONLY when status is 'shipped' or 'verified' does it add verificationChecklist (items {text, category} drawn from outcomes [category 'outcome'], health_metrics ['health_metric'], verification.manualChecks ['manual_check'], and active constitution_rules ['constitution']) and verificationChecklistCount. Fires a Slack 'intent_status_changed' notification (fire-and-forget). No billing gate, no rate limiting on this endpoint.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id path parameter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required ONLY for cookie-session auth; identifies the target workspace. Ignored for API-key auth.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "validated",
                      "approved",
                      "shipped",
                      "verified"
                    ],
                    "description": "Target status. draft/validated/approved clear shipped_at and verified_at; shipped sets shipped_at and clears verified_at; verified sets verified_at."
                  },
                  "verificationResult": {
                    "type": "object",
                    "description": "Optional. When present it is saved to verification_result and a summary is logged as an intent_implementation_notes row (source 'verification_checkpoint')."
                  }
                },
                "required": [
                  "status"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — returns { id, status, updatedAt, shippedAt, verifiedAt }, plus verificationChecklist + verificationChecklistCount for shipped/verified transitions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "3f2a9c1e-8b4d-4e2a-9c1e-7a6b5c4d3e2f",
                  "status": "shipped",
                  "updatedAt": "2026-06-07T12:00:00.000Z",
                  "shippedAt": "2026-06-07T12:00:00.000Z",
                  "verifiedAt": null,
                  "verificationChecklist": [
                    {
                      "text": "New user reaches first intent in under 10 min",
                      "category": "outcome"
                    },
                    {
                      "text": "activation_rate",
                      "category": "health_metric"
                    },
                    {
                      "text": "New user can create an intent in under 10 min",
                      "category": "manual_check"
                    },
                    {
                      "text": "Never ship a flow that adds a new required signup field",
                      "category": "constitution"
                    }
                  ],
                  "verificationChecklistCount": 4
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body (zod) — { error, details }; OR cookie-session auth missing workspace_id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid/expired API key, or no Bearer key and no valid session cookie",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Lacks write scope; OR cookie auth not a member of the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Intent not found (PGRST116) within this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "DB error updating status or unexpected internal error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/api/v1/intents/{id}/notes": {
      "post": {
        "operationId": "post_intents_id_notes",
        "tags": [
          "notes"
        ],
        "summary": "Log an implementation note against an intent.",
        "x-stability": "stable",
        "x-scope": "write",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "general 300/h",
        "description": "Rate-limited by the 'general' bucket (300/h sliding window, keyed user:<userId> or ip:<ip>). The created row's created_by is set to the authenticated userId, but the POST response does NOT echo createdBy (only id, intentId, note, source, createdAt). The GET list response DOES include createdBy.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id path parameter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required ONLY for cookie-session auth; identifies the target workspace. Ignored for API-key auth.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "note": {
                    "type": "string",
                    "description": "Non-empty after trim."
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "mcp",
                      "cli",
                      "github",
                      "manual",
                      "verification_checkpoint",
                      "linear",
                      "jira",
                      "execution_sync"
                    ],
                    "description": "Origin of the note; defaults to 'manual' when omitted."
                  }
                },
                "required": [
                  "note"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created — returns { id, intentId, note, source, createdAt }",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
                  "intentId": "3f2a9c1e-8b4d-4e2a-9c1e-7a6b5c4d3e2f",
                  "note": "Shipped behind flag; rollout to 10% of workspaces.",
                  "source": "cli",
                  "createdAt": "2026-06-07T13:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body (zod) — { error, details }; OR cookie-session auth missing workspace_id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid/expired API key, or no Bearer key and no valid session cookie",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Lacks write scope; OR cookie auth not a member of the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Intent not found within this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (general bucket, 300/h) — { error } with Retry-After header",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "DB error creating note or unexpected internal error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      },
      "get": {
        "operationId": "get_intents_id_notes",
        "tags": [
          "notes"
        ],
        "summary": "List implementation notes for an intent, newest first.",
        "x-stability": "stable",
        "x-scope": "read",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "none",
        "description": "Default order: created_at descending (newest first). Each note includes createdBy (unlike the POST create response, which omits it). No rate limiting on this GET.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id path parameter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required ONLY for cookie-session auth; identifies the target workspace. Ignored for API-key auth.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK — returns { notes, count }",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "notes": [
                    {
                      "id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
                      "intentId": "3f2a9c1e-8b4d-4e2a-9c1e-7a6b5c4d3e2f",
                      "note": "Shipped behind flag; rollout to 10% of workspaces.",
                      "source": "cli",
                      "createdBy": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                      "createdAt": "2026-06-07T13:00:00.000Z"
                    }
                  ],
                  "count": 1
                }
              }
            }
          },
          "400": {
            "description": "Cookie-session auth only: workspace_id query param missing",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid/expired API key, or no Bearer key and no valid session cookie",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Lacks read scope; OR cookie auth not a member of the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Intent not found within this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "DB error fetching notes or unexpected internal error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/api/v1/intents/{id}/findings": {
      "post": {
        "operationId": "post_intents_id_findings",
        "tags": [
          "findings"
        ],
        "summary": "Record a write-back finding against an intent: a contradiction that building the spec surfaced. The inverse of a decision — the next agent that pulls this intent reads open findings as 'the spec may be stale here' so the refinement loop converges instead of repeating a dead premise.",
        "x-stability": "beta",
        "x-scope": "write",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "general 300/h",
        "description": "Rate-limited by the 'general' bucket (300/h sliding window, keyed user:<userId> or ip:<ip>). Findings are stored as a read-modify-write append on the intent's implementation_findings JSONB array (prior findings are preserved); the merged set is sanitized (capped length, malformed legacy rows dropped) before persisting. Every new finding is created with status 'open' and a server-assigned id + recordedAt (epoch ms); target, correction, and source are echoed back only when supplied. openCount is the number of findings still in status 'open' after the write.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id path parameter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required ONLY for cookie-session auth; identifies the target workspace. Ignored for API-key auth.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "assumption": {
                    "type": "string",
                    "description": "What the spec assumed or said before it was built. Non-empty after trim."
                  },
                  "finding": {
                    "type": "string",
                    "description": "What building it actually revealed — the fact that contradicts the assumption. Non-empty after trim."
                  },
                  "target": {
                    "type": "string",
                    "description": "Which part of the spec this contradicts: 'objective', 'outcome:<id>', 'constraint:<index>', 'edgeCase:<id>', 'check:<id>' (a verification check — recording this flips that check to failing), or plain prose. Non-empty after trim when provided."
                  },
                  "correction": {
                    "type": "string",
                    "description": "Proposed correction to the intent, if any. Non-empty after trim when provided."
                  },
                  "source": {
                    "type": "string",
                    "description": "Provenance: where this came from, e.g. 'claude-code @ owner/repo'. Non-empty after trim when provided."
                  }
                },
                "required": [
                  "assumption",
                  "finding"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created — returns { intentId, finding, openCount }",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "intentId": "3f2a9c1e-8b4d-4e2a-9c1e-7a6b5c4d3e2f",
                  "finding": {
                    "id": "b7c8d9e0-1f2a-4b3c-8d4e-5f6a7b8c9d0e",
                    "assumption": "Onboarding completes in a single page load.",
                    "finding": "The OAuth callback forces a full reload, so step state is lost.",
                    "target": "outcome:c4d3e2f1-a0b9-4c8d-7e6f-5a4b3c2d1e0f",
                    "correction": "Persist step state in the session before the OAuth redirect.",
                    "source": "claude-code @ acme/web",
                    "status": "open",
                    "recordedAt": 1749300000000
                  },
                  "openCount": 1
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body (zod) — { error, details }; OR cookie-session auth missing workspace_id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid/expired API key, or no Bearer key and no valid session cookie",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Lacks write scope; OR cookie auth not a member of the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Intent not found within this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (general bucket, 300/h) — { error } with Retry-After header",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "DB error recording the finding or unexpected internal error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      },
      "get": {
        "operationId": "get_intents_id_findings",
        "tags": [
          "findings"
        ],
        "summary": "List the write-back findings for an intent. Optionally filter by status.",
        "x-stability": "beta",
        "x-scope": "read",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "none",
        "description": "Findings come from the intent's implementation_findings JSONB array, sanitized on read (capped length, malformed legacy rows dropped). The optional status query param filters in-memory after the sanitize pass; reconciled findings include a reconciledAt timestamp. count reflects the returned (post-filter) set. No rate limiting on this GET.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id path parameter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Optional exact-match filter on finding status. Omit to return findings of every status.",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "reconciled",
                "dismissed"
              ]
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required ONLY for cookie-session auth; identifies the target workspace. Ignored for API-key auth.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK — returns { intentId, findings, count }",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "intentId": "3f2a9c1e-8b4d-4e2a-9c1e-7a6b5c4d3e2f",
                  "findings": [
                    {
                      "id": "b7c8d9e0-1f2a-4b3c-8d4e-5f6a7b8c9d0e",
                      "assumption": "Onboarding completes in a single page load.",
                      "finding": "The OAuth callback forces a full reload, so step state is lost.",
                      "target": "outcome:c4d3e2f1-a0b9-4c8d-7e6f-5a4b3c2d1e0f",
                      "correction": "Persist step state in the session before the OAuth redirect.",
                      "source": "claude-code @ acme/web",
                      "status": "open",
                      "recordedAt": 1749300000000
                    }
                  ],
                  "count": 1
                }
              }
            }
          },
          "401": {
            "description": "Invalid/expired API key, or no Bearer key and no valid session cookie",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Lacks read scope; OR cookie auth not a member of the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Intent not found within this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "DB error fetching findings or unexpected internal error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/api/v1/intents/{id}/evidence": {
      "post": {
        "operationId": "post_intents_id_evidence",
        "tags": [
          "evidence"
        ],
        "summary": "Link and/or unlink evidence items to/from an intent. Dual-writes the intent_spec_evidence join table and the linked_intent_ids array on evidence_items.",
        "x-stability": "stable",
        "x-scope": "write",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "general 300/h",
        "description": "evidenceIds in the response is re-fetched from intent_spec_evidence after applying link/unlink, so it reflects ALL currently-linked evidence (not just what was changed). link/unlink counts only increment for IDs that exist in the workspace. Cookie-session auth requires ?workspace_id= and a workspace membership.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id path parameter.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "link": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Evidence item UUIDs to link. Validated against the workspace; unknown IDs silently skipped. At least one of link/unlink must be a non-empty array."
                  },
                  "unlink": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Evidence item UUIDs to unlink. Validated against the workspace; unknown IDs silently skipped. At least one of link/unlink must be a non-empty array."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Link/unlink applied; returns counts and the intent's full current evidence ID list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "intentId": "3f2a9c1e-8b4d-4e2a-9c1e-7a6b5c4d3e2f",
                  "evidenceIds": [
                    "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
                    "b2c3d4e5-6f7a-4b8c-9d0e-1f2a3b4c5d6e"
                  ],
                  "linked": 2,
                  "unlinked": 0
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body (zod) — e.g. neither link nor unlink is a non-empty array",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid API key or session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Authenticated but lacks write scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Intent not found in this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "general rate limit exceeded (300/h per user)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/api/v1/intents/{id}/verify": {
      "post": {
        "operationId": "post_intents_id_verify",
        "tags": [
          "intents"
        ],
        "summary": "AI-grade an implementation against the intent spec. Evaluates each outcome, constraint, constitution rule, and edge case via Gemini structured output; stores the result on the intent and logs a verification_checkpoint implementation note.",
        "x-stability": "stable",
        "x-scope": "write",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "none",
        "description": "category enum is outcome|constraint|constitution|edge_case; status enum is pass|fail|unclear. pass is true only when zero items fail; unclear counts as half credit toward score (0-100). Side effects: writes verification_result + updated_at on intent_specs, and inserts an intent_implementation_notes row with source 'verification_checkpoint'. No rate limiter on this route despite being AI-backed.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id path parameter.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "summary": {
                    "type": "string",
                    "description": "Implementation summary the AI grades against the spec. Trimmed, must be non-empty."
                  },
                  "codeChanges": {
                    "type": "string",
                    "description": "Optional code-changes blob; first 15000 chars are included in the AI prompt."
                  }
                },
                "required": [
                  "summary"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification graded; returns the VerificationResult object",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "pass": false,
                  "score": 72,
                  "results": [
                    {
                      "item": "Users can reset password from the login screen",
                      "category": "outcome",
                      "status": "pass",
                      "reasoning": "The summary describes a reset link added to the login screen wired to the existing reset flow."
                    },
                    {
                      "item": "No PII is written to logs",
                      "category": "constitution",
                      "status": "unclear",
                      "reasoning": "The summary does not mention logging behavior, so compliance cannot be determined."
                    }
                  ],
                  "summary": "The core reset flow is implemented and matches the primary outcome. Logging and one edge case are unaddressed, lowering confidence."
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body (zod) — e.g. missing/empty summary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid API key or session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Authenticated but lacks write scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Intent not found in this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "AI verification service temporarily unavailable (Gemini API error / 429 / 503)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/api/v1/intents/{id}/prompt": {
      "get": {
        "operationId": "get_intents_id_prompt",
        "tags": [
          "intents"
        ],
        "summary": "Generate the formatted agent prompt for an intent. Assembles spec + workspace strategy + product + constitution rules + linked evidence and returns both a markdown/string prompt and a structured JSON payload.",
        "x-stability": "stable",
        "x-scope": "read",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "none",
        "description": "json is an opaque object whose exact shape is produced by generateAgentPrompt (varies by agent_type/format) — fields above are illustrative. The 422 body is { error, readiness: { isSubstantive, isReadyForExecution, score, blockers[], warnings[] }, suggestion }. implementation_context is stripped from the export unless the workspace flag implementation_context_enabled is on. Constitution rules are prefixed [BLOCKING]/[advisory] by enforcement level. Date fields (createdAt/updatedAt/shippedAt/verifiedAt) are converted to epoch ms internally before prompt generation.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id path parameter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "agent_type",
            "in": "query",
            "required": false,
            "description": "Target agent: cursor|windsurf|claude-code|codex|generic. Default 'claude-code'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "mode",
            "in": "query",
            "required": false,
            "description": "draft|execute. Default 'execute'. In 'execute' mode the intent must pass readiness (meaningful title + objective + >=1 outcome) or 422.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "json|markdown. Default 'markdown'. Shapes the prompt string.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required only when authenticating via session cookie (ignored for API-key auth).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns { prompt, json }",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "prompt": "# Intent: Add password reset\n\n## Objective\nLet users recover access without contacting support...",
                  "json": {
                    "title": "Add password reset",
                    "objective": "Let users recover access without contacting support.",
                    "outcomes": [
                      "Users can reset password from the login screen"
                    ],
                    "constraints": [],
                    "edgeCases": [],
                    "constitutionRules": [
                      "[BLOCKING] No PII in logs"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid API key or session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Authenticated but lacks read scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Intent not found in this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "422": {
            "description": "mode=execute but intent is not ready for execution; body includes { error, readiness, suggestion }",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/api/v1/intents/{id}/share": {
      "post": {
        "operationId": "post_intents_id_share",
        "tags": [
          "intents"
        ],
        "summary": "Generate (or return existing) a share token for the intent, used to build public review links. Idempotent: returns the existing token if one is already set.",
        "x-stability": "stable",
        "x-scope": "write",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "none",
        "description": "token is a raw crypto.randomUUID() (v4 UUID, no prefix), generated by reviewService.generateShareToken and persisted to intent_specs.share_token. No 201 — created and pre-existing tokens both return 200. No rate limiter on this route.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id path parameter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required only when authenticating via session cookie (ignored for API-key auth).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns { token } (existing or newly generated)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "token": "7a6b5c4d-3e2f-4a1b-9c8d-2e1f0a9b8c7d"
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid API key or session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Authenticated but lacks write scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Intent not found in this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Failed to generate share link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      },
      "delete": {
        "operationId": "delete_intents_id_share",
        "tags": [
          "intents"
        ],
        "summary": "Revoke the intent's share token, invalidating all existing review links (sets intent_specs.share_token to null).",
        "x-stability": "stable",
        "x-scope": "write",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "none",
        "description": "Revocation is unconditional once ownership is verified — succeeds even if no token currently exists. No rate limiter on this route.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id path parameter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required only when authenticating via session cookie (ignored for API-key auth).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Share token revoked; returns { success: true }",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "success": true
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid API key or session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Authenticated but lacks write scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Intent not found in this workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Failed to revoke share link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/api/v1/intents/{id}/outcomes": {
      "get": {
        "operationId": "get_intents_id_outcomes",
        "tags": [
          "outcomes"
        ],
        "summary": "Fetch all outcome measurements recorded for an intent.",
        "x-stability": "stable",
        "x-scope": "none",
        "x-auth": "cookie-session-only",
        "x-rateLimit": "none",
        "description": "Cookie-session only — uses createServerClient + auth.getUser(); does NOT inspect the Authorization header and rejects pm_live_ API keys. No workspace_id param and no explicit workspace-membership/scope check on this route (relies on Supabase RLS for the session user). Default sort: outcome_index ascending, then measured_at descending. baselineValue/unit may be null; met may be null.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id path parameter.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns { measurements } ordered by outcome_index asc, then measured_at desc",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "measurements": [
                    {
                      "id": "9c1e7a6b-5c4d-4e2a-8b4d-3f2a9c1e7a6b",
                      "intentId": "3f2a9c1e-8b4d-4e2a-9c1e-7a6b5c4d3e2f",
                      "outcomeIndex": 0,
                      "outcomeText": "Password reset completion rate",
                      "baselineValue": "42%",
                      "actualValue": "68%",
                      "unit": "%",
                      "met": true,
                      "source": "manual",
                      "recordedBy": "d3e2f1a0-9b8c-4d7e-8f6a-5b4c3d2e1f0a",
                      "measuredAt": "2026-06-07T12:34:56.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No valid Supabase session cookie (Unauthorized)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Failed to fetch measurements / internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      },
      "post": {
        "operationId": "post_intents_id_outcomes",
        "tags": [
          "outcomes"
        ],
        "summary": "Record a new outcome measurement for an intent.",
        "x-stability": "stable",
        "x-scope": "none",
        "x-auth": "cookie-session-only",
        "x-rateLimit": "none",
        "description": "Cookie-session only — rejects pm_live_ API keys; no Authorization-header inspection. Manual validation (not zod): outcomeIndex rejected only when undefined/null so 0 passes; outcomeText/actualValue must be non-empty strings. recorded_by is set to the session user id; source defaults to 'manual'. workspace_id is derived from the intent row (no membership check beyond RLS). Inserts into intent_outcome_measurements.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id path parameter.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "outcomeIndex": {
                    "type": "number",
                    "description": "Index of the outcome being measured. Required (rejected only when undefined/null, so 0 is valid)."
                  },
                  "outcomeText": {
                    "type": "string",
                    "description": "Outcome text snapshot. Required non-empty string; trimmed before insert."
                  },
                  "actualValue": {
                    "type": "string",
                    "description": "Measured value. Required non-empty string; trimmed before insert."
                  },
                  "baselineValue": {
                    "type": "string",
                    "description": "Baseline value before the change. Stored null if falsy."
                  },
                  "unit": {
                    "type": "string",
                    "description": "Unit of measure. Stored null if falsy."
                  },
                  "met": {
                    "type": "boolean",
                    "description": "Whether the outcome target was met. Stored null if not provided (uses ?? null)."
                  },
                  "source": {
                    "type": "string",
                    "description": "Provenance label. Defaults to 'manual' when falsy."
                  }
                },
                "required": [
                  "outcomeIndex",
                  "outcomeText",
                  "actualValue"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Measurement recorded; returns the created measurement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "9c1e7a6b-5c4d-4e2a-8b4d-3f2a9c1e7a6b",
                  "intentId": "3f2a9c1e-8b4d-4e2a-9c1e-7a6b5c4d3e2f",
                  "outcomeIndex": 0,
                  "outcomeText": "Password reset completion rate",
                  "baselineValue": "42%",
                  "actualValue": "68%",
                  "unit": "%",
                  "met": true,
                  "source": "manual",
                  "recordedBy": "d3e2f1a0-9b8c-4d7e-8f6a-5b4c3d2e1f0a",
                  "measuredAt": "2026-06-07T12:34:56.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Missing required field — outcomeIndex (undefined/null), outcomeText, or actualValue",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No valid Supabase session cookie (Unauthorized)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Intent not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Failed to record measurement / internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/api/v1/intents/{id}/analyze-codebase": {
      "post": {
        "operationId": "post_intents_id_analyze-codebase",
        "tags": [
          "analyze-codebase"
        ],
        "summary": "Spin up a Vercel Sandbox, shallow-clone the bound GitHub repo, gather a spec-targeted codebase slice, and synthesize an Implementation Context via Gemini. Streams progress as NDJSON. Sole writer of intent_specs.implementation_context.",
        "x-stability": "beta",
        "x-scope": "admin",
        "x-auth": "cookie-session-only",
        "x-rateLimit": "analyzeCodebase 30/24h (per workspace)",
        "description": "Cookie-session only — uses createClient() from @/lib/supabase/server + auth.getUser(); does NOT accept pm_live_ API keys. Role gate requires owner|admin|editor membership (treated as admin scope). Double-gated: paid plan (pro/team — free is 403) AND workspace flag implementation_context_enabled. Response is streamed NDJSON, Content-Type 'application/x-ndjson; charset=utf-8', Cache-Control no-store. Each line is a ProgressEvent { phase, message, partialResult?, updatedSpec?, error? }; errors after the 200 surface as a {phase:'error'} line, not an HTTP error. Rate limit keyed by workspace:<id>, NOT user; example products (is_example) are exempt from the cap. analyzedSpecVersion is a 16-char sha256 of canonical spec content. updatedSpec is the full mapIntentRowToApiIntent shape.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "id path parameter.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Streaming NDJSON of ProgressEvent lines (cloning, analyzing, writing, done; or a final error line). HTTP 200 returned immediately before work completes.",
            "content": {
              "application/x-ndjson": {
                "schema": {
                  "type": "string"
                },
                "example": "{\"phase\":\"cloning\",\"message\":\"Cloning acme/payroll-web (default branch)\"}\n{\"phase\":\"analyzing\",\"message\":\"Synthesizing implementation context from 37 files\"}\n{\"phase\":\"writing\",\"message\":\"Persisting implementation context\",\"partialResult\":{\"relevantAreas\":[{\"path\":\"src/auth/reset.ts\",\"reason\":\"Holds the existing reset-token flow this intent extends\"}],\"currentBehavior\":\"Reset is admin-only via a CLI script.\",\"liftEstimate\":\"medium\",\"risks\":[\"Token reuse if expiry is not enforced\"],\"verificationSuggestions\":[\"Add an e2e test for the reset link flow\"],\"analyzedAt\":\"2026-06-07T12:34:56.000Z\",\"analyzedRepo\":{\"provider\":\"github\",\"owner\":\"acme\",\"name\":\"payroll-web\",\"ref\":\"main\"},\"analyzedSpecVersion\":\"a1b2c3d4e5f6a7b8\"}}\n{\"phase\":\"done\",\"message\":\"Analysis complete\",\"updatedSpec\":{\"id\":\"3f2a9c1e-8b4d-4e2a-9c1e-7a6b5c4d3e2f\",\"workspaceId\":\"7a6b5c4d-3e2f-4a1b-9c8d-2e1f0a9b8c7d\",\"productId\":\"b2c3d4e5-6f7a-4b8c-9d0e-1f2a3b4c5d6e\",\"status\":\"draft\",\"version\":3,\"title\":\"Add password reset\",\"objective\":\"Let users recover access without contacting support.\",\"outcomes\":[{\"id\":\"o1\",\"text\":\"Users can reset password from the login screen\"}],\"healthMetrics\":[],\"constraints\":[],\"verification\":{},\"externalLinks\":[],\"evidenceAnchors\":{},\"implementationContext\":{\"relevantAreas\":[{\"path\":\"src/auth/reset.ts\",\"reason\":\"Holds the existing reset-token flow\"}],\"currentBehavior\":\"Reset is admin-only via a CLI script.\",\"liftEstimate\":\"medium\",\"risks\":[\"Token reuse if expiry is not enforced\"],\"verificationSuggestions\":[\"Add an e2e test for the reset link flow\"],\"analyzedAt\":\"2026-06-07T12:34:56.000Z\",\"analyzedRepo\":{\"provider\":\"github\",\"owner\":\"acme\",\"name\":\"payroll-web\",\"ref\":\"main\"},\"analyzedSpecVersion\":\"a1b2c3d4e5f6a7b8\"},\"createdAt\":\"2026-06-01T09:00:00.000Z\",\"updatedAt\":\"2026-06-07T12:34:56.000Z\",\"shippedAt\":null,\"verifiedAt\":null,\"edgeCases\":[],\"evidenceIds\":[],\"relations\":[]}}"
              }
            }
          },
          "400": {
            "description": "No GitHub repo/binding configured, missing installation, or no default repo for the binding",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No valid Supabase session (Authentication required)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Not owner/admin/editor of the workspace; OR plan is free; OR implementation_context_enabled flag is off",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Intent not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Daily analysis limit reached for this workspace (30/24h); body { error, retryAfterSeconds } + Retry-After header",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Service not configured (Supabase service role key / URL missing)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/api/v1/evidence": {
      "get": {
        "operationId": "get_evidence",
        "tags": [
          "evidence"
        ],
        "summary": "Query active evidence items for the authenticated workspace, with filtering and pagination.",
        "x-stability": "stable",
        "x-scope": "read",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "none",
        "description": "List path. Filters on status='active' only. Default sort created_at DESC. 'count' is the exact total matching the filters (count: 'exact'), falling back to the returned page length if null. Every field is always present in the object (nullable columns serialize as null; tags/linkedIntentIds coalesce to []).",
        "parameters": [
          {
            "name": "productId",
            "in": "query",
            "required": false,
            "description": "Filter to a single product.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Filter by evidence type: friction | quote | observation | metric | request. 400 if invalid.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "severity",
            "in": "query",
            "required": false,
            "description": "Filter by severity: low | medium | high | critical. 400 if invalid.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Case-insensitive ILIKE substring match on content.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tags",
            "in": "query",
            "required": false,
            "description": "Comma-separated tag list; matches rows whose tags array contains ALL given tags.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, default 50, capped at 200.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Pagination offset, default 0.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required ONLY for cookie-session auth (no Bearer key); ignored for API-key auth. Without it, cookie auth returns 400.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Evidence list returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "evidence": [
                    {
                      "id": "3f2a9c1e-8b4d-4e2a-9c1e-7a6b5c4d3e2f",
                      "productId": "b1c2d3e4-f5a6-4789-b0c1-2d3e4f5a6b7c",
                      "workspaceId": "7a6b5c4d-3e2f-4a1b-9c8d-0e1f2a3b4c5d",
                      "type": "friction",
                      "content": "Users abandon checkout when the address form rejects valid postal codes.",
                      "source": "Support ticket #4821",
                      "sourceUrl": "https://support.example.com/t/4821",
                      "severity": "high",
                      "sentiment": "negative",
                      "tags": [
                        "checkout",
                        "forms"
                      ],
                      "stage": "Checkout",
                      "linkedIntentIds": [
                        "c9d8e7f6-a5b4-4321-9876-543210fedcba"
                      ],
                      "createdAt": "2026-06-01T10:15:30.000Z",
                      "updatedAt": "2026-06-02T08:00:00.000Z"
                    }
                  ],
                  "count": 1,
                  "limit": 50,
                  "offset": 0
                }
              }
            }
          },
          "400": {
            "description": "Invalid type/severity enum value, or (cookie auth) missing workspace_id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid API key or no valid session cookie.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "API key lacks read scope; or (cookie auth) not a member of the workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "DB or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      },
      "post": {
        "operationId": "post_evidence",
        "tags": [
          "evidence"
        ],
        "summary": "Create a new evidence item under a product in the authenticated workspace.",
        "x-stability": "stable",
        "x-scope": "write",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "general 300/h",
        "description": "Billing-gated: a 403 with body { error:'plan_limit_reached', metric:'evidence_items', current, limit, plan } is returned when the evidence_items quota is hit (distinct from the scope 403 which has body { error: 'Insufficient permissions...' }). Row stored with status='active', triage_status='inbox', created_by=auth.userId. Fires a Slack notify + PostHog evidence_created event (fire-and-forget). linkedIntentIds is [] on a fresh create.",
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required ONLY for cookie-session auth (no Bearer key); ignored for API-key auth.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content": {
                    "type": "string",
                    "description": "Non-empty evidence text; trimmed before storage."
                  },
                  "type": {
                    "type": "string",
                    "description": "One of friction | quote | observation | metric | request."
                  },
                  "productId": {
                    "type": "string",
                    "description": "Product must belong to the workspace (else 404)."
                  },
                  "source": {
                    "type": "string",
                    "description": "Free-text source label; stored as null if omitted."
                  },
                  "sourceUrl": {
                    "type": "string",
                    "description": "Source URL; stored as null if omitted."
                  },
                  "severity": {
                    "type": "string",
                    "description": "low | medium | high | critical. 400 if invalid."
                  },
                  "sentiment": {
                    "type": "string",
                    "description": "positive | negative | neutral | mixed. 400 if invalid."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Tag array; defaults to []."
                  },
                  "stage": {
                    "type": "string",
                    "description": "Free-text journey stage; stored as null if omitted."
                  }
                },
                "required": [
                  "content",
                  "type",
                  "productId"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Evidence created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "d4e5f6a7-b8c9-4012-a345-6789bcdef012",
                  "productId": "b1c2d3e4-f5a6-4789-b0c1-2d3e4f5a6b7c",
                  "workspaceId": "7a6b5c4d-3e2f-4a1b-9c8d-0e1f2a3b4c5d",
                  "type": "friction",
                  "content": "Users abandon checkout when the address form rejects valid postal codes.",
                  "source": "Support ticket #4821",
                  "sourceUrl": "https://support.example.com/t/4821",
                  "severity": "high",
                  "sentiment": "negative",
                  "tags": [
                    "checkout",
                    "forms"
                  ],
                  "stage": "Checkout",
                  "linkedIntentIds": [],
                  "createdAt": "2026-06-07T12:00:00.000Z",
                  "updatedAt": "2026-06-07T12:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Missing/empty content, missing/invalid type, missing productId, or invalid severity/sentiment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid API key or no valid session cookie.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "API key lacks write scope; cookie auth not a workspace member; OR billing limit reached (body { error: 'plan_limit_reached', metric: 'evidence_items', current, limit, plan }).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "productId not found in this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (general bucket, 300/h).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "DB or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/api/v1/workspace": {
      "get": {
        "operationId": "get_workspace",
        "tags": [
          "workspace"
        ],
        "summary": "Get workspace details: name, url key, tags, strategy, active products, and active constitution rules.",
        "x-stability": "stable",
        "x-scope": "read",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "none",
        "description": "strategy is read from workspaces.settings.strategy (null when unset). products only includes status='active' rows, sorted created_at ASC. Optional product columns (description/productVision/northStar/targetAudience/manifest/icon/color) are omitted from JSON when falsy (mapped to undefined); defaultContext defaults to {}. constitutionRules only includes is_active=true rows, sorted created_at DESC; rationale/scope/examples omitted when null/non-array; enforcement defaults to 'required'.",
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required ONLY for cookie-session auth (no Bearer key); ignored for API-key auth.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Workspace returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "7a6b5c4d-3e2f-4a1b-9c8d-0e1f2a3b4c5d",
                  "name": "Acme Product Team",
                  "urlKey": "acme-product",
                  "tags": [
                    "beta"
                  ],
                  "strategy": null,
                  "products": [
                    {
                      "id": "b1c2d3e4-f5a6-4789-b0c1-2d3e4f5a6b7c",
                      "workspaceId": "7a6b5c4d-3e2f-4a1b-9c8d-0e1f2a3b4c5d",
                      "name": "Checkout",
                      "slug": "checkout",
                      "description": "The purchase flow.",
                      "productVision": "Frictionless checkout in under 30 seconds.",
                      "northStar": "Completed checkouts per week",
                      "targetAudience": "Returning shoppers",
                      "defaultContext": {},
                      "manifest": null,
                      "icon": "cart",
                      "color": "#3A8A8C",
                      "status": "active",
                      "createdBy": "e1f2a3b4-c5d6-4789-a012-3b4c5d6e7f80",
                      "createdAt": "2026-05-01T09:00:00.000Z",
                      "updatedAt": "2026-06-01T09:00:00.000Z"
                    }
                  ],
                  "constitutionRules": [
                    {
                      "id": "a1b2c3d4-e5f6-4789-9012-3456789abcde",
                      "category": "privacy",
                      "text": "Never log raw PII.",
                      "rationale": "Compliance with GDPR.",
                      "scope": "all-products",
                      "enforcement": "required",
                      "examples": [
                        "Mask email addresses in logs."
                      ],
                      "isActive": true,
                      "createdAt": "2026-04-10T11:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Cookie auth missing workspace_id query param.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid API key or no valid session cookie.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "API key lacks read scope; or cookie auth not a workspace member.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Workspace not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Products fetch failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/api/v1/workspace/constitution": {
      "get": {
        "operationId": "get_workspace_constitution",
        "tags": [
          "workspace"
        ],
        "summary": "Get the active constitution rules for the authenticated workspace.",
        "x-stability": "stable",
        "x-scope": "read",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "none",
        "description": "Only returns is_active=true rules, sorted created_at DESC. rationale/scope omitted when null; examples omitted when not an array; enforcement defaults to 'required'. count is rules.length.",
        "parameters": [
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required ONLY for cookie-session auth (no Bearer key); ignored for API-key auth.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Rules returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "rules": [
                    {
                      "id": "a1b2c3d4-e5f6-4789-9012-3456789abcde",
                      "category": "privacy",
                      "text": "Never log raw PII.",
                      "rationale": "Compliance with GDPR.",
                      "scope": "all-products",
                      "enforcement": "required",
                      "examples": [
                        "Mask email addresses in logs."
                      ],
                      "isActive": true,
                      "createdAt": "2026-04-10T11:00:00.000Z"
                    }
                  ],
                  "count": 1
                }
              }
            }
          },
          "400": {
            "description": "Cookie auth missing workspace_id query param.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid API key or no valid session cookie.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "API key lacks read scope; or cookie auth not a workspace member.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "DB or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/api/v1/export": {
      "get": {
        "operationId": "get_export",
        "tags": [
          "export"
        ],
        "summary": "Export workspace data as agent context files: CLAUDE.md, AGENTS.md, .cursorrules, or intent.md.",
        "x-stability": "stable",
        "x-scope": "read",
        "x-auth": "api-key-or-cookie",
        "x-rateLimit": "none",
        "description": "SUCCESS RESPONSE IS A FILE DOWNLOAD, NOT JSON. claude-md sets Content-Type text/markdown + filename CLAUDE.md, filters out non-substantive intents (evaluateIntentReadiness.isSubstantive) and prepends a <!-- PATHMODE:WARNINGS --> block via getContextWarnings (plus a note when stub intents were filtered). agents-md is identical to claude-md (same agent-agnostic '# Pathmode Intent Context' body) but sets filename AGENTS.md — the native instruction file for OpenAI Codex and modern Cursor. cursorrules sets text/plain + filename .cursorrules. intent-md sets text/markdown + filename intent.md. Intents fetched via INTENT_WITH_RELATIONS_SELECT, sorted updated_at DESC; createdAt/updatedAt/shippedAt/verifiedAt are converted to epoch-millis numbers before generation. 422 and 404 bodies are JSON.",
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "claude-md (default) | agents-md | cursorrules | intent-md. Any other value yields 400.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "intent_id",
            "in": "query",
            "required": false,
            "description": "For cursorrules/intent-md: target a specific intent; if omitted, pickBestIntent chooses one. For claude-md it influences product resolution.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "product_id",
            "in": "query",
            "required": false,
            "description": "For claude-md: include this product's manifest context (explicit override; else resolved from intent, else first active product).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": false,
            "description": "Required ONLY for cookie-session auth (no Bearer key); ignored for API-key auth.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File generated; body is markdown/plaintext (see Content-Type), not JSON.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "# CLAUDE.md\n# (success responses are NOT JSON — body is the generated markdown/plaintext file)\n# Content-Type: text/markdown; charset=utf-8 (claude-md, agents-md, intent-md)\n# Content-Type: text/plain; charset=utf-8 (cursorrules)\n# Content-Disposition: attachment; filename=\"CLAUDE.md\" | \"AGENTS.md\" | \".cursorrules\" | \"intent.md\"\n#\n# JSON is only returned on error/422, e.g.:\n# {\n#   \"error\": \"Intent is not ready to export as agent context.\",\n#   \"readiness\": { \"isReadyForExecution\": false, \"isSubstantive\": false },\n#   \"suggestion\": \"Add a real objective and at least one concrete outcome before exporting agent rules.\"\n# }"
              }
            }
          },
          "400": {
            "description": "Invalid format value; or cookie auth missing workspace_id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid API key or no valid session cookie.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "API key lacks read scope; or cookie auth not a workspace member.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "cursorrules/intent-md: requested intent_id not found, or no intents exist in the workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "422": {
            "description": "cursorrules/intent-md: target intent is not ready for execution (returns JSON { error, readiness, suggestion }).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/api/v1/api-keys": {
      "get": {
        "operationId": "get_api-keys",
        "tags": [
          "api-keys"
        ],
        "summary": "List the current user's API keys (key value never returned).",
        "x-stability": "stable",
        "x-scope": "none",
        "x-auth": "cookie-session-only",
        "x-rateLimit": "apiKeysRead 120/h",
        "description": "Cookie session only; uses createClient from @/lib/supabase/server + getUser, never inspects the Authorization header, so a pm_live_ Bearer token is NOT accepted. Lists keys for the authenticated user (eq user_id), sorted created_at DESC. The raw key is never returned — only keyPrefix. If admin client unavailable or api_keys table missing (code 42P01), returns 200 with { keys: [], count: 0, message }.",
        "responses": {
          "200": {
            "description": "Keys returned (or empty list with a 'message' field if SUPABASE_SERVICE_ROLE_KEY/the api_keys table is not configured).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "keys": [
                    {
                      "id": "f1e2d3c4-b5a6-4978-8a90-1b2c3d4e5f60",
                      "workspaceId": "7a6b5c4d-3e2f-4a1b-9c8d-0e1f2a3b4c5d",
                      "name": "CI deploy key",
                      "keyPrefix": "pm_live_3f2a9c1e",
                      "scopes": [
                        "read",
                        "write"
                      ],
                      "lastUsedAt": "2026-06-06T18:22:10.000Z",
                      "expiresAt": null,
                      "createdAt": "2026-05-20T14:00:00.000Z"
                    }
                  ],
                  "count": 1
                }
              }
            }
          },
          "401": {
            "description": "No valid session cookie (rejects API keys — this route does NOT call authenticateApiRoute).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (apiKeysRead bucket, 120/h).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "DB or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      },
      "post": {
        "operationId": "post_api-keys",
        "tags": [
          "api-keys"
        ],
        "summary": "Create a new API key for a workspace the user owns/admins; returns the secret key exactly once.",
        "x-stability": "stable",
        "x-scope": "admin",
        "x-auth": "cookie-session-only",
        "x-rateLimit": "apiKeysWrite 20/h",
        "description": "Cookie session only — never inspects Authorization header; pm_live_ Bearer NOT accepted. Caller must be admin or owner of workspaceId (workspace_members.role). Requested scopes are capped to deriveScopesFromRole(role); exceeding them is a 403. Billing-gated on the api_keys metric (403 with plan_limit_reached body). The secret 'key' (format pm_live_<64 hex chars>) is returned ONLY in this 201 response and can never be retrieved again — afterward only keyPrefix is exposed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Human label for the key."
                  },
                  "workspaceId": {
                    "type": "string",
                    "description": "Workspace the key belongs to; caller must be admin/owner."
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Subset of read | write | admin. Defaults to ['read','write']. Must be non-empty and within the scopes the caller's role permits."
                  }
                },
                "required": [
                  "name",
                  "workspaceId"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Key created; secret 'key' returned once.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "f1e2d3c4-b5a6-4978-8a90-1b2c3d4e5f60",
                  "key": "pm_live_3f2a9c1e8b4d4e2a9c1e7a6b5c4d3e2f0a1b2c3d4e5f60718293a4b5c6d7e8f9",
                  "name": "CI deploy key",
                  "keyPrefix": "pm_live_3f2a9c1e",
                  "scopes": [
                    "read",
                    "write"
                  ],
                  "createdAt": "2026-06-07T12:00:00.000Z",
                  "message": "Save this key now. It cannot be retrieved again."
                }
              }
            }
          },
          "400": {
            "description": "Missing name, missing workspaceId, empty/non-array scopes, or unrecognized scope values.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No valid session cookie (API keys not accepted).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Caller is not workspace admin/owner; requested scopes exceed role-derived scopes; OR billing limit reached (body { error:'plan_limit_reached', metric:'api_keys', current, limit, plan }).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (apiKeysWrite bucket, 20/h).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Membership lookup failure, insert failure, or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "SUPABASE_SERVICE_ROLE_KEY not configured (admin client unavailable).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      },
      "delete": {
        "operationId": "delete_api-keys",
        "tags": [
          "api-keys"
        ],
        "summary": "Revoke (delete) one of the current user's API keys by id.",
        "x-stability": "stable",
        "x-scope": "none",
        "x-auth": "cookie-session-only",
        "x-rateLimit": "apiKeysWrite 20/h",
        "description": "Cookie session only — never inspects Authorization header; pm_live_ Bearer NOT accepted. Delete filters on both id=keyId and user_id=current user, so a user can only revoke their own keys. No workspace role check (unlike POST). Returns a fixed { success:true, message } body regardless of whether a row actually matched.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "keyId": {
                    "type": "string",
                    "description": "Id of the api_keys row to delete; scoped to the authenticated user (eq user_id)."
                  }
                },
                "required": [
                  "keyId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Key revoked (delete is scoped to eq id + eq user_id; succeeds even if no row matched).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "success": true,
                  "message": "API key revoked"
                }
              }
            }
          },
          "400": {
            "description": "Missing keyId.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No valid session cookie (API keys not accepted).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (apiKeysWrite bucket, 20/h).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Delete failure or internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "SUPABASE_SERVICE_ROLE_KEY not configured (admin client unavailable).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      }
    }
  }
}
