{
  "openapi": "3.0.3",
  "info": {
    "title": "Abundera QR Check API",
    "version": "1.0.0",
    "description": "QR / barcode safety classification service.",
    "contact": {
      "name": "Abundera",
      "email": "support@abundera.ai"
    }
  },
  "servers": [
    {
      "url": "https://check.qr.abundera.ai",
      "description": "Production"
    }
  ],
  "paths": {
    "/.well-known/abundera-capabilities.json": {
      "get": {
        "tags": [
          ".well-known"
        ],
        "summary": "Fetch Abundera Check product capabilities document",
        "description": "JSON document describing the check.qr.abundera.ai API surface, OAuth-style\nscopes, and rate-limit tiers. Follows RFC 8615 (well-known URI) and is\naggregated by abundera.ai's federated /account/api-keys UI.\n\nConsumers must ignore unknown fields (RFC 8259 §4). Minor schema_version\nbumps are backward-compatible.\n\nPublic; no authentication required. CORS-open so cross-origin aggregators\nand integrators can fetch directly.\n\nCaching: `Cache-Control: public, max-age=60, stale-while-revalidate=600`.\nA weak ETag (first 32 hex of body SHA-256) supports `If-None-Match` 304s.\n",
        "responses": {
          "200": {
            "description": "Capabilities document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "$schema": {
                      "type": "string"
                    },
                    "schema_version": {
                      "type": "string"
                    },
                    "product": {
                      "type": "object"
                    },
                    "api": {
                      "type": "object"
                    },
                    "auth": {
                      "type": "object"
                    },
                    "rate_limits": {
                      "type": "object"
                    }
                  },
                  "additionalProperties": false
                }
              }
            }
          },
          "304": {
            "description": "Not modified (ETag matched)"
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "retry_after": {
                              "type": "integer",
                              "description": "Seconds to wait before retry. Present on 429."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "operationId": "getWellKnownAbunderaCapabilitiesJson"
      }
    },
    "/.well-known/gpc.json": {
      "get": {
        "tags": [
          ".well-known"
        ],
        "summary": "known/gpc.json",
        "description": "Global Privacy Control signal acknowledgement. Declares that this\nsite honors the GPC header (Sec-GPC: 1) as a valid opt-out of sale\nand sharing of personal information under CCPA/CPRA. Format per\nhttps://privacycg.github.io/gpc-spec/.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    }
                  },
                  "additionalProperties": false
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "retry_after": {
                              "type": "integer",
                              "description": "Seconds to wait before retry. Present on 429."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "operationId": "getWellKnownGpcJson"
      }
    },
    "/.well-known/llms.txt": {
      "get": {
        "tags": [
          ".well-known"
        ],
        "summary": "known/llms.txt",
        "description": "Permanent alias to /llms.txt. Some LLM crawlers probe the well-known\nlocation before the root; this 301 keeps a single canonical file.\nSee ~/projects/siteops/docs/LLMS-TXT-STANDARD.md.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    }
                  },
                  "additionalProperties": false
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "retry_after": {
                              "type": "integer",
                              "description": "Seconds to wait before retry. Present on 429."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "operationId": "getWellKnownLlmsTxt"
      }
    },
    "/.well-known/qr-safety-policy.json": {
      "get": {
        "tags": [
          ".well-known"
        ],
        "summary": "policy.json",
        "description": "Family-canonical declaration of how this Abundera Check instance\nclassifies QR / barcode payload safety. Lets external integrators\n(browsers, MDMs, mail filters, sister-product safety pipelines)\nconsume verdicts knowing exactly what the verdict schema means.\n\nPair with /.well-known/qr-redirect-policy.json on qr.abundera.ai ,\none declares \"how I mint redirects\", the other declares \"how I\nclassify them.\"\n\nSchema: ~/projects/siteops/docs/WELL-KNOWN-CATALOG.md",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    }
                  },
                  "additionalProperties": false
                }
              }
            }
          },
          "304": {
            "description": "Not modified"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "retry_after": {
                              "type": "integer",
                              "description": "Seconds to wait before retry. Present on 429."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "operationId": "getWellKnownQrSafetyPolicyJson"
      }
    },
    "/api/billing/session": {
      "get": {
        "tags": [
          "billing"
        ],
        "summary": "Read the sanitized result of a founding-member checkout",
        "description": "Sanitized read of a paid Stripe Checkout Session for the post-checkout success modal. Public: the session id is the credential (Stripe-issued, single-use, one-hour replay window). Only sessions created by this product that are paid answer 200; every other case answers 404 so a probe learns nothing about other products' sessions.\n",
        "parameters": [
          {
            "name": "session_id",
            "in": "query",
            "required": true,
            "description": "Stripe Checkout Session id from the success URL.",
            "schema": {
              "type": "string",
              "pattern": "^cs_",
              "maxLength": 255
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The paid session, reduced to what the modal shows",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "cohort",
                    "plan_label",
                    "price_label",
                    "customer_name"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "description": "Stripe session status"
                    },
                    "cohort": {
                      "type": "string",
                      "nullable": true,
                      "description": "Checkout metadata type, for example founding_member"
                    },
                    "plan_label": {
                      "type": "string"
                    },
                    "price_label": {
                      "type": "string",
                      "description": "Amount actually paid, for example $39.00 USD/yr"
                    },
                    "customer_name": {
                      "type": "string",
                      "nullable": true
                    }
                  },
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "session_id missing or not a Checkout Session id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No matching paid session inside the replay window",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "retry_after": {
                              "type": "integer",
                              "description": "Seconds to wait before retry. Present on 429."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Stripe returned an unreadable response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Stripe is not configured in this environment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "operationId": "getBillingSession"
      }
    },
    "/api/coverage": {
      "get": {
        "tags": [
          "coverage"
        ],
        "summary": "Public QR Type Catalog manifest",
        "description": "Returns the live QR Type Catalog grouped by category, with status counts and credential-risk highlights. Powers the public /coverage/ page and is stable as a JSON API for third-party docs. Single source of truth is `~/projects/abundera-shared/data/qr-type-catalog.json`. Cached at the edge for 1 hour.\n",
        "responses": {
          "200": {
            "description": "Coverage manifest",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "catalog_version",
                    "totals",
                    "categories",
                    "types"
                  ],
                  "properties": {
                    "catalog_version": {
                      "type": "string",
                      "description": "ISO date (YYYY-MM-DD)"
                    },
                    "totals": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "shipped": {
                          "type": "integer"
                        },
                        "partial": {
                          "type": "integer"
                        },
                        "backlog": {
                          "type": "integer"
                        },
                        "credential_risk": {
                          "type": "integer"
                        }
                      },
                      "additionalProperties": false
                    },
                    "categories": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    },
                    "national_rollouts": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    },
                    "types": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "category",
                          "human_name",
                          "status"
                        ],
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "category": {
                            "type": "string"
                          },
                          "subcategory": {
                            "type": "string"
                          },
                          "human_name": {
                            "type": "string"
                          },
                          "summary": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "shipped",
                              "partial",
                              "backlog"
                            ]
                          },
                          "credential_risk": {
                            "type": "boolean"
                          },
                          "scanner_supported": {
                            "type": "boolean"
                          },
                          "generator_supported": {
                            "type": "boolean"
                          },
                          "patents": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "additionalProperties": false
                      }
                    }
                  },
                  "additionalProperties": false
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "retry_after": {
                              "type": "integer",
                              "description": "Seconds to wait before retry. Present on 429."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "operationId": "getCoverage"
      }
    },
    "/api/scan": {
      "post": {
        "tags": [
          "scan"
        ],
        "summary": "Classify a decoded QR / barcode payload",
        "description": "Production embodiment of patents QR-17 (multi-hop redirect-chain mutability) and QR-18 (multi-modal payload threat classification). Public endpoint, no auth. Anonymous tier rate-limited to 10/min burst AND 3/24h per IP (both gates; daily is strictest). No Turnstile. Raw payload is never persisted; verdict cache is keyed by SHA-256(payload_type || discriminator || server_salt).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "payload"
                ],
                "additionalProperties": false,
                "properties": {
                  "payload": {
                    "type": "string",
                    "maxLength": 4096,
                    "description": "Decoded payload text (URL, vCard, WiFi config, etc.)"
                  },
                  "client_decoded_type": {
                    "type": "string",
                    "maxLength": 64,
                    "description": "Optional client-side hint about payload kind."
                  },
                  "decoder_format": {
                    "type": "string",
                    "description": "Symbology the browser-side decoder reported. Same ids as DECODER_FORMATS in this handler and SCAN_FORMATS in site/js/scan-decoder.js.",
                    "enum": [
                      "qr_code",
                      "aztec",
                      "data_matrix",
                      "pdf417",
                      "code_128",
                      "code_39",
                      "code_93",
                      "codabar",
                      "ean_13",
                      "ean_8",
                      "itf",
                      "upc_a",
                      "upc_e",
                      "maxicode",
                      "gs1_databar",
                      "gs1_databar_expanded"
                    ]
                  },
                  "decoder_source": {
                    "type": "string",
                    "enum": [
                      "camera",
                      "upload",
                      "paste",
                      "type",
                      "deep_link",
                      "vision_model"
                    ]
                  },
                  "embedded_logo_hint": {
                    "type": "object",
                    "description": "Optional. QR-22-CIP browser-side classifier output for the QR's embedded brand logo. Cross-checked server-side against brand-list.json canonical_host vs. the resolved URL host. Ignored unless EMBEDDED_LOGO_DETECTION_ENABLED is on.",
                    "properties": {
                      "brand_label": {
                        "type": "string",
                        "maxLength": 64
                      },
                      "confidence": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 1
                      },
                      "model_version": {
                        "type": "string",
                        "maxLength": 64
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verdict",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "payload_type",
                    "threat_class",
                    "findings",
                    "schema_version"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "payload_type": {
                      "type": "string"
                    },
                    "subtype": {
                      "type": "string"
                    },
                    "threat_class": {
                      "type": "string",
                      "enum": [
                        "likely_safe",
                        "suspicious",
                        "likely_dangerous",
                        "hard_blocked",
                        "unverifiable"
                      ]
                    },
                    "mutability": {
                      "type": "string",
                      "enum": [
                        "immutable",
                        "dynamic",
                        "redirector",
                        "unknown"
                      ]
                    },
                    "attribution": {
                      "type": "object"
                    },
                    "chain": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "findings": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "sub_payloads": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "disclosure": {
                      "type": "string"
                    },
                    "latency_ms": {
                      "type": "integer"
                    },
                    "payload_preview": {
                      "type": "string",
                      "description": "First 160 characters of the caller's own payload, computed per request and never written to the verdict cache"
                    },
                    "schema_version": {
                      "type": "integer",
                      "enum": [
                        1
                      ]
                    },
                    "decoder_format": {
                      "type": "string"
                    },
                    "decoder_source": {
                      "type": "string"
                    },
                    "visitor": {
                      "type": "object"
                    },
                    "learn_more_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "info_snippet": {
                      "type": "string"
                    },
                    "feedback_token": {
                      "type": "string",
                      "nullable": true,
                      "description": "Opaque proof-of-scan token bound to this payload's hash and threat_class. Required by POST /api/v1/feedback and POST /api/v1/mutation-alerts/subscribe for the same payload. Expires after 24h; single-use once spent on /api/v1/feedback. null only when the server has no MUTATION_ALERT_KEY configured."
                    }
                  },
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or schema validation failure",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "invalid_json",
                                "validation"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "rate_limited"
                              ]
                            },
                            "retry_after": {
                              "type": "integer",
                              "description": "Seconds until the limit resets."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "config_error",
                                "internal_error"
                              ]
                            },
                            "request_id": {
                              "type": "string",
                              "description": "Correlation id for the server log entry. Present on internal_error."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "operationId": "postScan"
      }
    },
    "/api/v1/auth/whoami": {
      "get": {
        "tags": [
          "auth"
        ],
        "summary": "RBAC introspection for the current credential",
        "description": "Side-effect-free. Returns `{ authenticated, product, user_id, email,\nname, auth_source, role, tier, scopes, key, rate_limit }` for the\npresented JWT/API key, or `{ authenticated:false, reason }` (200)\nwhen absent/invalid. Never cached.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Introspection result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "authenticated": {
                      "type": "boolean"
                    },
                    "product": {
                      "type": "string"
                    },
                    "user_id": {
                      "type": "string",
                      "nullable": true
                    },
                    "email": {
                      "type": "string",
                      "nullable": true
                    },
                    "auth_source": {
                      "type": "string",
                      "nullable": true,
                      "enum": [
                        "api_key",
                        "jwt",
                        "session"
                      ]
                    },
                    "role": {
                      "type": "string",
                      "nullable": true
                    },
                    "tier": {
                      "type": "string",
                      "nullable": true
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "key": {
                      "type": "object",
                      "nullable": true
                    },
                    "rate_limit": {
                      "type": "object",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "retry_after": {
                              "type": "integer",
                              "description": "Seconds to wait before retry. Present on 429."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "operationId": "getV1AuthWhoami"
      }
    },
    "/api/v1/feedback": {
      "post": {
        "tags": [
          "feedback"
        ],
        "summary": "Submit feedback on a scan verdict",
        "description": "Collects user feedback on a verdict produced by the scan API. No authentication\nis required. The scanned payload is never stored, only a SHA-256 hash is kept,\nmatching the privacy posture of the verdict cache (QR-17/18 §6.4.x).\n\nRequires `feedback_token`, the proof-of-scan token POST /api/scan minted on the\nverdict this report is about (functions/lib/feedback-token.js). The token must\nverify, be unexpired (24h), match this request's `payload_hash` and\n`verdict_class`, and not have been spent before (single-use). Without it (or with\na bad one) the report is rejected: this closes the gap where any 64-hex\npayload_hash plus free-text verdict_class was accepted with no proof the caller\never scanned anything.\n\nEach report is stored in the RATE_LIMIT_KV namespace under a key of the form\n`feedback:<ts>:<hash-prefix>` with a 90-day TTL. No D1 dependency.\n\nFeedback is advisory only (docs/FEEDBACK-POLICY.md): it never changes a verdict,\na cache entry, or a warning automatically. Each accepted report also updates a\nper-payload aggregate (functions/lib/feedback-aggregate.js, `fbagg:<payload_hash>`)\ncounting one vote per source per day, a source being a daily-rotating HMAC of the\ncaller's IP network prefix and ASN. A repeat report from the same source on the\nsame payload the same day still answers 200 with a `report_id` (the dedupe is\nnever revealed) but writes no second `feedback:` record. Operators read the\naggregate via `GET /api/v1/ops/feedback?view=aggregate`.\n\nRate-limited to 10 requests per hour per IP address. Operator IPs (as configured\nin the environment) bypass the rate limit. If the KV binding or MUTATION_ALERT_KEY\nis unavailable the endpoint returns 503 (fails closed; the token cannot be\nverified without the key). Rate-limit failures are non-fatal and fail open so\nlegitimate feedback is not blocked.\n\nRequires `turnstile_token` (Cloudflare Turnstile, execute mode) ONLY when\nTURNSTILE_SECRET is configured in the environment; while unset the check is\nskipped entirely (no widget exists yet). See functions/lib/turnstile-gate.js.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "payload_hash",
                  "verdict_class",
                  "reason_class",
                  "feedback_token"
                ],
                "properties": {
                  "payload_hash": {
                    "type": "string",
                    "minLength": 64,
                    "maxLength": 64,
                    "pattern": "^[0-9a-fA-F]{64}$",
                    "description": "SHA-256 hex digest of the originally scanned payload. Must match the hash bound into feedback_token."
                  },
                  "verdict_class": {
                    "type": "string",
                    "maxLength": 32,
                    "enum": [
                      "likely_safe",
                      "suspicious",
                      "likely_dangerous",
                      "hard_blocked",
                      "unverifiable"
                    ],
                    "description": "The verdict's threat_class as returned by /api/scan (functions/lib/scan-base.js THREAT). Must match the class bound into feedback_token."
                  },
                  "reason_class": {
                    "type": "string",
                    "maxLength": 24,
                    "enum": [
                      "false_positive",
                      "false_negative",
                      "wrong_type",
                      "stale_data",
                      "missing_feature",
                      "other"
                    ],
                    "description": "Reason category explaining why the verdict appears incorrect."
                  },
                  "feedback_token": {
                    "type": "string",
                    "maxLength": 512,
                    "description": "Proof-of-scan token from the /api/scan response's `feedback_token` field. Single-use."
                  },
                  "turnstile_token": {
                    "type": "string",
                    "maxLength": 2048,
                    "description": "Cloudflare Turnstile response token (execute mode). Required only when TURNSTILE_SECRET is configured server-side; otherwise ignored."
                  },
                  "comment": {
                    "type": "string",
                    "maxLength": 600,
                    "description": "Optional free-text elaboration from the user."
                  },
                  "email": {
                    "type": "string",
                    "maxLength": 200,
                    "format": "email",
                    "description": "Optional contact email for follow-up."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Feedback accepted and stored",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "report_id"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "report_id": {
                      "type": "string",
                      "description": "Unique identifier for this report, e.g. fb_<timestamp>_<hash-prefix>."
                    }
                  },
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Request body is malformed or fails validation",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "invalid_json",
                                "validation",
                                "invalid_payload_hash",
                                "feedback_token_missing",
                                "feedback_token_malformed",
                                "feedback_token_invalid",
                                "feedback_token_expired",
                                "turnstile_token_missing"
                              ]
                            },
                            "errors": {
                              "type": "array",
                              "description": "Present when error is \"validation\"; lists field-level failures.",
                              "items": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "feedback_token does not match this request or has already been used, or turnstile_token failed Cloudflare verification",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "feedback_token_mismatch",
                                "feedback_token_used",
                                "turnstile_verification_failed"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Per-IP rate limit exceeded (10 per hour)",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the rate limit window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "rate_limited"
                              ]
                            },
                            "retry_after": {
                              "type": "integer",
                              "description": "Seconds until the rate limit window resets."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "KV write failed",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "storage_error"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "KV binding or MUTATION_ALERT_KEY not configured (fails closed; feedback_token cannot be verified without the key)",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "not_configured"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "operationId": "postV1Feedback"
      }
    },
    "/api/v1/founding-members": {
      "get": {
        "tags": [
          "founding-members"
        ],
        "summary": "Check founding member offer availability for Check QR",
        "description": "Returns whether the Check QR founding member offer is still open, how many days remain until the deadline, and which tiers are available.\n\nThe client uses this response to render a live countdown and disable purchase buttons after the deadline passes. The deadline for this product is 2027-01-01T08:00:00Z.\n\nNo authentication required. Public endpoint. Responses are cached for one hour (Cache-Control: public, max-age=3600). No side effects.\n",
        "responses": {
          "200": {
            "description": "Offer status returned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "available",
                    "deadline",
                    "days_remaining",
                    "product",
                    "tiers"
                  ],
                  "properties": {
                    "available": {
                      "type": "boolean",
                      "description": "True if the current time is before the deadline."
                    },
                    "deadline": {
                      "type": "string",
                      "format": "date-time",
                      "description": "ISO 8601 timestamp of when the founding member offer closes."
                    },
                    "days_remaining": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Whole days left until the deadline, floored at 0 after it passes."
                    },
                    "product": {
                      "type": "string",
                      "description": "Product identifier for this surface.",
                      "enum": [
                        "check"
                      ]
                    },
                    "tiers": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "personal",
                          "family",
                          "starter",
                          "pro"
                        ]
                      },
                      "description": "Founding member tiers available for this product."
                    }
                  },
                  "additionalProperties": false
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "retry_after": {
                              "type": "integer",
                              "description": "Seconds to wait before retry. Present on 429."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "operationId": "getV1FoundingMembers"
      },
      "options": {
        "tags": [
          "founding-members"
        ],
        "summary": "CORS preflight for founding member endpoint",
        "description": "Handles CORS preflight requests for GET /api/v1/founding-members. Returns the allowed methods and headers. No authentication required.\n",
        "responses": {
          "204": {
            "description": "Preflight accepted"
          }
        },
        "operationId": "optionsV1FoundingMembers"
      }
    },
    "/api/v1/mutation-alerts/confirm": {
      "post": {
        "tags": [
          "mutation-alerts"
        ],
        "summary": "Confirm a mutation-alert subscription via double-opt-in",
        "description": "Completes the double-opt-in flow for a mutation-alert subscription. The caller\nsupplies the subscription_id and the confirm_token from the emailed confirmation\nlink. The token's keyed hash is compared in constant time against the pending\nKV record.\n\nOn first confirmation the endpoint flips the subscription state from pending to\nactive, mints a 48-hex-char unsubscribe token, removes the confirm token hash from\nstorage, and extends the KV TTL to 365 days so the record survives until the\ndigest cron job picks it up.\n\nIf the subscription is already active the call is idempotent: the existing\nunsubscribe token is returned along with `already: true`.\n\nNo authentication is required. The confirm_token (minimum 48 chars) serves as\nthe bearer credential. Rate-limited to 20 attempts per IP per hour and 10 per\nsubscription per hour; a limiter outage refuses the request rather than\nletting it through. No PII is included in any response body.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subscription_id",
                  "confirm_token"
                ],
                "properties": {
                  "subscription_id": {
                    "type": "string",
                    "maxLength": 80,
                    "description": "Opaque identifier for the pending subscription record."
                  },
                  "confirm_token": {
                    "type": "string",
                    "minLength": 48,
                    "maxLength": 80,
                    "description": "Secret token from the emailed confirmation link; used to prove ownership of the address."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscription confirmed (or already active)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "state",
                    "unsubscribe_token",
                    "subscription_id"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "state": {
                      "type": "string",
                      "enum": [
                        "active"
                      ]
                    },
                    "already": {
                      "type": "boolean",
                      "description": "Present and true only when the subscription was already active before this call."
                    },
                    "unsubscribe_token": {
                      "type": "string",
                      "description": "Token the subscriber can use to cancel the subscription."
                    },
                    "subscription_id": {
                      "type": "string",
                      "description": "Echo of the subscription_id from the stored record."
                    }
                  },
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body or schema validation failure",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "invalid_json",
                                "validation"
                              ]
                            },
                            "errors": {
                              "type": "array",
                              "description": "Present when error is \"validation\"; lists field-level messages.",
                              "items": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "confirm_token does not match the stored record, or the record predates emailed confirmation",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "bad_token"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No pending subscription found for the given subscription_id, or it has expired",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "not_found_or_expired"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "rate_limited"
                              ]
                            },
                            "retry_after": {
                              "type": "integer",
                              "description": "Seconds until the rate limit window resets."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Internal error, corrupt KV record or storage write failure",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "corrupt_record",
                                "storage_error"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "KV namespace or MUTATION_ALERT_KEY not configured, or the rate limiter is unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "not_configured",
                                "rate_limit_unavailable"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "operationId": "postV1MutationAlertsConfirm"
      }
    },
    "/api/v1/mutation-alerts/subscribe": {
      "post": {
        "tags": [
          "mutation-alerts"
        ],
        "summary": "Subscribe to QR payload mutation alerts",
        "description": "Captures intent to receive mutation watchlist alerts for a given QR payload hash (QR-17). Stores a pending subscription record in KV. The full scanned payload is never stored; the record holds a keyed hash of the payload digest, a keyed hash of the address for lookup, and the address encrypted under a server-held key.\n\nDouble opt-in: the confirmation link is emailed to the subscriber. The confirm token is never included in the response. The subscription remains in `state: \"pending\"` until confirmed via `POST /api/v1/mutation-alerts/confirm`. Unconfirmed rows expire after 7 days. Confirmed subscriptions persist for 1 year.\n\nRequires a valid `feedback_token` (from the same POST /api/scan response as `payload_hash`) proving the caller scanned this payload; the token need not be unused, but it must verify and its bound hash must match `payload_hash`. Fixes the report where any 64-hex payload_hash was accepted with no proof anyone scanned anything.\n\nNo authentication is required. Rate-limited to 10 subscription attempts per IP per hour and 3 per address per hour. Operator IPs (configured via environment) bypass the per-IP limit. Requires `RATE_LIMIT_KV` and `MUTATION_ALERT_KEY`; returns 503 `not_configured` if either is absent (fails closed; feedback_token cannot be verified without the key). Returns 503 `feature_unavailable` while no confirmation sender is configured.\n\nRequires `turnstile_token` (Cloudflare Turnstile, execute mode) ONLY when\nTURNSTILE_SECRET is configured in the environment; while unset the check is\nskipped entirely (no widget exists yet). See functions/lib/turnstile-gate.js.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "payload_hash",
                  "feedback_token",
                  "email"
                ],
                "properties": {
                  "payload_hash": {
                    "type": "string",
                    "minLength": 64,
                    "maxLength": 64,
                    "pattern": "^[0-9a-fA-F]{64}$",
                    "description": "SHA-256 hex digest of the scanned QR payload. Must match the hash bound into feedback_token."
                  },
                  "feedback_token": {
                    "type": "string",
                    "maxLength": 512,
                    "description": "Proof-of-scan token from the /api/scan response's `feedback_token` field for this payload. Not single-use here."
                  },
                  "turnstile_token": {
                    "type": "string",
                    "maxLength": 2048,
                    "description": "Cloudflare Turnstile response token (execute mode). Required only when TURNSTILE_SECRET is configured server-side; otherwise ignored."
                  },
                  "email": {
                    "type": "string",
                    "maxLength": 200,
                    "format": "email",
                    "description": "Subscriber email address. Lowercased and trimmed, then stored encrypted."
                  },
                  "frequency": {
                    "type": "string",
                    "enum": [
                      "daily",
                      "weekly"
                    ],
                    "default": "daily",
                    "description": "Desired alert frequency. Defaults to daily if omitted."
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 80,
                    "description": "Optional free-text note the user associates with this subscription."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscription created in pending state; confirmation email sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "subscription_id",
                    "state",
                    "next_step"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "subscription_id": {
                      "type": "string",
                      "description": "Stable identifier for this subscription, derived from keyed hashes of the payload digest and the address."
                    },
                    "state": {
                      "type": "string",
                      "description": "Subscription state; always \"pending\" until confirmed.",
                      "enum": [
                        "pending"
                      ]
                    },
                    "next_step": {
                      "type": "string",
                      "description": "Human-readable instruction telling the subscriber to open the emailed confirmation link."
                    }
                  },
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Validation or input error",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "invalid_json",
                                "validation",
                                "invalid_payload_hash",
                                "feedback_token_missing",
                                "feedback_token_malformed",
                                "feedback_token_invalid",
                                "feedback_token_expired",
                                "turnstile_token_missing"
                              ]
                            },
                            "errors": {
                              "type": "array",
                              "description": "Field-level validation errors, present when error is \"validation\".",
                              "items": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "feedback_token does not match this payload_hash, or turnstile_token failed Cloudflare verification",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "feedback_token_mismatch",
                                "turnstile_verification_failed"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "rate_limited"
                              ]
                            },
                            "retry_after": {
                              "type": "integer",
                              "description": "Seconds until the rate limit window resets."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "KV storage write failed",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "storage_error"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "502": {
            "description": "The confirmation email could not be sent; no subscription was kept",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "delivery_failed"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Required binding or secret missing, or the confirmation sender is not configured yet",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "not_configured",
                                "feature_unavailable"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "operationId": "postV1MutationAlertsSubscribe"
      }
    },
    "/api/v1/mutation-alerts/unsubscribe": {
      "post": {
        "tags": [
          "mutation-alerts"
        ],
        "summary": "Unsubscribe from mutation alerts",
        "description": "Removes an active mutation-alert subscription identified by `subscription_id`.\nThe caller must supply the `unsubscribe_token` that was minted at confirmation\ntime; the token is compared in constant time to prevent timing attacks.\n\nNo authentication header is required, the unsubscribe token itself is the\ncredential. Tokens rotate with each email digest delivery so a leaked token\nfrom an old digest cannot be used against a still-active subscriber.\n\nThe operation is idempotent: if the subscription has already been deleted,\nthe endpoint returns HTTP 200 with `already: true` rather than an error,\nso a double-click or bookmark refresh on the unsubscribe page does not\nsurface an error to the user.\n\nRate-limited to 20 attempts per IP per hour. The endpoint is\nunauthenticated and open-CORS.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subscription_id",
                  "unsubscribe_token"
                ],
                "properties": {
                  "subscription_id": {
                    "type": "string",
                    "maxLength": 80,
                    "description": "Opaque identifier for the subscription record."
                  },
                  "unsubscribe_token": {
                    "type": "string",
                    "minLength": 48,
                    "maxLength": 80,
                    "description": "Single-use token issued at confirmation time, rotated per digest delivery."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscription deleted (or was already absent)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "state"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "state": {
                      "type": "string",
                      "enum": [
                        "deleted"
                      ]
                    },
                    "already": {
                      "type": "boolean",
                      "description": "Present and true when the subscription was already gone before this call."
                    }
                  },
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body or schema validation failure",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "invalid_json",
                                "validation"
                              ]
                            },
                            "errors": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "Field-level validation messages; present only when error is \"validation\"."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Unsubscribe token does not match the stored record",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "bad_token"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "rate_limited"
                              ]
                            },
                            "retry_after": {
                              "type": "integer",
                              "description": "Seconds until the rate limit window resets."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Corrupt KV record or storage deletion failure",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "corrupt_record",
                                "storage_error"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "KV binding not configured",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "not_configured"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "operationId": "postV1MutationAlertsUnsubscribe"
      }
    },
    "/api/v1/payments/founding-checkout": {
      "post": {
        "tags": [
          "payments"
        ],
        "summary": "Create a Stripe Checkout Session for a Founding Member subscription",
        "description": "Creates a Stripe Checkout Session for a Check Founding Member subscription at one of four annual pricing tiers (personal, family, starter, pro). No authentication is required, this is a public endpoint.\n\nThe founding offer has a hard deadline of 2027-01-01T08:00:00Z. Requests after that date receive 410 Gone.\n\nTier-to-price mapping is resolved server-side via environment variables (`STRIPE_FOUNDING_PRICE_*`). The caller supplies only the tier name and an optional pre-fill email address. On success the response contains a Stripe-hosted Checkout URL the client should redirect to.\n\n3DS/SCA is forced for all card payments (`request_three_d_secure: any`) to shift fraud-dispute liability. Stripe Radar handles further risk scoring.\n\nRate limit: 5 founding-checkout attempts per IP per hour via `RATE_LIMIT_KV`. Operator IPs are exempt. Rate-limit failures are fail-open (a KV error does not block the request).\n\nSession metadata records `type=founding_member`, `plan=<tier>`, and `product=qrcheck` at both the Session and Subscription levels. Promotion codes are disabled; billing address collection is automatic.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tier"
                ],
                "properties": {
                  "tier": {
                    "type": "string",
                    "enum": [
                      "personal",
                      "family",
                      "starter",
                      "pro"
                    ],
                    "description": "Founding member pricing tier. Determines which Stripe price ID is used."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Optional customer email to pre-fill in Stripe Checkout."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stripe Checkout Session created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri",
                      "description": "Stripe-hosted Checkout URL. Redirect the user here to complete payment."
                    },
                    "id": {
                      "type": "string",
                      "description": "Stripe Checkout Session ID."
                    }
                  },
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body or unsupported tier",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "invalid_json",
                                "invalid_tier",
                                "stripe_error"
                              ]
                            },
                            "message": {
                              "type": "string"
                            },
                            "detail": {
                              "type": "string",
                              "description": "Stripe error message, present only when error is stripe_error."
                            },
                            "stripe_status": {
                              "type": "integer",
                              "description": "HTTP status returned by Stripe, present only when error is stripe_error."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "410": {
            "description": "Founding Member offer has closed",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "founding_offer_closed"
                              ]
                            },
                            "message": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Per-IP rate limit exceeded",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the rate limit window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "rate_limited"
                              ]
                            },
                            "retry_after": {
                              "type": "integer",
                              "description": "Seconds until the next attempt is allowed."
                            },
                            "message": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Network error reaching Stripe or unparseable Stripe response",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "network_error",
                                "stripe_error"
                              ]
                            },
                            "detail": {
                              "type": "string",
                              "description": "Error detail string, present for network_error."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "502": {
            "description": "Stripe returned a 5xx error",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "stripe_error"
                              ]
                            },
                            "detail": {
                              "type": "string"
                            },
                            "stripe_status": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Stripe not configured or tier price ID missing",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "type": "string",
                              "enum": [
                                "not_configured",
                                "tier_not_configured"
                              ]
                            },
                            "message": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "operationId": "postV1PaymentsFoundingCheckout"
      }
    },
    "/api/whereami": {
      "get": {
        "tags": [
          "whereami"
        ],
        "summary": "Return visitor's country code from Cloudflare geo header",
        "description": "Returns the visitor's ISO 3166-1 alpha-2 country code as detected by Cloudflare's\nedge network via the `cf-ipcountry` request header.\n\nIntended for picking a demo redirect destination guaranteed to be in a different\ncountry than the visitor. No authentication required.\n\nNo PII is captured or logged. The only Cloudflare geo field read is the two-letter\ncountry code. IP address, ASN, city, latitude, longitude, and all other enrichment\nfields are explicitly ignored.\n\nCloudflare may set the header to `\"XX\"` for unknown origins or `\"T1\"` for Tor exit\nnodes; both values are passed through unchanged. When running outside a Cloudflare\ncontext (local dev, missing CF edge), the header is absent and `country` is `null`.\n\nNo rate limits apply. No side effects. Responses are not cached (`cache-control: no-store`).\n",
        "responses": {
          "200": {
            "description": "Country code resolved (or null if CF context unavailable)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "country"
                  ],
                  "properties": {
                    "country": {
                      "nullable": true,
                      "oneOf": [
                        {
                          "type": "string",
                          "minLength": 2,
                          "maxLength": 2,
                          "description": "Null when the cf-ipcountry header is absent (local dev or missing Cloudflare edge context)."
                        }
                      ]
                    }
                  },
                  "additionalProperties": false
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "retry_after": {
                              "type": "integer",
                              "description": "Seconds to wait before retry. Present on 429."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "operationId": "getWhereami"
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "JWT bearer token issued by the Abundera hub."
      }
    },
    "schemas": {
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "ok",
          "error",
          "request_id"
        ],
        "description": "Canonical error envelope shared by every Abundera product (docs/API-CONSISTENCY-CONTRACT.md). Emitted by functions/lib/response.js:fail().",
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              false
            ],
            "description": "False on error."
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "additionalProperties": true,
            "description": "Additional machine-readable fields (validation field names, plan/tier hints, retry hints, etc.) merge in here per error code.",
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable error code."
              },
              "message": {
                "type": "string",
                "description": "Human-readable detail."
              },
              "remediation": {
                "type": "string",
                "description": "How to fix the request, in one sentence."
              },
              "doc_url": {
                "type": "string",
                "description": "Deep link into the central errors reference."
              }
            }
          },
          "request_id": {
            "type": "string",
            "nullable": true,
            "description": "Request correlation id, echoed as the X-Request-ID header."
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": ".well-known"
    },
    {
      "name": "auth"
    },
    {
      "name": "billing"
    },
    {
      "name": "coverage"
    },
    {
      "name": "feedback"
    },
    {
      "name": "founding-members"
    },
    {
      "name": "mutation-alerts"
    },
    {
      "name": "payments"
    },
    {
      "name": "scan"
    },
    {
      "name": "whereami"
    }
  ]
}