{
  "openapi": "3.1.0",
  "info": {
    "title": "Abundera QR Pro API",
    "version": "1.0.0",
    "description": "REST API for Abundera QR Pro, create, edit, and analyze dynamic QR codes. Bearer-token auth, explicit rate limits, privacy-first analytics.",
    "contact": {
      "name": "Abundera QR Pro",
      "email": "support@abundera.ai"
    }
  },
  "servers": [
    {
      "url": "https://pro.qr.abundera.ai",
      "description": "Production"
    }
  ],
  "paths": {
    "/.well-known/abundera-capabilities.json": {
      "get": {
        "tags": [
          ".well-known"
        ],
        "summary": "Fetch Abundera product capabilities document",
        "description": "Returns a JSON document describing this product's API surface, OAuth-style scopes,\nwebhook event types, notification topics, and rate-limit tiers. Follows the RFC 8615\nwell-known URI pattern (analogous to OIDC Discovery and NodeInfo).\n\nThe document is consumed by the abundera.ai hub's federated /account/api-keys/ and\n/account/webhooks/ UIs, which aggregate capabilities documents from every product in\nthe family. Consumers must ignore unknown fields (RFC 8259 \u00a74). Minor schema_version\nbumps are backward-compatible; major bumps require aggregator updates.\n\nNo authentication is required. The document is fully public and served with\n`Access-Control-Allow-Origin: *` so cross-origin aggregators can fetch it directly.\n\nCaching: `Cache-Control: public, max-age=60, stale-while-revalidate=600`. A weak ETag\nis derived from the SHA-256 of the serialized body (first 32 hex chars). Conditional\nGET with `If-None-Match` is supported; a matching ETag returns 304 with no body.\n",
        "parameters": [
          {
            "in": "header",
            "name": "If-None-Match",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Weak ETag from a prior response. If it matches the current document hash, the server returns 304 Not Modified with no body.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Capabilities document",
            "headers": {
              "ETag": {
                "description": "Weak ETag of the response body (W/\"<32-hex-chars>\")",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string"
                }
              },
              "Access-Control-Allow-Origin": {
                "schema": {
                  "type": "string"
                }
              },
              "Access-Control-Expose-Headers": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "$schema",
                    "schema_version",
                    "product",
                    "api",
                    "auth",
                    "webhooks",
                    "notifications",
                    "rate_limits"
                  ],
                  "properties": {
                    "$schema": {
                      "type": "string",
                      "description": "URL of the JSON Schema that governs this document's structure"
                    },
                    "schema_version": {
                      "type": "string",
                      "description": "Semver-style major.minor version of this capabilities document"
                    },
                    "product": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Machine-readable product identifier"
                        },
                        "name": {
                          "type": "string",
                          "description": "Human-readable product name"
                        },
                        "status": {
                          "type": "string",
                          "description": "Lifecycle status (e.g. ga",
                          "beta)": null
                        },
                        "dashboard_url": {
                          "type": "string",
                          "description": "URL to the product dashboard"
                        },
                        "docs_url": {
                          "type": "string",
                          "description": "URL to product documentation"
                        },
                        "support_url": {
                          "type": "string",
                          "description": "URL to product support"
                        }
                      }
                    },
                    "api": {
                      "type": "object",
                      "properties": {
                        "base_url": {
                          "type": "string",
                          "description": "Base URL for all API requests"
                        },
                        "openapi_url": {
                          "type": "string",
                          "description": "URL to the OpenAPI specification"
                        },
                        "auth_methods": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Supported authentication method identifiers"
                        }
                      }
                    },
                    "auth": {
                      "type": "object",
                      "properties": {
                        "api_key_prefix": {
                          "type": "string",
                          "description": "String prefix all API keys issued by this product begin with"
                        },
                        "scopes": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "label": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string"
                              },
                              "default": {
                                "type": "boolean"
                              }
                            }
                          }
                        }
                      }
                    },
                    "webhooks": {
                      "type": "object",
                      "properties": {
                        "api_url": {
                          "type": "string",
                          "description": "URL for webhook management API"
                        },
                        "signing_scheme": {
                          "type": "string",
                          "description": "Algorithm used to sign payloads"
                        },
                        "signature_header": {
                          "type": "string",
                          "description": "HTTP header carrying the signature"
                        },
                        "events": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "label": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    },
                    "notifications": {
                      "type": "object",
                      "properties": {
                        "prefs_api_url": {
                          "type": "string",
                          "description": "Hub endpoint for reading/writing notification preferences"
                        },
                        "topic_prefix": {
                          "type": "string",
                          "description": "Namespace prefix prepended to topic IDs when calling the hub"
                        },
                        "topics": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "label": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string"
                              },
                              "default": {
                                "type": "boolean"
                              }
                            }
                          }
                        }
                      }
                    },
                    "rate_limits": {
                      "type": "object",
                      "properties": {
                        "unit": {
                          "type": "string",
                          "description": "Unit of measurement for limit values"
                        },
                        "tiers": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "description": "Tier identifier"
                              },
                              "window": {
                                "type": "string",
                                "description": "Rolling time window (e.g. 1m",
                                "1d)": null
                              },
                              "limit": {
                                "type": "integer",
                                "description": "Maximum requests allowed within the window"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not Modified, ETag matched, body omitted",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getWellKnownAbunderaCapabilitiesJson",
        "security": []
      },
      "options": {
        "tags": [
          ".well-known"
        ],
        "summary": "CORS preflight for capabilities endpoint",
        "description": "Handles CORS preflight requests for the capabilities document endpoint.\nAllows GET and OPTIONS from any origin, and permits the If-None-Match request header.\nMax-Age is set to 86400 seconds (24 hours) to minimize preflight overhead for aggregators.\n",
        "responses": {
          "204": {
            "description": "CORS preflight accepted",
            "headers": {
              "Access-Control-Allow-Origin": {
                "schema": {
                  "type": "string"
                }
              },
              "Access-Control-Allow-Methods": {
                "schema": {
                  "type": "string"
                }
              },
              "Access-Control-Allow-Headers": {
                "schema": {
                  "type": "string"
                }
              },
              "Access-Control-Max-Age": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "operationId": "optionsWellKnownAbunderaCapabilitiesJson",
        "security": []
      }
    },
    "/.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."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited"
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getWellKnownGpcJson",
        "security": []
      }
    },
    "/.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."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited"
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getWellKnownLlmsTxt",
        "security": []
      }
    },
    "/api/health": {
      "get": {
        "tags": [
          "health"
        ],
        "summary": "Liveness probe reporting runtime, sweeper, and backup health",
        "description": "Public liveness probe intended for external uptime monitors and the customer-facing\nstatus page. Requires no authentication. Returns a JSON object summarising three\nlayers of health:\n\n1. **Bindings**, verifies that the D1 database binding (`DB`) and KV namespace\n   binding (`KV`) are present on the Worker runtime. This check is purely in-process\n   and has no external dependencies.\n\n2. **Sweeper heartbeat**, performs a single KV `get` for the key\n   `sweeper:last_heartbeat`, written by the scheduled sweeper cron on every\n   successful run. Reports the timestamp of the last run, elapsed age in seconds and\n   hours, and the error count from that run. Considered stale when the heartbeat is\n   older than 48 hours or the last run recorded one or more errors.\n\n3. **Nightly backup summary**, executes one `SELECT \u2026 GROUP BY \u2026 LIMIT 1` query\n   against the `backups` D1 table (introduced in migration 021) to surface the most\n   recent backup's timestamp, table count, and ciphertext byte total. Considered\n   stale when the most recent backup is older than 30 hours. If the `backups` table\n   does not yet exist (pre-migration-021 database) the backup fields are returned as\n   `null` and the top-level `ok` is not degraded.\n\nThe top-level `ok` field is `false` when any of the following are true: bindings are\nmissing, `sweeper.ok` is explicitly `false`, or `backup.ok` is explicitly `false`. A\n`null` value for either subsystem (no data recorded yet) is treated as neutral and\ndoes not cause `ok` to become `false`, so a first-deploy before any cron run does\nnot trigger an alert.\n\nResponses are served with `Cache-Control: public, max-age=60` so that monitors\npolling at five-minute intervals do not generate excessive KV or D1 traffic.\n",
        "responses": {
          "200": {
            "description": "Health status retrieved (check `ok` field to determine actual health)",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "time",
                    "has_db",
                    "has_kv",
                    "sweeper",
                    "backup"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "False if bindings are missing, sweeper.ok is false, or backup.ok is false. A null subsystem value (never run) does not set this to false.\n"
                    },
                    "time": {
                      "type": "string",
                      "format": "date-time",
                      "description": "ISO 8601 timestamp at the moment the response was generated."
                    },
                    "has_db": {
                      "type": "boolean",
                      "description": "True when the D1 `DB` binding is present on the Worker runtime."
                    },
                    "has_kv": {
                      "type": "boolean",
                      "description": "True when the KV namespace `KV` binding is present on the Worker runtime."
                    },
                    "sweeper": {
                      "type": "object",
                      "required": [
                        "last_run_at",
                        "age_seconds",
                        "age_hours",
                        "errors_last_run",
                        "ok",
                        "stale_threshold_hours"
                      ],
                      "properties": {
                        "last_run_at": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Unix epoch seconds of the last sweeper heartbeat, or null if no heartbeat has been recorded."
                        },
                        "age_seconds": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Seconds elapsed since the last sweeper run, or null if no heartbeat has been recorded."
                        },
                        "age_hours": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "description": "Age in hours rounded to one decimal place, or null if no heartbeat has been recorded."
                        },
                        "errors_last_run": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Number of errors recorded during the last sweeper run, or null if not present in the heartbeat."
                        },
                        "ok": {
                          "type": [
                            "boolean",
                            "null"
                          ],
                          "description": "True when the heartbeat is fresher than 48 hours and errors_last_run is 0 or null. False when stale or errors were recorded. Null when no heartbeat has ever been written.\n"
                        },
                        "stale_threshold_hours": {
                          "type": "integer",
                          "const": 48,
                          "description": "The staleness threshold in hours used to evaluate sweeper.ok."
                        }
                      }
                    },
                    "backup": {
                      "type": "object",
                      "required": [
                        "last_backup_at",
                        "age_seconds",
                        "tables_backed_up",
                        "total_bytes",
                        "ok",
                        "stale_threshold_hours"
                      ],
                      "properties": {
                        "last_backup_at": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Unix epoch seconds of the most recent backup's earliest created_at, or null if no backup row exists."
                        },
                        "age_seconds": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Seconds elapsed since the last backup, or null if no backup row exists."
                        },
                        "tables_backed_up": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Number of table rows recorded in the most recent backup batch, or null if no backup row exists."
                        },
                        "total_bytes": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Total ciphertext bytes across all tables in the most recent backup batch, or null if no backup row exists."
                        },
                        "ok": {
                          "type": [
                            "boolean",
                            "null"
                          ],
                          "description": "True when the most recent backup is fresher than 30 hours. False when stale. Null when no backup row exists (neutral; does not degrade top-level ok).\n"
                        },
                        "stale_threshold_hours": {
                          "type": "integer",
                          "const": 30,
                          "description": "The staleness threshold in hours used to evaluate backup.ok."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getHealth",
        "security": []
      }
    },
    "/api/status": {
      "get": {
        "tags": [
          "status"
        ],
        "summary": "Retrieve aggregated system status for Abundera QR Pro",
        "description": "Returns a real-time aggregated status snapshot for the Abundera QR Pro product.\nEach request probes all registered component endpoints live (4-second timeout per\nprobe), combining the fresh probe results with historical uptime data stored in\nCloudflare KV under the key `cache:status`. The KV entry is written by a separate\nredirect-worker cron job that runs every five minutes and records status snapshots\nin the `status_snapshots` namespace.\n\nThe response is cached for 60 seconds in KV so that downstream consumers and\ndashboards do not hammer the upstream health endpoints. The two components probed\nare the redirect worker (https://aqr.net/health) and the Dashboard API itself\n(https://pro.qr.abundera.ai/api/health).\n\nThis endpoint is explicitly listed in the PUBLIC_API_PATHS set by the auth\nmiddleware and therefore requires no authentication. No request body or query\nparameters are accepted.\n",
        "responses": {
          "200": {
            "description": "Aggregated status payload returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "product": {
                      "type": "string",
                      "description": "Human-readable product name, always \"Abundera QR Pro\""
                    },
                    "generatedAt": {
                      "type": "string",
                      "format": "date-time",
                      "description": "ISO-8601 timestamp at which this response was generated"
                    },
                    "overall": {
                      "type": "string",
                      "enum": [
                        "operational",
                        "degraded",
                        "outage"
                      ],
                      "description": "Rolled-up status across all components. \"operational\" when all components are healthy, \"degraded\" when one or more are unhealthy but not all, \"outage\" when all are unhealthy.\n"
                    },
                    "components": {
                      "type": "array",
                      "description": "Per-component live probe results",
                      "items": {
                        "type": "object",
                        "properties": {
                          "component": {
                            "type": "string",
                            "description": "Machine-readable component identifier (e.g. \"redirect\", \"api\")"
                          },
                          "label": {
                            "type": "string",
                            "description": "Human-readable component label"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "operational",
                              "degraded",
                              "outage"
                            ],
                            "description": "Status of this individual component"
                          },
                          "latencyMs": {
                            "type": "integer",
                            "description": "Round-trip probe latency in milliseconds"
                          },
                          "checkedAt": {
                            "type": "string",
                            "format": "date-time",
                            "description": "ISO-8601 timestamp of when the probe completed"
                          }
                        }
                      }
                    },
                    "history": {
                      "type": "array",
                      "description": "Historical status snapshots written by the redirect-worker cron, ordered from most recent to oldest\n",
                      "items": {
                        "type": "object",
                        "properties": {
                          "recordedAt": {
                            "type": "string",
                            "format": "date-time",
                            "description": "ISO-8601 timestamp of when the snapshot was recorded"
                          },
                          "overall": {
                            "type": "string",
                            "enum": [
                              "operational",
                              "degraded",
                              "outage"
                            ]
                          },
                          "components": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "component": {
                                  "type": "string"
                                },
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "operational",
                                    "degraded",
                                    "outage"
                                  ]
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Status aggregation encountered a fatal error (e.g. KV unavailable or all probes failed at the infrastructure level)\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false
                    },
                    "error": {
                      "type": "string",
                      "description": "Short machine-readable error identifier"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getStatus",
        "security": []
      }
    },
    "/api/status/history": {
      "get": {
        "tags": [
          "status"
        ],
        "summary": "Retrieve historical daily uptime and incidents",
        "description": "Returns a time-series of daily uptime percentages and associated incident\nrecords aggregated from the `status_snapshots` and `status_incidents` tables\n(introduced in migration 034). The caller selects a rolling look-back window\nvia the `range` query parameter; if omitted the default window is the last 30\ndays.\n\nResults are served from a KV cache (binding name `KV`) with a five-minute\nTTL. Cache misses trigger a live database read and a subsequent cache\npopulation before the response is returned.\n\nNo authentication is required; the endpoint is publicly accessible. No\nwrite side-effects occur, the handler is strictly read-only apart from the\nKV cache refresh.\n",
        "parameters": [
          {
            "name": "range",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "7d",
                "30d",
                "90d"
              ],
              "default": "30d"
            },
            "description": "Rolling look-back window for the history data. Accepted values are `7d` (7 days), `30d` (30 days), and `90d` (90 days). Defaults to `30d` when the parameter is absent.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Historical uptime and incident data for the requested range",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "range": {
                      "type": "string",
                      "enum": [
                        "7d",
                        "30d",
                        "90d"
                      ],
                      "description": "The look-back window that was applied to the query."
                    },
                    "days": {
                      "type": "array",
                      "description": "Ordered list of daily uptime entries, one per calendar day within the requested range, from oldest to most recent.\n",
                      "items": {
                        "type": "object",
                        "properties": {
                          "date": {
                            "type": "string",
                            "format": "date",
                            "description": "Calendar date for this entry (YYYY-MM-DD)."
                          },
                          "uptime": {
                            "type": "number",
                            "format": "float",
                            "description": "Uptime percentage for the day expressed as a value between 0 and 100.\n"
                          },
                          "incidents": {
                            "type": "array",
                            "description": "Incidents recorded on this day.",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "description": "Unique incident identifier."
                                },
                                "title": {
                                  "type": "string",
                                  "description": "Short human-readable incident title."
                                },
                                "severity": {
                                  "type": "string",
                                  "description": "Severity level of the incident."
                                },
                                "started_at": {
                                  "type": "string",
                                  "format": "date-time",
                                  "description": "ISO 8601 timestamp when the incident began."
                                },
                                "resolved_at": {
                                  "type": "string",
                                  "format": "date-time",
                                  "nullable": true,
                                  "description": "ISO 8601 timestamp when the incident was resolved, or null if still ongoing."
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getStatusHistory",
        "security": []
      }
    },
    "/api/status/subscribe": {
      "post": {
        "tags": [
          "status"
        ],
        "summary": "Subscribe an email address to status-page event alerts",
        "description": "Accepts an email address and a Cloudflare Turnstile token, validates the\nemail for correct format and against a disposable-domain blocklist, then\ncreates or refreshes a pending subscription record in D1 and sends a\nverification email via the Zepto transactional email transport.\n\nThe email must pass both structural validation and a disposable-domain\nhygiene check before Turnstile is verified or any database write occurs.\nIf the hygiene check detects a close-but-invalid address it may return a\nsuggested correction in the response body.\n\nNo authentication is required; the endpoint is protected by a Cloudflare\nTurnstile challenge instead.\n\nRate-limited to 3 requests per hour per originating IP address using a\nshared token-bucket helper backed by KV. Requests that exceed the limit\nreceive a 429 with a `Retry-After` header indicating when the bucket\nresets.\n\nSide effects: may write or update a subscriber row in D1 and enqueue a\ntransactional verification email to the supplied address.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "turnstileToken"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Email address to subscribe. Normalised to lowercase and trimmed before validation.\n"
                  },
                  "turnstileToken": {
                    "type": "string",
                    "description": "Client-side Cloudflare Turnstile challenge token obtained from the widget on the calling page.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscription created or refreshed; verification email dispatched.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request body was not valid JSON, the email failed format or disposable-domain hygiene validation, or the Turnstile token was rejected.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable reason for rejection."
                    },
                    "suggestion": {
                      "type": "string",
                      "description": "Present only when the hygiene check detected a likely typo and can suggest a corrected address.\n"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-IP rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "Too many requests. Try again later."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postStatusSubscribe",
        "security": []
      },
      "get": {
        "tags": [
          "status"
        ],
        "summary": "Verify a status-page subscription via emailed token",
        "description": "Confirms a pending email subscription. The caller supplies the hex token\nthat was embedded in the verification email. If the token is valid and\nnot expired the subscription is marked as verified in D1 and the request\nis redirected with HTTP 302 to the status page at\n`https://pro.qr.abundera.ai/status/?verified=1`.\n\nNo authentication or Turnstile challenge is required; possession of the\nsingle-use token from the verification email is the proof of ownership.\n\nNo request body is expected; all input is carried in the query string.\n",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Hex verification token delivered in the subscription confirmation email."
          }
        ],
        "responses": {
          "302": {
            "description": "Token accepted; browser redirected to the status page with `?verified=1` appended.\n"
          },
          "400": {
            "description": "Token missing, malformed, expired, or not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "suggestion": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getStatusSubscribe",
        "security": []
      },
      "delete": {
        "tags": [
          "status"
        ],
        "summary": "Unsubscribe an email address from status-page alerts",
        "description": "Removes an existing status-page email subscription identified by the\nhex token supplied in the query string. The token is the same value\ndelivered in the original verification email and any subsequent alert\nemails.\n\nNo authentication is required; possession of the unsubscribe token is\ntreated as sufficient proof of intent. No request body is expected.\n\nSide effects: deletes or deactivates the subscriber row in D1.\n",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Hex token identifying the subscription to cancel."
          }
        ],
        "responses": {
          "200": {
            "description": "Subscription successfully removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Token missing, malformed, or not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "deleteStatusSubscribe",
        "security": []
      }
    },
    "/api/v1/admin/archival-sweep": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "sweep?dry_run=1",
        "description": "Patent QR-05 \u00a76.x: trigger-driven automatic archival sweep. Evaluates\nevery code in the preservation (Keep-Alive) state against the inactivity\ntrigger from the defined Markush group:\n  - inactivity criterion: fewer than ARCHIVAL_MIN_SCANS scans in the\n    rolling LOOKBACK_DAYS window\n\nOn dry_run, returns the candidate set without state transitions. On\nnon-dry runs (admin-only), advances the affected codes to status =\n'archived'.\n\nAuthor intent: prevents the preservation tier from being used as a\nfree general-purpose URL shortener while preserving low-traffic\nlegitimate prints (the LOOKBACK_DAYS window is multi-year by design).",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "ARCHIVAL_MIN_SCANS": {
                      "description": "Response field detected from handler."
                    },
                    "LOOKBACK_DAYS": {
                      "description": "Response field detected from handler."
                    },
                    "archived": {
                      "description": "Response field detected from handler."
                    },
                    "candidate_count": {
                      "description": "Response field detected from handler."
                    },
                    "candidates": {
                      "description": "Response field detected from handler."
                    },
                    "dry_run": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "false": {
                      "description": "Response field detected from handler."
                    },
                    "length": {
                      "description": "Response field detected from handler."
                    },
                    "lookback_days": {
                      "description": "Response field detected from handler."
                    },
                    "threshold_scans": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "getV1AdminArchivalSweep"
      }
    },
    "/api/v1/admin/digest": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Fires the weekly or monthly digest push to every user with the",
        "description": "Fires the weekly or monthly digest push to every user with the\nmatching topic opt-in (weekly_digest / monthly_summary). Driven by\nqr-redirect-worker cron:\n  weekly_digest  , Monday 08:00 UTC\n  monthly_summary, 1st of month 08:00 UTC\n\nService-secret gated. The worker POSTs with { kind: 'weekly' |\n'monthly' }; this endpoint enumerates opted-in users, computes a\nshort per-user stat line (total scans in the window), and fires one\npush per user through the full gate stack (master / DND / topic).\n\nDeliberately thin on content, the push is a hook, not the entire\ndigest. Clicking takes the user to /stats/ which has the full\nbreakdown. Longer email digests can plug in here later without\nchanging the trigger surface.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "description": "Field read by the handler."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "candidates": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "kind": {
                      "description": "Response field detected from handler."
                    },
                    "length": {
                      "description": "Response field detected from handler."
                    },
                    "sent": {
                      "description": "Response field detected from handler."
                    },
                    "suppressed": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    },
                    "valid": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "500": {
            "description": "Internal server error"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "postV1AdminDigest"
      }
    },
    "/api/v1/admin/enterprise": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "Service-secret gated. Called from an internal admin surface (or the",
        "description": "Service-secret gated. Called from an internal admin surface (or the\nsales-ops Linear workflow via a webhook automation) after the Stripe\ninvoice is issued + countersigned MSA is on file.\n\nBody (POST):\n  {\n    user_id: \"<uuid>\",\n    overrides: {\n      codes_limit?: int | null,\n      scan_limit_monthly?: int | null,\n      seat_limit?: int,\n      max_teams?: int,\n      analytics_days?: int,\n      audit_retention_days?: int,\n      ip_allowlist?: [\"10.0.0.0/8\", ...],\n      sso_required?: bool,\n      support_tier?: \"standard\" | \"priority\" | \"dedicated\",\n      data_region?: \"us\" | \"eu\" | \"fedramp\",\n      notes?: string\n    },\n    contract_end?: unix_seconds | null    (null = evergreen)\n  }\n\nvalue; explicit null clears).",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "data_region": {
                      "description": "Response field detected from handler."
                    },
                    "email": {
                      "description": "Response field detected from handler."
                    },
                    "enterprise_contract_end": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "id": {
                      "description": "Response field detected from handler."
                    },
                    "overrides": {
                      "description": "Response field detected from handler."
                    },
                    "plan": {
                      "description": "Response field detected from handler."
                    },
                    "plan_status": {
                      "description": "Response field detected from handler."
                    },
                    "user_id": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Not found"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "getV1AdminEnterprise"
      },
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Service-secret gated. Called from an internal admin surface (or the",
        "description": "Service-secret gated. Called from an internal admin surface (or the\nsales-ops Linear workflow via a webhook automation) after the Stripe\ninvoice is issued + countersigned MSA is on file.\n\nBody (POST):\n  {\n    user_id: \"<uuid>\",\n    overrides: {\n      codes_limit?: int | null,\n      scan_limit_monthly?: int | null,\n      seat_limit?: int,\n      max_teams?: int,\n      analytics_days?: int,\n      audit_retention_days?: int,\n      ip_allowlist?: [\"10.0.0.0/8\", ...],\n      sso_required?: bool,\n      support_tier?: \"standard\" | \"priority\" | \"dedicated\",\n      data_region?: \"us\" | \"eu\" | \"fedramp\",\n      notes?: string\n    },\n    contract_end?: unix_seconds | null    (null = evergreen)\n  }\n\nvalue; explicit null clears).",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contract_end": {
                    "description": "Field read by the handler."
                  },
                  "overrides": {
                    "description": "Field read by the handler."
                  },
                  "user_id": {
                    "description": "Field read by the handler."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "data_region": {
                      "description": "Response field detected from handler."
                    },
                    "email": {
                      "description": "Response field detected from handler."
                    },
                    "enterprise_contract_end": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "id": {
                      "description": "Response field detected from handler."
                    },
                    "overrides": {
                      "description": "Response field detected from handler."
                    },
                    "plan": {
                      "description": "Response field detected from handler."
                    },
                    "plan_status": {
                      "description": "Response field detected from handler."
                    },
                    "user_id": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Not found"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "postV1AdminEnterprise"
      },
      "patch": {
        "tags": [
          "admin"
        ],
        "summary": "Service-secret gated. Called from an internal admin surface (or the",
        "description": "Service-secret gated. Called from an internal admin surface (or the\nsales-ops Linear workflow via a webhook automation) after the Stripe\ninvoice is issued + countersigned MSA is on file.\n\nBody (POST):\n  {\n    user_id: \"<uuid>\",\n    overrides: {\n      codes_limit?: int | null,\n      scan_limit_monthly?: int | null,\n      seat_limit?: int,\n      max_teams?: int,\n      analytics_days?: int,\n      audit_retention_days?: int,\n      ip_allowlist?: [\"10.0.0.0/8\", ...],\n      sso_required?: bool,\n      support_tier?: \"standard\" | \"priority\" | \"dedicated\",\n      data_region?: \"us\" | \"eu\" | \"fedramp\",\n      notes?: string\n    },\n    contract_end?: unix_seconds | null    (null = evergreen)\n  }\n\nvalue; explicit null clears).",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contract_end": {
                    "description": "Field read by the handler."
                  },
                  "overrides": {
                    "description": "Field read by the handler."
                  },
                  "user_id": {
                    "description": "Field read by the handler."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "data_region": {
                      "description": "Response field detected from handler."
                    },
                    "email": {
                      "description": "Response field detected from handler."
                    },
                    "enterprise_contract_end": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "id": {
                      "description": "Response field detected from handler."
                    },
                    "overrides": {
                      "description": "Response field detected from handler."
                    },
                    "plan": {
                      "description": "Response field detected from handler."
                    },
                    "plan_status": {
                      "description": "Response field detected from handler."
                    },
                    "user_id": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Not found"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "patchV1AdminEnterprise"
      }
    },
    "/api/v1/admin/gdpr-log": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "Admin-only, returns the monthly rollup + recent events from",
        "description": "Admin-only, returns the monthly rollup + recent events from\ngdpr_requests. Used for the LEGAL_RISK monthly review (\"confirm we\nhonored the 30-day SLA on every deletion request\").\n\nAuth: X-Service-Secret header must match env.ABUNDERA_SERVICE_SECRET.\nSame pattern the abundera.ai service-to-service bridge uses; no\nseparate admin session yet. Intentional: this is run by internal\nreview cron / manual curl, not by end users.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "action": {
                      "description": "Response field detected from handler."
                    },
                    "created_at": {
                      "description": "Response field detected from handler."
                    },
                    "email": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "honored_at": {
                      "description": "Response field detected from handler."
                    },
                    "id": {
                      "description": "Response field detected from handler."
                    },
                    "metadata": {
                      "description": "Response field detected from handler."
                    },
                    "null": {
                      "description": "Response field detected from handler."
                    },
                    "recent": {
                      "description": "Response field detected from handler."
                    },
                    "rollup": {
                      "description": "Response field detected from handler."
                    },
                    "source": {
                      "description": "Response field detected from handler."
                    },
                    "user_id": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "getV1AdminGdprLog"
      }
    },
    "/api/v1/admin/orgs": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "Service-secret gated (ABUNDERA_SERVICE_SECRET)",
        "description": "Service-secret gated (ABUNDERA_SERVICE_SECRET).\n\nFederated: every operation forwards to abundera.ai's\n/auth/service/orgs/* endpoints. Pro QR holds no `organizations`\nD1 table; the only local state is `users.org_id` which we maintain\nhere as a denormalized cache of the user's primary org.\n\n  {\n    user_id, name, support_email?, brand_color?, accent_color?,\n    logo_url?, footer_text?, short_url_domain?, dashboard_domain?\n  }\n\ntrigger registerHostname / revokeHostname on the federation side.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "org": {
                      "description": "Response field detected from handler."
                    },
                    "org_id": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Conflict"
          },
          "502": {
            "description": "Bad gateway"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "getV1AdminOrgs"
      },
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Service-secret gated (ABUNDERA_SERVICE_SECRET)",
        "description": "Service-secret gated (ABUNDERA_SERVICE_SECRET).\n\nFederated: every operation forwards to abundera.ai's\n/auth/service/orgs/* endpoints. Pro QR holds no `organizations`\nD1 table; the only local state is `users.org_id` which we maintain\nhere as a denormalized cache of the user's primary org.\n\n  {\n    user_id, name, support_email?, brand_color?, accent_color?,\n    logo_url?, footer_text?, short_url_domain?, dashboard_domain?\n  }\n\ntrigger registerHostname / revokeHostname on the federation side.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "accent_color": {
                    "description": "Field read by the handler."
                  },
                  "brand_color": {
                    "description": "Field read by the handler."
                  },
                  "dashboard_domain": {
                    "description": "Field read by the handler."
                  },
                  "logo_url": {
                    "description": "Field read by the handler."
                  },
                  "name": {
                    "description": "Field read by the handler."
                  },
                  "primary_color": {
                    "description": "Field read by the handler."
                  },
                  "short_url_domain": {
                    "description": "Field read by the handler."
                  },
                  "support_email": {
                    "description": "Field read by the handler."
                  },
                  "user_id": {
                    "description": "Field read by the handler."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "org": {
                      "description": "Response field detected from handler."
                    },
                    "org_id": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Conflict"
          },
          "502": {
            "description": "Bad gateway"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "postV1AdminOrgs"
      },
      "patch": {
        "tags": [
          "admin"
        ],
        "summary": "Service-secret gated (ABUNDERA_SERVICE_SECRET)",
        "description": "Service-secret gated (ABUNDERA_SERVICE_SECRET).\n\nFederated: every operation forwards to abundera.ai's\n/auth/service/orgs/* endpoints. Pro QR holds no `organizations`\nD1 table; the only local state is `users.org_id` which we maintain\nhere as a denormalized cache of the user's primary org.\n\n  {\n    user_id, name, support_email?, brand_color?, accent_color?,\n    logo_url?, footer_text?, short_url_domain?, dashboard_domain?\n  }\n\ntrigger registerHostname / revokeHostname on the federation side.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "accent_color": {
                    "description": "Field read by the handler."
                  },
                  "brand_color": {
                    "description": "Field read by the handler."
                  },
                  "dashboard_domain": {
                    "description": "Field read by the handler."
                  },
                  "logo_url": {
                    "description": "Field read by the handler."
                  },
                  "name": {
                    "description": "Field read by the handler."
                  },
                  "primary_color": {
                    "description": "Field read by the handler."
                  },
                  "short_url_domain": {
                    "description": "Field read by the handler."
                  },
                  "support_email": {
                    "description": "Field read by the handler."
                  },
                  "user_id": {
                    "description": "Field read by the handler."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "org": {
                      "description": "Response field detected from handler."
                    },
                    "org_id": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Conflict"
          },
          "502": {
            "description": "Bad gateway"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "patchV1AdminOrgs"
      }
    },
    "/api/v1/admin/push-fire": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Service-secret-gated trigger endpoint. Called by qr-redirect-worker",
        "description": "Service-secret-gated trigger endpoint. Called by qr-redirect-worker\nscheduled jobs (anomaly detection, code-expiring sweep, scan-cap\nabuse detector) to fire a push notification through the full pro.qr\npolicy gate stack (master toggle \u2192 federated DND \u2192 topic opt-in \u2192\ndelivery log).\n\nKeeps the worker stateless about policy: the worker sees \"anomaly\ndetected for code X owned by user U\" and POSTs here; pro.qr decides\nwhether to deliver, suppresses correctly, and writes the audit row.\n\nAuth: X-Service-Secret header must match env.ABUNDERA_SERVICE_SECRET.\n\nBody:\n  {\n    user_id:   \"<uuid>\",\n    topic?:    \"anomaly_alerts\" | \"code_expiring\" | \"quota_warning\"\n               | \"weekly_digest\" | \"monthly_summary\" | \"team_invite\"\n               | null,        // absent = transactional (bypass opt-in)\n    title:     \"Scan surge detected\",\n    body:      \"Code 'summer-2026' hit 3x its 14-day baseline...\",\n    url?:      \"/codes/edit/?id=abc123\"\n  }\n\nResponse: the send-result envelope from sendTopicPush /\n  sendTransactionalPush, { sent, failed, cleaned, suppressed? }.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "description": "Field read by the handler."
                  },
                  "title": {
                    "description": "Field read by the handler."
                  },
                  "topic": {
                    "description": "Field read by the handler."
                  },
                  "url": {
                    "description": "Field read by the handler."
                  },
                  "user_id": {
                    "description": "Field read by the handler."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "required": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    },
                    "valid": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "500": {
            "description": "Internal server error"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "postV1AdminPushFire"
      }
    },
    "/api/v1/admin/redirect-hosts": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "hosts        list all rows",
        "description": "X-Service-Secret gated (pattern from functions/api/admin/reservations/index.js).\n\n  {\n    host: \"enterprise.aqr.net\",                     // required\n    rule_kind: \"mirror\" | \"shortener\" | \"static_404\",\n    // mirror:\n    mirror_target: \"https://enterprise.qr.abundera.ai\",\n    mirror_status: 301 | 302,                       // default 301\n    // shortener:\n    prefix: \"\" | \"/r\",                              // default \"\"\n    product: \"qr\",\n    tenant_id: \"...\",\n    // meta:\n    status: \"active\" | \"paused\" | \"revoked\",        // default active\n    notes: \"...\"\n  }\n\nOn success: D1 INSERT, then KV PUT host:<host> with the rendered HostRule.\nIdempotent, duplicate host returns 409 (use PATCH to update).",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "count": {
                      "description": "Response field detected from handler."
                    },
                    "created": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "host": {
                      "description": "Response field detected from handler."
                    },
                    "hosts": {
                      "description": "Response field detected from handler."
                    },
                    "label": {
                      "description": "Response field detected from handler."
                    },
                    "length": {
                      "description": "Response field detected from handler."
                    },
                    "message": {
                      "description": "Response field detected from handler."
                    },
                    "null": {
                      "description": "Response field detected from handler."
                    },
                    "reason": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "301": {
            "description": "Moved permanently"
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Forbidden"
          },
          "409": {
            "description": "Conflict"
          },
          "500": {
            "description": "Internal server error"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "getV1AdminRedirectHosts"
      },
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "hosts        create a host",
        "description": "X-Service-Secret gated (pattern from functions/api/admin/reservations/index.js).\n\n  {\n    host: \"enterprise.aqr.net\",                     // required\n    rule_kind: \"mirror\" | \"shortener\" | \"static_404\",\n    // mirror:\n    mirror_target: \"https://enterprise.qr.abundera.ai\",\n    mirror_status: 301 | 302,                       // default 301\n    // shortener:\n    prefix: \"\" | \"/r\",                              // default \"\"\n    product: \"qr\",\n    tenant_id: \"...\",\n    // meta:\n    status: \"active\" | \"paused\" | \"revoked\",        // default active\n    notes: \"...\"\n  }\n\nOn success: D1 INSERT, then KV PUT host:<host> with the rendered HostRule.\nIdempotent, duplicate host returns 409 (use PATCH to update).",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "description": "Request payload. Exact field set depends on the endpoint; check the handler source for body destructuring patterns."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "count": {
                      "description": "Response field detected from handler."
                    },
                    "created": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "host": {
                      "description": "Response field detected from handler."
                    },
                    "hosts": {
                      "description": "Response field detected from handler."
                    },
                    "label": {
                      "description": "Response field detected from handler."
                    },
                    "length": {
                      "description": "Response field detected from handler."
                    },
                    "message": {
                      "description": "Response field detected from handler."
                    },
                    "null": {
                      "description": "Response field detected from handler."
                    },
                    "reason": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "301": {
            "description": "Moved permanently"
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Forbidden"
          },
          "409": {
            "description": "Conflict"
          },
          "500": {
            "description": "Internal server error"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "postV1AdminRedirectHosts"
      }
    },
    "/api/v1/admin/redirect-hosts/{host}": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "host    read one",
        "description": "Same X-Service-Secret pattern as the index handler.\n\n  { mirror_target, mirror_status, prefix, product, tenant_id,\n    status, branding_json, notes }\n\nOn every PATCH or DELETE we mirror the change to KV (D1 first per\nADR-0002). PATCH with status='paused' or 'revoked' deletes the KV\nkey so the worker stops serving the host without us having to delete\nthe D1 audit trail.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "parameters": [
          {
            "name": "host",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "deleted": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "host": {
                      "description": "Response field detected from handler."
                    },
                    "message": {
                      "description": "Response field detected from handler."
                    },
                    "row": {
                      "description": "Response field detected from handler."
                    },
                    "rule": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    },
                    "updated": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Conflict"
          },
          "500": {
            "description": "Internal server error"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "getV1AdminRedirectHostsHost"
      },
      "patch": {
        "tags": [
          "admin"
        ],
        "summary": "host    update mirror_target / status / etc",
        "description": "Same X-Service-Secret pattern as the index handler.\n\n  { mirror_target, mirror_status, prefix, product, tenant_id,\n    status, branding_json, notes }\n\nOn every PATCH or DELETE we mirror the change to KV (D1 first per\nADR-0002). PATCH with status='paused' or 'revoked' deletes the KV\nkey so the worker stops serving the host without us having to delete\nthe D1 audit trail.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "parameters": [
          {
            "name": "host",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "branding_json": {
                    "description": "Field read by the handler."
                  },
                  "mirror_status": {
                    "description": "Field read by the handler."
                  },
                  "mirror_target": {
                    "description": "Field read by the handler."
                  },
                  "notes": {
                    "description": "Field read by the handler."
                  },
                  "prefix": {
                    "description": "Field read by the handler."
                  },
                  "product": {
                    "description": "Field read by the handler."
                  },
                  "status": {
                    "description": "Field read by the handler."
                  },
                  "tenant_id": {
                    "description": "Field read by the handler."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "deleted": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "host": {
                      "description": "Response field detected from handler."
                    },
                    "message": {
                      "description": "Response field detected from handler."
                    },
                    "row": {
                      "description": "Response field detected from handler."
                    },
                    "rule": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    },
                    "updated": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Conflict"
          },
          "500": {
            "description": "Internal server error"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "patchV1AdminRedirectHostsHost"
      },
      "delete": {
        "tags": [
          "admin"
        ],
        "summary": "host    remove (refuses if reserved=1)",
        "description": "Same X-Service-Secret pattern as the index handler.\n\n  { mirror_target, mirror_status, prefix, product, tenant_id,\n    status, branding_json, notes }\n\nOn every PATCH or DELETE we mirror the change to KV (D1 first per\nADR-0002). PATCH with status='paused' or 'revoked' deletes the KV\nkey so the worker stops serving the host without us having to delete\nthe D1 audit trail.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "parameters": [
          {
            "name": "host",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "deleted": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "host": {
                      "description": "Response field detected from handler."
                    },
                    "message": {
                      "description": "Response field detected from handler."
                    },
                    "row": {
                      "description": "Response field detected from handler."
                    },
                    "rule": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    },
                    "updated": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Conflict"
          },
          "500": {
            "description": "Internal server error"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "deleteV1AdminRedirectHostsHost"
      }
    },
    "/api/v1/admin/reservations": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "X-Service-Secret gated (pattern from functions/api/admin/enterprise.js)",
        "description": "X-Service-Secret gated (pattern from functions/api/admin/enterprise.js).\nCalled from internal admin surfaces (dashboard tooling, Linear automation,\nor ad-hoc `curl` with the secret). No user-session auth path.\n\nBody (POST):\n  {\n    shortcode: \"menu\",              // required, lowercased on write\n    reason: \"brand\" | \"profanity\" | \"internal\" | \"vanity_hold\" | ...,\n    category?: \"brand.tech\",\n    allows_sale?: bool,              // default false\n    notes?: \"Apple Inc. trademark hold\"\n  }\n\nQuery (GET):\n  ?category=brand.tech\n  ?reason=brand\n  ?q=app              (prefix match)\n  ?grantedOnly=1       (only currently-leased reservations)\n  ?limit=100&offset=0\n\nBody (DELETE):\n  { shortcode: \"menu\" }\n\nEvery mutation calls logAction() with actor_type=admin (see\nfunctions/lib/audit-log.js) so reservation changes are auditable even\nthough X-Service-Secret actions lack a user_id.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "RESERVATION_REASONS": {
                      "description": "Response field detected from handler."
                    },
                    "allowed": {
                      "description": "Response field detected from handler."
                    },
                    "already_existed": {
                      "description": "Response field detected from handler."
                    },
                    "count": {
                      "description": "Response field detected from handler."
                    },
                    "deleted": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "granted_to_user_id": {
                      "description": "Response field detected from handler."
                    },
                    "length": {
                      "description": "Response field detected from handler."
                    },
                    "limit": {
                      "description": "Response field detected from handler."
                    },
                    "message": {
                      "description": "Response field detected from handler."
                    },
                    "offset": {
                      "description": "Response field detected from handler."
                    },
                    "reason": {
                      "description": "Response field detected from handler."
                    },
                    "reservation": {
                      "description": "Response field detected from handler."
                    },
                    "reservations": {
                      "description": "Response field detected from handler."
                    },
                    "rows": {
                      "description": "Response field detected from handler."
                    },
                    "total": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Created"
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Conflict"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "getV1AdminReservations"
      },
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "X-Service-Secret gated (pattern from functions/api/admin/enterprise.js)",
        "description": "X-Service-Secret gated (pattern from functions/api/admin/enterprise.js).\nCalled from internal admin surfaces (dashboard tooling, Linear automation,\nor ad-hoc `curl` with the secret). No user-session auth path.\n\nBody (POST):\n  {\n    shortcode: \"menu\",              // required, lowercased on write\n    reason: \"brand\" | \"profanity\" | \"internal\" | \"vanity_hold\" | ...,\n    category?: \"brand.tech\",\n    allows_sale?: bool,              // default false\n    notes?: \"Apple Inc. trademark hold\"\n  }\n\nQuery (GET):\n  ?category=brand.tech\n  ?reason=brand\n  ?q=app              (prefix match)\n  ?grantedOnly=1       (only currently-leased reservations)\n  ?limit=100&offset=0\n\nBody (DELETE):\n  { shortcode: \"menu\" }\n\nEvery mutation calls logAction() with actor_type=admin (see\nfunctions/lib/audit-log.js) so reservation changes are auditable even\nthough X-Service-Secret actions lack a user_id.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "allows_sale": {
                    "description": "Field read by the handler."
                  },
                  "category": {
                    "description": "Field read by the handler."
                  },
                  "notes": {
                    "description": "Field read by the handler."
                  },
                  "reason": {
                    "description": "Field read by the handler."
                  },
                  "shortcode": {
                    "description": "Field read by the handler."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "RESERVATION_REASONS": {
                      "description": "Response field detected from handler."
                    },
                    "allowed": {
                      "description": "Response field detected from handler."
                    },
                    "already_existed": {
                      "description": "Response field detected from handler."
                    },
                    "count": {
                      "description": "Response field detected from handler."
                    },
                    "deleted": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "granted_to_user_id": {
                      "description": "Response field detected from handler."
                    },
                    "length": {
                      "description": "Response field detected from handler."
                    },
                    "limit": {
                      "description": "Response field detected from handler."
                    },
                    "message": {
                      "description": "Response field detected from handler."
                    },
                    "offset": {
                      "description": "Response field detected from handler."
                    },
                    "reason": {
                      "description": "Response field detected from handler."
                    },
                    "reservation": {
                      "description": "Response field detected from handler."
                    },
                    "reservations": {
                      "description": "Response field detected from handler."
                    },
                    "rows": {
                      "description": "Response field detected from handler."
                    },
                    "total": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Created"
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Conflict"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "postV1AdminReservations"
      },
      "delete": {
        "tags": [
          "admin"
        ],
        "summary": "X-Service-Secret gated (pattern from functions/api/admin/enterprise.js)",
        "description": "X-Service-Secret gated (pattern from functions/api/admin/enterprise.js).\nCalled from internal admin surfaces (dashboard tooling, Linear automation,\nor ad-hoc `curl` with the secret). No user-session auth path.\n\nBody (POST):\n  {\n    shortcode: \"menu\",              // required, lowercased on write\n    reason: \"brand\" | \"profanity\" | \"internal\" | \"vanity_hold\" | ...,\n    category?: \"brand.tech\",\n    allows_sale?: bool,              // default false\n    notes?: \"Apple Inc. trademark hold\"\n  }\n\nQuery (GET):\n  ?category=brand.tech\n  ?reason=brand\n  ?q=app              (prefix match)\n  ?grantedOnly=1       (only currently-leased reservations)\n  ?limit=100&offset=0\n\nBody (DELETE):\n  { shortcode: \"menu\" }\n\nEvery mutation calls logAction() with actor_type=admin (see\nfunctions/lib/audit-log.js) so reservation changes are auditable even\nthough X-Service-Secret actions lack a user_id.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "RESERVATION_REASONS": {
                      "description": "Response field detected from handler."
                    },
                    "allowed": {
                      "description": "Response field detected from handler."
                    },
                    "already_existed": {
                      "description": "Response field detected from handler."
                    },
                    "count": {
                      "description": "Response field detected from handler."
                    },
                    "deleted": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "granted_to_user_id": {
                      "description": "Response field detected from handler."
                    },
                    "length": {
                      "description": "Response field detected from handler."
                    },
                    "limit": {
                      "description": "Response field detected from handler."
                    },
                    "message": {
                      "description": "Response field detected from handler."
                    },
                    "offset": {
                      "description": "Response field detected from handler."
                    },
                    "reason": {
                      "description": "Response field detected from handler."
                    },
                    "reservation": {
                      "description": "Response field detected from handler."
                    },
                    "reservations": {
                      "description": "Response field detected from handler."
                    },
                    "rows": {
                      "description": "Response field detected from handler."
                    },
                    "total": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Created"
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Conflict"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "deleteV1AdminReservations"
      }
    },
    "/api/v1/admin/reservations/grant": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Batch-grant reserved slugs to an Enterprise Scale customer as vanity",
        "description": "Batch-grant reserved slugs to an Enterprise Scale customer as vanity\nredirects. Pattern: Apple Inc. signs an Enterprise Scale contract that\nincludes apple, appl, apl as vanity slots \u2192 admin posts this endpoint\nonce with all three slugs \u2192 each becomes an active `codes` row on\nApple's data-plane shard pointing to Apple's destination URL.\n\nX-Service-Secret gated. Always audit-logs per slug.\n\nBody:\n  {\n    user_id: \"U_apple\",                // target grantee (Enterprise Scale)\n    contract_id: \"ENT-2026-0047\",      // groups related grants for revoke\n    shortcodes: [\"apple\", \"appl\", \"apl\"],\n    destination_url: \"https://apple.com\",\n    expires_at?: 1782000000,           // unix seconds; null/omitted = evergreen\n    label?: \"Apple vanity\"             // optional codes.label (defaults to shortcode)\n  }\n\nFlow per slug:\n  1. Verify reservation exists, allows_sale=1, not granted to someone else\n  2. Flip reservation fields (control plane): granted_to_user_id/at/expires/contract\n  3. INSERT row into target user's data-plane shard\n     (codes table, custom_slug_from_reservation=1, status=active)\n  4. PUT KV entry sc:{slug} so the worker can resolve\n  5. Increment users.custom_slugs_in_use += 1\n  6. Audit log\n\nOn per-slug failure:\n  - Best-effort rollback: mark_reservation_released() + attempt DELETE on\n    the data-plane row + DELETE KV entry. If any rollback step also fails,\n    we log LOUD and proceed, the nightly orphan-grant sweeper will clean\n    up stuck state.\n  - Response always contains a per-slug status array so the operator\n    knows what landed and what didn't.\n\nResponse shape:\n  {\n    ok: bool,                // true only if every slug succeeded\n    contract_id,\n    user_id,\n    granted: [\"apple\", \"appl\"],\n    failed: [{ shortcode: \"apl\", reason: \"not_sellable\" }],\n  }",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contract_id": {
                    "description": "Field read by the handler."
                  },
                  "destination_url": {
                    "description": "Field read by the handler."
                  },
                  "expires_at": {
                    "description": "Field read by the handler."
                  },
                  "label": {
                    "description": "Field read by the handler."
                  },
                  "shortcodes": {
                    "description": "Field read by the handler."
                  },
                  "user_id": {
                    "description": "Field read by the handler."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "contract_id": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "failed": {
                      "description": "Response field detected from handler."
                    },
                    "granted": {
                      "description": "Response field detected from handler."
                    },
                    "max": {
                      "description": "Response field detected from handler."
                    },
                    "raw": {
                      "description": "Response field detected from handler."
                    },
                    "reason": {
                      "description": "Response field detected from handler."
                    },
                    "shortcode": {
                      "description": "Response field detected from handler."
                    },
                    "user_id": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Not found"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "postV1AdminReservationsGrant"
      }
    },
    "/api/v1/admin/reservations/revoke": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Revoke an Enterprise Scale vanity grant. Two call shapes:",
        "description": "Revoke an Enterprise Scale vanity grant. Two call shapes:\n\n  1. By contract_id, revoke ALL slugs granted under a contract:\n     { contract_id: \"ENT-2026-0047\", grace_seconds?: 86400 }\n\n  2. By shortcode, revoke a single slug:\n     { shortcode: \"apple\", grace_seconds?: 86400 }\n\nSemantics:\n  - For each target slug:\n      a) Transition the grantee's code row to status='grace' with\n         grace_until = now + grace_seconds.\n      b) Update the KV entry to { status: 'grace' } so the worker\n         serves the grace page rather than the old redirect.\n      c) Clear the grant fields on shortcode_reservations\n         (granted_to_user_id \u2192 NULL, etc.) so the reservation row\n         returns to \"held, not leased\" state. The reservation itself\n         is NOT deleted, the slug remains reserved for future sales.\n  - Once grace_until passes, the nightly sweeper (scheduled.js) will\n    hard-delete the code row + KV entry and decrement the user's\n    custom_slugs_in_use counter.\n  - grace_seconds=0 revokes immediately (hard-delete on this call,\n    no grace period). Useful for trademark takedowns.\n\nX-Service-Secret gated. Always audit-logs per slug.\n\nResponse:\n  {\n    ok: bool,\n    revoked: [\"apple\", \"appl\"],\n    failed: [{ shortcode: \"apl\", reason: \"not_granted\" }],\n  }",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contract_id": {
                    "description": "Field read by the handler."
                  },
                  "grace_seconds": {
                    "description": "Field read by the handler."
                  },
                  "shortcode": {
                    "description": "Field read by the handler."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "failed": {
                      "description": "Response field detected from handler."
                    },
                    "graceSecs": {
                      "description": "Response field detected from handler."
                    },
                    "grace_seconds": {
                      "description": "Response field detected from handler."
                    },
                    "revoked": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Conflict"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "postV1AdminReservationsRevoke"
      }
    },
    "/api/v1/admin/sla": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "Admin SLA endpoints, service-secret-authed",
        "description": "Admin SLA endpoints, service-secret-authed.\n\n  Record a support-ticket event. Unix seconds for timestamps.\n  responded_at = null \u2192 still open.\n\n  Rollup: per-plan median + p90 response time, count of open tickets,\n  count of tickets exceeding the <24h Agency commitment.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "agency_24h_breaches": {
                      "description": "Response field detected from handler."
                    },
                    "agency_target_hours": {
                      "description": "Response field detected from handler."
                    },
                    "breachCount": {
                      "description": "Response field detected from handler."
                    },
                    "days": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "openCount": {
                      "description": "Response field detected from handler."
                    },
                    "open_tickets": {
                      "description": "Response field detected from handler."
                    },
                    "perPlan": {
                      "description": "Response field detected from handler."
                    },
                    "per_plan": {
                      "description": "Response field detected from handler."
                    },
                    "required": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    },
                    "window_days": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "getV1AdminSla"
      },
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Admin SLA endpoints, service-secret-authed",
        "description": "Admin SLA endpoints, service-secret-authed.\n\n  Record a support-ticket event. Unix seconds for timestamps.\n  responded_at = null \u2192 still open.\n\n  Rollup: per-plan median + p90 response time, count of open tickets,\n  count of tickets exceeding the <24h Agency commitment.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "description": "Field read by the handler."
                  },
                  "plan_at_time": {
                    "description": "Field read by the handler."
                  },
                  "received_at": {
                    "description": "Field read by the handler."
                  },
                  "responded_at": {
                    "description": "Field read by the handler."
                  },
                  "subject": {
                    "description": "Field read by the handler."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "agency_24h_breaches": {
                      "description": "Response field detected from handler."
                    },
                    "agency_target_hours": {
                      "description": "Response field detected from handler."
                    },
                    "breachCount": {
                      "description": "Response field detected from handler."
                    },
                    "days": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "openCount": {
                      "description": "Response field detected from handler."
                    },
                    "open_tickets": {
                      "description": "Response field detected from handler."
                    },
                    "perPlan": {
                      "description": "Response field detected from handler."
                    },
                    "per_plan": {
                      "description": "Response field detected from handler."
                    },
                    "required": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    },
                    "window_days": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "postV1AdminSla"
      }
    },
    "/api/v1/admin/status-notify": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "Fires status-subscriber outage emails for a specified set of down",
        "description": "Fires status-subscriber outage emails for a specified set of down\ncomponents. Called from qr-redirect-worker when a new incident is\nopened (the worker detects \"component just went down\" via the\nINSERT OR IGNORE meta.changes signal).\n\nAuth: X-Service-Secret header must match env.ABUNDERA_SERVICE_SECRET.\n\nBody: { components: [ { component: string, reason?: string }, ... ] }\nResponse: { ok: true, sent: <int>, errors: <array> }\n\nIdempotency: callers are expected to debounce, one call per new\nincident (not per poll tick). The worker only calls this when an\nincident row was freshly inserted, so repeat pollings of the same\nopen incident don't re-spam subscribers.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "components": {
                    "description": "Field read by the handler."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "components": {
                      "description": "Response field detected from handler."
                    },
                    "detail": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "expected": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "500": {
            "description": "Internal server error"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "postV1AdminStatusNotify"
      }
    },
    "/api/v1/admin/status-update": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "update  \u2192 create a new status update",
        "description": "Auth: X-Service-Secret must match env.ABUNDERA_SERVICE_SECRET.\n\nFields:\n  severity  , \"P1\" | \"P2\" | \"P3\" | \"P4\"\n  component , free-text label (\"redirect\", \"api\", \"dashboard\", ...)\n  status    , \"investigating\" | \"identified\" | \"monitoring\" | \"resolved\"\n  title     , short headline shown on the status page\n  message   , markdown-flavored body (plain text fine)\n  created_by, admin identifier; any non-empty string\n\nThe public /api/status aggregator surfaces these rows via the\nshared status-api lib. P1/P2 non-resolved rows force major_outage /\ndegraded. Resolved rows drop out after 7 days.\n\nNo D1-admin UI for launch. Operations via curl or the internal\nrunbook. A web UI lives as post-launch work.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "component": {
                    "description": "Field read by the handler."
                  },
                  "created_by": {
                    "description": "Field read by the handler."
                  },
                  "id": {
                    "description": "Field read by the handler."
                  },
                  "message": {
                    "description": "Field read by the handler."
                  },
                  "severity": {
                    "description": "Field read by the handler."
                  },
                  "status": {
                    "description": "Field read by the handler."
                  },
                  "title": {
                    "description": "Field read by the handler."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "detail": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "id": {
                      "description": "Response field detected from handler."
                    },
                    "required": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    },
                    "valid": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "postV1AdminStatusUpdate"
      },
      "patch": {
        "tags": [
          "admin"
        ],
        "summary": "update  \u2192 update an existing update (by id)",
        "description": "Auth: X-Service-Secret must match env.ABUNDERA_SERVICE_SECRET.\n\nFields:\n  severity  , \"P1\" | \"P2\" | \"P3\" | \"P4\"\n  component , free-text label (\"redirect\", \"api\", \"dashboard\", ...)\n  status    , \"investigating\" | \"identified\" | \"monitoring\" | \"resolved\"\n  title     , short headline shown on the status page\n  message   , markdown-flavored body (plain text fine)\n  created_by, admin identifier; any non-empty string\n\nThe public /api/status aggregator surfaces these rows via the\nshared status-api lib. P1/P2 non-resolved rows force major_outage /\ndegraded. Resolved rows drop out after 7 days.\n\nNo D1-admin UI for launch. Operations via curl or the internal\nrunbook. A web UI lives as post-launch work.",
        "security": [
          {
            "ServiceSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "component": {
                    "description": "Field read by the handler."
                  },
                  "created_by": {
                    "description": "Field read by the handler."
                  },
                  "id": {
                    "description": "Field read by the handler."
                  },
                  "message": {
                    "description": "Field read by the handler."
                  },
                  "severity": {
                    "description": "Field read by the handler."
                  },
                  "status": {
                    "description": "Field read by the handler."
                  },
                  "title": {
                    "description": "Field read by the handler."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "detail": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "id": {
                      "description": "Response field detected from handler."
                    },
                    "required": {
                      "description": "Response field detected from handler."
                    },
                    "true": {
                      "description": "Response field detected from handler."
                    },
                    "valid": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Not found"
          },
          "500": {
            "description": "Internal server error"
          },
          "503": {
            "description": "Service unavailable"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "patchV1AdminStatusUpdate"
      }
    },
    "/api/v1/audit": {
      "get": {
        "tags": [
          "audit"
        ],
        "summary": "Returns recent `audit_log` rows for whichever scope the user is",
        "description": "Returns recent `audit_log` rows for whichever scope the user is\ncurrently acting in (personal or team), matching how /api/codes\nand /api/webhooks resolve scope. Used by the federated\n/account/audit/ surface on abundera.ai (ADR 081); the bridge at\nabundera.ai/auth/audit?product=qrpro forwards the caller's cookie\nto this handler so the product enforces its own auth.\n\nQuery:\n  ?limit=N       page size (1-200, default 50)\n  ?offset=N      zero-based offset (default 0)\n  ?from=ISO      lower-bound ISO 8601 timestamp (inclusive)\n  ?to=ISO        upper-bound ISO 8601 timestamp (exclusive)\n  ?action=str    substring match on `action` column (case-insensitive)\n  ?actor=str     substring match on actor email (case-insensitive)\n  ?sort=asc|desc default desc\n\nReturns { entries, total, limit, offset, scope }.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "action": {
                      "description": "Response field detected from handler."
                    },
                    "entries": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "filters": {
                      "description": "Response field detected from handler."
                    },
                    "from": {
                      "description": "Response field detected from handler."
                    },
                    "limit": {
                      "description": "Response field detected from handler."
                    },
                    "offset": {
                      "description": "Response field detected from handler."
                    },
                    "sort": {
                      "description": "Response field detected from handler."
                    },
                    "to": {
                      "description": "Response field detected from handler."
                    },
                    "total": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error"
          },
          "429": {
            "description": "Rate limited"
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1Audit"
      }
    },
    "/api/v1/auth/logout": {
      "get": {
        "tags": [
          "auth"
        ],
        "summary": "Redirect to login page (logout via GET)",
        "description": "Redirects the caller to `https://abundera.ai/login`. There is no server-side session\nto invalidate, logout is handled client-side by the browser (cookie clearing). This\nendpoint exists so non-browser API callers and any GET-based logout links get a\nconsistent redirect rather than a 404.\n\nNo authentication is required. No request body is read. No cookies or tokens are\ninspected or cleared server-side.\n",
        "responses": {
          "302": {
            "description": "Redirect to abundera.ai login page",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "example": "https://abundera.ai/login"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1AuthLogout",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Redirect to login page (logout via POST)",
        "description": "Redirects the caller to `https://abundera.ai/login`. There is no server-side session\nto invalidate, logout is handled client-side by the browser (cookie clearing). This\nendpoint exists so non-browser API callers that POST a logout action get a consistent\nredirect rather than a 404.\n\nNo authentication is required. No request body is read. No cookies or tokens are\ninspected or cleared server-side.\n",
        "responses": {
          "302": {
            "description": "Redirect to abundera.ai login page",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "example": "https://abundera.ai/login"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1AuthLogout",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "description": "Request payload. Exact field set depends on the endpoint; check the handler source for body destructuring patterns."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "auth"
        ],
        "summary": "Redirect to login page (logout via DELETE)",
        "description": "Redirects the caller to `https://abundera.ai/login`. There is no server-side session\nto invalidate, logout is handled client-side by the browser (cookie clearing). This\nendpoint mirrors the POST and GET variants for REST clients that issue DELETE for\nsession teardown.\n\nNo authentication is required. No request body is read. No cookies or tokens are\ninspected or cleared server-side.\n",
        "responses": {
          "302": {
            "description": "Redirect to abundera.ai login page",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "example": "https://abundera.ai/login"
                }
              }
            }
          },
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "deleteV1AuthLogout",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/auth/me": {
      "get": {
        "tags": [
          "auth"
        ],
        "summary": "Get current authenticated user's profile",
        "description": "Returns the authenticated user's profile assembled from the local D1 mirror and\nselected claims from the abundera.ai JWT. Middleware upstream guarantees that\n`context.data.user` and `context.data.jwt` are populated before this handler\nruns, unauthenticated requests are rejected before reaching this function.\n\nThe response includes plan status, billing identifiers, free-tier verification\nunlock signals, and optional workspace/team context. Stripe fields are null for\nusers who have never entered a paid flow. The `active_codes_count` field allows\nthe account page to render a usage meter without a second round-trip.\n\nNo side effects. No rate limiting applied at this handler layer.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Authenticated user profile",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "User's unique identifier in the local D1 mirror"
                    },
                    "email": {
                      "type": "string",
                      "format": "email",
                      "description": "User's email address"
                    },
                    "plan": {
                      "type": "string",
                      "description": "Current plan name (e.g. free",
                      "pro)": null
                    },
                    "plan_status": {
                      "type": "string",
                      "description": "Subscription status (e.g. active",
                      "canceled": null,
                      "past_due)": null
                    },
                    "grace_until": {
                      "type": "string",
                      "nullable": true,
                      "description": "ISO 8601 timestamp until which a lapsed paid plan still grants access"
                    },
                    "keepalive_expires_at": {
                      "type": "string",
                      "nullable": true,
                      "description": "Expiry timestamp for a keepalive extension on the plan"
                    },
                    "current_team_id": {
                      "type": "string",
                      "nullable": true,
                      "description": "ID of the team currently selected in this user's session"
                    },
                    "workspace_label": {
                      "type": "string",
                      "nullable": true,
                      "description": "Display label for the current workspace"
                    },
                    "created_at": {
                      "type": "string",
                      "description": "ISO 8601 timestamp when the user account was created"
                    },
                    "stripe_customer_id": {
                      "type": "string",
                      "nullable": true,
                      "description": "Stripe customer ID; null if user has never entered a paid flow"
                    },
                    "stripe_sub_id": {
                      "type": "string",
                      "nullable": true,
                      "description": "Stripe subscription ID; null if no active or past subscription"
                    },
                    "active_codes_count": {
                      "type": "integer",
                      "description": "Number of active QR codes owned by this user; 0 if none"
                    },
                    "phone_verified": {
                      "type": "integer",
                      "enum": [
                        0,
                        1
                      ],
                      "description": "1 if the user has verified a phone number (free-tier unlock signal)"
                    },
                    "phone_last4": {
                      "type": "string",
                      "nullable": true,
                      "description": "Last four digits of the verified phone number"
                    },
                    "payment_method_verified": {
                      "type": "integer",
                      "enum": [
                        0,
                        1
                      ],
                      "description": "1 if the user has verified a payment method (free-tier unlock signal)"
                    },
                    "org_id": {
                      "type": "string",
                      "nullable": true,
                      "description": "Organization ID if this user belongs to an org"
                    },
                    "abundera_sub": {
                      "type": "string",
                      "nullable": true,
                      "description": "Subject claim (sub) from the abundera.ai JWT"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token, middleware rejected the request before the handler ran"
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1AuthMe"
      }
    },
    "/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/session/API key, or `{ authenticated:false, reason }`\n(200) when absent/invalid. Never cached.\n",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          }
        ],
        "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": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1AuthWhoami"
      }
    },
    "/api/v1/billing/checkout": {
      "post": {
        "tags": [
          "billing"
        ],
        "summary": "Create a Stripe Checkout Session for a subscription or one-time payment",
        "description": "Initiates a Stripe Checkout Session for the authenticated user, returning a\nredirect URL to the hosted Stripe payment page.\n\nThe endpoint supports two checkout modes:\n- **subscription** (default): used for recurring plans (Solo, Business, Team,\n  Agency, Keep-Alive monthly/annual). Stripe will reject a non-recurring price\n  passed through this path.\n- **payment**: used for one-time purchases (Keep-Alive Decade). The caller must\n  explicitly pass `mode: \"payment\"` to trigger this path.\n\nIf the user does not yet have a Stripe customer record, one is created via the\nfederated abundera.ai customer service and the resulting `stripe_customer_id` is\npersisted back to the D1 `users` table.\n\n**Grace carry-over**: if the user is currently within a 90-day post-cancellation\ngrace window and is purchasing a Keep-Alive product, the remaining grace days are\napplied as a Stripe `trial_period_days` (for subscriptions) or passed as\n`grace_carry_over_days` metadata (for one-time payments, applied at webhook time\nby extending `keepalive_expires_at`). This ensures users are not double-charged\nfor access they were already promised.\n\nAuthentication is required. The handler reads `data.user` populated by upstream\nmiddleware; requests without a valid session will not reach this handler.\n\nRequires the `STRIPE_SECRET_KEY` environment variable to be configured; returns\n503 otherwise. Stripe errors propagate with their original HTTP status codes.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "price_id"
                ],
                "properties": {
                  "price_id": {
                    "type": "string",
                    "description": "Stripe Price ID to check out. Must be a string beginning with `price_`. Stripe will enforce that the price is compatible with the chosen mode (recurring vs. one-time).\n",
                    "pattern": "^price_",
                    "example": "price_1OqXxxLkdIwHu7ixAbCdEfGh"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "subscription",
                      "payment"
                    ],
                    "default": "subscription",
                    "description": "Checkout mode. Pass `\"payment\"` for one-time purchases (e.g. Keep-Alive Decade). Omit or pass any other value to default to `\"subscription\"` for recurring plans.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "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.\n"
                    },
                    "id": {
                      "type": "string",
                      "description": "Stripe Checkout Session ID (cs_\u2026)."
                    },
                    "grace_carry_over_days": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Number of grace days credited as a trial (subscription) or passed as metadata (one-time payment). Zero when no grace carry-over applies.\n"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing price_id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_price_id"
                      ]
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Stripe is not configured on this deployment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "stripe_not_configured"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Stripe API error or unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "stripe_error"
                      ]
                    },
                    "message": {
                      "type": "string",
                      "description": "Error message propagated from the Stripe client or runtime."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1BillingCheckout"
      }
    },
    "/api/v1/billing/checkout-public": {
      "post": {
        "tags": [
          "billing"
        ],
        "summary": "Create a public Stripe Checkout session for a subscription",
        "description": "Unauthenticated entry point for the \"payment-as-invite\" cold-signup flow. A\nvisitor who has no account hits the pricing CTA and this endpoint hands back\na Stripe-hosted Checkout URL. No bearer token or session cookie is required;\nthe endpoint is explicitly exempted from the authentication middleware via\nPUBLIC_API_PATHS.\n\nOn successful Stripe Checkout completion the webhook calls the abundera.ai\nservice with `create_invite: true`, which mints a registration invite and\nsends an activation email to the address Stripe collected (either supplied\nhere or entered on the hosted page).\n\nOnly subscription-mode prices are accepted. One-time payment prices (e.g.\nthe Keep-Alive Decade product) require a signed-in account and will be\nrejected with `payment_mode_requires_signed_in`.\n\n**Coupon handling:** Only coupons explicitly whitelisted server-side\n(currently `FOUNDING_LAUNCH_2026`) are attached to the session. Any other\ncoupon value supplied by the client is silently dropped and the buyer pays\nfull price; the checkout is not aborted.\n\n**Rate limits:**\n- Per client IP: 20 requests/hour for standard (full-price) purchases;\n  5 requests/hour for cohort-discounted purchases (i.e. when the\n  `FOUNDING_LAUNCH_2026` coupon is requested).\n- Per email address (only when `email` is provided): 3 requests/hour.\n- Rate-limit infrastructure failures are fail-open so a KV outage never\n  blocks a legitimate purchase.\n\n**Email validation:** When `email` is provided and the service secret is\nconfigured, the address is checked against the abundera.ai email-validate\nservice (disposable domains, role prefixes, typos). This call is also\nfail-open; Stripe Radar acts as a secondary defence.\n\n**Abuse guard:** Cohort-discounted purchases additionally run a\nrefund/chargeback blocklist check before the Stripe API call. Full-price\npurchases skip this check entirely.\n\nSide effects on success: a Stripe Checkout Session is created and a row is\nwritten to the internal `stripe_events_log` store recording the outcome,\nsession ID, cohort, email, and request duration.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "price_id"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "maxLength": 254,
                    "description": "Optional customer email address. If omitted, Stripe Checkout collects it on the hosted page and the webhook retrieves it from the resulting Stripe customer object. Must be a well-formed email address if supplied; disposable or role-prefix addresses may be rejected by the hub validation service.\n"
                  },
                  "price_id": {
                    "type": "string",
                    "pattern": "^price_",
                    "description": "Stripe Price ID for the subscription plan to purchase. Must begin with \"price_\". Only subscription prices are accepted; supplying `mode: \"payment\"` alongside any price is rejected.\n"
                  },
                  "coupon": {
                    "type": "string",
                    "description": "Optional Stripe coupon ID to apply at checkout. Only values in the server-side whitelist (currently \"FOUNDING_LAUNCH_2026\") are honoured; all others are silently ignored.\n"
                  },
                  "mode": {
                    "type": "string",
                    "description": "If set to \"payment\" the request is immediately rejected with `payment_mode_requires_signed_in`. Any other value (or omitting the field) is accepted; the session is always created in subscription mode.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stripe Checkout Session created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "description": "Stripe-hosted Checkout URL to which the client should redirect the visitor.\n"
                    },
                    "id": {
                      "type": "string",
                      "description": "Stripe Checkout Session ID (cs_\u2026)."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request validation error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_email",
                        "email_rejected",
                        "invalid_price_id",
                        "payment_mode_requires_signed_in"
                      ],
                      "description": "Machine-readable error code."
                    },
                    "message": {
                      "type": "string",
                      "description": "Human-readable explanation. Present for `payment_mode_requires_signed_in`.\n"
                    },
                    "reason": {
                      "type": "string",
                      "description": "Present when `error` is `email_rejected`; describes why the address was refused (e.g. disposable domain, role prefix).\n"
                    },
                    "suggestion": {
                      "type": "string",
                      "nullable": true,
                      "description": "Present when `error` is `email_rejected`; may contain an alternative address suggestion from the validation service.\n"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Purchase blocked by abuse / blocklist check",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "ineligible"
                      ]
                    },
                    "message": {
                      "type": "string",
                      "description": "Explains that the purchase could not be processed and directs the user to contact support.\n"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "rate_limited",
                        "rate_limited_email"
                      ],
                      "description": "`rate_limited` for IP-based limits; `rate_limited_email` for per-email limits.\n"
                    },
                    "message": {
                      "type": "string",
                      "description": "Human-readable explanation. Present only for `rate_limited_email`.\n"
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Suggested number of seconds to wait before retrying."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Stripe API error or unexpected server failure",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "stripe_error"
                      ]
                    },
                    "message": {
                      "type": "string",
                      "description": "Error message propagated from the Stripe SDK or runtime."
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Stripe not configured on this deployment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "stripe_not_configured"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1BillingCheckoutPublic",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/billing/portal": {
      "post": {
        "tags": [
          "billing"
        ],
        "summary": "Create a Stripe Customer Portal session for self-serve billing management",
        "description": "Generates a short-lived Stripe-hosted Customer Portal URL for the authenticated\nuser, allowing them to self-serve plan changes, cancellations, payment method\nupdates, and invoice downloads. The portal session redirects back to the account\npage at https://pro.qr.abundera.ai/account/ on completion.\n\nAuthentication is required. The handler reads the resolved user from the request\ncontext (`data.user`), which is populated by upstream authentication middleware\nthat validates a bearer JWT. Unauthenticated requests will be rejected before\nreaching this handler.\n\nThe user must have previously completed at least one checkout via\n`/api/billing/checkout` so that a `stripe_customer_id` is on record. Users\nwithout a Stripe customer ID will receive a 400 error.\n\nThe endpoint delegates session creation to Stripe's API using the configured\n`STRIPE_SECRET_KEY` environment variable. If that variable is absent the\nendpoint returns 503. Any error propagated by the Stripe SDK is surfaced as a\nstripe_error response, with the HTTP status taken from the Stripe error object\nwhen available, falling back to 500.\n\nNo quota or rate-limit logic is implemented within this handler itself; limits\nare governed by Stripe's API and any platform-level Cloudflare rate rules.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "description": "Request payload. Exact field set depends on the endpoint; check the handler source for body destructuring patterns."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Portal session created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "url"
                  ],
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri",
                      "description": "The Stripe-hosted Customer Portal URL. Redirect the user to this URL to allow them to manage their subscription, payment methods, and invoices.\n"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "User does not have an associated Stripe customer ID",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_customer"
                      ],
                      "description": "Returned when the authenticated user has no stripe_customer_id on record, meaning they have not yet completed a checkout.\n"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected Stripe API error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "message"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "stripe_error"
                      ]
                    },
                    "message": {
                      "type": "string",
                      "description": "The error message propagated from the Stripe SDK."
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Stripe integration is not configured on this deployment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "stripe_not_configured"
                      ],
                      "description": "Returned when the STRIPE_SECRET_KEY environment variable is absent, indicating Stripe has not been configured for this deployment."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1BillingPortal"
      }
    },
    "/api/v1/billing/session": {
      "get": {
        "tags": [
          "billing"
        ],
        "summary": "Retrieve sanitized Stripe Checkout Session for post-checkout modal",
        "description": "Reads a Stripe Checkout Session by its session ID and returns a sanitized\nsubset of fields suitable for rendering a post-checkout success modal (ADR 084).\n\nAuth model: public, no end-user authentication required. The session_id\nparameter itself serves as the auth token, it is a random, single-use,\nshort-lived value issued by Stripe.\n\nHardening rules enforced server-side:\n- Rejects any session_id that does not begin with \"cs_\".\n- Rejects sessions where payment_status is not \"paid\" (prevents leaking\n  names or prices for abandoned checkouts).\n- Rejects sessions older than 1 hour (replay window protection).\n\nOn Stripe API failure the handler returns 404 rather than 500, keeping\nthe client's graceful fallback path tight.\n\nSide effects: every lookup attempt (success, skip, or failure) is written\nto the Stripe event log via logStripeEvent.\n",
        "parameters": [
          {
            "name": "session_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^cs_"
            },
            "description": "Stripe Checkout Session ID. Must begin with \"cs_\"."
          }
        ],
        "responses": {
          "200": {
            "description": "Session found, paid, and within the 1-hour replay window",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "description": "Stripe session status value"
                    },
                    "cohort": {
                      "type": "string",
                      "nullable": true,
                      "description": "Value of session.metadata.type; null if absent or not a string"
                    },
                    "plan_label": {
                      "type": "string",
                      "description": "Product name from the first line item; empty string if unavailable"
                    },
                    "price_label": {
                      "type": "string",
                      "description": "Pre-formatted price string (e.g. \"$99.00 USD/yr\"); empty string if unavailable"
                    },
                    "customer_name": {
                      "type": "string",
                      "nullable": true,
                      "description": "Full name from customer_details; null if unavailable"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "session_id query parameter is missing or does not start with \"cs_\"",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_session_id"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Session not found, fetch failed, payment not completed, or session expired",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found",
                        "not_paid",
                        "expired"
                      ]
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "STRIPE_SECRET_KEY environment variable is not configured",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_configured"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1BillingSession",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/byo/register": {
      "post": {
        "tags": [
          "byo"
        ],
        "summary": "Register or update a BYO delegation endpoint for a QR code",
        "description": "Registers a Bring-Your-Own (BYO) delegation endpoint and Ed25519 trust anchor\nfor a QR code owned by the authenticated user, implementing Patent QR-12 \u00a76.2\nprovisioning. Once registered, scan resolution for the code is delegated to the\ncustomer-controlled HTTPS endpoint; the centralized destination URL stored in the\nplatform is no longer authoritative for that code.\n\nIf a delegation record already exists for the given code, the operation performs\nan upsert: it overwrites the delegation URL, trust anchor public key, resets the\nsequence high-water mark to zero, and clears any cached destination URL and cache\nexpiry. The updated record is also mirrored into KV storage so the edge scan-time\nresolver picks up the change immediately.\n\nAuthentication is required. The caller must present a valid bearer JWT. The handler\nverifies that the authenticated user owns the target code by checking the `user_id`\ncolumn in the `codes` table; callers who do not own the code receive a 403 response.\n\nThe `delegation_url` must be an HTTPS URL (scheme check enforced). The\n`trust_anchor_pub` must be a base64-encoded raw Ed25519 public key (32 bytes),\nthough byte-length validation is not enforced server-side beyond type checking.\n\nNo explicit rate limits are documented in the handler code.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code_id",
                  "delegation_url",
                  "trust_anchor_pub"
                ],
                "properties": {
                  "code_id": {
                    "type": "string",
                    "description": "The unique identifier of the QR code for which BYO delegation is being registered. Must be owned by the authenticated user.\n"
                  },
                  "delegation_url": {
                    "type": "string",
                    "description": "The HTTPS URL of the customer-controlled .well-known delegation endpoint. Must begin with https://; http URLs are rejected.\n"
                  },
                  "trust_anchor_pub": {
                    "type": "string",
                    "description": "Base64-encoded raw Ed25519 public key (32 bytes) used as the trust anchor for verifying signed responses from the delegation endpoint.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Delegation registered or updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "code_id": {
                      "type": "string",
                      "description": "The code identifier for which delegation was registered."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request payload or delegation URL scheme",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_payload",
                        "delegation_url_must_be_https"
                      ]
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Authenticated user does not own the specified code",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "forbidden"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No code found with the given code_id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "code_not_found"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1ByoRegister"
      }
    },
    "/api/v1/byo/resolve": {
      "get": {
        "tags": [
          "byo"
        ],
        "summary": "Resolve current destination URL for a BYO-delegated QR code",
        "description": "Fetches the current destination URL for a QR code that uses BYO (Bring Your Own) delegation.\nImplements pull-mode delegated resolution per patent QR-12 \u00a76.3 and \u00a76.7.\n\nOn each call, the handler first checks whether a cached destination is still valid (per the\nstored TTL). If so, it returns the cached URL immediately without contacting the customer's\ndelegation endpoint. Otherwise, it fetches the delegation endpoint, validates the Ed25519\nsignature against the registered trust anchor, and bumps the sequence high-water mark to\nprevent replay attacks (\u00a76.10).\n\nOn a successful live fetch, the resolved destination and updated sequence number are written\nback to D1 and mirrored to KV for the worker's scan path.\n\nNo authentication is required for this endpoint. The trust anchor validation at the delegation\nlayer is the integrity mechanism.\n",
        "parameters": [
          {
            "name": "code_id",
            "in": "query",
            "required": true,
            "description": "The BYO code identifier to resolve. Must correspond to a registered delegation row.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Destination resolved (either from cache or live delegation fetch)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "destination",
                    "sequence",
                    "cached"
                  ],
                  "properties": {
                    "destination": {
                      "type": "string",
                      "description": "The current destination URL for the delegated code."
                    },
                    "sequence": {
                      "type": "integer",
                      "description": "The sequence high-water mark at time of resolution (replay protection)."
                    },
                    "cached": {
                      "type": "boolean",
                      "description": "True if the response was served from the local TTL cache; false if fetched live."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing required query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "missing_code_id"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No delegation record found for the given code_id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_delegated"
                      ]
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "Live delegation fetch failed (upstream endpoint error or signature validation failure)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Error token returned by the delegation resolver."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1ByoResolve",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/codes": {
      "get": {
        "tags": [
          "codes"
        ],
        "summary": "List QR codes in current scope",
        "description": "Returns a paginated, filterable list of dynamic QR codes belonging to the authenticated user's current scope.\n\nScope is resolved automatically: if the user is on a Team plan with an active team membership, codes are team-scoped (any member may list); otherwise codes are user-scoped (Solo, Business, or personal workspace).\n\nAll query parameters are optional. Results include a `short_url` field derived from each code's shortcode. The response also surfaces the resolved scope, the user's plan, and the plan's code limit (null for unlimited plans).\n\nRequires a valid bearer JWT (middleware sets `data.user`).\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "1-indexed page number. Defaults to 1.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "description": "Number of codes per page. Defaults to 25, maximum 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Full-text search across label, tags, shortcode, and destination URL (LIKE match, escaped).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "description": "Filter by exact whole-token tag value.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by code lifecycle status.",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "paused",
                "grace",
                "expired"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Sort order for results.",
            "schema": {
              "type": "string",
              "enum": [
                "created_desc",
                "created_asc",
                "label_asc",
                "label_desc"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of codes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "codes": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "description": "Serialized code row with appended short_url",
                        "properties": {
                          "short_url": {
                            "type": "string",
                            "format": "uri"
                          }
                        }
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "page": {
                          "type": "integer"
                        },
                        "page_size": {
                          "type": "integer"
                        },
                        "total": {
                          "type": "integer"
                        },
                        "total_pages": {
                          "type": "integer"
                        },
                        "has_more": {
                          "type": "boolean"
                        }
                      }
                    },
                    "filters": {
                      "type": "object",
                      "description": "Echo of parsed filter parameters applied to the query"
                    },
                    "scope": {
                      "type": "object",
                      "description": "Resolved scope context for the current request",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "team",
                            "user"
                          ]
                        },
                        "team_id": {
                          "type": "string",
                          "description": "Present when type is team"
                        },
                        "role": {
                          "type": "string",
                          "description": "Caller's role within the team; present when type is team"
                        },
                        "workspace_label": {
                          "type": "string",
                          "nullable": true,
                          "description": "Present when type is user"
                        }
                      }
                    },
                    "plan": {
                      "type": "string",
                      "description": "User's current plan identifier"
                    },
                    "plan_limit": {
                      "type": "integer",
                      "nullable": true,
                      "description": "Maximum codes allowed on this plan; null for unlimited"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error"
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1Codes"
      },
      "post": {
        "tags": [
          "codes"
        ],
        "summary": "Create a new dynamic QR code",
        "description": "Creates a new dynamic QR code in the authenticated user's current scope.\n\nScope is resolved the same way as GET: team-scoped when the user is on a Team plan with an active membership (admin role required to create); otherwise user-scoped.\n\nThe destination URL is required. All other fields are optional. A shortcode is auto-generated unless `custom_slug` is provided. Custom slugs require a paid plan, a minimum length determined by plan tier, and a per-plan cap on the number of custom slugs.\n\nFree-tier accounts are subject to additional guardrails before the code is written:\n- Rate limit: 5 codes per hour and 20 codes per day per user. Exceeding either returns 429.\n- Safe Browsing check: if `GOOGLE_SAFE_BROWSING_API_KEY` is configured, the destination URL is checked against Google Safe Browsing. A positive match returns 400 with `error: destination_flagged`.\n\nPaid tiers bypass both the rate limit and the Safe Browsing check.\n\nKeep-Alive plan accounts cannot create new codes and receive 402 with `error: plan_cannot_create`.\n\nRequires a valid bearer JWT (middleware sets `data.user`).\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Destination URL the QR code redirects to."
                  },
                  "label": {
                    "type": "string",
                    "description": "Human-readable name for the code."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "List of whole-token tag strings for filtering and organization."
                  },
                  "qr_type": {
                    "type": "string",
                    "description": "QR payload type identifier (e.g. url, vcard, wifi). Normalized by the handler."
                  },
                  "qr_data": {
                    "type": "object",
                    "description": "Structured payload data for non-URL QR types."
                  },
                  "style_json": {
                    "type": "object",
                    "description": "Visual styling options for the QR code (colors, dot shape, etc.)."
                  },
                  "logo_key": {
                    "type": "string",
                    "description": "R2 object key for a logo image to embed in the QR code."
                  },
                  "logo_svg": {
                    "type": "string",
                    "description": "Raw SVG string for a logo to embed in the QR code."
                  },
                  "frame_style": {
                    "type": "string",
                    "description": "Frame/border style identifier."
                  },
                  "frame_text": {
                    "type": "string",
                    "description": "CTA text rendered inside the frame."
                  },
                  "export_format": {
                    "type": "string",
                    "description": "Preferred export format (e.g. svg, png, pdf)."
                  },
                  "starts_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO 8601 datetime after which the code becomes active. Must be before expires_at if both are provided."
                  },
                  "expires_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO 8601 datetime after which the code stops redirecting."
                  },
                  "password": {
                    "type": "string",
                    "description": "Optional password that protects the redirect. Stored as a KDF hash; the plaintext is never persisted."
                  },
                  "custom_slug": {
                    "type": "string",
                    "description": "Desired vanity shortcode. Requires a paid plan, meets minimum length for the plan tier, must not be reserved for another user, and must not exceed the plan's custom-slug cap."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Code created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "True when the request succeeded."
                    },
                    "NOT": {
                      "description": "Response field detected from handler."
                    },
                    "codes": {
                      "description": "Response field detected from handler."
                    },
                    "created_at": {
                      "description": "Response field detected from handler."
                    },
                    "env": {
                      "description": "Response field detected from handler."
                    },
                    "error": {
                      "description": "Response field detected from handler."
                    },
                    "expiresAt": {
                      "description": "Response field detected from handler."
                    },
                    "expires_at": {
                      "description": "Response field detected from handler."
                    },
                    "exportFormat": {
                      "description": "Response field detected from handler."
                    },
                    "export_format": {
                      "description": "Response field detected from handler."
                    },
                    "field": {
                      "description": "Response field detected from handler."
                    },
                    "frameStyle": {
                      "description": "Response field detected from handler."
                    },
                    "frameText": {
                      "description": "Response field detected from handler."
                    },
                    "frame_style": {
                      "description": "Response field detected from handler."
                    },
                    "frame_text": {
                      "description": "Response field detected from handler."
                    },
                    "has_more": {
                      "description": "Response field detected from handler."
                    },
                    "id": {
                      "description": "Response field detected from handler."
                    },
                    "label": {
                      "description": "Response field detected from handler."
                    },
                    "logoKey": {
                      "description": "Response field detected from handler."
                    },
                    "logoSvg": {
                      "description": "Response field detected from handler."
                    },
                    "logo_key": {
                      "description": "Response field detected from handler."
                    },
                    "logo_svg": {
                      "description": "Response field detected from handler."
                    },
                    "malware": {
                      "description": "Response field detected from handler."
                    },
                    "max": {
                      "description": "Response field detected from handler."
                    },
                    "message": {
                      "description": "Response field detected from handler."
                    },
                    "minLen": {
                      "description": "Response field detected from handler."
                    },
                    "min_length": {
                      "description": "Response field detected from handler."
                    },
                    "now": {
                      "description": "Response field detected from handler."
                    },
                    "null": {
                      "description": "Response field detected from handler."
                    },
                    "page": {
                      "description": "Response field detected from handler."
                    },
                    "page_size": {
                      "description": "Response field detected from handler."
                    },
                    "pagination": {
                      "description": "Response field detected from handler."
                    },
                    "passwordKdf": {
                      "description": "Response field detected from handler."
                    },
                    "password_protected": {
                      "description": "Response field detected from handler."
                    },
                    "password_set_at": {
                      "description": "Response field detected from handler."
                    },
                    "phishing": {
                      "description": "Response field detected from handler."
                    },
                    "plan": {
                      "description": "Response field detected from handler."
                    },
                    "qrData": {
                      "description": "Response field detected from handler."
                    },
                    "qrType": {
                      "description": "Response field detected from handler."
                    },
                    "qr_data": {
                      "description": "Response field detected from handler."
                    },
                    "qr_type": {
                      "description": "Response field detected from handler."
                    },
                    "reason": {
                      "description": "Response field detected from handler."
                    },
                    "retryAfter": {
                      "description": "Response field detected from handler."
                    },
                    "retry_after": {
                      "description": "Response field detected from handler."
                    },
                    "short_url": {
                      "description": "Response field detected from handler."
                    },
                    "shortcode": {
                      "description": "Response field detected from handler."
                    },
                    "startsAt": {
                      "description": "Response field detected from handler."
                    },
                    "starts_at": {
                      "description": "Response field detected from handler."
                    },
                    "status": {
                      "description": "Response field detected from handler."
                    },
                    "styleJson": {
                      "description": "Response field detected from handler."
                    },
                    "style_json": {
                      "description": "Response field detected from handler."
                    },
                    "tags": {
                      "description": "Response field detected from handler."
                    },
                    "teamId": {
                      "description": "Response field detected from handler."
                    },
                    "team_id": {
                      "description": "Response field detected from handler."
                    },
                    "threatType": {
                      "description": "Response field detected from handler."
                    },
                    "threat_type": {
                      "description": "Response field detected from handler."
                    },
                    "total": {
                      "description": "Response field detected from handler."
                    },
                    "totalPages": {
                      "description": "Response field detected from handler."
                    },
                    "total_pages": {
                      "description": "Response field detected from handler."
                    },
                    "updated_at": {
                      "description": "Response field detected from handler."
                    },
                    "url": {
                      "description": "Response field detected from handler."
                    },
                    "used": {
                      "description": "Response field detected from handler."
                    },
                    "window": {
                      "description": "Response field detected from handler."
                    },
                    "withShortUrls": {
                      "description": "Response field detected from handler."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error or flagged destination",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_custom_slug",
                        "destination_flagged"
                      ]
                    },
                    "reason": {
                      "type": "string",
                      "description": "Present for invalid_custom_slug"
                    },
                    "threat_type": {
                      "type": "string",
                      "description": "Present for destination_flagged"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Plan restriction prevents the operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "plan_cannot_create",
                        "custom_slug_requires_paid_plan",
                        "custom_slug_too_short"
                      ]
                    },
                    "plan": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "min_length": {
                      "type": "integer",
                      "description": "Present for custom_slug_too_short"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Custom slug is already reserved or taken",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "shortcode_reserved"
                      ]
                    },
                    "reason": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Free-tier rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "rate_limited"
                      ]
                    },
                    "window": {
                      "type": "string",
                      "enum": [
                        "hour",
                        "day"
                      ]
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds until the bucket resets"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error"
          }
        },
        "operationId": "postV1Codes"
      }
    },
    "/api/v1/codes/bulk": {
      "post": {
        "tags": [
          "codes"
        ],
        "summary": "Perform bulk operations on multiple QR codes",
        "description": "Applies a single action to a batch of up to 500 QR codes in one request. Supported actions\nare: add_tag, remove_tag, set_tags, move, set_group, archive, restore, and delete.\n\nThe caller's scope is resolved automatically. In team scope, admin-level membership is required\nfor tag operations (add_tag, remove_tag, set_tags), archive, restore, and set_group; owner-level\nmembership is required for the move action. In personal scope all actions are permitted on the\ncaller's own codes.\n\nEach code ID in the batch is validated against the caller's current scope to prevent insecure\ndirect object references. IDs that cannot be found or fall outside scope are returned in the\nskipped array rather than aborting the entire batch, so valid rows are always processed.\n\nFor archive and delete actions the code status is set to 'grace' and a 90-day countdown\n(grace_until) is recorded. The restore action reverses this if the grace window has not yet\nexpired. Both status transitions are propagated to Cloudflare KV on a best-effort basis so that\nredirect behaviour stays consistent; KV failures are logged but do not roll back the D1 write.\n\nThe move action reassigns codes to a different team. The caller must own both the source and\ndestination teams. Passing null for team_id moves codes to the caller's personal scope.\n\nThe account must be in a writable state (requireWritableAccount). A valid bearer JWT is required.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action",
                  "ids"
                ],
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "add_tag",
                      "remove_tag",
                      "set_tags",
                      "move",
                      "set_group",
                      "archive",
                      "restore",
                      "delete"
                    ],
                    "description": "The bulk operation to perform on all listed code IDs."
                  },
                  "ids": {
                    "type": "array",
                    "description": "List of code IDs to operate on. Maximum 500 per request. Each ID must be a string shorter than 64 characters.",
                    "maxItems": 500,
                    "items": {
                      "type": "string",
                      "maxLength": 63
                    }
                  },
                  "args": {
                    "type": "object",
                    "description": "Action-specific arguments. Required shape depends on the chosen action.",
                    "properties": {
                      "tag": {
                        "type": "string",
                        "maxLength": 60,
                        "description": "Tag name to add or remove. Used by add_tag and remove_tag. Must not contain commas."
                      },
                      "tags": {
                        "type": "string",
                        "nullable": true,
                        "maxLength": 240,
                        "description": "Replacement CSV tag string, or null to clear all tags. Used by set_tags."
                      },
                      "team_id": {
                        "type": "string",
                        "nullable": true,
                        "description": "Destination team UUID for the move action, or null to move codes to personal scope. Caller must own the destination team."
                      },
                      "group_id": {
                        "type": "string",
                        "nullable": true,
                        "description": "Group ID within the caller's current scope for set_group, or null to unassign the group."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch processed. Returns the count of updated rows and details of any skipped rows.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated": {
                      "type": "integer",
                      "description": "Number of codes successfully updated."
                    },
                    "skipped": {
                      "type": "array",
                      "description": "Codes that were not updated, with the reason for each.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "The code ID that was skipped."
                          },
                          "reason": {
                            "type": "string",
                            "enum": [
                              "not_found_or_out_of_scope",
                              "already_terminated",
                              "not_in_grace",
                              "grace_expired",
                              "code_terminated",
                              "tag_already_present"
                            ],
                            "description": "Machine-readable reason the row was skipped."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body, action, IDs, or action-specific arguments.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_action",
                        "no_ids",
                        "batch_too_large",
                        "invalid_id_in_batch",
                        "invalid_team_id",
                        "invalid_tag",
                        "invalid_tags",
                        "invalid_group_id",
                        "group_not_in_scope",
                        "code_already_in_team"
                      ]
                    },
                    "action": {
                      "type": "string",
                      "description": "Echoed back when error is invalid_action."
                    },
                    "limit": {
                      "type": "integer",
                      "description": "Maximum allowed batch size. Present when error is batch_too_large."
                    },
                    "given": {
                      "type": "integer",
                      "description": "Number of IDs supplied by the caller. Present when error is batch_too_large."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller lacks the required role for the action in team scope, or does not own the destination team.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "destination_team_not_owned"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Destination team not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "destination_team_not_found"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1CodesBulk"
      }
    },
    "/api/v1/codes/check-slug": {
      "get": {
        "tags": [
          "codes"
        ],
        "summary": "Check if a custom shortcode slug is available",
        "description": "Preflight availability check for custom shortcode input fields. Returns an\navailability verdict so the UI can render inline feedback as the user types.\n\nDoes not claim the slug. The only mutation path is POST /api/codes.\n\nAuth is required, the middleware must populate data.user before this handler\nruns. Unauthenticated requests receive a 401.\n\nNo rate-limit charge is applied. This is a cheap read endpoint intended for\nUX polish, safe to call on every keystroke.\n\nWhen available is false, the reason field explains why:\n- invalid, empty input, bad alphabet, consecutive hyphens, or wrong length\n- plan_not_eligible, the user's plan (Free or KA) does not support custom slugs\n- tier_too_short, the slug is shorter than the user's plan minimum\n- cap_reached, the user has reached their tier's maximum custom slug count\n- reserved, the slug is in the reservations table and not granted to this user\n- taken, the slug already exists in KV (claimed by someone else)\n\nA reservation granted to the requesting user counts as available from that\nuser's perspective.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "query",
            "required": false,
            "description": "The candidate shortcode slug to check. Omitting or sending an empty value returns available=false with reason=invalid.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Availability verdict (all non-error paths return 200)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "available"
                  ],
                  "properties": {
                    "available": {
                      "type": "boolean"
                    },
                    "slug": {
                      "type": "string",
                      "description": "Echoed back only when available is true."
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "invalid",
                        "plan_not_eligible",
                        "tier_too_short",
                        "cap_reached",
                        "reserved",
                        "taken"
                      ],
                      "description": "Present only when available is false."
                    },
                    "detail": {
                      "type": "string",
                      "description": "Additional validation detail; present when reason is invalid and the slug failed format validation."
                    },
                    "plan": {
                      "type": "string",
                      "description": "The user's plan name; present when reason is plan_not_eligible or tier_too_short."
                    },
                    "min_length": {
                      "type": "integer",
                      "description": "Minimum slug length for the user's plan; present when reason is tier_too_short."
                    },
                    "max": {
                      "type": "integer",
                      "description": "Maximum custom slugs allowed for the user's plan; present when reason is cap_reached."
                    },
                    "used": {
                      "type": "integer",
                      "description": "Number of custom slugs the user has already claimed; present when reason is cap_reached."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "unauthorized"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1CodesCheckSlug"
      }
    },
    "/api/v1/codes/derive-shortcode": {
      "post": {
        "tags": [
          "codes"
        ],
        "summary": "Derive the deterministic shortcode for a URL without registering it",
        "description": "Computes and returns the shortcode that would be assigned to the given destination URL\nunder the authenticated user's identity, without persisting any record or consuming any\nquota. The derivation is fully deterministic: given the same URL and user identity the\nsame shortcode is always produced, provided the server-side deterministic salt\n(`SHORTCODE_DETERMINISTIC_SALT`) is configured.\n\nThis endpoint backs the verification-without-lookup property described in Patent QR-03.\nA caller or third-party auditor holding a claimed `(shortcode, url, userId)` tuple can\nconfirm its authenticity by calling this endpoint with the claimed URL and comparing the\nreturned shortcode against the claimed one.\n\nAuthentication is required. The handler reads `data.user.id` (populated by upstream\nmiddleware) to scope the derivation to the requesting user. Requests without a valid\nauthenticated session will be rejected before reaching this handler.\n\nIf the `SHORTCODE_DETERMINISTIC_SALT` environment variable is not configured on the\nserver, the endpoint returns `501 Not Implemented`. No shortcode is stored and no side\neffects occur regardless of the outcome.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "The destination URL for which to derive the shortcode. The value is normalised server-side before derivation; unnormalised and normalised forms of the same URL will therefore produce the same shortcode.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Shortcode successfully derived",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "shortcode"
                  ],
                  "properties": {
                    "shortcode": {
                      "type": "string",
                      "description": "The deterministic shortcode that would be assigned to the supplied URL under the requesting user's identity.\n"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The supplied URL is missing or could not be normalised",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_url"
                      ]
                    }
                  }
                }
              }
            }
          },
          "501": {
            "description": "Deterministic shortcode derivation is not enabled on this server",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "deterministic_disabled"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1CodesDeriveShortcode"
      }
    },
    "/api/v1/codes/import": {
      "post": {
        "tags": [
          "codes"
        ],
        "summary": "Import a free-site QR design as a new Pro dynamic code",
        "description": "Accepts a design state payload from the qr.abundera.ai free site and creates a\nnew Pro dynamic QR code owned by the authenticated user. This is the server-side\nentry point for the \"Save as Dynamic\" flow, typically invoked from the /go/save/\nlanding page.\n\nThe handler resolves the destination URL, normalises all visual design fields\n(style, logo, frame), generates a UUID for the new code record and a unique\nshortcode, writes the code row to the user's shard database, and publishes the\nshortcode-to-URL mapping to KV. On success it returns the new code's identifiers\nand all normalised fields so the caller can redirect the user straight to the\nedit page.\n\nAuthentication is enforced by upstream middleware which populates `data.user`;\nthe handler additionally calls `requireWritableAccount` to reject suspended or\nread-only accounts. `enforceCodeLimit` is called before insertion and will return\nan error if the user has reached their plan's code quota. No explicit rate limit\nbeyond the plan quota is applied at this layer.\n\nSide effects: one row inserted into the `codes` table in the user's shard\ndatabase; one KV key written for the shortcode resolver.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "destination"
                ],
                "properties": {
                  "destination": {
                    "type": "string",
                    "description": "The URL the shortcode should redirect to. Also accepted as `url` for compatibility with the /api/codes field name; `destination` takes precedence.\n"
                  },
                  "url": {
                    "type": "string",
                    "description": "Alias for `destination`. Used when the payload originates from a context that follows the /api/codes naming convention.\n"
                  },
                  "style": {
                    "description": "Visual style descriptor for the QR code. May be provided as a JSON object or a serialised JSON string. Also accepted as `style_json`. Contains sub-fields such as fg, bg, dot, and eye colours.\n",
                    "oneOf": [
                      {
                        "type": "object"
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "style_json": {
                    "description": "Alias for `style`. Accepted when the payload uses the API field name rather than the shorter carrier name.\n",
                    "oneOf": [
                      {
                        "type": "object"
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "logo_key": {
                    "type": "string",
                    "nullable": true,
                    "description": "Identifier for a preset logo to embed in the QR code centre (e.g. \"crypto.bitcoin\"). Null clears the preset logo.\n"
                  },
                  "logo_svg": {
                    "type": "string",
                    "nullable": true,
                    "description": "Raw SVG markup for a custom logo. Rarely present in v1 carrier payloads; more commonly added after import via the edit flow.\n"
                  },
                  "frame_style": {
                    "type": "string",
                    "nullable": true,
                    "description": "Visual frame style applied around the QR code (e.g. \"rounded\"). Null means no frame.\n"
                  },
                  "frame_text": {
                    "type": "string",
                    "nullable": true,
                    "description": "Call-to-action text rendered inside the frame (e.g. \"SCAN ME\"). Ignored when frame_style is null.\n"
                  },
                  "export_format": {
                    "type": "string",
                    "nullable": true,
                    "description": "Preferred raster/vector export format for the code image (e.g. \"png\", \"svg\").\n"
                  },
                  "label": {
                    "type": "string",
                    "nullable": true,
                    "description": "Human-readable label for the code, used for organisation within the dashboard (e.g. \"Spring campaign\").\n"
                  },
                  "tags": {
                    "type": "string",
                    "nullable": true,
                    "description": "Comma-separated tag string associated with the code (e.g. \"q2,print\").\n"
                  },
                  "v": {
                    "type": "integer",
                    "description": "Carrier format version number. Reserved for future use; currently ignored by the handler.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Dynamic code created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "UUID of the newly created code record."
                    },
                    "shortcode": {
                      "type": "string",
                      "description": "Generated shortcode token used in the redirect URL."
                    },
                    "short_url": {
                      "type": "string",
                      "description": "Fully qualified public short URL that resolves via the shortcode."
                    },
                    "url": {
                      "type": "string",
                      "description": "Normalised destination URL the shortcode redirects to."
                    },
                    "label": {
                      "type": "string",
                      "nullable": true,
                      "description": "Normalised label for the code."
                    },
                    "tags": {
                      "type": "string",
                      "nullable": true,
                      "description": "Normalised comma-separated tag string."
                    },
                    "style_json": {
                      "nullable": true,
                      "description": "Normalised style descriptor, or null if none was provided.",
                      "oneOf": [
                        {
                          "type": "object"
                        },
                        {
                          "type": "string"
                        }
                      ]
                    },
                    "logo_key": {
                      "type": "string",
                      "nullable": true,
                      "description": "Normalised preset logo key, or null."
                    },
                    "logo_svg": {
                      "type": "string",
                      "nullable": true,
                      "description": "Normalised custom logo SVG markup, or null."
                    },
                    "frame_style": {
                      "type": "string",
                      "nullable": true,
                      "description": "Normalised frame style, or null."
                    },
                    "frame_text": {
                      "type": "string",
                      "nullable": true,
                      "description": "Normalised frame text, or null."
                    },
                    "export_format": {
                      "type": "string",
                      "nullable": true,
                      "description": "Normalised export format, or null."
                    },
                    "created_at": {
                      "type": "integer",
                      "description": "Unix timestamp (seconds) at which the code was created."
                    },
                    "updated_at": {
                      "type": "integer",
                      "description": "Unix timestamp (seconds) at which the code was last updated."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error or plan limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error identifier."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or account is not writable",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error identifier."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Account suspended or write access denied",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error identifier."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ],
                      "description": "Constant error token indicating an unhandled server-side failure."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1CodesImport"
      }
    },
    "/api/v1/codes/preflight": {
      "post": {
        "tags": [
          "codes"
        ],
        "summary": "Evaluate preflight verdict for QR code stylistic parameters",
        "description": "Accepts a candidate set of QR code stylistic parameters and returns a\npreflight verdict indicating whether the combination is safe to proceed\nwith for rendering or export. This endpoint implements Patent QR-07 and\nis intended to be called by clients before invoking any server-side\nrender or export endpoint.\n\nThe handler does not perform any rendering, export, or persistent\nside-effects, it only evaluates the provided parameters against\npreflight rules defined in the shared `evaluatePreflight` utility.\nServer-side renderers are expected to gate via `gateExportOrThrow()`\nindependently; this endpoint exposes the same verdict to clients so\nthey can surface feedback early.\n\nNo authentication is required by this handler. There are no documented\nrate limits or quotas enforced at the handler level.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "fgColor",
                  "bgColor"
                ],
                "properties": {
                  "fgColor": {
                    "type": "string",
                    "description": "Foreground (module) color of the QR code, used by the preflight evaluator to assess contrast and legibility.\n"
                  },
                  "bgColor": {
                    "type": "string",
                    "description": "Background color of the QR code, used together with fgColor to evaluate contrast ratio and stylistic safety.\n"
                  },
                  "errorCorrection": {
                    "type": "string",
                    "description": "Error correction level for the QR code (e.g. L, M, Q, H). Influences whether logo coverage or other degradations are tolerable.\n"
                  },
                  "logoCoverageFraction": {
                    "type": "number",
                    "description": "Fraction of the QR code area obscured by an overlaid logo, expressed as a value between 0 and 1. Evaluated against error correction capacity.\n"
                  },
                  "quietZoneModules": {
                    "type": "integer",
                    "description": "Number of modules reserved for the quiet zone border around the QR code. Preflight may enforce a minimum value.\n"
                  },
                  "finderPatternIntegrity": {
                    "type": "number",
                    "description": "A measure of how intact or stylistically modified the finder patterns are, used to assess scannability risk.\n"
                  },
                  "timingPatternIntegrity": {
                    "type": "number",
                    "description": "A measure of how intact or stylistically modified the timing patterns are, used to assess scannability risk.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Preflight passed, parameters are considered safe to proceed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true,
                      "description": "Indicates the preflight evaluation passed."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Preflight failed, one or more parameters are unsafe or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "Indicates the preflight evaluation did not pass."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1CodesPreflight",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/codes/print-sheet": {
      "post": {
        "tags": [
          "codes"
        ],
        "summary": "Solve QR + fallback-text layout for a commercial label format",
        "description": "Computes a per-cell layout solution for placing a QR code and an accessible\nfallback text string within a commercial label bounding box. The caller may\nidentify the target label by a named format (e.g. `avery-5160`) or supply an\nexplicit bounding box in points; exactly one of the two must be present.\n\nThe solver jointly determines the largest feasible QR module scale and\nfallback typographic scale that simultaneously satisfy three constraint sets:\nISO/IEC 18004 quiet-zone requirements, WCAG 2.2 AA minimum text size, and a\ndecode-distance minimum-module-size constraint derived from Patent QR-09.\nAn optional padding value in points is subtracted from all four sides of the\nbounding box before the solve.\n\nWhen a feasible solution exists the endpoint returns HTTP 200 with the full\nlayout geometry. When no feasible solution exists, for example because the\nbounding box is too small for the requested module count or the fallback text\ncannot meet WCAG minimums, the endpoint returns HTTP 422 with a structured\nviolation report describing which constraints were infeasible and by how much.\n\nNo authentication is required; the handler reads the request body directly\nwithout inspecting credentials or session state. No persistent side effects\nare produced; the operation is a pure computation.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "fallbackText",
                  "qrModuleCount"
                ],
                "properties": {
                  "labelFormat": {
                    "type": "string",
                    "enum": [
                      "avery-5160"
                    ],
                    "description": "Named commercial label format that defines the bounding box. Mutually exclusive with `boundingBox`; supply one or the other.\n"
                  },
                  "boundingBox": {
                    "type": "object",
                    "description": "Explicit bounding box in points. Mutually exclusive with `labelFormat`; supply one or the other.\n",
                    "properties": {
                      "width_pt": {
                        "type": "number",
                        "description": "Width of the usable label area in points."
                      },
                      "height_pt": {
                        "type": "number",
                        "description": "Height of the usable label area in points."
                      }
                    }
                  },
                  "fallbackText": {
                    "type": "string",
                    "description": "Human-readable fallback string to be typeset alongside the QR code. Must satisfy WCAG 2.2 AA minimum text-size constraints within the resolved bounding box.\n"
                  },
                  "qrModuleCount": {
                    "type": "integer",
                    "minimum": 21,
                    "description": "Number of modules along one side of the QR symbol (version parameter). Must be at least 21, corresponding to QR version 1.\n"
                  },
                  "padding_pt": {
                    "type": "number",
                    "description": "Uniform inset in points applied to all four sides of the bounding box before the layout solve. Defaults to zero when omitted.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Feasible layout solution found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "No feasible layout solution, structured constraint-violation report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1CodesPrintSheet",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/codes/print-sheet-pdf": {
      "post": {
        "tags": [
          "codes"
        ],
        "summary": "Render a print-ready QR label sheet as a PDF",
        "description": "Generates a multi-cell, print-ready PDF sheet where each cell contains a QR\ncode image (supplied by the caller as a base64-encoded PNG) and an accessible\nfallback typographic representation. Cell positions are computed by the\nserver-side layout solver (Patent QR-09), which jointly satisfies ISO/IEC 18004\nquiet-zone requirements, WCAG 2.2 AA minimum text size, and decode-distance\nminimum module size.\n\nBefore any PDF is produced the request passes through the QR-07 export gate\n(gateExportOrThrow). If any styling parameter fails preflight, low contrast\nbetween foreground and background colours, error-correction capacity overflow\ncaused by logo coverage, or broken finder/timing patterns, the gate raises a\n422 with a structured violations array and PDF generation is aborted. There is\nno bypass for this gate.\n\nThe layout solver is invoked next. If the requested label format or bounding\nbox produces an infeasible layout (e.g. the QR module size falls below the\ndecode-distance minimum) a 422 is returned with a structured violations array\nbefore any PDF work begins.\n\nOn success the response body is a raw PDF binary (application/pdf) with a\nContent-Disposition attachment header suggesting the filename\n\"qr-print-sheet.pdf\". The response is not cached (Cache-Control: no-store).\n\nNo authentication check is performed by this handler; access control must be\nenforced upstream (e.g. at the Cloudflare Pages routing layer).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "fallbackText",
                  "qrModuleCount",
                  "qrPngB64",
                  "styleParams"
                ],
                "properties": {
                  "labelFormat": {
                    "type": "string",
                    "enum": [
                      "avery-5160"
                    ],
                    "description": "Named label stock. Determines the bounding box dimensions fed to the layout solver when boundingBox is omitted.\n"
                  },
                  "boundingBox": {
                    "type": "object",
                    "description": "Explicit cell bounding box in points. Used by the layout solver when labelFormat is not supplied or needs to be overridden.\n",
                    "properties": {
                      "width_pt": {
                        "type": "number",
                        "description": "Cell width in PDF points."
                      },
                      "height_pt": {
                        "type": "number",
                        "description": "Cell height in PDF points."
                      }
                    }
                  },
                  "fallbackText": {
                    "type": "string",
                    "description": "Human-readable text placed below (or beside) the QR image in each cell as an accessible typographic fallback. Passed directly to the layout solver, which computes its position and font size.\n"
                  },
                  "qrModuleCount": {
                    "type": "integer",
                    "description": "Number of QR modules along one side of the symbol. Used by the layout solver to compute the minimum rendered side length that satisfies decode-distance requirements.\n"
                  },
                  "qrPngB64": {
                    "type": "string",
                    "description": "Base64-encoded PNG of the pre-rendered QR code image. May be prefixed with a data-URI header (data:image/png;base64,\u2026). Must be at least 16 characters.\n"
                  },
                  "padding_pt": {
                    "type": "number",
                    "description": "Optional additional intra-cell padding in points forwarded to the layout solver.\n"
                  },
                  "styleParams": {
                    "type": "object",
                    "description": "Styling inputs evaluated by the QR-07 preflight gate. If any field fails the gate's checks the request is rejected with 422 before PDF generation begins.\n",
                    "properties": {
                      "fgColor": {
                        "type": "string",
                        "description": "Foreground (module) colour of the QR code."
                      },
                      "bgColor": {
                        "type": "string",
                        "description": "Background colour of the QR code."
                      },
                      "errorCorrection": {
                        "type": "string",
                        "description": "QR error-correction level (e.g. L, M, Q, H). Used by the preflight gate to validate logo coverage headroom.\n"
                      },
                      "logoCoverageFraction": {
                        "type": "number",
                        "description": "Fraction of the QR symbol area obscured by a logo overlay (0\u20131). Checked against error-correction capacity.\n"
                      },
                      "quietZoneModules": {
                        "type": "integer",
                        "description": "Width of the quiet zone in modules. Validated against ISO/IEC 18004 minimums.\n"
                      },
                      "finderPatternIntegrity": {
                        "type": "boolean",
                        "description": "Whether the three finder patterns are intact. A false value causes preflight to reject the export.\n"
                      },
                      "timingPatternIntegrity": {
                        "type": "boolean",
                        "description": "Whether the horizontal and vertical timing patterns are intact. A false value causes preflight to reject the export.\n"
                      }
                    }
                  },
                  "pageSize": {
                    "type": "string",
                    "enum": [
                      "letter",
                      "a4"
                    ],
                    "default": "letter",
                    "description": "Output page size. \"letter\" is 612 \u00d7 792 pt; \"a4\" is 595.28 \u00d7 841.89 pt. Defaults to \"letter\" when omitted or unrecognised.\n"
                  },
                  "sheetCols": {
                    "type": "integer",
                    "description": "Number of cell columns per page. When omitted the handler calculates the maximum that fits the page width given the solved cell width.\n"
                  },
                  "sheetRows": {
                    "type": "integer",
                    "description": "Number of cell rows per page. When omitted the handler calculates the maximum that fits the page height given the solved cell height.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Print-ready PDF sheet generated successfully.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "The qrPngB64 field is absent or too short.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "missing_qr_png_b64"
                      ]
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Export rejected. Either the QR-07 preflight gate detected a styling violation, or the QR-09 layout solver determined that the requested label dimensions are infeasible. The violations array is present for layout failures; preflight failures return the structure raised by gateExportOrThrow.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "layout_infeasible"
                      ],
                      "description": "Present when the layout solver returned an infeasible result. Absent for preflight failures, which return their own error structure.\n"
                    },
                    "violations": {
                      "type": "array",
                      "description": "Structured list of constraint violations returned by the layout solver. Present when error is layout_infeasible.\n",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1CodesPrintSheetPdf",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/codes/sign-issuance": {
      "post": {
        "tags": [
          "codes"
        ],
        "summary": "Produce and anchor a signed issuance record for a QR code",
        "description": "Creates a canonical content hash for the supplied QR code parameters, signs it with the\nprovided Ed25519 private key (supplied as a JWK, in production this originates from an\nHSM-bound environment key; in staging it may be an in-memory keypair), and publishes the\nresulting signed record to at least two mutually-independent anchor repositories (Rekor\nand a TSA), satisfying the plurality requirement of Patent QR-02 claim 1.\n\nThis is the canonical RTP entry point for every code creation in production and must be\ncalled after `deriveShortcode()` has produced a valid short code. The issued timestamp is\nset server-side to the current Unix epoch (seconds) at the moment the request is processed.\n\nThe destination URL must be an absolute HTTPS URL; plain HTTP or protocol-relative URLs\nare rejected with a 400. All five body fields are required; missing or wrongly-typed\nfields are rejected before any cryptographic work is attempted.\n\nNo authentication header is enforced by this handler itself, access control is expected\nto be enforced at the network or gateway layer before requests reach this function. There\nare no explicit rate limits implemented in the handler code.\n\nSide effects: the signed issuance record is published to external anchor services (Rekor\ntransparency log and a TSA); these publications are irreversible.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "short_code",
                  "destination_url",
                  "org_id",
                  "private_key_jwk",
                  "public_key_b64"
                ],
                "properties": {
                  "short_code": {
                    "type": "string",
                    "description": "The short code identifier previously produced by deriveShortcode()."
                  },
                  "destination_url": {
                    "type": "string",
                    "description": "The full absolute destination URL the QR code resolves to. Must begin with \"https://\"; HTTP or protocol-relative URLs are rejected.\n"
                  },
                  "org_id": {
                    "type": "string",
                    "description": "Identifier of the organisation that owns this QR code."
                  },
                  "private_key_jwk": {
                    "type": "object",
                    "description": "An Ed25519 private key expressed as a JSON Web Key (JWK). Used to sign the canonical content hash via the Web Crypto API. In production this value comes from an HSM-bound environment secret.\n"
                  },
                  "public_key_b64": {
                    "type": "string",
                    "description": "The raw 32-byte Ed25519 public key corresponding to private_key_jwk, encoded as a Base64 string. Included in the anchor publication payload.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed issuance record created and anchored successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "issued_at": {
                      "type": "integer",
                      "description": "Unix epoch timestamp (seconds) at which the record was issued, set server-side."
                    },
                    "content_hash_hex": {
                      "type": "string",
                      "description": "Hex-encoded canonical content hash covering short_code, destination_url, org_id, and issued_at."
                    },
                    "signature_b64": {
                      "type": "string",
                      "description": "Base64-encoded Ed25519 signature over the canonical content hash."
                    },
                    "short_code": {
                      "type": "string",
                      "description": "The short code echoed back from the request body."
                    },
                    "destination_url": {
                      "type": "string",
                      "description": "The destination URL echoed back from the request body."
                    },
                    "org_id": {
                      "type": "string",
                      "description": "The organisation identifier echoed back from the request body."
                    },
                    "plurality_satisfied": {
                      "type": "boolean",
                      "description": "Whether at least two mutually-independent anchor repositories confirmed publication."
                    },
                    "anchors": {
                      "type": "object",
                      "description": "Responses from each anchor repository.",
                      "properties": {
                        "rekor": {
                          "type": "object",
                          "description": "Response payload returned by the Rekor transparency log anchor."
                        },
                        "tsa": {
                          "type": "object",
                          "description": "Response payload returned by the TSA (timestamp authority) anchor."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request payload or signing failure",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_payload",
                        "destination_must_be_https",
                        "sign_failed"
                      ],
                      "description": "Machine-readable error code. \"invalid_payload\" indicates a missing or wrongly-typed body field. \"destination_must_be_https\" indicates the destination_url does not begin with \"https://\". \"sign_failed\" indicates the Web Crypto import or signing operation failed.\n"
                    },
                    "detail": {
                      "type": "string",
                      "description": "Human-readable error detail. Present only when error is \"sign_failed\"; contains the stringified exception message."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1CodesSignIssuance",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/codes/stale": {
      "get": {
        "tags": [
          "codes"
        ],
        "summary": "List stale codes that haven't been scanned recently",
        "description": "Returns active and paused codes that have never been scanned, or whose last scan is older\nthan the requested threshold. Codes in grace or expired states are excluded.\n\nThe `days` parameter sets the staleness window (clamped to 7\u2013365, default 90). The `limit`\nparameter caps the result set (clamped to 1\u2013500, default 100).\n\nUseful for library hygiene: identify codes worth archiving when they show no scan activity\nover a meaningful period.\n\nRequires a valid bearer JWT. The query runs against the data shard for the authenticated\nuser's resolved scope (personal or team).\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Staleness threshold in days. Clamped to 7\u2013365. Defaults to 90.",
            "schema": {
              "type": "integer",
              "minimum": 7,
              "maximum": 365,
              "default": 90
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of codes to return. Clamped to 1\u2013500. Defaults to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Stale codes returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "codes": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "stale_days": {
                      "type": "integer",
                      "description": "The effective staleness threshold used for this query"
                    },
                    "count": {
                      "type": "integer",
                      "description": "Number of codes returned"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1CodesStale"
      }
    },
    "/api/v1/codes/tags": {
      "get": {
        "tags": [
          "codes"
        ],
        "summary": "List distinct tags with counts for the caller's scope",
        "description": "Returns the tag facet for the authenticated caller's current scope: a list of distinct\ntags with their QR code counts, sorted by count descending (tiebreak: name ascending),\ncapped at 50 entries.\n\nThe response also includes `plain_tags` (unnamespaced tags) and `grouped_tags`\n(namespace-prefixed tags, grouped by prefix) derived from the same facet data.\n\nScope is resolved automatically: if the caller belongs to a team, the facet covers\nthat team's codes; otherwise it covers only the caller's personal codes.\n\nResults are KV-cached for 5 minutes per scope. Stale counts affect only the facet\nsidebar ordering, never correctness of code data. The `cached` field in the response\nindicates whether the result came from cache.\n\nRequires a valid bearer JWT. No explicit rate limit beyond the auth layer.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Tag facet returned (live or from cache)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tags": {
                      "type": "array",
                      "description": "Full tag facet, sorted by count DESC then name ASC, max 50 entries",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string",
                            "description": "Tag name"
                          },
                          "count": {
                            "type": "integer",
                            "description": "Number of codes carrying this tag"
                          }
                        }
                      }
                    },
                    "plain_tags": {
                      "type": "array",
                      "description": "Subset of tags without a namespace prefix",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "count": {
                            "type": "integer"
                          }
                        }
                      }
                    },
                    "grouped_tags": {
                      "type": "object",
                      "description": "Tags with a namespace prefix, keyed by prefix string",
                      "additionalProperties": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string"
                            },
                            "count": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    },
                    "scope": {
                      "type": "object",
                      "description": "Scope under which the facet was computed",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "team",
                            "user"
                          ]
                        },
                        "team_id": {
                          "type": "string",
                          "description": "Present only when type is \"team\""
                        }
                      }
                    },
                    "cached": {
                      "type": "boolean",
                      "description": "True if the response was served from KV cache"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or token invalid"
          },
          "500": {
            "description": "Unexpected server error"
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1CodesTags"
      }
    },
    "/api/v1/codes/top": {
      "get": {
        "tags": [
          "codes"
        ],
        "summary": "Top-N leaderboard of most-scanned codes",
        "description": "Returns the highest-scan codes for the caller's current scope over a configurable\ntrailing window. Drives the \"most active codes\" dashboard tile.\n\nScope is resolved from the authenticated user's team or personal account context.\nThe query runs against the appropriate shard database for that scope.\n\nBoth `days` and `limit` are clamped server-side: `days` to [1, 365] (default 30),\n`limit` to [1, 50] (default 10). Non-numeric or missing values fall back to defaults.\n\nRequires a valid bearer JWT. No write side effects.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Trailing window in days to aggregate scan counts over. Clamped to [1, 365], default 30.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365,
              "default": 30
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of codes to return. Clamped to [1, 50], default 10.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Leaderboard returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "top",
                    "days",
                    "limit"
                  ],
                  "properties": {
                    "top": {
                      "type": "array",
                      "description": "Ordered list of top codes by scan count, highest first.",
                      "items": {
                        "type": "object"
                      }
                    },
                    "days": {
                      "type": "integer",
                      "description": "Effective trailing window used for the query."
                    },
                    "limit": {
                      "type": "integer",
                      "description": "Effective maximum count used for the query."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1CodesTop"
      }
    },
    "/api/v1/codes/{id}": {
      "get": {
        "tags": [
          "codes"
        ],
        "summary": "Fetch a single QR code by ID",
        "description": "Returns a single QR code record the authenticated user has access to. Scope is\ndetermined by the user's current team context: if the user has an active team\n(`current_team_id`), the code must belong to that team and any member may read\nit. Otherwise, only personal codes (those with no `team_id`) owned by the caller\nare accessible.\n\nRequires a valid bearer JWT. Returns the serialized code fields plus a\n`short_url` constructed from the code's shortcode.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique ID of the QR code to retrieve."
          }
        ],
        "responses": {
          "200": {
            "description": "Code found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "shortcode": {
                      "type": "string"
                    },
                    "short_url": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "tags": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "qr_type": {
                      "type": "string"
                    },
                    "qr_data": {
                      "type": "string"
                    },
                    "style_json": {
                      "type": "string"
                    },
                    "logo_key": {
                      "type": "string"
                    },
                    "logo_svg": {
                      "type": "string"
                    },
                    "frame_style": {
                      "type": "string"
                    },
                    "frame_text": {
                      "type": "string"
                    },
                    "export_format": {
                      "type": "string"
                    },
                    "password_protected": {
                      "type": "boolean"
                    },
                    "starts_at": {
                      "type": "integer"
                    },
                    "expires_at": {
                      "type": "integer"
                    },
                    "created_at": {
                      "type": "integer"
                    },
                    "updated_at": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Code not found or not accessible in current scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1CodesId"
      },
      "patch": {
        "tags": [
          "codes"
        ],
        "summary": "Update a QR code's fields or move it between scopes",
        "description": "Partially updates a QR code. All body fields are optional; at least one\nmust be present or a `no_fields` error is returned.\n\n**Auth and scope**: requires a valid bearer JWT. Scope is resolved from the\ncaller's current team context. On a team plan, the caller must hold `admin`\nor `owner` role to mutate. Moving a code out of a team requires `owner` role\non the source team; moving into another team requires the caller to own that\ndestination team.\n\n**Keep-Alive plan restrictions**: destination URL edits may be blocked for\nKeep-Alive plan users (plan-specific policy). Design customization fields\n(`qr_type`, `qr_data`, `style_json`, `logo_key`, `logo_svg`, `frame_style`,\n`frame_text`, `export_format`) are entirely blocked on Keep-Alive; only URL,\nlabel, and tag edits are permitted.\n\n**Status constraints**: codes in `grace` or `expired` status cannot be edited.\n\n**Password**: send an empty string or `null` to clear the password gate; send\na non-empty string to set or rotate it. The verifier is never returned.\n\n**Schedule**: `starts_at` and `expires_at` are independently nullable ISO-8601\ntimestamps. After normalization, the pair is validated so `starts_at` < `expires_at`.\n\nOn success, writes an audit-log entry, fires any configured webhooks, and\nupdates the KV shortcode entry to reflect the new destination.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique ID of the QR code to update."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "New destination URL."
                  },
                  "label": {
                    "type": "string",
                    "description": "Human-readable label for the code."
                  },
                  "tags": {
                    "type": "string",
                    "description": "Comma-separated tag string."
                  },
                  "qr_type": {
                    "type": "string",
                    "description": "QR payload type (blocked on Keep-Alive plan)."
                  },
                  "qr_data": {
                    "type": "string",
                    "description": "Serialized QR payload data (blocked on Keep-Alive plan)."
                  },
                  "style_json": {
                    "type": "string",
                    "description": "JSON string describing QR visual style (blocked on Keep-Alive plan)."
                  },
                  "logo_key": {
                    "type": "string",
                    "description": "R2 key for the logo asset (blocked on Keep-Alive plan)."
                  },
                  "logo_svg": {
                    "type": "string",
                    "description": "Inline SVG string for the logo (blocked on Keep-Alive plan)."
                  },
                  "frame_style": {
                    "type": "string",
                    "description": "Frame style identifier (blocked on Keep-Alive plan)."
                  },
                  "frame_text": {
                    "type": "string",
                    "description": "Text rendered inside the QR frame (blocked on Keep-Alive plan)."
                  },
                  "export_format": {
                    "type": "string",
                    "description": "Preferred export format (blocked on Keep-Alive plan)."
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "paused"
                    ],
                    "description": "Set the code to active or paused. Grace/expired codes reject this field."
                  },
                  "starts_at": {
                    "type": "string",
                    "nullable": true,
                    "description": "ISO-8601 activation timestamp, or null to clear."
                  },
                  "expires_at": {
                    "type": "string",
                    "nullable": true,
                    "description": "ISO-8601 expiry timestamp, or null to clear."
                  },
                  "password": {
                    "type": "string",
                    "nullable": true,
                    "description": "Set or rotate the password gate (non-empty string), or clear it\n(empty string or null). Never echoed in the response.\n"
                  },
                  "team_id": {
                    "type": "string",
                    "nullable": true,
                    "description": "Move the code to a different team (UUID string), to the caller's\npersonal workspace (null), or leave unchanged (omit the field).\n"
                  },
                  "group_id": {
                    "type": "string",
                    "nullable": true,
                    "description": "Assign the code to a group in the current scope (UUID string),\nremove it from any group (null), or leave unchanged (omit the field).\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Code updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "shortcode": {
                      "type": "string"
                    },
                    "short_url": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "tags": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "qr_type": {
                      "type": "string"
                    },
                    "qr_data": {
                      "type": "string"
                    },
                    "style_json": {
                      "type": "string"
                    },
                    "logo_key": {
                      "type": "string"
                    },
                    "logo_svg": {
                      "type": "string"
                    },
                    "frame_style": {
                      "type": "string"
                    },
                    "frame_text": {
                      "type": "string"
                    },
                    "export_format": {
                      "type": "string"
                    },
                    "password_protected": {
                      "type": "boolean"
                    },
                    "starts_at": {
                      "type": "integer"
                    },
                    "expires_at": {
                      "type": "integer"
                    },
                    "created_at": {
                      "type": "integer"
                    },
                    "updated_at": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error or no fields provided",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_fields",
                        "invalid_status",
                        "invalid_team_id",
                        "invalid_group_id",
                        "code_already_in_team"
                      ]
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Plan restriction, Keep-Alive users cannot edit design fields",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "keepalive_readonly_field"
                      ]
                    },
                    "plan": {
                      "type": "string",
                      "enum": [
                        "keepalive"
                      ]
                    },
                    "field": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller does not own the destination team",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "destination_team_not_owned"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Code not found, or destination team not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found",
                        "destination_team_not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Code is in grace or expired status and cannot be edited",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "code_not_editable"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "patchV1CodesId"
      },
      "delete": {
        "tags": [
          "codes"
        ],
        "summary": "Soft-delete a QR code (enters 90-day grace period)",
        "description": "Soft-deletes a QR code by setting its status to `grace` and recording a\n`grace_until` timestamp 90 days in the future. The shortcode KV entry is\nupdated to reflect the grace state so the redirect worker can serve an\nappropriate response during the grace window. A hard delete is not performed\nimmediately.\n\n**Auth and scope**: requires a valid bearer JWT. On a team plan, the caller\nmust hold `admin` or `owner` role. Codes already in `grace` or `expired`\nstatus cannot be deleted again.\n\nOn success, writes an audit-log entry and fires any configured webhooks.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique ID of the QR code to soft-delete."
          }
        ],
        "responses": {
          "200": {
            "description": "Code entered grace period",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "grace"
                      ]
                    },
                    "grace_until": {
                      "type": "integer",
                      "description": "Unix timestamp when the grace period ends."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Code not found or not accessible in current scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Code is already in grace or expired status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "code_not_editable"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "deleteV1CodesId"
      }
    },
    "/api/v1/codes/{id}/analytics": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get scan analytics for a QR code",
        "description": "Returns aggregated scan analytics for a single QR code owned by the authenticated user or their team.\n\nSupports configurable time ranges (7d, 30d, 90d, 1y, 3y) capped by the user's plan retention limit. Daily granularity is available on all plans; hourly granularity requires Team or Agency tier and is capped at a 7-day window.\n\nThe response includes a zero-filled timeseries, per-country breakdown, per-device breakdown, per-city breakdown (top 50), and a total scan count. Country, device, and city dimensions are always drawn from daily rollup data regardless of the requested granularity. All dimensional aggregates are passed through k-anonymity noise-floor suppression, small cohorts are folded into an \"Other\" bucket.\n\nScope: if the user belongs to a team, any team member may read analytics for any code owned by that team. Otherwise only the code owner may access the data.\n\nAuthentication: bearer JWT required (reads `data.user` set by auth middleware).\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "QR code identifier."
          },
          {
            "name": "range",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "7d",
                "30d",
                "90d",
                "1y",
                "3y"
              ],
              "default": "30d"
            },
            "description": "Requested time range. Silently capped to the plan's maximum retention window."
          },
          {
            "name": "granularity",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "day",
                "hour"
              ],
              "default": "day"
            },
            "description": "Bucket size for the timeseries. Hourly granularity requires Team or Agency plan and is limited to a 7-day window."
          }
        ],
        "responses": {
          "200": {
            "description": "Analytics data returned successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "range": {
                      "type": "string",
                      "description": "The requested range string (e.g. \"30d\"), echoed back."
                    },
                    "days": {
                      "type": "integer",
                      "description": "Effective number of days used after plan-cap is applied."
                    },
                    "granularity": {
                      "type": "string",
                      "enum": [
                        "day",
                        "hour"
                      ],
                      "description": "Effective granularity used."
                    },
                    "total": {
                      "type": "integer",
                      "description": "Total scan count over the effective range."
                    },
                    "timeseries": {
                      "type": "array",
                      "description": "Zero-filled ordered array of scan counts per bucket.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "bucket": {
                            "type": "string",
                            "description": "Day bucket in YYYY-MM-DD format (daily) or YYYY-MM-HH format (hourly)."
                          },
                          "scans": {
                            "type": "integer",
                            "description": "Scan count for this bucket."
                          }
                        }
                      }
                    },
                    "by_country": {
                      "type": "array",
                      "description": "Scan counts by country code, with small cohorts folded into an Other bucket.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string",
                            "description": "ISO country code or \"Other\"."
                          },
                          "total": {
                            "type": "integer"
                          }
                        }
                      }
                    },
                    "by_device": {
                      "type": "array",
                      "description": "Scan counts by device type, with small cohorts folded into an Other bucket.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string",
                            "description": "Device type string or \"Other\"."
                          },
                          "total": {
                            "type": "integer"
                          }
                        }
                      }
                    },
                    "by_city": {
                      "type": "array",
                      "description": "Top 50 cities by scan volume, noise-floor suppressed. Empty array on shards where scan_cities table does not yet exist.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string",
                            "description": "City name or \"Other\"."
                          },
                          "country": {
                            "type": "string",
                            "description": "ISO country code for the city."
                          },
                          "total": {
                            "type": "integer"
                          }
                        }
                      }
                    },
                    "privacy": {
                      "type": "object",
                      "description": "Privacy disclosure metadata describing which dimensions had k-anonymity applied."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Plan does not support hourly granularity.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "insufficient_plan"
                      ]
                    },
                    "required": {
                      "type": "string",
                      "enum": [
                        "team_or_agency"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Code not found or not owned by the authenticated user or their team.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1CodesIdAnalytics"
      }
    },
    "/api/v1/codes/{id}/analytics.csv": {
      "get": {
        "tags": [
          "codes"
        ],
        "summary": "Export analytics for a single QR code as CSV",
        "description": "Returns a multi-section CSV file containing scan analytics for the specified QR code over a\nrequested time window. The export mirrors the three sections shown in the dashboard UI:\ntimeseries (per-day or per-hour scan counts), scans by country, and scans by device type.\n\nThe authenticated user must own the code, either directly (no team) or via the resolved team\nscope. Returns 404 if the code is not found under the caller's scope.\n\nThe time window is controlled by the `range` query parameter. The actual window returned may\nbe shorter than requested if the caller's plan limits the analytics history (e.g. free plan\ncaps at 30 days). Hourly granularity is only available on plans that include it; otherwise the\nendpoint always returns daily buckets regardless of the `granularity` parameter.\n\nCountry and device rows have k-anonymity applied (same transformation as the JSON analytics\nendpoint, closing the CSV bypass path). Dimensional values below the threshold are folded into\na catch-all label.\n\nThe response carries `Content-Disposition: attachment` with a filename that includes the\nshortcode, range key, and export date so multiple downloads do not overwrite each other.\n`Cache-Control: no-store` is set on all responses.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Internal ID of the QR code to export analytics for."
          },
          {
            "name": "range",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "7d",
                "30d",
                "90d",
                "1y",
                "3y"
              ],
              "default": "30d"
            },
            "description": "Time window for the export. Mapped to 7, 30, 90, 365, or 1095 days respectively. The effective window is capped by the plan's analytics history limit.\n"
          },
          {
            "name": "granularity",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "day",
                "hour"
              ],
              "default": "day"
            },
            "description": "Bucket size for the timeseries section. `hour` is only honoured when the caller's plan includes hourly analytics; otherwise `day` is used.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "CSV export returned successfully.",
            "headers": {
              "Content-Disposition": {
                "schema": {
                  "type": "string"
                },
                "description": "attachment; filename=\"analytics_{shortcode}_{range}_{date}.csv\"\n"
              },
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "Multi-section CSV. Comment lines begin with `#` and carry export metadata\n(shortcode, label, window_days, granularity, generated_at, privacy notice).\nThree data sections follow, each preceded by a `## section_name` line and a\nheader row, separated by blank lines:\n\n`## timeseries`, columns: bucket (ISO date or ISO datetime), scans (integer)\n\n`## by_country`, columns: country (ISO 3166-1 alpha-2 or catch-all label), scans (integer)\n\n`## by_device`, columns: device_type (string or catch-all label), scans (integer)\n"
                }
              }
            }
          },
          "404": {
            "description": "QR code not found under the caller's scope.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "const": "code not found"
                }
              }
            }
          }
        },
        "operationId": "getV1CodesIdAnalyticsCsv"
      }
    },
    "/api/v1/codes/{id}/deep-stats": {
      "get": {
        "tags": [
          "codes"
        ],
        "summary": "Get deep analytics stats for a single QR code",
        "description": "Returns a comprehensive analytics payload for one QR code identified by `id`. The code must belong to the authenticated user's personal scope or a team they belong to; a 404 is returned if the code does not exist or is out of scope.\n\nThe response includes:\n- **narrative**, 2\u20134 sentence auto-synthesized insights summary\n- **deltas**, w7/w30/w90 scan windows with current, prior, and delta_pct, plus a 30-day daily sparkline array\n- **projection**, linear regression over the sparkline forecasting next-30-day scans, slope per day, and how many days the fit is based on (null if fewer than 7 data points)\n- **anomaly**, z-score of yesterday's scan count vs the 14-day baseline; null when the baseline is flat or the z-score is within \u00b13.0\n- **velocity**, current and prior 7-day per-day rate, growth percentage, tier label (viral/hot/normal/slow/dead), and estimated half-life days\n- **bullet**, actual vs target per-day rate derived from the trailing 23-day baseline, ratio, and band (good/ok/bad); null when the baseline is zero\n- **day_of_life**, daily scan series anchored at the first scan date (up to 60 days)\n- **yoy**, last 30 days vs the same 30-day window one year prior\n- **peer_benchmark**, this code's 30-day scan percentile vs peer codes in the same scope; null when the scope has five or fewer peers\n- **top_country**, top scanning country code to support weather-overlay calls\n\nRequires a valid bearer JWT. No explicit rate limit is applied at this endpoint beyond the auth layer.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The numeric or string identifier of the QR code record."
          }
        ],
        "responses": {
          "200": {
            "description": "Deep stats payload for the requested code",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "shortcode": {
                          "type": "string"
                        },
                        "label": {
                          "type": "string"
                        }
                      }
                    },
                    "narrative": {
                      "type": "string",
                      "description": "Auto-synthesized 2\u20134 line insights summary for this code."
                    },
                    "deltas": {
                      "type": "object",
                      "properties": {
                        "windows": {
                          "type": "object",
                          "properties": {
                            "w7": {
                              "type": "object",
                              "properties": {
                                "current": {
                                  "type": "integer"
                                },
                                "prior": {
                                  "type": "integer"
                                },
                                "delta_pct": {
                                  "type": "number",
                                  "nullable": true
                                }
                              }
                            },
                            "w30": {
                              "type": "object",
                              "properties": {
                                "current": {
                                  "type": "integer"
                                },
                                "prior": {
                                  "type": "integer"
                                },
                                "delta_pct": {
                                  "type": "number",
                                  "nullable": true
                                }
                              }
                            },
                            "w90": {
                              "type": "object",
                              "properties": {
                                "current": {
                                  "type": "integer"
                                },
                                "prior": {
                                  "type": "integer"
                                },
                                "delta_pct": {
                                  "type": "number",
                                  "nullable": true
                                }
                              }
                            }
                          }
                        },
                        "sparkline": {
                          "type": "array",
                          "items": {
                            "type": "integer"
                          },
                          "description": "30-element array of daily scan counts, oldest first."
                        }
                      }
                    },
                    "projection": {
                      "nullable": true,
                      "type": "object",
                      "properties": {
                        "next_30d_scans": {
                          "type": "integer"
                        },
                        "slope_per_day": {
                          "type": "number"
                        },
                        "based_on_days": {
                          "type": "integer"
                        }
                      }
                    },
                    "anomaly": {
                      "nullable": true,
                      "type": "object",
                      "properties": {
                        "direction": {
                          "type": "string",
                          "enum": [
                            "surge",
                            "drop"
                          ]
                        },
                        "z_score": {
                          "type": "number"
                        },
                        "latest_scans": {
                          "type": "integer"
                        },
                        "baseline_mean": {
                          "type": "number"
                        },
                        "baseline_days": {
                          "type": "integer"
                        }
                      }
                    },
                    "velocity": {
                      "type": "object",
                      "properties": {
                        "current_per_day": {
                          "type": "number"
                        },
                        "prior_per_day": {
                          "type": "number"
                        },
                        "growth_pct": {
                          "type": "number",
                          "nullable": true
                        },
                        "tier": {
                          "type": "string",
                          "enum": [
                            "viral",
                            "hot",
                            "normal",
                            "slow",
                            "dead"
                          ]
                        },
                        "half_life_days": {
                          "type": "integer",
                          "nullable": true
                        }
                      }
                    },
                    "bullet": {
                      "nullable": true,
                      "type": "object",
                      "properties": {
                        "actual_per_day": {
                          "type": "number"
                        },
                        "target_per_day": {
                          "type": "number"
                        },
                        "ratio": {
                          "type": "number"
                        },
                        "band": {
                          "type": "string",
                          "enum": [
                            "good",
                            "ok",
                            "bad"
                          ]
                        }
                      }
                    },
                    "day_of_life": {
                      "nullable": true,
                      "type": "array",
                      "items": {
                        "type": "integer"
                      },
                      "description": "Daily scan counts starting from the first scan day, up to 60 entries."
                    },
                    "yoy": {
                      "nullable": true,
                      "type": "object",
                      "description": "Year-over-year comparison of the last 30 days vs the same window one year prior."
                    },
                    "peer_benchmark": {
                      "nullable": true,
                      "type": "object",
                      "description": "Percentile rank of this code's 30-day scans vs peers in the same scope; null if five or fewer peers exist."
                    },
                    "top_country": {
                      "nullable": true,
                      "type": "string",
                      "description": "ISO country code of the top scanning country for this code."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Code not found or out of scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1CodesIdDeepStats"
      }
    },
    "/api/v1/codes/{id}/heatmaps": {
      "get": {
        "tags": [
          "codes"
        ],
        "summary": "Fetch all heatmap datasets for a QR code",
        "description": "Returns three privacy-safe binned analytics datasets for a single QR code in one round-trip, so the edit page can render calendar, hourly-DOW, and geo heatmaps without multiple requests.\n\n**Calendar** (`calendar`): daily scan counts for the requested window (default 365 days, max 1095). Zero-filled so the calendar grid is always dense. The window is capped by the caller's plan retention limit.\n\n**Hourly \u00d7 DOW grid** (`hourly_dow`): a 24\u00d77 matrix of scan counts by day-of-week (0=Sunday through 6=Saturday) and hour (0\u201323), derived from the `scans_hourly` table. Only populated for Team and Agency plans; Free and Pro callers receive an empty array. The DOW window is always the last 90 days regardless of the `days` parameter.\n\n**Geo** (`geo`): top countries by scan count. Countries below a noise floor of 5 scans are folded into an \"Other\" bucket. At most 50 country rows are returned before noise-floor folding.\n\nScope resolution delegates to `resolveCodeScope`, which grants access to any team member for team-owned codes and to the owning user for personal codes. The code must be visible in the caller's resolved scope or a 404 is returned.\n\nRequires a valid bearer JWT (the handler reads `data.user` populated by upstream auth middleware).\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID of the QR code to fetch heatmap data for."
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 365,
              "minimum": 1,
              "maximum": 1095
            },
            "description": "Number of days of history for the calendar dataset. Capped at 1095 and further capped by the caller's plan retention window. Invalid or missing values fall back to 365.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Heatmap datasets returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code_id": {
                      "type": "string",
                      "description": "The QR code ID echoed back from the path parameter."
                    },
                    "calendar": {
                      "type": "array",
                      "description": "Zero-filled daily scan counts for the requested window.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "day": {
                            "type": "string",
                            "format": "date",
                            "description": "Calendar date in YYYY-MM-DD format."
                          },
                          "scans": {
                            "type": "integer",
                            "description": "Number of scans on this day."
                          }
                        }
                      }
                    },
                    "hourly_dow": {
                      "type": "array",
                      "description": "24\u00d77 scan count grid by day-of-week and hour. Empty array for plans without hourly analytics (Free, Pro).\n",
                      "items": {
                        "type": "object",
                        "properties": {
                          "dow": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 6,
                            "description": "Day of week (0=Sunday, 6=Saturday)."
                          },
                          "hour": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 23,
                            "description": "Hour of day in UTC (0\u201323)."
                          },
                          "scans": {
                            "type": "integer",
                            "description": "Number of scans in this DOW/hour bucket."
                          }
                        }
                      }
                    },
                    "hourly_available": {
                      "type": "boolean",
                      "description": "Whether the caller's plan includes hourly analytics. False means hourly_dow is always empty."
                    },
                    "geo": {
                      "type": "array",
                      "description": "Top countries by scan count with noise floor applied. Countries below 5 scans are folded into an \"Other\" entry.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "country": {
                            "type": "string",
                            "description": "ISO 3166-1 alpha-2 country code, or \"Other\" for the noise-floor bucket."
                          },
                          "scans": {
                            "type": "integer",
                            "description": "Number of scans attributed to this country."
                          }
                        }
                      }
                    },
                    "window_days": {
                      "type": "integer",
                      "description": "Actual number of days used for the calendar dataset after all caps are applied."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Code not found or not visible in the caller's scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1CodesIdHeatmaps"
      }
    },
    "/api/v1/codes/{id}/lifetime": {
      "get": {
        "tags": [
          "codes"
        ],
        "summary": "Get lifetime campaign stats and ROI for a QR code",
        "description": "Returns lifetime scan statistics for the specified QR code: first scan timestamp, latest scan timestamp, peak day and its scan count, number of days with any scan activity, and total scans. Returns zeroed values when the code has never been scanned.\n\nAlso returns an `roi` block. When the code has a `print_cost_cents` value set, the block includes cost-per-scan, cost-per-print (if `print_count` is set), and a break-even projection. The break-even estimate targets $0.10 cost-per-scan and projects days to reach that threshold at the current scan rate. When no print cost is stored, the `roi` block contains only `print_cost_cents: null` and `print_count`.\n\nRequires a valid bearer JWT. The code must belong to the authenticated user's personal scope or an active team scope. Returns 404 when the code does not exist or is not accessible to the caller.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "QR code ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Lifetime stats and ROI",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "first_day": {
                      "type": "string",
                      "nullable": true,
                      "description": "ISO date of first scan",
                      "or null if never scanned": null
                    },
                    "last_day": {
                      "type": "string",
                      "nullable": true,
                      "description": "ISO date of most recent scan"
                    },
                    "peak_day": {
                      "type": "string",
                      "nullable": true,
                      "description": "ISO date of the day with the highest scan count"
                    },
                    "peak_day_count": {
                      "type": "integer",
                      "nullable": true,
                      "description": "Scan count on the peak day"
                    },
                    "days_with_scans": {
                      "type": "integer",
                      "description": "Number of distinct days with at least one scan"
                    },
                    "total_scans": {
                      "type": "integer",
                      "description": "Total scan count across all time"
                    },
                    "roi": {
                      "type": "object",
                      "description": "ROI block. Fields vary based on whether print_cost_cents is set.",
                      "properties": {
                        "print_cost_cents": {
                          "type": "integer",
                          "nullable": true,
                          "description": "Total print run cost in cents",
                          "or null if not set": null
                        },
                        "print_count": {
                          "type": "integer",
                          "nullable": true,
                          "description": "Number of copies printed",
                          "or null if not set": null
                        },
                        "cost_per_scan_usd": {
                          "type": "number",
                          "nullable": true,
                          "description": "Cost in USD per scan to date; null when total_scans is zero"
                        },
                        "cost_per_print_usd": {
                          "type": "number",
                          "nullable": true,
                          "description": "Cost in USD per printed copy; null when print_count is not set"
                        },
                        "break_even": {
                          "nullable": true,
                          "description": "Break-even projection toward $0.10 cost-per-scan target; null when already at or past target or insufficient data",
                          "type": "object",
                          "properties": {
                            "target_scans": {
                              "type": "integer",
                              "description": "Total scans needed to reach $0.10 cost-per-scan"
                            },
                            "still_needed": {
                              "type": "integer",
                              "description": "Additional scans still required"
                            },
                            "days_to_hit": {
                              "type": "integer",
                              "description": "Estimated days to reach target at current scan rate"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Code not found or not accessible",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1CodesIdLifetime"
      },
      "put": {
        "tags": [
          "codes"
        ],
        "summary": "Set print run cost and quantity for ROI tracking",
        "description": "Stores the print run's total cost (in cents) and printed copy count on the code row. These values are used by the GET endpoint to compute cost-per-scan, cost-per-print, and break-even projections without re-prompting the customer.\n\nBoth fields accept null or empty string to clear a previously stored value. Non-null values are coerced to non-negative integers; invalid numeric input is treated as zero.\n\nRequires a valid bearer JWT. The code must belong to the authenticated user's personal scope or an active team scope.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "QR code ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "print_cost_cents": {
                    "nullable": true,
                    "description": "Total cost of the print run in cents. Pass null or empty string to clear.",
                    "oneOf": [
                      {
                        "type": "integer"
                      },
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "print_count": {
                    "nullable": true,
                    "description": "Number of copies in the print run. Pass null or empty string to clear.",
                    "oneOf": [
                      {
                        "type": "integer"
                      },
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Values stored",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "print_cost_cents": {
                      "type": "integer",
                      "nullable": true
                    },
                    "print_count": {
                      "type": "integer",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Code not found or not accessible",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "putV1CodesIdLifetime"
      }
    },
    "/api/v1/codes/{id}/public-stats": {
      "get": {
        "tags": [
          "codes"
        ],
        "summary": "Get public stats sharing state for a code",
        "description": "Returns the current public-stats sharing state for the specified QR code. Indicates whether\na shareable token exists and, if so, provides the share URL.\n\nThe authenticated user must own the code (personal scope: `user_id` match with no team) or\nbe a member of the team that owns the code (team scope). Team role is not restricted for\nreads, any member may check the sharing state.\n\nRequires a valid bearer JWT passed as an Authorization header.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The QR code ID to check public-stats sharing state for."
          }
        ],
        "responses": {
          "200": {
            "description": "Current sharing state for the code",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean",
                      "description": "Whether a valid share token exists."
                    },
                    "shareUrl": {
                      "type": "string",
                      "description": "The public share URL if sharing is enabled; absent or null when disabled."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Code not found or not owned by the caller",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1CodesIdPublicStats"
      },
      "post": {
        "tags": [
          "codes"
        ],
        "summary": "Enable public stats sharing for a code",
        "description": "Mints a new shareable token that allows anyone with the resulting URL to view analytics for\nthe specified QR code without authenticating. If a token already exists, this operation\nreplaces it.\n\nThe authenticated user must own the code. In team context the caller must hold the `owner`\nrole; non-owners receive a 403. Ownership in personal context requires that the code's\n`user_id` matches the authenticated user and the code has no team.\n\nOn success the action `code.public_stats.enable` is written to the audit log.\n\nRequires a valid bearer JWT.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The QR code ID to enable public stats sharing for."
          }
        ],
        "responses": {
          "200": {
            "description": "Token minted; sharing is now enabled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    },
                    "shareUrl": {
                      "type": "string",
                      "description": "The newly minted public share URL."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller lacks the required team owner role",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Code not found or not owned by the caller",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error during token minting",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1CodesIdPublicStats",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "description": "Request payload. Exact field set depends on the endpoint; check the handler source for body destructuring patterns."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "codes"
        ],
        "summary": "Revoke all public stats share tokens for a code",
        "description": "Revokes every existing shareable token for the specified QR code, disabling public access\nto its analytics. Any previously distributed share URLs stop working immediately.\n\nThe authenticated user must own the code. In team context the caller must hold the `owner`\nrole; non-owners receive a 403. Ownership in personal context requires that the code's\n`user_id` matches the authenticated user and the code has no team.\n\nOn success the action `code.public_stats.disable` is written to the audit log.\n\nRequires a valid bearer JWT.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The QR code ID whose share tokens should be revoked."
          }
        ],
        "responses": {
          "200": {
            "description": "All tokens revoked; sharing is now disabled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller lacks the required team owner role",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Code not found or not owned by the caller",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error during token revocation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "deleteV1CodesIdPublicStats"
      }
    },
    "/api/v1/config/short-url-base": {
      "get": {
        "tags": [
          "config"
        ],
        "summary": "Get the short URL base for the current user or org",
        "description": "Returns the base URL the dashboard should use when displaying or previewing short URLs, for example, the slug-prefix label on the new-code form (\"aqr.net/<slug>\") and the availability hint.\n\nResolution order: if the authenticated user belongs to a white-label org with a verified custom short domain, that domain is returned as the base. Otherwise the environment-configured default (env.SHORT_URL_BASE, falling back to https://aqr.net/) is returned.\n\nAuth is cookie-based, matching the /api/auth/me session model. Every authenticated user can call this endpoint regardless of plan; it is used to render the dashboard correctly.\n",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Short URL base resolved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "base",
                    "base_label",
                    "org_domain"
                  ],
                  "properties": {
                    "base": {
                      "type": "string",
                      "description": "Canonical base URL including trailing slash, e.g. \"https://aqr.net/\""
                    },
                    "base_label": {
                      "type": "string",
                      "description": "Host and path without scheme, for inline display, e.g. \"aqr.net/\""
                    },
                    "org_domain": {
                      "type": "string",
                      "nullable": true,
                      "description": "Verified white-label short domain for the user's org, or null if none"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1ConfigShortUrlBase"
      }
    },
    "/api/v1/groups": {
      "get": {
        "tags": [
          "groups"
        ],
        "summary": "List groups visible in the current scope",
        "description": "Returns all groups accessible to the authenticated user within their current scope. The\nscope is resolved automatically: if the user has an active team context, team-scoped groups\nare returned; otherwise personal groups belonging to the user are returned.\n\nEach group object includes a `code_count` field reflecting the number of QR codes assigned\nto that group. Any authenticated team member may call this endpoint regardless of their role.\nPersonal-scope callers must be the owner of the account.\n\nAuthentication is required. The caller's identity is read from `data.user`, which is\npopulated by an upstream middleware that validates the bearer JWT.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Groups retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "groups": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "code_count": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error"
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1Groups"
      },
      "post": {
        "tags": [
          "groups"
        ],
        "summary": "Create a new group in the current scope",
        "description": "Creates a group within the resolved scope of the authenticated user. Scope resolution\nfollows the same logic as the GET operation: team scope is used when the user has an active\nteam, and personal scope otherwise.\n\nIn team scope, the caller must hold the `admin` role or higher; members with lesser roles\nwill receive a 403 response. In personal scope, the user must own the account and the\naccount must be in a writable state (enforced by `requireWritableAccount`, which may reject\nsuspended or over-quota accounts).\n\nOn success the newly created group is returned with HTTP 201 and an audit log entry with\naction `group.create` is written, recording the group id, name, acting user, and team id.\n\nThe request body is parsed as JSON; if the body is missing or malformed it is treated as\nan empty object and default values are applied by the underlying `createGroup` helper.\n\nAuthentication is required via a bearer JWT resolved upstream into `data.user`.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Display name for the new group."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Group created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "group": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request (e.g. account not writable or validation failure)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller lacks the required role (admin or higher) in the team scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error"
          }
        },
        "operationId": "postV1Groups"
      }
    },
    "/api/v1/groups/{id}": {
      "get": {
        "tags": [
          "groups"
        ],
        "summary": "Fetch a single group by ID with its code count",
        "description": "Retrieves a single group record scoped to the authenticated user's personal or team\ncontext. The scope is resolved from the authenticated user's session, personal users\nsee only their own groups, while team-scoped users see only groups belonging to that\nteam.\n\nIf a group matching the given `id` is found within the resolved scope, the response\nincludes all group fields plus a `code_count` integer reflecting how many QR codes\nare currently assigned to that group.\n\nAuthentication is required via bearer JWT. No role restriction applies for reads ,\nany member of the team (or the personal user) may call this endpoint.\n\nNo mutations or side effects are performed.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the group to retrieve."
          }
        ],
        "responses": {
          "200": {
            "description": "Group found and returned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "group": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "description": {
                          "type": "string"
                        },
                        "color": {
                          "type": "string"
                        },
                        "user_id": {
                          "type": "string"
                        },
                        "code_count": {
                          "type": "integer",
                          "description": "Number of QR codes assigned to this group"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Group not found within the resolved scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1GroupsId"
      },
      "patch": {
        "tags": [
          "groups"
        ],
        "summary": "Rename or update a group's description or color",
        "description": "Partially updates a group identified by `id` within the authenticated user's resolved\nscope. Accepted body fields are `name`, `description`, and `color`; only the fields\npresent in the request body are applied (the underlying `updateGroup` helper performs\na partial update).\n\nAuthentication is required via bearer JWT. For team-scoped requests the caller must\nhold the `admin` role or higher; personal-scope callers must own the group (enforced\nimplicitly by the scoped lookup).\n\nThe account must not be in a read-only / locked state (`requireWritableAccount` is\nenforced). If the account is non-writable the handler returns a structured error with\nthe status and body set by the plan library.\n\nOn success an audit log entry (`group.patch`) is written recording which fields were\nchanged.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the group to update."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "New display name for the group."
                  },
                  "description": {
                    "type": "string",
                    "description": "New human-readable description for the group."
                  },
                  "color": {
                    "type": "string",
                    "description": "New color value for the group (e.g. a hex color string)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Group updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "group": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "description": {
                          "type": "string"
                        },
                        "color": {
                          "type": "string"
                        },
                        "user_id": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller lacks the required admin role for a team-scoped request, or account is non-writable",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Group not found within the resolved scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected internal error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "patchV1GroupsId"
      },
      "delete": {
        "tags": [
          "groups"
        ],
        "summary": "Delete a group and reassign its codes to no group",
        "description": "Permanently deletes the group identified by `id` within the authenticated user's\nresolved scope. All QR codes previously assigned to this group are reassigned to\n`null` (no group) rather than being deleted themselves.\n\nAuthentication is required via bearer JWT. The account must not be in a read-only /\nlocked state (`requireWritableAccount` is enforced).\n\n**Role and ownership rules:**\n- Personal scope: the group must belong to the authenticated user (enforced by the\n  scoped lookup; no extra ownership check is required).\n- Team scope: the caller must hold the `admin` role or higher. Even among admins,\n  deletion is further restricted to the group's original creator or the team owner.\n  If the caller is an admin but neither the creator nor the team owner, a `403` with\n  `delete_requires_creator_or_owner` is returned.\n\nOn success an audit log entry (`group.delete`) is written recording the group's name.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the group to delete."
          }
        ],
        "responses": {
          "200": {
            "description": "Group deleted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller lacks the required admin role, account is non-writable, or the caller is an admin but is neither the group creator nor the team owner",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "delete_requires_creator_or_owner"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Group not found within the resolved scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected internal error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "deleteV1GroupsId"
      }
    },
    "/api/v1/groups/{id}/heatmaps": {
      "get": {
        "tags": [
          "groups"
        ],
        "summary": "Retrieve aggregated heatmap analytics for a group",
        "description": "Returns scan analytics aggregated across every QR code belonging to the\nspecified group (where codes.group_id = :id). The response includes three\nviews of the data:\n\n**Calendar series**, a zero-filled daily scan count array spanning the\nrequested window (default 365 days, maximum 1 095 days). The window is\nfurther capped by the `ANALYTICS_DAYS` retention limit for the caller's\nplan. Each entry carries a `day` (YYYY-MM-DD) and a `scans` count.\n\n**Hourly day-of-week grid**, a list of `{dow, hour, scans}` rows covering\nthe last 90 days, populated only when the caller's plan includes hourly\nanalytics (`hasHourlyAnalytics`). If the plan does not qualify the array\nis empty and `hourly_available` is `false`.\n\n**Geo breakdown**, top-50 countries by scan volume. Entries with fewer\nthan 5 scans are collapsed into a synthetic `\"Other\"` country entry.\nCountries are ordered by scan count descending.\n\nAccess is governed by `resolveCodeScope` and `getScopedGroup`: the caller\nmust be authenticated (bearer JWT) and must have permission to resolve the\ngroup within their team/personal scope. A 404 is returned if the group\ndoes not exist or is outside the caller's scope. Any unexpected failure\nreturns a generic 500 error object.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The group ID whose codes' scans should be aggregated."
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1095,
              "default": 365
            },
            "description": "Number of calendar days to include in the daily scan series. Values below 1 are coerced to 365; values above 1 095 are clamped to 1 095. The effective window is also capped by the retention limit of the caller's plan.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Heatmap analytics aggregated for the group",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "group_id": {
                      "type": "string",
                      "description": "The resolved group ID used for the query."
                    },
                    "calendar": {
                      "type": "array",
                      "description": "Zero-filled daily scan totals for every day in the requested window, ordered oldest to newest.\n",
                      "items": {
                        "type": "object",
                        "properties": {
                          "day": {
                            "type": "string",
                            "format": "date",
                            "description": "Date in YYYY-MM-DD format."
                          },
                          "scans": {
                            "type": "integer",
                            "description": "Total scans across all codes in the group for this day."
                          }
                        }
                      }
                    },
                    "hourly_dow": {
                      "type": "array",
                      "description": "Scan counts bucketed by day-of-week (0 = Sunday \u2026 6 = Saturday) and UTC hour (0\u201323), covering the last 90 days. Empty when hourly analytics are not available on the caller's plan.\n",
                      "items": {
                        "type": "object",
                        "properties": {
                          "dow": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 6,
                            "description": "Day of week (0 = Sunday, 6 = Saturday)."
                          },
                          "hour": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 23,
                            "description": "UTC hour of day."
                          },
                          "scans": {
                            "type": "integer",
                            "description": "Total scans for this dow+hour bucket."
                          }
                        }
                      }
                    },
                    "hourly_available": {
                      "type": "boolean",
                      "description": "Whether the hourly_dow grid is populated. False when the caller's plan does not include hourly analytics.\n"
                    },
                    "geo": {
                      "type": "array",
                      "description": "Top countries by scan volume. Entries with fewer than 5 scans are merged into a synthetic \"Other\" entry appended at the end.\n",
                      "items": {
                        "type": "object",
                        "properties": {
                          "country": {
                            "type": "string",
                            "description": "ISO country code as stored in scans.country, or the literal string \"Other\" for the noise-floor aggregate.\n"
                          },
                          "scans": {
                            "type": "integer",
                            "description": "Total scans attributed to this country."
                          }
                        }
                      }
                    },
                    "window_days": {
                      "type": "integer",
                      "description": "The effective number of days used for the calendar series after applying all clamping and plan-retention caps.\n"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Group not found or outside the caller's scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or token invalid"
          },
          "403": {
            "description": "Caller does not have permission to access this group's data"
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1GroupsIdHeatmaps"
      }
    },
    "/api/v1/groups/{id}/public-stats": {
      "get": {
        "tags": [
          "groups"
        ],
        "summary": "Get public stats sharing state for a group",
        "description": "Returns the current public-stats sharing state for the specified group,\nincluding whether a shareable token exists and the corresponding share URL\nif one has been minted.\n\nAny authenticated user who can resolve the group within their current scope\n(personal or team) may call this endpoint. No elevated role is required for\nread access.\n\nThe group is resolved via the caller's scope (personal or team), so the\n`{id}` must be visible to the authenticated user. Returns 404 if the group\ncannot be found in the caller's scope.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the group whose public-stats state is being queried."
          }
        ],
        "responses": {
          "200": {
            "description": "Current public-stats sharing state for the group",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean",
                      "description": "Whether a public-stats share token is currently active for this group."
                    },
                    "token": {
                      "type": "string",
                      "description": "The active share token, if one exists."
                    },
                    "url": {
                      "type": "string",
                      "description": "The full shareable URL granting read-only access to the group's aggregated analytics."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Group not found in the caller's scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "forbidden"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1GroupsIdPublicStats"
      },
      "post": {
        "tags": [
          "groups"
        ],
        "summary": "Mint a shareable public stats token for a group",
        "description": "Mints a new public-stats share token for the specified group, enabling\nread-only access to the group's aggregated analytics at `/p/<token>/`.\n\nCalling this endpoint while a token already exists will mint a new one\n(the previous token may or may not be invalidated depending on the\nunderlying `mintPublicStatsToken` implementation). A successful mint is\nrecorded in the audit log under the action `group.public_stats.enable`.\n\nAuthorization rules differ by scope:\n- **Team-scoped groups**: only the team owner (role `owner`) may mint a\n  token. Plain team members are denied.\n- **Personal-scoped groups**: only the group's owner (`group.user_id`)\n  may mint a token.\n\nRequires a valid bearer JWT identifying the authenticated user.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the group for which to mint a public-stats token."
          }
        ],
        "responses": {
          "200": {
            "description": "Token successfully minted; returns the new share token and URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "The newly minted share token."
                    },
                    "url": {
                      "type": "string",
                      "description": "The full shareable URL granting read-only access to the group's aggregated analytics."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Group not found in the caller's scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller lacks permission to mint a token for this group",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "forbidden"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1GroupsIdPublicStats",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "description": "Request payload. Exact field set depends on the endpoint; check the handler source for body destructuring patterns."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "groups"
        ],
        "summary": "Revoke all public stats share tokens for a group",
        "description": "Revokes all existing public-stats share tokens for the specified group,\nimmediately invalidating any previously distributed share URLs for the\ngroup's analytics page at `/p/<token>/`.\n\nA successful revocation is recorded in the audit log under the action\n`group.public_stats.disable`.\n\nAuthorization rules mirror those for minting:\n- **Team-scoped groups**: only the team owner (role `owner`) may revoke\n  tokens. Plain team members are denied.\n- **Personal-scoped groups**: only the group's owner (`group.user_id`)\n  may revoke tokens.\n\nRequires a valid bearer JWT identifying the authenticated user.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the group whose public-stats tokens should be revoked."
          }
        ],
        "responses": {
          "200": {
            "description": "All share tokens successfully revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Confirms that the revocation operation completed successfully."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Group not found in the caller's scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller lacks permission to revoke tokens for this group",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "forbidden"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "deleteV1GroupsIdPublicStats"
      }
    },
    "/api/v1/groups/{id}/stats": {
      "get": {
        "tags": [
          "groups"
        ],
        "summary": "Get rollup statistics for a specific group",
        "description": "Returns the same narrative stat tiles produced by the scope-stats and team-stats\nendpoints, codes-by-status breakdown, 30-day and 90-day scan counts, period\ndeltas with sparkline data, Pareto concentration, and linear projection, but\nfiltered to QR codes belonging to the requested group. This endpoint drives the\n/stats/?group=<id> view and the per-group stat tiles on the Groups page.\n\nAccess control is enforced via `getScopedGroup`, which resolves the group within\nthe caller's current scope: team-scoped groups require team membership; personal-\nscoped groups require user ownership. Any caller who can see the group's codes in\nthe list view is permitted to retrieve the rollup, no additional gating applies.\n\nAuthentication is required. The handler reads `data.user` (populated by upstream\nbearer-token middleware) and uses it to resolve the code scope and locate the\ncorrect shard database.\n\nA per-group stats cache is consulted before executing the 20+ underlying queries.\nOn a cache hit the cached blob is returned immediately with `cache.hit: true`. On\na miss, stats, a peer-group benchmark, and the group's code count are fetched\nconcurrently; the result is written back to the cache asynchronously via\n`waitUntil` (or a detached promise) before the response is returned.\n\nNo mutation of user or group data occurs. The only side effect is a cache write\nthat may complete after the response is sent.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the group whose stats are being requested."
          }
        ],
        "responses": {
          "200": {
            "description": "Group statistics returned successfully (cache hit or miss)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "group": {
                      "type": "object",
                      "description": "Metadata about the resolved group, plus its code count.",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Unique identifier of the group."
                        },
                        "name": {
                          "type": "string",
                          "description": "Display name of the group."
                        },
                        "description": {
                          "type": "string",
                          "nullable": true,
                          "description": "Optional description of the group."
                        },
                        "color": {
                          "type": "string",
                          "nullable": true,
                          "description": "Display color associated with the group."
                        },
                        "created_at": {
                          "type": "string",
                          "description": "ISO 8601 timestamp of when the group was created."
                        },
                        "code_count": {
                          "type": "integer",
                          "description": "Number of QR codes currently assigned to this group."
                        }
                      }
                    },
                    "group_peer_benchmark": {
                      "type": "object",
                      "description": "Benchmark data comparing this group against peer groups in the same scope."
                    },
                    "cache": {
                      "type": "object",
                      "description": "Indicates whether the response was served from the stats cache.",
                      "properties": {
                        "hit": {
                          "type": "boolean",
                          "description": "True if the response was served from cache; false if freshly computed."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Group not found or not accessible within the caller's scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or bearer token invalid"
          },
          "403": {
            "description": "Caller does not have access to the requested group"
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1GroupsIdStats"
      }
    },
    "/api/v1/invites": {
      "post": {
        "tags": [
          "invites"
        ],
        "summary": "Create an invite and promote solo workspace to team if needed",
        "description": "Creates a new team invite for the given email address and role. This is the\n\"bare-path\" invite endpoint, intended for use before the caller knows whether\nthe authenticated owner already has a real team. If the owner has no existing\nteam, this call transparently promotes their solo virtual workspace into a\nfull team (\"promotion on first invite\") before issuing the invite. Subsequent\ncalls are idempotent with respect to team creation, the same endpoint\ncontinues to work once a team already exists.\n\nAfter the invite record is created, the endpoint attempts to send an invite\nemail to the specified address. Email delivery failure is logged but does not\ncause the request to fail; the invite token remains valid regardless.\n\nThe response includes the created invite object and a team summary. The\n`team.promoted` flag is `true` when this specific call triggered the\nworkspace-to-team promotion, allowing the UI to display a confirmation\nmessage. The flag is `false` when the owner's team already existed before\nthis call.\n\nAuthentication is required. The authenticated user must be on a Team or\nAgency plan; users on lower-tier plans receive a 403. The handler reads the\ncurrent user from `data.user`, which is populated upstream by session/JWT\nmiddleware and must be present.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "role"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Email address of the person being invited to the team.\n"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "admin",
                      "member"
                    ],
                    "description": "Role to assign to the invitee within the team.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invite created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invite": {
                      "type": "object",
                      "description": "The newly created invite record as returned by createInvite.\n",
                      "properties": {
                        "email": {
                          "type": "string",
                          "format": "email",
                          "description": "Email address the invite was sent to."
                        },
                        "role": {
                          "type": "string",
                          "enum": [
                            "admin",
                            "member"
                          ],
                          "description": "Role assigned to the invitee."
                        }
                      }
                    },
                    "team": {
                      "type": "object",
                      "description": "Summary of the owner's team after the operation.",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Unique identifier of the team."
                        },
                        "name": {
                          "type": "string",
                          "description": "Display name of the team."
                        },
                        "promoted": {
                          "type": "boolean",
                          "description": "True if this call caused the owner's solo workspace to be promoted to a real team for the first time; false if the team already existed before this call.\n"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Authenticated user's plan does not include team features",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "insufficient_plan"
                      ],
                      "description": "Machine-readable error code."
                    },
                    "required": {
                      "type": "string",
                      "enum": [
                        "team_or_agency"
                      ],
                      "description": "Minimum plan required to use this endpoint."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server-side error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ],
                      "description": "Generic internal error code."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1Invites"
      }
    },
    "/api/v1/invites/{token}": {
      "get": {
        "tags": [
          "invites"
        ],
        "summary": "Look up an invite by token",
        "description": "Retrieves the metadata for a pending invite identified by its token. This is\nused by the invite-acceptance UI to display the team name, inviter email,\ninvited email address, role, and expiry before the user confirms.\n\nThe endpoint is authentication-required: the caller must supply a valid bearer\nJWT. In addition to the invite fields, the response includes the currently\nsigned-in user's email and a boolean indicating whether it matches the address\nthe invite was sent to. This allows the front-end to warn the user if they are\nabout to accept an invite on behalf of the wrong account.\n\nThe `inviter_id` field is intentionally withheld from the response; only the\ninviter's email address is exposed, consistent with what the recipient already\nsaw in the invitation email.\n\nReturns 404 when the token is not found or has expired.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The opaque invite token embedded in the invitation link."
          }
        ],
        "responses": {
          "200": {
            "description": "Invite found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invite": {
                      "type": "object",
                      "properties": {
                        "team_id": {
                          "type": "string",
                          "description": "Identifier of the team the invite belongs to."
                        },
                        "team_name": {
                          "type": "string",
                          "description": "Display name of the team."
                        },
                        "inviter_email": {
                          "type": "string",
                          "format": "email",
                          "description": "Email address of the user who created the invite."
                        },
                        "email": {
                          "type": "string",
                          "format": "email",
                          "description": "Email address the invite was sent to."
                        },
                        "role": {
                          "type": "string",
                          "description": "Role that will be assigned to the user upon acceptance."
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time",
                          "description": "ISO 8601 timestamp after which the invite is no longer valid."
                        }
                      }
                    },
                    "current_user_email": {
                      "type": "string",
                      "format": "email",
                      "description": "Email address of the currently authenticated user."
                    },
                    "email_matches": {
                      "type": "boolean",
                      "description": "True when the authenticated user's email (trimmed and lowercased) matches the address the invite was sent to.\n"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Invite not found or expired",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invite_invalid_or_expired"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1InvitesToken"
      },
      "post": {
        "tags": [
          "invites"
        ],
        "summary": "Accept a pending team invite",
        "description": "Accepts the invite identified by the token on behalf of the currently\nauthenticated user. The handler calls `acceptInvite`, which validates that\nthe token is still valid and that the signed-in user's email matches the\naddress the invite was issued to; if either check fails the library throws\na structured error that is forwarded to the caller as the appropriate HTTP\nstatus.\n\nOn success two webhook events are fired asynchronously:\n\n- `invite.accepted`, carries `team_id`, `user_id`, `email`, and `role`.\n- `team.member_added`, carries the same fields plus `via: \"invite\"`.\n\nBoth events are scoped to the team and the accepting user. If the runtime\nsupports `waitUntil`, delivery is deferred; otherwise the promises are\nfire-and-forget.\n\nA transactional push notification is also sent to the inviter (if they are a\ndifferent user from the acceptor) informing them that their invitation was\naccepted. Transactional pushes bypass topic opt-in but still respect master\nopt-out and Do Not Disturb settings.\n\nThe endpoint is authentication-required: a valid bearer JWT must be present\nso the handler can identify which user is accepting the invite.\n\nNo request body is needed; all required information is derived from the token\npath parameter and the authenticated user's session.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The opaque invite token embedded in the invitation link."
          }
        ],
        "responses": {
          "200": {
            "description": "Invite successfully accepted; returns the result from acceptInvite",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "team_id": {
                      "type": "string",
                      "description": "Identifier of the team the user has joined."
                    },
                    "role": {
                      "type": "string",
                      "nullable": true,
                      "description": "Role assigned to the user within the team."
                    },
                    "inviter_id": {
                      "type": "string",
                      "nullable": true,
                      "description": "User ID of the person who created the invite, used internally to route the acceptance push notification.\n"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Invite not found, expired, or email mismatch",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code returned by the acceptInvite library."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected internal error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1InvitesToken",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "description": "Request payload. Exact field set depends on the endpoint; check the handler source for body destructuring patterns."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        }
      }
    },
    "/api/v1/invites/{token}/public": {
      "get": {
        "tags": [
          "invites"
        ],
        "summary": "Fetch public invite details by token",
        "description": "Returns the minimal, unauthenticated details needed to render the invite\nlanding page before a user signs in or creates an account.\n\nAuthentication is not required. The 256-bit random token embedded in the\nURL serves as the access control mechanism, possession of the token is\ntreated as proof that the caller is the intended recipient, since the token\nis delivered exclusively via the invitation email.\n\nOnly non-sensitive fields are exposed: the team name, the role being\noffered, the invited email address (already known to the recipient from\nthe email they received), and the expiry timestamp. Internal identifiers\nsuch as the inviter's user ID and the team's internal ID are never\nreturned.\n\nNo side effects are produced by this endpoint. It is a pure read\noperation. If the token does not exist or has expired, a 404 is returned\nwith a stable error code that the client can use to display an appropriate\nmessage.\n",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The 256-bit random invite token delivered to the recipient in their invitation email. Acts as the sole access control credential for this unauthenticated endpoint.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Invite found and details returned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invite": {
                      "type": "object",
                      "properties": {
                        "team_name": {
                          "type": "string",
                          "description": "Human-readable name of the team the user is being invited to join."
                        },
                        "role": {
                          "type": "string",
                          "description": "Role that will be assigned to the user upon accepting the invitation."
                        },
                        "invited_email": {
                          "type": "string",
                          "format": "email",
                          "description": "Email address the invitation was issued to, used by the UI for pre-fill or matching."
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time",
                          "description": "ISO 8601 timestamp after which the invite is no longer valid."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Token does not match any active or valid invite",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invite_invalid_or_expired"
                      ],
                      "description": "Stable error code indicating the token was not found or has expired."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1InvitesTokenPublic",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/invites/{token}/register-bridge": {
      "post": {
        "tags": [
          "invites"
        ],
        "summary": "Initiate account creation or login bridge for a team invite",
        "description": "Unauthenticated endpoint called by the invite accept page when a visitor\nchooses \"Create an account.\" Given a valid 256-bit team invite token, this\nhandler looks up the pending invitation and forwards the invitee's email\naddress, team name, and a post-auth return URL to the abundera.ai\n`/auth/team-invite-bridge` service.\n\nThe bridge service determines whether the invitee's email already has an\nAbundera account. If so, it returns a `login_url`; otherwise it mints a\nfresh auth invite and returns a `register_url` pre-filled with the\ninvitee's email. The response is passed straight through to the caller,\nwhich redirects the user accordingly. After authentication completes, the\nuser is returned to the invite accept page via the `return_url` query\nparameter to complete team onboarding as normal.\n\nSecurity model: the 256-bit invite token serves as the access control\ncredential. Only data already present in the invitation email (email\naddress and team name) is forwarded to the bridge. No user session or\nbearer token is required. The handler additionally requires the\n`ABUNDERA_SERVICE_SECRET` environment variable to be configured; if it is\nabsent the endpoint returns 503 rather than falling through to an insecure\npath. The upstream bridge call is authenticated via an `X-Service-Secret`\nheader using that secret.\n\nThis endpoint is rate-limited by the platform middleware. The Origin header\ncheck is exempted because this is a cross-auth unauthenticated POST where\nthe invite token is the secret.\n",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The 256-bit team invite token extracted from the invitation link. Acts as the sole access credential for this unauthenticated endpoint.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Bridge call succeeded. The response body is passed straight through from the abundera.ai bridge service. Contains either a `register_url` (new user) or a `login_url` (existing user), along with a `user_exists` flag used by the frontend to decide where to redirect.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user_exists": {
                      "type": "boolean",
                      "description": "Whether the invitee's email already has an Abundera account.\n"
                    },
                    "login_url": {
                      "type": "string",
                      "description": "Redirect URL for existing Abundera users to authenticate. Present when `user_exists` is true.\n"
                    },
                    "register_url": {
                      "type": "string",
                      "description": "Redirect URL for new users to complete registration, pre-filled with the invitee's email. Present when `user_exists` is false.\n"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "The invite token does not correspond to a valid or non-expired invitation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invite_invalid_or_expired"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "The upstream abundera.ai bridge service returned a non-OK response or an unparseable body.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "bridge_upstream_failed",
                        "bridge_bad_response"
                      ]
                    },
                    "status": {
                      "type": "integer",
                      "description": "The HTTP status code returned by the upstream bridge service. Only present when `error` is `bridge_upstream_failed`.\n"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "The bridge is not configured on this deployment (missing ABUNDERA_SERVICE_SECRET).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "bridge_not_configured"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1InvitesTokenRegisterBridge",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "description": "Request payload. Exact field set depends on the endpoint; check the handler source for body destructuring patterns."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        }
      }
    },
    "/api/v1/keepalive/renew": {
      "post": {
        "tags": [
          "keepalive"
        ],
        "summary": "Renew a QR route keep-alive via signed Ed25519 payload",
        "description": "Accepts a signed keep-alive renewal payload for a QR route identifier and\ninserts it into the epoch-keyed Bloom filter stored in Cloudflare KV. This\nimplements the server-side renewal verification step described in Patent\nQR-13, independent claim 1.\n\nThe handler verifies an Ed25519 signature over the concatenated string\n`${route_id}|${epoch}|${timestamp}` using the raw public key supplied\ninline in the request body. The timestamp must be within 300 seconds of\nthe server clock; requests outside this window are rejected with\n`timestamp_skew`. If the signature does not verify, the request is\nrejected with `invalid_signature`.\n\nOn successful verification the route identifier is added to the in-memory\nBloom filter for the given epoch. The filter is flushed to KV either when\n1 000 renewals have accumulated in the current Worker isolate or when at\nleast 60 seconds have elapsed since the last flush, whichever comes first.\nThis batched-flush strategy is an intentional performance optimisation;\nup to 1 000 pending renewals may be lost if the Worker isolate is evicted\nbefore a flush occurs, but clients are expected to re-fire renewals\nperiodically so no durable data loss results.\n\nThe baseline TTL of the routing record is never modified by this endpoint.\nPublic-key anchoring to the routing record (Patent QR-13 \u00a76.4 step b) is\nnot yet enforced; the public key is accepted inline and is used only for\nsignature verification.\n\nNo authentication beyond the Ed25519 signature is required. There are no\ndocumented rate limits enforced by this handler.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "route_id",
                  "epoch",
                  "timestamp",
                  "signature",
                  "public_key"
                ],
                "properties": {
                  "route_id": {
                    "type": "string",
                    "description": "The route identifier to renew. Added to the epoch-keyed Bloom filter on success.\n"
                  },
                  "epoch": {
                    "type": "string",
                    "description": "The epoch bucket into which the renewal is recorded, e.g. \"2027-Q1\".\n"
                  },
                  "timestamp": {
                    "type": "number",
                    "description": "Unix timestamp in whole seconds at the moment the renewal was generated. Must be within 300 seconds of server time.\n"
                  },
                  "signature": {
                    "type": "string",
                    "description": "Base64-encoded Ed25519 signature over the string `${route_id}|${epoch}|${timestamp}`.\n"
                  },
                  "public_key": {
                    "type": "string",
                    "description": "Base64-encoded raw Ed25519 public key (32 bytes) corresponding to the private key used to produce `signature`.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Renewal accepted and route identifier inserted into epoch filter",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "epoch",
                    "n"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "epoch": {
                      "type": "string",
                      "description": "The epoch value echoed from the request."
                    },
                    "n": {
                      "type": "integer",
                      "description": "Current estimated cardinality of the Bloom filter for this epoch after the insertion.\n"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request rejected due to a missing or incorrectly typed field, or because the timestamp differs from server time by more than 300 seconds.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_payload",
                        "timestamp_skew"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Ed25519 signature verification failed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_signature"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1KeepaliveRenew",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/keepalive/revoke": {
      "post": {
        "tags": [
          "keepalive"
        ],
        "summary": "Revoke a keepalive route from the Cuckoo-filter epoch state",
        "description": "Explicitly revokes a previously registered route from the Cuckoo-filter aggregate\nstate for a given epoch, immediately disabling resolution for that route. This\nimplements Patent QR-13 \u00a76.8 explicit revocation and acts as the counterpart to\nthe /api/keepalive/renew endpoint.\n\nThe caller must supply a valid Ed25519 signature over the canonical message\n`revoke|${route_id}|${epoch}|${timestamp}` using the raw 32-byte Ed25519 public\nkey also provided in the request body. The \"revoke|\" prefix ensures that a\nrenewal signature cannot be replayed as a revocation signature and vice versa.\n\nThe timestamp field must be a Unix epoch value in seconds and must fall within\n300 seconds of the server's current time. Requests outside this skew window are\nrejected with a `timestamp_skew` error to prevent replay attacks.\n\nNo bearer token or session cookie is required; authentication is achieved\nentirely through the Ed25519 signature verification. Upon successful verification\nthe route is removed from the in-memory Cuckoo filter for the specified epoch\nand the updated filter is persisted back to KV storage. The `removed` field in\nthe response indicates whether the route was actually found and deleted from the\nfilter.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "route_id",
                  "epoch",
                  "timestamp",
                  "signature",
                  "public_key"
                ],
                "properties": {
                  "route_id": {
                    "type": "string",
                    "description": "Identifier of the route to revoke from the Cuckoo filter."
                  },
                  "epoch": {
                    "type": "string",
                    "description": "Epoch string identifying which Cuckoo-filter aggregate state to modify."
                  },
                  "timestamp": {
                    "type": "integer",
                    "description": "Unix timestamp in seconds at which the revocation request was generated. Must be within 300 seconds of the server clock.\n"
                  },
                  "signature": {
                    "type": "string",
                    "description": "Base64-encoded Ed25519 signature over the message `revoke|${route_id}|${epoch}|${timestamp}` produced with the private key corresponding to public_key.\n"
                  },
                  "public_key": {
                    "type": "string",
                    "description": "Base64-encoded raw Ed25519 public key (32 bytes) used to verify the signature.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Route successfully revoked from the epoch Cuckoo filter",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "epoch",
                    "route_id",
                    "removed",
                    "n"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "epoch": {
                      "type": "string",
                      "description": "The epoch whose Cuckoo filter was modified."
                    },
                    "route_id": {
                      "type": "string",
                      "description": "The route identifier that was targeted for removal."
                    },
                    "removed": {
                      "type": "boolean",
                      "description": "Whether the route_id was found in the filter and successfully deleted. False if the entry was not present.\n"
                    },
                    "n": {
                      "type": "integer",
                      "description": "Number of items remaining in the Cuckoo filter after the operation."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request payload or timestamp outside acceptable skew window",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_payload",
                        "timestamp_skew"
                      ],
                      "description": "`invalid_payload`, one or more required fields are missing or of the wrong type. `timestamp_skew`, the timestamp deviates from the server clock by more than 300 seconds.\n"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Ed25519 signature verification failed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_signature"
                      ],
                      "description": "The provided signature could not be verified against the supplied public key and message, or the key/signature bytes could not be decoded."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1KeepaliveRevoke",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/keybound/register": {
      "post": {
        "tags": [
          "keybound"
        ],
        "summary": "Register a TEE-bound public key routing record",
        "description": "Creates a new keybound routing record that associates a caller-supplied\npublic key with an HTTPS destination URL. The record is written to D1 as\nthe system of record and simultaneously mirrored to KV so that the\nscan-time QR redirect worker can resolve it on the hot path without\ntouching D1.\n\nAuthorization model: there is no session, no user identity, and no\npassword. The supplied public key itself is the sole authorization\nprincipal (Patent QR-11 claim 1). Any caller that possesses a valid\nkey pair may register a route; subsequent mutation endpoints are\nexpected to verify possession of the corresponding private key.\n\nNo explicit rate limiting is enforced in this handler. The only\nserver-side validation performed is that `public_key` is a string of\nat least 40 characters and that `destination` is a string beginning\nwith `https://`. All other structural or cryptographic validation of\nthe public key is deferred to later operations.\n\nSide effects: one row is inserted into the `keybound_routes` D1 table\nand one key of the form `kb:{routing_id}` is written to KV with an\ninitial `sequence_hwm` of 0 and a status of `active`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "public_key",
                  "destination"
                ],
                "properties": {
                  "public_key": {
                    "type": "string",
                    "minLength": 40,
                    "description": "Base64-encoded public key that will be permanently bound to this routing record. Must be at least 40 characters. Acts as the sole authorization principal for future operations on this route.\n"
                  },
                  "destination": {
                    "type": "string",
                    "description": "The HTTPS URL to which QR scans resolving this routing ID will be redirected. Must begin with `https://`.\n",
                    "pattern": "^https://"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Routing record created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "routing_id",
                    "public_url"
                  ],
                  "properties": {
                    "routing_id": {
                      "type": "string",
                      "description": "The server-generated unique identifier for this routing record, used as the key in both D1 and KV.\n"
                    },
                    "public_url": {
                      "type": "string",
                      "description": "The fully-qualified URL that encodes this routing record, of the form `https://qr.abundera.ai/k/{routing_id}`. Suitable for embedding in a QR code.\n"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request body failed validation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_payload"
                      ],
                      "description": "Returned when `public_key` is absent or shorter than 40 characters, or when `destination` is absent or does not begin with `https://`."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1KeyboundRegister",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/keybound/update": {
      "post": {
        "tags": [
          "keybound"
        ],
        "summary": "Update a keybound route's destination via Ed25519 signature",
        "description": "Modifies the destination URL of an existing keybound route. This endpoint implements\nPatent QR-11 claim 2 and enforces a strict cryptographic authorization model: the\nonly accepted proof of authority is a valid Ed25519 signature over the concatenated\nstring `${routing_id}|${new_destination}|${sequence}`, verified against the public\nkey bound to the routing record at creation time.\n\nThere is no admin override, no session-based access, and no recovery path. If the\nbound private key is lost, the route cannot be updated.\n\nThe `sequence` field must be strictly greater than the stored `sequence_hwm` (high-water\nmark) for the routing record, providing replay protection. The `timestamp` field must\nbe within a 300-second skew window of the server's current time.\n\nOn success, the handler updates the `keybound_routes` database row (setting\n`destination`, `sequence_hwm`, and `updated_at`) and mirrors the new state to KV\nunder the key `kb:{routing_id}`, preserving any previously stored `created_at`,\n`bound_pubkey_b64`, and `status` fields from the existing KV entry.\n\nNo rate limiting or quota enforcement is visible in this handler.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "routing_id",
                  "new_destination",
                  "sequence",
                  "signature"
                ],
                "properties": {
                  "routing_id": {
                    "type": "string",
                    "description": "The unique identifier of the keybound route to update. Must match an existing record in the keybound_routes table.\n"
                  },
                  "new_destination": {
                    "type": "string",
                    "description": "The new destination URL for the route. Must begin with `https://`.\n"
                  },
                  "sequence": {
                    "type": "integer",
                    "description": "A monotonically increasing counter that must be strictly greater than the stored sequence_hwm for the routing record, providing replay protection.\n"
                  },
                  "timestamp": {
                    "type": "integer",
                    "description": "Unix timestamp in seconds at the time of signing. Must be within 300 seconds of the server's current time.\n"
                  },
                  "signature": {
                    "type": "string",
                    "description": "Base64-encoded Ed25519 signature over the UTF-8 string `${routing_id}|${new_destination}|${sequence}`, produced with the private key corresponding to the route's bound public key.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Route destination updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "routing_id",
                    "sequence"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "routing_id": {
                      "type": "string",
                      "description": "The routing ID of the updated route."
                    },
                    "sequence": {
                      "type": "integer",
                      "description": "The new sequence high-water mark now stored for this route."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request payload or destination URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_payload",
                        "destination_must_be_https"
                      ],
                      "description": "`invalid_payload` indicates one or more required fields are missing or of the wrong type. `destination_must_be_https` indicates the provided destination URL does not begin with `https://`.\n"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Signature verification failed or sequence/timestamp rejected",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "A string describing the verification failure returned by verifyKeyboundUpdate (e.g. invalid signature, sequence too low, timestamp out of window).\n"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No keybound route found for the given routing_id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "not_found"
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1KeyboundUpdate",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/org": {
      "get": {
        "tags": [
          "org"
        ],
        "summary": "Read the authenticated caller's org configuration",
        "description": "Returns the organisation record associated with the currently authenticated user.\nThe record is resolved from an upstream federated source (abundera.ai's\n/auth/service/orgs/{id}/branding); Pro QR holds no local organisations table.\n\nAuthentication is required. The handler reads `data.org` and `data.user`, which\nare populated by upstream middleware that validates the bearer JWT. If the\nauthenticated user is not associated with an Enterprise organisation, the\nendpoint returns 404.\n\nDomain-level fields such as `short_url_domain` and `dashboard_domain` are\nadmin-only and are not included in this response; use /api/admin/orgs for those.\n\nNo side effects. No mutations are performed by this method.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Org record for the authenticated user",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "org": {
                      "type": "object",
                      "description": "The caller's organisation configuration object as resolved from upstream."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "The authenticated user has no associated organisation (non-Enterprise account)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1Org"
      },
      "patch": {
        "tags": [
          "org"
        ],
        "summary": "Update self-serve branding fields for the caller's org",
        "description": "Partially updates the caller's organisation's branding configuration. Only the\norganisation owner (the user whose `id` matches `org.owner_user_id`) may call\nthis method; any other authenticated member receives 403.\n\nThis endpoint is a federated pass-through: updates are forwarded to\nabundera.ai's /auth/service/orgs/{id}/branding via an internal service client\nauthenticated with `ABUNDERA_SERVICE_SECRET`. Pro QR holds no local\norganisations table; canonical state lives upstream.\n\nAccepted fields are limited to the self-serve allow-list: `display_name`,\n`primary_color`, `accent_color`, `support_email`, `email_from_name`,\n`logo_url`, and `footer_text`. Legacy field aliases `name` (maps to\n`display_name`) and `brand_color` (maps to `primary_color`) are also accepted\nfor backwards compatibility with existing dashboard clients.\n\nDomain fields (`short_url_domain`, `dashboard_domain`) are admin-only and\ncannot be updated via this endpoint.\n\nOn success the upstream branding record is updated, the local org cache is\ninvalidated, the updated org is re-resolved from upstream, and the flattened\nlegacy-shape org object is returned so that existing dashboard render code\ncontinues to work without a UI redeploy.\n\nReturns 400 if no patchable fields are provided, if a color value is not a\nvalid hex string, if `logo_url` is present but not a valid HTTPS URL, or if\n`support_email` is present but does not contain an `@` character.\n\nReturns 502 if the upstream branding update call fails.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "display_name": {
                    "type": "string",
                    "description": "Human-readable display name for the organisation."
                  },
                  "name": {
                    "type": "string",
                    "description": "Legacy alias for display_name. Mapped to display_name before forwarding."
                  },
                  "primary_color": {
                    "type": "string",
                    "description": "Primary brand colour as a hex string (#RGB or #RRGGBB). Must be a valid hex colour value.\n"
                  },
                  "brand_color": {
                    "type": "string",
                    "description": "Legacy alias for primary_color. Mapped to primary_color before forwarding."
                  },
                  "accent_color": {
                    "type": "string",
                    "description": "Accent colour as a valid hex string (#RGB or"
                  },
                  "support_email": {
                    "type": "string",
                    "description": "Support contact email address. Must contain an @ character. May be set to null to clear the value.\n"
                  },
                  "email_from_name": {
                    "type": "string",
                    "description": "The \"From\" display name used in outbound emails sent on behalf of the org."
                  },
                  "logo_url": {
                    "type": "string",
                    "description": "Absolute HTTPS URL pointing to the organisation's logo image. Must use the https: protocol. May be set to null to clear the value.\n"
                  },
                  "footer_text": {
                    "type": "string",
                    "description": "Custom footer text displayed in org-branded pages and emails."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Branding updated successfully; returns the refreshed, flattened org object",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "org": {
                      "type": "object",
                      "description": "The re-resolved and flattened organisation object returned after the upstream update and cache invalidation.\n"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error or no patchable fields supplied",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable validation message. Possible values include \"no patchable fields provided\", \"primary_color must be a valid hex color (#RGB or #RRGGBB)\", \"accent_color must be a valid hex color\", \"logo_url must use HTTPS\", \"logo_url must be a valid HTTPS URL\", \"support_email must be a valid email address\".\n"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The authenticated user is not the owner of the organisation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "forbidden"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "The authenticated user has no associated organisation (non-Enterprise account)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "Upstream branding update request to abundera.ai failed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "branding_update_failed"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "patchV1Org"
      }
    },
    "/api/v1/org/brand": {
      "get": {
        "tags": [
          "org"
        ],
        "summary": "Fetch minimal brand config for an org",
        "description": "Returns a minimal set of branding fields, display name, accent colour, and\nlogo URL, for the specified organisation. This data is consumed by the\nabundera.ai login page when a user is redirected from a white-label dashboard\ndomain (e.g. app.acmecorp.com \u2192 abundera.ai/login?brand=org_xxx) so that the\nlogin UI can render with the correct brand identity.\n\nThe org to resolve is determined in one of two ways. If the request arrives on\na custom domain, the middleware has already resolved `data.org` and no query\nparameter is needed. If an explicit `id` query parameter is supplied it takes\nprecedence and the org is resolved fresh from the database.\n\nThis is a fully public endpoint, no authentication or session cookie is\nrequired. It is allowlisted in `_middleware.js`.\n\nOnly organisations that exist in the database are returned. Orgs that are not\nfound yield a 404. The response body is intentionally minimal: no internal\nconfiguration, no domain details, and no sensitive fields are exposed.\n\nResponses are cacheable for 60 seconds (`Cache-Control: public, max-age=60`)\nto reduce database load during bursts of auth redirects, while still allowing\nbranding changes to propagate quickly.\n",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The organisation ID to look up (e.g. `org_xxx`). Optional when the request arrives on a white-label custom domain whose org has already been resolved by the middleware. If both are present this parameter takes precedence.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Brand config found and returned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "name",
                    "color",
                    "logo_url"
                  ],
                  "properties": {
                    "name": {
                      "type": "string",
                      "description": "Display name of the organisation."
                    },
                    "color": {
                      "type": "string",
                      "description": "Accent colour as a CSS hex string. Defaults to `#60a5fa` when the org has not configured a brand colour.\n",
                      "example": "#60a5fa"
                    },
                    "logo_url": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Absolute URL to the organisation's logo image, or null if no logo has been configured.\n"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation not found or not a white-label org",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1OrgBrand",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/org/logo": {
      "get": {
        "tags": [
          "org-logo"
        ],
        "summary": "Serve an org logo from R2 storage",
        "description": "Retrieves a previously uploaded organisation logo from R2 object storage and streams it\ndirectly to the caller. This endpoint is intentionally unauthenticated so that logos can\nbe embedded in public-facing email templates and other externally visible surfaces.\n\nThe caller must supply the `key` query parameter, which must begin with the prefix\n`org-logos/`. Any request whose key is absent or does not start with that prefix receives\na 404 response. Likewise, if the key is valid but no object exists in the bucket under\nthat key, a 404 is returned.\n\nSuccessful responses are served with a `Cache-Control: public, max-age=86400` header\n(24-hour caching) and `Access-Control-Allow-Origin: *` to permit cross-origin use. The\n`Content-Type` is taken from the object's stored HTTP metadata, falling back to\n`image/png` if none is recorded.\n\nNo rate limiting or authentication is enforced by this handler.\n",
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^org-logos/"
            },
            "description": "The R2 object key of the logo to retrieve. Must begin with `org-logos/`. Typically of the form `org-logos/<org_id>/logo.<ext>` as written by the PUT operation.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Logo image bytes",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=86400"
                }
              },
              "Access-Control-Allow-Origin": {
                "schema": {
                  "type": "string",
                  "example": "*"
                }
              },
              "X-Content-Type-Options": {
                "schema": {
                  "type": "string",
                  "example": "nosniff"
                }
              }
            },
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/webp": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "Key missing, invalid prefix, or no object found in R2"
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1OrgLogo",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "put": {
        "tags": [
          "org-logo"
        ],
        "summary": "Upload a new org logo to R2 storage",
        "description": "Accepts a base64-encoded image and stores it in R2 as the canonical logo for the\nauthenticated user's organisation. Only the organisation owner may call this endpoint;\nany authenticated user whose `user.id` does not match `org.owner_user_id` receives a\n403 response.\n\nThe handler reads a JSON body containing the MIME type and the raw image bytes encoded\nas a standard base64 string. Supported MIME types are `image/png`, `image/jpeg`,\n`image/svg+xml`, and `image/webp`. The decoded payload must not exceed 1 MB (1,048,576\nbytes); larger uploads are rejected with 413.\n\nThe logo is stored under the R2 key `org-logos/<org_id>/logo.<ext>`, where the\nextension is derived from the MIME type. Because the key is deterministic, uploading a\nnew logo silently overwrites the previous one, there is no versioning.\n\nAfter a successful R2 write the handler persists the resulting public URL to the\nfederated branding record via an internal `updateBranding` call and invalidates the\nin-memory org cache. If the branding update fails a 502 is returned, though the object\nwill already have been written to R2.\n\nAuthentication is required. The handler expects `data.org` and `data.user` to have been\npopulated by upstream middleware (bearer-token-based auth). If `data.org` is absent the\nrequest is treated as 404. R2 storage must be configured in the environment; its absence\nyields 503.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "mime_type",
                  "data"
                ],
                "properties": {
                  "filename": {
                    "type": "string",
                    "description": "Original filename of the image. Read from the body but not used in storage key derivation; provided for client bookkeeping.\n"
                  },
                  "mime_type": {
                    "type": "string",
                    "enum": [
                      "image/png",
                      "image/jpeg",
                      "image/svg+xml",
                      "image/webp"
                    ],
                    "description": "MIME type of the image. Determines the file extension used in the R2 key and the Content-Type stored with the object.\n"
                  },
                  "data": {
                    "type": "string",
                    "description": "Base64-encoded image bytes. The decoded size must not exceed 1,048,576 bytes (1 MB).\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Logo uploaded and branding record updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "logo_url": {
                      "type": "string",
                      "format": "uri",
                      "description": "Fully-qualified URL that can be used to retrieve the uploaded logo via the GET operation on this same endpoint.\n"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid or missing request body fields",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "Unsupported type. Use PNG, JPG, SVG, or WebP.",
                        "Missing base64 data",
                        "Invalid base64 data"
                      ]
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Authenticated user is not the organisation owner",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "forbidden"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Organisation not found in request context",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "Decoded image exceeds the 1 MB size limit",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "Logo must be under 1MB"
                      ]
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "R2 write succeeded but the branding record could not be updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "branding_update_failed"
                      ]
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "R2 storage bucket is not configured in the environment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "storage not configured"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "putV1OrgLogo"
      }
    },
    "/api/v1/org/verify-domains": {
      "post": {
        "tags": [
          "org"
        ],
        "summary": "Register or verify a custom hostname for the caller's org",
        "description": "Initiates or advances verification of a custom hostname associated with the\nauthenticated user's organisation. The caller must be the org owner; any\nother authenticated user receives 403.\n\nThe `type` field selects which hostname to operate on: `short_url` maps to\nthe org's `short_url_domain` field (CNAME target `aqr.net`) and `dashboard`\nmaps to `dashboard_domain` (CNAME target `pro-qr-abundera-ai.pages.dev`).\nThe relevant hostname must already be set on the org via a prior PATCH to\n`/api/admin/orgs`; if it is absent the request fails with 400.\n\nInternally this endpoint federates to the abundera.ai\n`/auth/service/orgs/{id}/hostnames` API. If a hostname row for the\nrequested purpose already exists the call drives verification (polling\nCloudflare for DCV and certificate provisioning); otherwise it registers the\nhostname first, which also kicks off provisioning. The operation is\nidempotent: repeated POST calls are safe and advance the state machine.\n\nOn success the org cache is invalidated so subsequent reads reflect the\nupdated status. A 502 is returned if the federated registration or\nverification call fails to return a row.\n\nAuthentication is required via bearer JWT. Only the org owner may call\nthis endpoint.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "short_url",
                      "dashboard"
                    ],
                    "description": "Which custom domain to register or verify. `short_url` targets the org's `short_url_domain`; `dashboard` targets `dashboard_domain`.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Hostname registered or verification state advanced successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "verified": {
                      "type": "boolean",
                      "description": "True when the hostname status is `active`."
                    },
                    "domain": {
                      "type": "string",
                      "description": "The hostname being verified."
                    },
                    "cf_hostname_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The Cloudflare or Resend identifier for the hostname row, or null if unavailable.\n"
                    },
                    "status": {
                      "type": "string",
                      "description": "Raw status value from the federated hostname row."
                    },
                    "ssl_status": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "SSL/certificate provisioning status, or null if not present."
                    },
                    "validation_records": {
                      "description": "Parsed DNS validation records required for DCV, or null if not yet available.\n",
                      "nullable": true
                    },
                    "expected_cname_target": {
                      "type": "string",
                      "description": "The CNAME target the customer must point their domain at. Either `aqr.net` or `pro-qr-abundera-ai.pages.dev`.\n"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, missing or invalid `type`, or hostname not set on org",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. One of: `\"type must be 'short_url' or 'dashboard'\"`, or a message indicating which domain field is not set on the org.\n"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not the org owner",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "forbidden"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Org not found in request context",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "Federated hostname registration or verification returned no row",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "verified": {
                      "type": "boolean",
                      "const": false
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "registration_or_verification_failed"
                      ]
                    },
                    "domain": {
                      "type": "string",
                      "description": "The hostname that failed registration or verification."
                    },
                    "expected_cname_target": {
                      "type": "string",
                      "description": "The CNAME target for the requested domain type."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1OrgVerifyDomains"
      },
      "get": {
        "tags": [
          "org"
        ],
        "summary": "Read current custom-domain verification state for the caller's org",
        "description": "Returns the current verification state for both custom domain types\n(`short_url_domain` and `dashboard_domain`) associated with the\nauthenticated user's organisation.\n\nThe response is assembled entirely from the flattened federated payload that\nmiddleware has already attached to the request context (`data.org`); no\nadditional RPC or database call is made by this handler.\n\nFor each domain type the response includes the configured hostname (or null\nif not set), a boolean verified flag, and, when a hostname is configured ,\nthe required CNAME record the customer must create (name and target).\n\nAuthentication is required via bearer JWT.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Current verification state for both custom domain types",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "short_url_domain": {
                      "type": "object",
                      "properties": {
                        "domain": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The configured short-URL hostname, or null if not set."
                        },
                        "verified": {
                          "type": "boolean",
                          "description": "True when `short_domain_verified` equals 1 on the org row."
                        },
                        "required_cname": {
                          "description": "The CNAME record the customer must create, or null if no hostname is configured.\n",
                          "nullable": true,
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "name": {
                              "type": "string",
                              "description": "The hostname the CNAME record should be placed on."
                            },
                            "target": {
                              "type": "string",
                              "description": "The CNAME target (`aqr.net`)."
                            }
                          }
                        }
                      }
                    },
                    "dashboard_domain": {
                      "type": "object",
                      "properties": {
                        "domain": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The configured dashboard hostname, or null if not set."
                        },
                        "verified": {
                          "type": "boolean",
                          "description": "True when `dashboard_domain_verified` equals 1 on the org row."
                        },
                        "required_cname": {
                          "description": "The CNAME record the customer must create, or null if no hostname is configured.\n",
                          "nullable": true,
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "name": {
                              "type": "string",
                              "description": "The hostname the CNAME record should be placed on."
                            },
                            "target": {
                              "type": "string",
                              "description": "The CNAME target (`pro-qr-abundera-ai.pages.dev`)."
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Org not found in request context",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1OrgVerifyDomains"
      }
    },
    "/api/v1/otp/provision-attest": {
      "post": {
        "tags": [
          "otp"
        ],
        "summary": "Verify hardware attestation and deliver TOTP seed",
        "description": "Implements Patent QR-08 claim 1 steps (e)\u2013(g). Accepts a signed hardware\nattestation envelope from the client device, performs chained attestation\nverification via `verifyAttestation`, and, only upon successful completion\nof all verification checks, returns the cryptographic TOTP seed that was\nheld in escrow under the given provisioning ID.\n\nThe nonce associated with the provisioning ID is consumed (deleted from KV)\nimmediately after successful seed delivery, per claim 1 step (h). This\nsingle-use guarantee ensures that each provisioning event requires a fresh\nserver-issued nonce; replaying a previously used provisioning ID returns\n`nonce_not_found_or_consumed`.\n\nIf attestation verification succeeds and the result includes a FIDO2/WebAuthn\ncredential, the credential is persisted to the `webauthn_credentials` D1\ntable so that subsequent assertion operations (rotation, recovery) can verify\nagainst the stored authenticator without re-running the registration ceremony.\nCredential persistence failure is non-fatal: seed delivery still proceeds, but\nre-attestation would be required for any rotation operation.\n\nNo bearer token or session cookie is required by this handler. The security\nmodel relies entirely on possession of a valid server-issued nonce (retrieved\nfrom KV under `otp:nonce:{provisioning_id}`) and the cryptographic integrity\nof the attestation envelope signature. Any verification failure results in a\n401 with a descriptive error token and no seed material is disclosed.\n\nNo explicit rate limiting is implemented in this handler beyond the nonce\nsingle-use constraint.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "provisioning_id",
                  "envelope"
                ],
                "properties": {
                  "provisioning_id": {
                    "type": "string",
                    "description": "Opaque identifier issued during the provisioning initiation step. Used to look up the stored nonce and pending seed in KV.\n"
                  },
                  "envelope": {
                    "type": "object",
                    "description": "Signed hardware attestation envelope produced by the client device. All fields are forwarded to `verifyAttestation` together with the server-stored nonce for chained verification.\n",
                    "properties": {
                      "nonce": {
                        "type": "string",
                        "description": "The server-issued nonce that was embedded in the attestation envelope by the device during the provisioning ceremony.\n"
                      },
                      "device_integrity": {
                        "type": "object",
                        "description": "Device integrity assertion block.",
                        "properties": {
                          "platform": {
                            "type": "string",
                            "description": "Platform identifier (e.g. \"android\", \"ios\")."
                          },
                          "attestation_format": {
                            "type": "string",
                            "description": "Attestation statement format (e.g. \"fido-u2f\", \"android-key\", \"apple\").\n"
                          },
                          "integrity_claims": {
                            "type": "object",
                            "description": "Platform-specific integrity claims returned by the hardware attestation API.\n"
                          }
                        }
                      },
                      "attestation_pubkey": {
                        "type": "string",
                        "description": "Public key included in the attestation statement, used during signature verification.\n"
                      },
                      "issued_at": {
                        "type": "string",
                        "description": "ISO 8601 or Unix-epoch timestamp indicating when the attestation envelope was generated on the device.\n"
                      },
                      "signature": {
                        "type": "string",
                        "description": "Cryptographic signature over the envelope contents, verified during chained attestation checks.\n"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Attestation verified; TOTP seed delivered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "provisioning_id",
                    "totp_seed"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "provisioning_id": {
                      "type": "string",
                      "description": "Echo of the provisioning ID supplied in the request."
                    },
                    "totp_seed": {
                      "type": "string",
                      "description": "The cryptographic authenticator seed held in escrow under this provisioning ID. Delivered only upon successful attestation verification. The associated nonce is consumed immediately after this response is issued.\n"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request body is missing required fields or is not valid JSON",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_payload"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Attestation verification failed; no seed material is disclosed. Covers all chained verification failures (signature mismatch, integrity claim rejection, replay detection, etc.).\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error token returned by `verifyAttestation` describing which verification step failed.\n"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No nonce found for the given provisioning ID. The nonce was never issued, has expired from KV, or has already been consumed by a prior successful attestation (single-use enforcement per claim 1 step (h)).\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "nonce_not_found_or_consumed"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1OtpProvisionAttest",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/otp/provision-init": {
      "post": {
        "tags": [
          "otp"
        ],
        "summary": "Initialise a TOTP provisioning session and return a nonce-bearing QR payload",
        "description": "Begins a new TOTP provisioning flow (Patent QR-08, claim 1, steps a and b).\n\nA cryptographically random provisioning ID and nonce are generated server-side.\nA fresh TOTP seed is also generated but is never returned to the caller; it is\nstored transiently in Cloudflare KV under the key `otp:nonce:<provisioning_id>`\nalongside the nonce and a creation timestamp.  The KV record expires automatically\nafter `expires_in` seconds (defined by the server constant `NONCE_TTL_SECONDS`).\n\nThe response includes a `qr_payload` string that the client is expected to encode\ninto a QR code and present to the end-user's authenticator device.  The payload\nembeds the provisioning ID and nonce in a URL fragment; it carries no seed\nmaterial whatsoever, satisfying claim 1 step (b) of the referenced patent.\n\nThe authenticator device must subsequently call the `validation_endpoint`\n(`/api/otp/provision-attest`) to complete attestation and retrieve the seed.\n\nNo authentication is required to call this endpoint, it is an unauthenticated\ninitiation step.  No rate-limit logic is present in the handler itself, though\nplatform-level or upstream rate limiting may apply.  Each call unconditionally\ncreates a new KV record; callers should avoid polling this endpoint excessively\nto prevent KV write exhaustion.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "description": "Request payload. Exact field set depends on the endpoint; check the handler source for body destructuring patterns."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Provisioning session created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "provisioning_id",
                    "nonce",
                    "validation_endpoint",
                    "qr_payload",
                    "expires_in"
                  ],
                  "properties": {
                    "provisioning_id": {
                      "type": "string",
                      "description": "Unique identifier for this provisioning session.  Used as the KV record key suffix and embedded in the QR payload URL path.\n"
                    },
                    "nonce": {
                      "type": "string",
                      "description": "Single-use random value bound to this provisioning session. Embedded in the QR payload URL fragment.  Must be presented during attestation to prove QR possession.\n"
                    },
                    "validation_endpoint": {
                      "type": "string",
                      "format": "uri",
                      "description": "Absolute URI of the attestation endpoint the authenticator device should call to complete provisioning.\n",
                      "example": "https://qr.abundera.ai/api/otp/provision-attest"
                    },
                    "qr_payload": {
                      "type": "string",
                      "format": "uri",
                      "description": "The string the client must encode into a QR code.  Takes the form `https://qr.abundera.ai/otp/<provisioning_id>#nonce=<nonce>`. Contains no TOTP seed material.\n",
                      "example": "https://qr.abundera.ai/otp/abc123#nonce=xyz789"
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Number of seconds until the server-side KV record for this provisioning session expires.  Matches the server constant `NONCE_TTL_SECONDS`."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1OtpProvisionInit",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/p/{token}/stats": {
      "get": {
        "tags": [
          "p-token-stats"
        ],
        "summary": "Retrieve public shared analytics for a code, team, or group",
        "description": "Returns aggregated, anonymised scan analytics for a QR code, team, or code\ngroup that has been shared via a public-stats token. The caller supplies the\nraw opaque token (minted by the corresponding public-stats POST endpoint);\nthe server SHA-256-hashes it internally and looks up the matching\n`public_stats_tokens` row to determine scope and ownership.\n\nNo authentication is required, this endpoint is intentionally public so\nthat share links can be opened by anyone with the URL. No personally\nidentifiable information, destination URLs, or raw shortcodes are ever\nincluded in the response.\n\nThe response shape varies by scope:\n- **code**, stats and heatmaps for a single QR code.\n- **team**, rolled-up stats and heatmaps across all codes in the team,\n  plus a code-count summary.\n- **group**, same roll-up as team but scoped to a code group, with an\n  additional `group` object carrying the group's colour tag.\n\nGeographic data is subject to a noise floor: any country with fewer than\nfive scans is collapsed into an `\"Other\"` bucket to prevent single-scan\ncountry leakage. Calendar heatmap data covers the trailing 365 days,\nzero-filled for days with no scans.\n\nA revoked token returns **410 Gone** so the consuming page can distinguish\na deliberate revocation from a generic missing-token 404.\n\nNo side effects. No rate-limit is enforced at the handler level.\n",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "Raw opaque public-stats token (20\u2013200 characters). The server hashes this value with SHA-256 to look up the corresponding share record. Tokens shorter than 20 characters or longer than 200 characters are rejected with 404.\n",
            "schema": {
              "type": "string",
              "minLength": 20,
              "maxLength": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Shared analytics payload. The exact set of top-level fields depends on the scope stored against the token.\n",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "title": "CodeScopeResponse",
                      "type": "object",
                      "properties": {
                        "scope": {
                          "type": "string",
                          "enum": [
                            "code"
                          ]
                        },
                        "title": {
                          "type": "string",
                          "nullable": true,
                          "description": "Label of the QR code, or null if unlabelled."
                        },
                        "stats": {
                          "type": "object",
                          "properties": {
                            "total_scans": {
                              "type": "integer",
                              "description": "Lifetime scan count for the code."
                            },
                            "days_active": {
                              "type": "integer",
                              "description": "Number of distinct days that recorded at least one scan."
                            },
                            "first_day": {
                              "type": "string",
                              "nullable": true,
                              "description": "ISO 8601 date (YYYY-MM-DD) of the earliest scan day, or null."
                            },
                            "last_day": {
                              "type": "string",
                              "nullable": true,
                              "description": "ISO 8601 date (YYYY-MM-DD) of the most recent scan day, or null."
                            },
                            "peak": {
                              "type": "integer",
                              "description": "Highest single-day scan count recorded."
                            }
                          }
                        },
                        "heatmaps": {
                          "type": "object",
                          "properties": {
                            "calendar": {
                              "type": "array",
                              "description": "365-entry array of daily scan counts, zero-filled, ordered from oldest to most recent.\n",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "day": {
                                    "type": "string",
                                    "description": "ISO 8601 date (YYYY-MM-DD)."
                                  },
                                  "scans": {
                                    "type": "integer"
                                  }
                                }
                              }
                            },
                            "geo": {
                              "type": "array",
                              "description": "Per-country scan counts. Countries with fewer than 5 scans are collapsed into an \"Other\" entry.\n",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "country": {
                                    "type": "string",
                                    "description": "ISO 3166-1 alpha-2 country code, or \"Other\"."
                                  },
                                  "scans": {
                                    "type": "integer"
                                  }
                                }
                              }
                            }
                          }
                        },
                        "shared_since": {
                          "type": "integer",
                          "description": "Unix timestamp (seconds) when the share token was minted."
                        }
                      }
                    },
                    {
                      "title": "TeamScopeResponse",
                      "type": "object",
                      "properties": {
                        "scope": {
                          "type": "string",
                          "enum": [
                            "team"
                          ]
                        },
                        "title": {
                          "type": "string",
                          "description": "Team name, falls back to \"Team\" if empty."
                        },
                        "summary": {
                          "type": "object",
                          "description": "Rollup of QR codes belonging to the team.",
                          "properties": {
                            "code_count": {
                              "type": "integer",
                              "description": "Total number of codes in the team."
                            },
                            "active": {
                              "type": "integer",
                              "description": "Number of codes with status \"active\"."
                            },
                            "paused": {
                              "type": "integer",
                              "description": "Number of codes with status \"paused\"."
                            }
                          }
                        },
                        "stats": {
                          "type": "object",
                          "properties": {
                            "total_scans": {
                              "type": "integer"
                            },
                            "days_active": {
                              "type": "integer"
                            },
                            "first_day": {
                              "type": "string",
                              "nullable": true
                            },
                            "last_day": {
                              "type": "string",
                              "nullable": true
                            },
                            "peak": {
                              "type": "integer"
                            }
                          }
                        },
                        "heatmaps": {
                          "type": "object",
                          "properties": {
                            "calendar": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "day": {
                                    "type": "string"
                                  },
                                  "scans": {
                                    "type": "integer"
                                  }
                                }
                              }
                            },
                            "geo": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "country": {
                                    "type": "string"
                                  },
                                  "scans": {
                                    "type": "integer"
                                  }
                                }
                              }
                            }
                          }
                        },
                        "shared_since": {
                          "type": "integer"
                        }
                      }
                    },
                    {
                      "title": "GroupScopeResponse",
                      "type": "object",
                      "properties": {
                        "scope": {
                          "type": "string",
                          "enum": [
                            "group"
                          ]
                        },
                        "title": {
                          "type": "string",
                          "description": "Group name, falls back to \"Group\" if empty."
                        },
                        "group": {
                          "type": "object",
                          "description": "Visual metadata for the code group.",
                          "properties": {
                            "color": {
                              "type": "string",
                              "nullable": true,
                              "description": "Group colour tag, or null if unset."
                            }
                          }
                        },
                        "summary": {
                          "type": "object",
                          "description": "Rollup of QR codes belonging to the group.",
                          "properties": {
                            "code_count": {
                              "type": "integer"
                            },
                            "active": {
                              "type": "integer"
                            },
                            "paused": {
                              "type": "integer"
                            }
                          }
                        },
                        "stats": {
                          "type": "object",
                          "properties": {
                            "total_scans": {
                              "type": "integer"
                            },
                            "days_active": {
                              "type": "integer"
                            },
                            "first_day": {
                              "type": "string",
                              "nullable": true
                            },
                            "last_day": {
                              "type": "string",
                              "nullable": true
                            },
                            "peak": {
                              "type": "integer"
                            }
                          }
                        },
                        "heatmaps": {
                          "type": "object",
                          "properties": {
                            "calendar": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "day": {
                                    "type": "string"
                                  },
                                  "scans": {
                                    "type": "integer"
                                  }
                                }
                              }
                            },
                            "geo": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "country": {
                                    "type": "string"
                                  },
                                  "scans": {
                                    "type": "integer"
                                  }
                                }
                              }
                            }
                          }
                        },
                        "shared_since": {
                          "type": "integer"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Token not found, token fails basic validation (length < 20 or > 200), or the underlying code/team/group record no longer exists.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_token",
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "Token has been explicitly revoked by its owner.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "revoked"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error or unexpected scope value on the token row.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_scope",
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1PTokenStats",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/push/prefs": {
      "get": {
        "tags": [
          "push-prefs"
        ],
        "summary": "Retrieve product-local and federated push preferences",
        "description": "Returns the authenticated user's push notification preferences for pro.qr, combining\ntwo data sources in a single response.\n\n**Local preferences** (stored in this product's D1 database) contain only the\nproduct-scoped master push toggle (`push_enabled`). All other mute/quiet-hours\nstate is owned by abundera.ai.\n\n**Federated preferences** are fetched cross-origin from\n`https://abundera.ai/auth/service/push-prefs` on every request, using an internal\nservice secret (`X-Service-Secret`). The upstream call is subject to a 2 500 ms\ntimeout. If the upstream is unavailable or returns a non-2xx status the federated\nfields (`dnd_until`, `quiet_hours_start`, `quiet_hours_end`, `tz`, `muted`,\n`muted_reason`) will be `null`/`false` and `federation_ok` will be `false`,\nallowing the UI to display a graceful degradation message rather than a stale or\nincorrect value.\n\nAuthentication is required; the handler reads the caller's identity from\n`data.user` which is populated by upstream middleware that validates a bearer JWT.\nNo explicit rate limit is enforced at this handler layer.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Push preferences retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "push_enabled": {
                      "type": "boolean",
                      "description": "Whether push notifications are enabled for this product (pro.qr)."
                    },
                    "dnd_until": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "ISO 8601 timestamp until which Do Not Disturb is active, as returned by abundera.ai. Null if DND is not active or federation was unavailable.\n"
                    },
                    "quiet_hours_start": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Quiet-hours start time (e.g. \"22:00\") from the federated user profile. Null if not configured or federation was unavailable.\n"
                    },
                    "quiet_hours_end": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Quiet-hours end time (e.g. \"08:00\") from the federated user profile. Null if not configured or federation was unavailable.\n"
                    },
                    "tz": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "IANA timezone string for the user, sourced from abundera.ai. Null if not configured or federation was unavailable.\n"
                    },
                    "muted": {
                      "type": "boolean",
                      "description": "True when the federated service indicates the user is currently muted (DND active, inside quiet hours, etc.). Always false when federation is unavailable.\n"
                    },
                    "muted_reason": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Human-readable reason the user is muted, as provided by abundera.ai (e.g. \"dnd\", \"quiet_hours\"). Null when not muted or unavailable.\n"
                    },
                    "federation_ok": {
                      "type": "boolean",
                      "description": "False when the abundera.ai upstream call failed or timed out, indicating the federated fields may not reflect current state.\n"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1PushPrefs"
      },
      "patch": {
        "tags": [
          "push-prefs"
        ],
        "summary": "Update the product-local push notification master toggle",
        "description": "Updates the authenticated user's product-scoped push notification master toggle for\npro.qr. Only `push_enabled` may be set via this endpoint; DND, quiet hours, and\ntimezone are user-level settings managed exclusively through abundera.ai\n(`PATCH https://abundera.ai/auth/service/push-prefs`).\n\nThe handler performs an upsert into `notification_prefs`: if a row for the user does\nnot yet exist it is created with `weekly_digest` and `anomaly_alerts` defaulting to\n`0`. If a row already exists only `push_enabled` and `updated_at` are modified;\nexisting digest and alert values are preserved by the `ON CONFLICT \u2026 DO UPDATE`\nclause.\n\nAfter writing, the handler re-reads both the local row and the federated preferences\nfrom abundera.ai (same cross-origin call as the GET, with the same 2 500 ms timeout\nand graceful-degradation behaviour) and returns the full merged preference object so\nthe caller does not need a subsequent GET.\n\nAuthentication is required; the handler reads the caller's identity from `data.user`\nwhich is populated by upstream middleware that validates a bearer JWT. No explicit\nrate limit is enforced at this handler layer.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "push_enabled"
                ],
                "properties": {
                  "push_enabled": {
                    "type": "boolean",
                    "description": "Set to true to enable push notifications for this product, false to disable them. The field must be present; omitting it returns a 400.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Preference updated; returns the merged preference object",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "push_enabled": {
                      "type": "boolean",
                      "description": "Reflects the newly persisted value."
                    },
                    "dnd_until": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "ISO 8601 timestamp until which Do Not Disturb is active, as returned by abundera.ai. Null if DND is not active or federation was unavailable.\n"
                    },
                    "quiet_hours_start": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Quiet-hours start time from the federated user profile. Null if not configured or federation was unavailable.\n"
                    },
                    "quiet_hours_end": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Quiet-hours end time from the federated user profile. Null if not configured or federation was unavailable.\n"
                    },
                    "tz": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "IANA timezone string for the user, sourced from abundera.ai. Null if not configured or federation was unavailable.\n"
                    },
                    "muted": {
                      "type": "boolean",
                      "description": "True when the federated service indicates the user is currently muted. Always false when federation is unavailable.\n"
                    },
                    "muted_reason": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Human-readable reason the user is muted as provided by abundera.ai. Null when not muted or unavailable.\n"
                    },
                    "federation_ok": {
                      "type": "boolean",
                      "description": "False when the abundera.ai upstream call failed or timed out during the post-write re-fetch.\n"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request body missing the required field",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_fields"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "patchV1PushPrefs"
      }
    },
    "/api/v1/push/subscribe": {
      "post": {
        "tags": [
          "push"
        ],
        "summary": "Save or update a Web Push subscription for the authenticated user",
        "description": "Persists a Web Push subscription (endpoint + VAPID keys) for the currently\nauthenticated user. The operation is idempotent: if a row already exists for\nthe same (user_id, endpoint) pair, the p256dh and auth keys are overwritten\nand created_at is refreshed, rather than inserting a duplicate. This allows\na browser that has had push permission revoked and re-granted to re-register\ncleanly.\n\nA single user may hold subscriptions from multiple browsers or devices\nsimultaneously; each subscription receives push deliveries independently.\nExpired or invalid subscriptions (HTTP 404/410 from the push service) are\ncleaned up automatically by the delivery layer and do not need to be removed\nmanually.\n\nRequires a valid bearer JWT. The authenticated user identity is sourced from\n`data.user` populated by upstream middleware.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "endpoint",
                  "keys"
                ],
                "properties": {
                  "endpoint": {
                    "type": "string",
                    "description": "The push service endpoint URL obtained from the browser PushSubscription object. Must begin with \"https://\" and must not exceed 2000 characters.\n",
                    "maxLength": 2000
                  },
                  "keys": {
                    "type": "object",
                    "required": [
                      "p256dh",
                      "auth"
                    ],
                    "description": "VAPID encryption keys from the browser PushSubscription.",
                    "properties": {
                      "p256dh": {
                        "type": "string",
                        "description": "Base64url-encoded P-256 ECDH public key. Must not exceed 500 characters.\n",
                        "maxLength": 500
                      },
                      "auth": {
                        "type": "string",
                        "description": "Base64url-encoded 16-byte authentication secret. Must not exceed 500 characters.\n",
                        "maxLength": 500
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscription saved or updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request body is missing, malformed, or fails validation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_subscription"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server-side error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1PushSubscribe"
      },
      "delete": {
        "tags": [
          "push"
        ],
        "summary": "Remove a Web Push subscription for the authenticated user",
        "description": "Deletes the push subscription row identified by the given endpoint for the\ncurrently authenticated user. Only the row belonging to the calling user is\naffected; other users' subscriptions for the same endpoint (if any) are\nuntouched.\n\nAfter a successful deletion an audit log entry is recorded with action\n`push.unsubscribe` and target type `push_subscription`, capturing the\nactor's user ID and email.\n\nIf the endpoint does not exist in the database the DELETE is a no-op and\nthe response is still `{ ok: true }`, because the desired state (no active\nsubscription for that endpoint) is already satisfied.\n\nRequires a valid bearer JWT. The authenticated user identity is sourced from\n`data.user` populated by upstream middleware.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "endpoint"
                ],
                "properties": {
                  "endpoint": {
                    "type": "string",
                    "description": "The push service endpoint URL of the subscription to remove. Must be a string; no additional format validation is applied beyond type checking.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscription removed (or was already absent)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request body is missing or endpoint field is not a string",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "endpoint_required"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server-side error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "deleteV1PushSubscribe"
      }
    },
    "/api/v1/push/subscriptions": {
      "get": {
        "tags": [
          "push-subscriptions"
        ],
        "summary": "List push subscriptions for the authenticated user",
        "description": "Returns all browser push subscriptions currently registered for the authenticated user,\nordered by registration date descending. Each row includes an opaque short identifier\n(the first 10 hex characters of the SHA-256 of the push endpoint URL), a human-friendly\nbrowser family label derived from the push service host (e.g. \"Chrome / Edge / Opera\",\n\"Firefox\", \"Safari\"), and the creation timestamp.\n\nThe endpoint URL itself is never returned; it is treated as a bearer-ish credential and\nkept server-side only. The short `id` is deterministic, so the UI can reference a specific\ndevice across requests without exposing the raw endpoint.\n\nAuthentication is required. The handler reads `data.user.id` from the session context,\nwhich is populated by upstream middleware that validates the bearer JWT. Unauthenticated\nrequests will be rejected before reaching this handler.\n\nNo rate limits are enforced at the handler level. Side effects: none, this is a read-only\noperation and produces no audit log entries.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Subscriptions retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subscriptions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Opaque 10-character lowercase hex identifier derived from SHA-256 of the push endpoint URL. Use this value to target a specific subscription in DELETE requests.\n",
                            "pattern": "^[0-9a-f]{10}$"
                          },
                          "label": {
                            "type": "string",
                            "description": "Human-friendly browser family label inferred from the push service host (e.g. \"Chrome / Edge / Opera\", \"Firefox\", \"Safari\", \"Windows (legacy)\"). Falls back to the raw host if unrecognised, or \"Unknown\" if the endpoint URL cannot be parsed.\n"
                          },
                          "created_at": {
                            "type": "string",
                            "description": "ISO 8601 timestamp (as stored in the database) indicating when this subscription was registered.\n"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1PushSubscriptions"
      },
      "delete": {
        "tags": [
          "push-subscriptions"
        ],
        "summary": "Revoke a specific push subscription by short id",
        "description": "Removes a single browser push subscription from the server, identified by the 10-character\nhex short id returned by `GET /api/push/subscriptions`. The caller must supply a JSON body\ncontaining the target `id`.\n\nThe handler locates the subscription by scanning all subscriptions belonging to the\nauthenticated user and comparing the SHA-256 prefix of each stored endpoint against the\nsupplied id. If a match is found the row is deleted immediately; if no match exists a 404\nis returned.\n\nImportant: the browser-side Push API subscription is NOT unsubscribed by this operation.\nThe browser retains its local subscription object. The next push delivery attempt to the\nnow-deleted endpoint will receive a 410 response from the push service, which triggers\nthe server's delivery-cleanup path to prune any remaining stale state.\n\nAuthentication is required. The handler reads `data.user.id` and `data.user.email` from\nthe session context populated by upstream bearer-JWT middleware.\n\nOn success an audit log entry of action `push.device_revoke` is written, recording the\nacting user and the short id of the revoked subscription. No other side effects occur.\n\nNo rate limits are enforced at the handler level.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "pattern": "^[0-9a-fA-F]{10}$",
                    "description": "The 10-character hex short id of the subscription to revoke, as returned by GET /api/push/subscriptions. Case-insensitive.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscription revoked successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": {
                      "type": "boolean",
                      "const": true
                    },
                    "id": {
                      "type": "string",
                      "description": "The short id of the revoked subscription, lowercased.",
                      "pattern": "^[0-9a-f]{10}$"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or malformed short id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_id"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No subscription matching the supplied id found for this user",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "deleteV1PushSubscriptions"
      }
    },
    "/api/v1/push/test": {
      "post": {
        "tags": [
          "push"
        ],
        "summary": "Send a test push notification to all subscribed browsers",
        "description": "Fires a one-off \"Test notification, it works!\" push message to every browser\nsubscription associated with the authenticated user. The notification is delivered\nwith a fixed title (\"Abundera QR Pro\"), body, URL (/account/), and tag (\"test-push\").\n\nUnlike production push dispatches, this endpoint bypasses all topic filtering,\nDo Not Disturb gating, and quiet-hours logic. Its sole purpose is to let a user\nverify end-to-end push delivery without needing to configure or wait for real\nnotification triggers.\n\nAuthentication is via session cookie only, the handler reads the user identity\nfrom `data.user.id` populated by cookie-auth middleware. Unauthenticated requests\nwill be rejected before reaching this handler.\n\nRate limiting is governed by the standard per-user middleware bucket shared across\nthe API. No additional quota is imposed by this endpoint; realistically a user\nwould only invoke it once or twice per minute during setup verification.\n\nSide effects: for each subscription endpoint that returns a permanent failure\n(e.g. HTTP 410 Gone from the push service), the subscription record is deleted\nfrom the database (reflected in the `cleaned` count).\n",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Test push dispatch completed (individual delivery failures do not change the status code)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "sent",
                    "failed",
                    "cleaned"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "sent": {
                      "type": "integer",
                      "description": "Number of subscription endpoints to which the push was successfully submitted"
                    },
                    "failed": {
                      "type": "integer",
                      "description": "Number of subscription endpoints that returned a transient or unhandled error"
                    },
                    "cleaned": {
                      "type": "integer",
                      "description": "Number of stale or expired subscription records removed during the dispatch attempt"
                    },
                    "suppressed": {
                      "type": "integer",
                      "description": "Number of pushes suppressed (present only when at least one was suppressed)"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected internal error during push dispatch",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1PushTest",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "description": "Request payload. Exact field set depends on the endpoint; check the handler source for body destructuring patterns."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        }
      }
    },
    "/api/v1/push/vapid-key": {
      "get": {
        "tags": [
          "push"
        ],
        "summary": "Retrieve the VAPID public key for push subscription setup",
        "description": "Returns this product's VAPID (Voluntary Application Server Identification) public key,\nwhich browsers need to call `PushManager.subscribe({ applicationServerKey })`. The public\nkey is one half of an asymmetric keypair and is safe to expose publicly by design; the\ncorresponding private key remains confidential in the server environment.\n\nServing the key from a dedicated endpoint rather than baking it into the JavaScript bundle\nat build time allows the key to be rotated by updating a worker secret alone, without\nrequiring a full redeployment of every page that depends on it.\n\nNo authentication is required. The key is identical for every caller and there are no\nper-user or per-session considerations.\n\nResponses are marked `public, max-age=300`, allowing shared caches (CDN edges, browser\ncaches) to serve the value for up to five minutes. This limits the delay between a key\nrotation and clients observing the new key to at most five minutes.\n\nIf the `VAPID_PUBLIC_KEY` environment variable has not been configured in the deployment,\nthe endpoint still returns HTTP 200 with `key` set to `null`, allowing subscriber UI code\nto detect and surface a \"Push not set up yet\" message rather than encountering an\nunhandled error.\n",
        "responses": {
          "200": {
            "description": "VAPID public key returned successfully (key may be null if not yet configured)",
            "headers": {
              "Cache-Control": {
                "description": "Public cache directive allowing shared caches to store the response for 300 seconds",
                "schema": {
                  "type": "string",
                  "example": "public, max-age=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "key"
                  ],
                  "properties": {
                    "key": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Base64url-encoded VAPID public key suitable for use as the `applicationServerKey` argument to `PushManager.subscribe()`. Null when VAPID_PUBLIC_KEY is not configured in the environment.",
                      "example": "BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1PushVapidKey",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/sales-lead": {
      "post": {
        "tags": [
          "sales-lead"
        ],
        "summary": "Submit an enterprise sales lead inquiry",
        "description": "Accepts an inbound enterprise lead from the public /sales/ page and emails\nthe contents to sales@abundera.ai via the Zepto transactional email transport.\nNo authentication is required, this endpoint is intentionally public so that\nprospects without accounts can submit inquiries.\n\nBefore any field validation is performed, the submission is screened with a\nCloudflare Turnstile token. This is the primary bot-prevention gate; invalid\nor missing tokens are rejected immediately with a 400 response so that bot\nsubmissions consume minimal compute.\n\nAfter passing Turnstile verification, the handler validates all required fields\n(name, email, company) and enforces maximum lengths on all accepted fields. The\nemail address is lowercased and checked against a basic format pattern. The\noptional `seats` field is accepted only when it is a non-negative integer; any\nother value causes it to be silently dropped rather than rejected.\n\nOn success the lead is dispatched as a plain-text email to sales@abundera.ai\nwith the submitter's email set as the Reply-To address. If the email dispatch\nfails for a transient reason the handler still returns a 200 response so the\nprospect does not see an error; the failure is logged server-side. The\nsubmission details are always written to the structured console log regardless\nof email outcome.\n\nThis endpoint is subject to the standard IP-based rate-limiting middleware\napplied across all API routes.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "email",
                  "company",
                  "turnstile"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Full name of the person submitting the inquiry."
                  },
                  "email": {
                    "type": "string",
                    "maxLength": 160,
                    "description": "Business email address of the submitter. Must match a basic local@domain.tld pattern. Stored and transmitted in lowercase.\n"
                  },
                  "company": {
                    "type": "string",
                    "maxLength": 160,
                    "description": "Name of the prospective customer's organisation."
                  },
                  "use_case": {
                    "type": "string",
                    "maxLength": 4000,
                    "description": "Free-text description of the prospect's intended use case. Optional but encouraged; omitted or empty values are accepted.\n"
                  },
                  "seats": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Rough expected seat count. Must be a non-negative integer. Values that are absent, null, non-integer, or negative are silently ignored rather than rejected.\n"
                  },
                  "source": {
                    "type": "string",
                    "maxLength": 80,
                    "description": "Identifies where the lead originated, e.g. \"pricing-cta\" or \"inbound\". Optional; included in the notification email when present.\n"
                  },
                  "turnstile": {
                    "type": "string",
                    "description": "Cloudflare Turnstile challenge token obtained from the client-side widget. Verified against the Turnstile API before any field validation takes place.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lead accepted and email dispatch attempted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Turnstile verification failed or a required field is missing, malformed, or exceeds its maximum length.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "turnstile_failed",
                        "invalid_field"
                      ],
                      "description": "\"turnstile_failed\" is returned when Cloudflare Turnstile rejects the token. \"invalid_field\" is returned when a field fails presence or length validation.\n"
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present only when error is \"turnstile_failed\". Contains the first error string returned by the Turnstile verification API.\n"
                    },
                    "field": {
                      "type": "string",
                      "enum": [
                        "name",
                        "email",
                        "company",
                        "use_case",
                        "source"
                      ],
                      "description": "Present only when error is \"invalid_field\". Names the specific field that failed validation.\n"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server-side error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "internal"
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1SalesLead",
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/v1/stats/recent-scans": {
      "get": {
        "tags": [
          "stats"
        ],
        "summary": "Retrieve recent scan activity across the caller's scope",
        "description": "Returns the most recently recorded scan activity rows within the authenticated\nuser's scope (individual, team, or agency). Because analytics are aggregated\ninto day-bucket rows rather than stored as individual scan events, \"recent\"\nrefers to the most recently updated daily aggregation buckets, up to the\nrequested limit.\n\nTeam and Agency plan holders may pass `?hourly=1` to switch the query to the\n`scans_hourly` table, providing finer-grained visibility into approximately the\nlast 24 hours of activity. Whether hourly mode is actually activated depends on\nboth the query parameter and the caller's plan; the response always reflects the\nmode that was used via `hourly_mode`, and advertises plan eligibility via\n`hourly_available`.\n\nAuthentication is required. The endpoint reads `data.user` to determine the\ncaller's plan and resolve the appropriate data shard for their scope. No\nwrite side-effects occur; this is a read-only operation.\n\nThe `limit` parameter is clamped server-side to the range [1, 100] regardless\nof the value supplied by the caller.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of recent scan activity rows to return. Clamped to the range [1, 100]. Defaults to 20 if omitted or unparseable.\n",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "hourly",
            "in": "query",
            "required": false,
            "description": "Pass `1` to request hourly-granularity data from the `scans_hourly` table instead of daily buckets. Only takes effect when the caller's plan includes hourly analytics; otherwise daily data is returned regardless of this value.\n",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Recent scan activity returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "events",
                    "hourly_mode",
                    "hourly_available"
                  ],
                  "properties": {
                    "events": {
                      "type": "array",
                      "description": "Array of recent scan activity records from the caller's scoped data shard, ordered as returned by the underlying stats query.\n",
                      "items": {
                        "type": "object"
                      }
                    },
                    "hourly_mode": {
                      "type": "boolean",
                      "description": "Indicates whether the response data was sourced from the hourly aggregation table (`true`) or the daily aggregation table (`false`). Reflects the effective mode after plan eligibility is checked.\n"
                    },
                    "hourly_available": {
                      "type": "boolean",
                      "description": "Indicates whether the caller's current plan includes access to hourly analytics data, regardless of whether `?hourly=1` was requested.\n"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or token invalid"
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1StatsRecentScans"
      }
    },
    "/api/v1/stats/tag-rollups": {
      "get": {
        "tags": [
          "stats"
        ],
        "summary": "Retrieve scan counts rolled up by tag for the caller's scope",
        "description": "Returns the total number of QR-code scans grouped by tag for the\nauthenticated user's current scope (personal, team, or agency) over a\ntrailing window of N calendar days. This lets agencies compare campaign\nperformance across clients, e.g. \"client-a\" vs \"client-b\", without\nhaving to filter each individual QR code.\n\nThe scope is resolved automatically from the authenticated user's\nidentity and team membership via `resolveCodeScope`. The appropriate\nshard database is then selected with `dataDbForScope` before the\naggregation query is executed.\n\nBoth query parameters are clamped to safe ranges server-side: `days` is\nclamped to [1, 365] and defaults to 30; `top` is clamped to [1, 100]\nand defaults to 20. Non-finite or missing values fall back to the\nrespective defaults.\n\nAuthentication is required. The handler reads `data.user`, which is\npopulated by upstream authentication middleware that validates a bearer\nJWT. Requests without a valid token will be rejected before the handler\nis invoked.\n\nThis endpoint is read-only and has no side effects on stored data.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Trailing window in calendar days over which to aggregate scan counts. Clamped to [1, 365]. Defaults to 30.\n",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365,
              "default": 30
            }
          },
          {
            "name": "top",
            "in": "query",
            "required": false,
            "description": "Maximum number of tags to return, ordered by descending scan count. Clamped to [1, 100]. Defaults to 20.\n",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tag rollup aggregation succeeded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "tags",
                    "days",
                    "top_tags"
                  ],
                  "properties": {
                    "tags": {
                      "type": "array",
                      "description": "Ordered list of tag scan-count rollups for the resolved scope, descending by total scans, limited to `top_tags` entries.\n",
                      "items": {
                        "type": "object",
                        "properties": {
                          "tag": {
                            "type": "string",
                            "description": "The tag value associated with one or more QR codes."
                          },
                          "scans": {
                            "type": "integer",
                            "description": "Total number of scans recorded for this tag within the requested window."
                          }
                        }
                      }
                    },
                    "days": {
                      "type": "integer",
                      "description": "The effective trailing-day window used for the query after clamping.",
                      "minimum": 1,
                      "maximum": 365
                    },
                    "top_tags": {
                      "type": "integer",
                      "description": "The effective maximum number of tags returned after clamping.",
                      "minimum": 1,
                      "maximum": 100
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or token invalid"
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1StatsTagRollups"
      }
    },
    "/api/v1/support": {
      "post": {
        "tags": [
          "support"
        ],
        "summary": "Submit a Pro.qr support ticket",
        "description": "Accepts support ticket submissions for the pro.qr product. Both authenticated\nand anonymous users may submit; a Cloudflare Turnstile token is always required\nas a bot-defence measure regardless of authentication state.\n\nWhen a valid session cookie is present the handler reads the caller's plan tier\nfrom the `users` table (falling back to the value embedded in the session JWT).\nTickets submitted by users on the `business`, `team`, or `agency` tiers are\nstored with `priority = \"high\"` and the outbound notification email is prefixed\nwith `[URGENT]` to surface them in the support inbox.\n\nEvery submission is persisted to the `support_tickets` D1 table and triggers a\ntransactional email to the address bound in `SUPPORT_TO`. The reply-to address\nis set to the authenticated account email when available, otherwise to the\nemail field supplied in the request body.\n\nRate limit: 5 submissions per IP per hour. This is intentionally looser than\nthe public contact form to accommodate Pro users who legitimately file several\ntickets (bug report, billing question, feature request) in a single session.\n\nRequires the `DB` and `SUPPORT_TO` environment bindings to be present; their\nabsence causes an immediate 500 response before any other processing occurs.\n",
        "security": [
          {
            "cookieAuth": []
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "purpose",
                  "message",
                  "turnstile"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254,
                    "description": "Reply-to address for anonymous submitters. For authenticated users the account email takes precedence for storage and reply-to, but this field is still required by the schema.\n"
                  },
                  "purpose": {
                    "type": "string",
                    "enum": [
                      "bug_report",
                      "billing_question",
                      "feature_request",
                      "account_help",
                      "integration_support",
                      "other"
                    ],
                    "description": "Category of the support request."
                  },
                  "message": {
                    "type": "string",
                    "maxLength": 8000,
                    "description": "Full ticket body. May include stack traces or reproduction steps; the higher character limit (vs the public contact form) accommodates that.\n"
                  },
                  "turnstile": {
                    "type": "string",
                    "maxLength": 2048,
                    "description": "Cloudflare Turnstile challenge response token. Required for all submissions including authenticated users.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ticket accepted, persisted, and notification email dispatched",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failure or Turnstile verification failure",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code produced by the contact-form pipeline."
                    }
                  }
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed, only POST is accepted",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "const": "Method Not Allowed"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (more than 5 submissions from this IP in the past hour)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Server-side configuration error (missing DB or SUPPORT_TO binding) or an unexpected failure during persistence or email dispatch\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false
                    },
                    "error": {
                      "type": "string",
                      "enum": [
                        "config_error"
                      ]
                    },
                    "missing": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Names of the environment bindings that were absent."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1Support"
      }
    },
    "/api/v1/teams": {
      "get": {
        "tags": [
          "teams"
        ],
        "summary": "List the current user's teams",
        "description": "Returns all teams that the authenticated user belongs to, along with the user's currently\nactive team identifier.\n\nAuthentication is required. The handler reads `data.user` which is populated by upstream\nmiddleware that validates a bearer JWT. Unauthenticated requests will be rejected before\nthis handler is reached.\n\nNo plan restrictions apply to listing teams. Any authenticated user may call this endpoint\nregardless of their subscription tier.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Team list returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "teams": {
                      "type": "array",
                      "description": "All teams the current user is a member of",
                      "items": {
                        "type": "object"
                      }
                    },
                    "current_team_id": {
                      "type": "string",
                      "nullable": true,
                      "description": "The ID of the team currently selected by the user, or null if no team is active"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1Teams"
      },
      "post": {
        "tags": [
          "teams"
        ],
        "summary": "Create a new team for the current user",
        "description": "Creates a new team owned by the authenticated user and returns the created team record.\n\nAuthentication is required. The handler reads `data.user`, which is populated by upstream\nmiddleware that validates a bearer JWT. Unauthenticated requests are rejected before this\nhandler is reached.\n\nTwo additional access checks are enforced before the team is created:\n\n1. **Writable account**, `requireWritableAccount` is called and will throw a structured\n   error (carrying `.status` and `.body`) if the account is read-only (e.g. a demo or\n   suspended account). That error is re-serialised and returned with its original HTTP\n   status code.\n\n2. **Team plan**, the user's plan is checked via `hasTeamAccess`. Only users on a\n   `team` or `agency` plan may create teams. Users on a lower-tier plan receive a 403\n   response with `error: \"insufficient_plan\"`.\n\nOn success the team is persisted to the database and returned with HTTP 201. The `name`\nfield is read from the request body; if the body is unparseable JSON the name will be\n`undefined` and the behaviour depends on `createTeam`'s own validation.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Display name for the new team"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Team created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "team": {
                      "type": "object",
                      "description": "The newly created team record"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient plan or read-only account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "insufficient_plan"
                      ],
                      "description": "Machine-readable error code"
                    },
                    "required": {
                      "type": "string",
                      "enum": [
                        "team_or_agency"
                      ],
                      "description": "Minimum plan tier required to perform this action"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ],
                      "description": "Opaque internal error indicator"
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1Teams"
      }
    },
    "/api/v1/teams/{id}": {
      "get": {
        "tags": [
          "teams"
        ],
        "summary": "Get team details and current user's membership role",
        "description": "Retrieves a team record along with the authenticated user's role within that team.\n\nThe team is looked up by the path parameter `id`. If no team exists with that identifier\na 404 is returned. The caller must be an existing member of the team; non-members receive\na 403 regardless of the team's existence.\n\nAuthentication is required. The handler reads `data.user` (populated upstream by a bearer\ntoken middleware) to identify the calling user.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Unique identifier of the team."
          }
        ],
        "responses": {
          "200": {
            "description": "Team record and caller's membership role",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "team": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        }
                      }
                    },
                    "role": {
                      "type": "string",
                      "description": "The calling user's role in this team (e.g. owner, admin, member)."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a member of the team",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_team_member"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Team not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1TeamsId"
      },
      "patch": {
        "tags": [
          "teams"
        ],
        "summary": "Rename a team or transfer ownership to another member",
        "description": "Updates one or both of two independent team attributes in a single request:\n\n**Rename (`name`):** Changes the team's display name. Requires the caller to hold at\nleast the `admin` role. The name must be a non-empty string of at most 120 characters;\nviolating either constraint returns 400 with `invalid_name`.\n\n**Transfer ownership (`transfer_to_user_id`):** Reassigns the `owner` role from the\ncurrent caller to the specified user. Only the current `owner` may perform this action.\nThe target user must already be a member of the team (enforced by the `transferOwnership`\nhelper).\n\nBoth operations may be performed in the same request; ownership transfer is processed\nfirst. If the caller's role is insufficient for a requested operation, a 403 is returned.\nThe response always contains the latest team record after all mutations are applied.\n\nAuthentication is required via bearer token. The handler reads `data.user` (set by\nupstream middleware) to identify the calling user and enforce role checks.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Unique identifier of the team to update."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "New display name for the team. Must be non-empty and at most 120 characters. Requires admin role or above.\n"
                  },
                  "transfer_to_user_id": {
                    "type": "string",
                    "description": "User ID of the team member who should become the new owner. Only the current owner may supply this field.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated team record",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "team": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Provided name is empty or exceeds 120 characters",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_name"
                      ]
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller lacks the required role for the requested operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Team not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "patchV1TeamsId"
      },
      "delete": {
        "tags": [
          "teams"
        ],
        "summary": "Delete a team and nullify associated codes and user references",
        "description": "Permanently deletes the specified team. Only the team `owner` may perform this action;\nany other role (or a non-member) results in a 403.\n\n**Cascade behaviour (defined at the database schema level):**\n\n* All team memberships and pending invites are removed via `ON DELETE CASCADE`.\n* QR codes whose `team_id` referenced this team are updated to `NULL` via\n  `ON DELETE SET NULL`, making them user-scoped (owned by their original creator).\n* Any user whose `current_team_id` pointed to this team is also set to `NULL` via\n  `ON DELETE SET NULL`.\n\nAfter the deletion is committed an audit-log entry with action `team.delete` is\nwritten, recording the acting user, the team ID, and the former team name.\n\nAuthentication is required via bearer token. The handler reads `data.user` (set by\nupstream middleware) to identify the calling user and enforce the owner role check.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Unique identifier of the team to delete."
          }
        ],
        "responses": {
          "200": {
            "description": "Team successfully deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not the team owner",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Team not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "deleteV1TeamsId"
      }
    },
    "/api/v1/teams/{id}/activity": {
      "get": {
        "tags": [
          "teams"
        ],
        "summary": "Retrieve team activity leaderboard and chronological feed",
        "description": "Returns two complementary views of audit-log activity for the specified team:\na rollup leaderboard aggregated per user over a configurable rolling window, and\na chronological feed of individual audit-log entries.\n\nThe leaderboard always returns at most 25 users, sorted by action count descending.\nThe feed length is controlled by the `limit` query parameter (default 50, max 200).\nThe rolling window for the leaderboard is controlled by the `days` query parameter\n(default 30, max 180). Both parameters are clamped to their documented ranges; any\nnon-finite value (e.g. NaN from a non-numeric string) falls back to the default.\n\nAuthentication is required. The caller must supply a valid bearer JWT. The resolved\nuser must be an active member (role `member` or higher) of the requested team.\nNon-members receive a 403 response. Unauthenticated requests are rejected before\nthe handler is reached by the authentication middleware that populates `data.user`.\n\nNo mutations are performed; this endpoint is read-only and produces no side effects.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Unique identifier of the team whose activity is being requested."
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 30,
              "minimum": 1,
              "maximum": 180
            },
            "description": "Rolling window in days over which the leaderboard action counts are aggregated. Values below 1 are clamped to 1; values above 180 are clamped to 180. Non-numeric values fall back to the default of 30.\n"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 200
            },
            "description": "Maximum number of entries to return in the chronological activity feed. Values below 1 are clamped to 1; values above 200 are clamped to 200. Non-numeric values fall back to the default of 50.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Leaderboard and feed returned successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "leaderboard": {
                      "type": "array",
                      "description": "Up to 25 users ranked by action count descending within the requested rolling window.\n",
                      "items": {
                        "type": "object",
                        "properties": {
                          "user_id": {
                            "type": "string",
                            "description": "Unique identifier of the team member."
                          },
                          "email": {
                            "type": "string",
                            "description": "Email address of the team member."
                          },
                          "action_count": {
                            "type": "integer",
                            "description": "Total number of actions performed within the window."
                          },
                          "last_action_at": {
                            "type": "string",
                            "description": "ISO 8601 timestamp of the member's most recent action."
                          },
                          "actions": {
                            "type": "array",
                            "description": "Breakdown of individual action types and their counts.",
                            "items": {
                              "type": "object"
                            }
                          }
                        }
                      }
                    },
                    "feed": {
                      "type": "array",
                      "description": "Chronological list of individual audit-log events for the team, up to the requested limit.\n",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Unique identifier of the audit-log entry."
                          },
                          "action": {
                            "type": "string",
                            "description": "Name of the action that was performed."
                          },
                          "target_type": {
                            "type": "string",
                            "description": "Resource type that was the target of the action."
                          },
                          "target_id": {
                            "type": "string",
                            "description": "Identifier of the target resource."
                          },
                          "details": {
                            "type": "string",
                            "description": "JSON-encoded string containing additional structured details about the action.\n"
                          },
                          "created_at": {
                            "type": "string",
                            "description": "ISO 8601 timestamp of when the action occurred."
                          },
                          "user_id": {
                            "type": "string",
                            "description": "Identifier of the user who performed the action."
                          },
                          "actor_email": {
                            "type": "string",
                            "description": "Email address of the user who performed the action."
                          }
                        }
                      }
                    },
                    "days": {
                      "type": "integer",
                      "description": "The effective rolling-window value used for the leaderboard query."
                    },
                    "limit": {
                      "type": "integer",
                      "description": "The effective feed-length limit used for the feed query."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The authenticated user is not a member of the specified team, or does not hold the minimum required role.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No valid bearer token was provided, or the token could not be resolved to an active user by the authentication middleware.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "An unexpected server-side error occurred.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1TeamsIdActivity"
      }
    },
    "/api/v1/teams/{id}/heatmaps": {
      "get": {
        "tags": [
          "teams"
        ],
        "summary": "Retrieve aggregated heatmap analytics for a team",
        "description": "Returns three heatmap datasets aggregated across all QR codes belonging to the\nspecified team: a daily calendar of scan counts, an hourly-by-day-of-week grid,\nand a geographic breakdown by country.\n\nThe caller must be an authenticated team member (role of \"member\" or higher).\nAuthentication is performed via a bearer JWT, and the resolved user identity is\nused both for membership verification and for determining the plan-based data\nretention cap.\n\nThe `calendar` dataset covers up to the requested `days` window, zero-filled for\ndays with no scans. The window is capped at 1095 days and further constrained by\nthe plan-level retention limit (`ANALYTICS_DAYS[plan]`; default 365 days).\n\nThe `hourly_dow` grid (day-of-week \u00d7 hour-of-day scan totals over the last 90\ndays) is only populated when the team owner's plan includes hourly analytics\n(Team/Agency tier). For lower-tier plans the array is returned empty and\n`hourly_available` is set to `false`.\n\nThe `geo` dataset lists up to 50 countries by descending scan volume. Countries\nwhose total falls below a noise floor of 5 scans are collapsed into a synthetic\n\"Other\" entry. No plan gate is applied to the calendar or geo datasets.\n\nNo mutations are performed; this endpoint is read-only.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The UUID or unique identifier of the team whose heatmap data is requested.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Number of past days to include in the calendar dataset. Defaults to 365. Values below 1 are coerced to 365. Values above 1095 are capped at 1095. Additionally capped by the plan-level analytics retention limit.\n",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1095,
              "default": 365
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Heatmap datasets successfully aggregated for the team.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "team_id": {
                      "type": "string",
                      "description": "The team identifier echoed from the path parameter."
                    },
                    "calendar": {
                      "type": "array",
                      "description": "Zero-filled daily scan counts for each day in the requested window, ordered from oldest to most recent.\n",
                      "items": {
                        "type": "object",
                        "properties": {
                          "day": {
                            "type": "string",
                            "format": "date",
                            "description": "ISO 8601 date string (YYYY-MM-DD)."
                          },
                          "scans": {
                            "type": "integer",
                            "description": "Total scans recorded on this day across all team codes."
                          }
                        }
                      }
                    },
                    "hourly_dow": {
                      "type": "array",
                      "description": "Hourly-by-day-of-week scan totals over the last 90 days. Empty array when the plan does not include hourly analytics.\n",
                      "items": {
                        "type": "object",
                        "properties": {
                          "dow": {
                            "type": "integer",
                            "description": "Day of week (0 = Sunday, 6 = Saturday).",
                            "minimum": 0,
                            "maximum": 6
                          },
                          "hour": {
                            "type": "integer",
                            "description": "Hour of day in UTC (0\u201323).",
                            "minimum": 0,
                            "maximum": 23
                          },
                          "scans": {
                            "type": "integer",
                            "description": "Total scans recorded at this dow/hour combination."
                          }
                        }
                      }
                    },
                    "hourly_available": {
                      "type": "boolean",
                      "description": "Whether the team owner's plan includes hourly analytics. When false, `hourly_dow` is always an empty array.\n"
                    },
                    "geo": {
                      "type": "array",
                      "description": "Country-level scan totals in descending order, limited to 50 entries. Countries below the 5-scan noise floor are collapsed into a single \"Other\" entry appended at the end.\n",
                      "items": {
                        "type": "object",
                        "properties": {
                          "country": {
                            "type": "string",
                            "description": "ISO country code as stored in the scans table, or the synthetic value \"Other\" for the noise-floor remainder bucket.\n"
                          },
                          "scans": {
                            "type": "integer",
                            "description": "Aggregated scan count for this country."
                          }
                        }
                      }
                    },
                    "window_days": {
                      "type": "integer",
                      "description": "The effective number of days used for the calendar window after all capping logic."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No valid bearer token provided or the resolved user is not a member of the team.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The authenticated user does not hold the required role within the team.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "An unexpected server-side error occurred.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1TeamsIdHeatmaps"
      }
    },
    "/api/v1/teams/{id}/invites": {
      "get": {
        "tags": [
          "teams"
        ],
        "summary": "List pending invites for a team",
        "description": "Returns all pending (not yet accepted or expired) invites for the specified team.\n\nThe caller must be an authenticated user (bearer JWT required) and must hold at\nleast the `admin` role within the team. The membership check and role assertion\nare performed before any invite data is returned.\n\nIf the caller is not a member of the team, or does not hold the `admin` role,\na 403 response is returned. Internal errors fall back to a generic 500 response.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the team whose pending invites are being listed."
          }
        ],
        "responses": {
          "200": {
            "description": "List of pending invites retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invites": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "email": {
                            "type": "string",
                            "format": "email"
                          },
                          "role": {
                            "type": "string"
                          },
                          "team_id": {
                            "type": "string"
                          },
                          "invited_by": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a team admin or is not a member of the team",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1TeamsIdInvites"
      },
      "post": {
        "tags": [
          "teams"
        ],
        "summary": "Create and email a team invite",
        "description": "Creates a new pending invite for the specified email address and role, then\nattempts to deliver an invite email via ZeptoMail. The invite record is\npersisted in D1 before the email is sent, so the invite remains valid even\nif email delivery fails. The admin can re-send by repeating the request.\n\nThe caller must be an authenticated user (bearer JWT required) and must hold\nat least the `admin` role within the team. The membership check and role\nassertion are performed before any invite is created.\n\nAs a best-effort side effect, if the invited email address already corresponds\nto a registered user account and that user is not the inviting admin, a push\nnotification is dispatched to that user on the `team_invite` topic. This push\nis fire-and-forget and will not cause the request to fail if it errors.\n\nOn success the newly created invite object is returned with HTTP 201. If the\ncaller lacks the admin role a 403 is returned. Validation or business-logic\nerrors surfaced by the team library are forwarded with their original status\ncode and body. Internal errors fall back to a generic 500 response.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the team to which the invite belongs."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "The email address of the person being invited."
                  },
                  "role": {
                    "type": "string",
                    "description": "The role to assign to the invitee upon acceptance (e.g. \"member\", \"admin\")."
                  },
                  "team_name": {
                    "type": "string",
                    "description": "Human-readable team name included in the invite email body. Defaults to an empty string if omitted.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invite created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invite": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "role": {
                          "type": "string"
                        },
                        "team_id": {
                          "type": "string"
                        },
                        "invited_by": {
                          "type": "string"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a team admin or is not a member of the team",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1TeamsIdInvites"
      }
    },
    "/api/v1/teams/{id}/invites/{invite_id}": {
      "delete": {
        "tags": [
          "teams"
        ],
        "summary": "Revoke a pending team invite",
        "description": "Permanently revokes a pending invite identified by `invite_id` within the team identified by `id`.\nThe invite record is looked up by both its own ID and the team ID to prevent cross-team access.\n\nAuthentication is required. The request must carry a valid bearer JWT; the resolved user identity\nis available on `data.user`. The caller must be a member of the team with at least the `admin` role ,\nlower-privileged members (e.g. `member`) will receive a 403 response.\n\nOn success the invite is deleted from the `team_invites` table and an audit log entry of type\n`invite.revoke` is written, recording the acting user, the team, and the email address that had\nbeen invited.\n\nNo rate limits are enforced at the handler level. Side effects: invite deletion is permanent and\ncannot be undone through this API; the invited address would need to be re-invited.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the team whose invite is being revoked."
          },
          {
            "name": "invite_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the pending invite to revoke."
          }
        ],
        "responses": {
          "200": {
            "description": "Invite successfully revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller lacks the required admin role within the team",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No matching invite found for the given invite_id and team id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_found"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server-side error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "deleteV1TeamsIdInvitesInviteId"
      }
    },
    "/api/v1/teams/{id}/members": {
      "get": {
        "tags": [
          "teams"
        ],
        "summary": "List members and roles for a team",
        "description": "Returns all members belonging to the specified team, including their roles.\n\nThe caller must be an authenticated user with at least the `member` role in\nthe target team. Authentication is established via a bearer JWT which the\nframework resolves into `data.user` before the handler runs. If the caller\nis not a member of the team (or has insufficient role), the error returned\nby `requireRole` is forwarded directly with its associated HTTP status code.\n\nNo side effects are produced by this endpoint; it is a pure read operation\nagainst the database. No rate limits are enforced by the handler itself.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the team whose members are being listed."
          }
        ],
        "responses": {
          "200": {
            "description": "Successfully retrieved the list of team members.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "members"
                  ],
                  "properties": {
                    "members": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The authenticated user is not a member of the team or does not hold a sufficient role.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1TeamsIdMembers"
      }
    },
    "/api/v1/teams/{id}/members/{user_id}": {
      "patch": {
        "tags": [
          "teams"
        ],
        "summary": "Change a team member's role",
        "description": "Updates the role of a specific team member identified by `user_id` within the team identified by `id`.\n\nThe authenticated user must be a member of the team with at least the `admin` role to perform this operation.\nThe target member's previous role is captured before the update so that the resulting webhook event can\ncommunicate whether the change was a promotion or a demotion.\n\nOwners cannot have their role changed through this endpoint; the underlying `changeRole` helper enforces\nthis restriction and will return an appropriate error response.\n\nA `team.role_changed` webhook event is dispatched asynchronously after a successful role change. The payload\nincludes the team ID, the affected user ID, the previous role, the new role, and the ID of the acting user.\n\nAuthentication is required via a bearer JWT. The resolved user is available on `data.user`.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the team."
          },
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the team member whose role is being changed."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "type": "string",
                    "description": "The new role to assign to the target member. Accepted values are determined by the team membership model (e.g. \"member\", \"admin\"). Setting this to the owner role is not permitted.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Role changed successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The authenticated user does not have the required role, or the target member is the team owner.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "The team or the target member was not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "An unexpected internal error occurred.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "internal"
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "patchV1TeamsIdMembersUserId"
      },
      "delete": {
        "tags": [
          "teams"
        ],
        "summary": "Remove a member from a team or leave the team",
        "description": "Removes the team member identified by `user_id` from the team identified by `id`.\n\nThere are two permitted actors for this operation:\n\n1. **Admins (and above):** Any member of the team holding at least the `admin` role may remove any other\n   non-owner member.\n2. **Self-leave:** A member may remove themselves from the team regardless of their own role, as long as\n   they are not the team owner. Owners must transfer ownership before they can leave.\n\nThe target member's role at the time of removal is captured and included in the outgoing webhook event.\nA `team.member_removed` webhook event is dispatched asynchronously after a successful removal. The payload\nincludes the team ID, the removed user ID, their previous role, the ID of the acting user, and a boolean\n`self_leave` flag indicating whether the removal was voluntary.\n\nAttempting to remove the team owner (via either path) is rejected by the underlying `removeMember` helper.\n\nAuthentication is required via a bearer JWT. The resolved user is available on `data.user`.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the team."
          },
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the team member to remove. May equal the authenticated user's own ID to perform a self-leave.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Member removed successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The authenticated user is neither an admin nor the target user, or the target user is the team owner and cannot be removed.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "The team or the target member was not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "An unexpected internal error occurred.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "internal"
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "deleteV1TeamsIdMembersUserId"
      }
    },
    "/api/v1/teams/{id}/public-stats": {
      "get": {
        "tags": [
          "teams"
        ],
        "summary": "Get public stats sharing state for a team",
        "description": "Returns the current public-stats sharing state for the specified team, including\nwhether a shareable token is active and the corresponding share URL if one exists.\n\nAuthentication is required. The caller must be an authenticated user and a member\nof the team (any role, including \"member\"). Non-members receive a 403 error.\n\nThis endpoint has no side effects, it is purely read-only and does not modify\nany tokens or sharing state.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the team whose public stats state is being queried."
          }
        ],
        "responses": {
          "200": {
            "description": "Current public stats sharing state for the team",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean",
                      "description": "Whether a public stats share token is currently active for this team."
                    },
                    "shareUrl": {
                      "type": "string",
                      "description": "The full shareable URL granting read-only access to team analytics, present only when enabled is true."
                    },
                    "token": {
                      "type": "string",
                      "description": "The active public stats token, present only when a token exists."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not a member of the team",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1TeamsIdPublicStats"
      },
      "post": {
        "tags": [
          "teams"
        ],
        "summary": "Mint a new shareable public stats token for a team",
        "description": "Mints a new shareable public-stats token for the specified team, granting\nanonymous read-only access to the team's aggregated analytics at /p/<token>/.\nThe generated share URL exposes no per-code destination data and no personally\nidentifiable information.\n\nAuthentication is required. The caller must be the team owner. Team members\nwith any lesser role (e.g. \"member\") are not permitted to mint tokens, because\nminting is destructive: any previously issued share URL for this team is\nimplicitly revoked and replaced by the new token. This prevents a plain member\nfrom silently rotating the team-wide share link.\n\nOn success, the action is recorded in the audit log as `team.public_stats.enable`.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the team for which a public stats token should be minted."
          }
        ],
        "responses": {
          "200": {
            "description": "New public stats token minted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "The newly minted public stats token."
                    },
                    "shareUrl": {
                      "type": "string",
                      "description": "The full shareable URL at which team analytics are publicly accessible."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not the team owner",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1TeamsIdPublicStats",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "description": "Request payload. Exact field set depends on the endpoint; check the handler source for body destructuring patterns."
                  }
                },
                "additionalProperties": true
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "teams"
        ],
        "summary": "Revoke all public stats share tokens for a team",
        "description": "Revokes all active public-stats share tokens for the specified team, immediately\ninvalidating any previously issued share URLs at /p/<token>/. After this operation,\nno anonymous party can access the team's aggregated analytics until a new token is\nminted via POST.\n\nAuthentication is required. The caller must be the team owner. Team members with\nany lesser role are not permitted to revoke tokens, for the same reason they cannot\nmint them, rotating or disabling the team-wide share link is a privileged action.\n\nOn success, the action is recorded in the audit log as `team.public_stats.disable`.\nThe response contains a `revoked: true` confirmation field.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the team whose public stats tokens should be revoked."
          }
        ],
        "responses": {
          "200": {
            "description": "All public stats tokens for the team have been revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not the team owner",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "deleteV1TeamsIdPublicStats"
      }
    },
    "/api/v1/teams/{id}/stats": {
      "get": {
        "tags": [
          "teams"
        ],
        "summary": "Get rollup statistics for a team",
        "description": "Returns aggregated statistics for the specified team, including QR code counts\nbroken down by status, scan counts over the last 30 and 90 days, member list,\nand seat usage metrics.\n\nAuthentication is required via a bearer JWT. The authenticated user must be a\nmember of the team (any role, including \"member\") to access this endpoint.\nMembership and role are verified before any data is returned; users who are not\nmembers of the requested team receive a 403 error.\n\nResults are served from a per-scope stats cache when available. On a cache miss\nthe stats are computed from the shard database selected for the team/user scope,\nand the computed result is written back to the cache asynchronously (via\n`waitUntil` when available, otherwise fire-and-forget). The response includes a\n`cache` object indicating whether the result was a cache hit or miss.\n\nThe depth or limits of certain statistics (e.g. scan windows) may vary based on\nthe authenticated user's plan.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unique identifier of the team whose stats are being requested."
          }
        ],
        "responses": {
          "200": {
            "description": "Team statistics returned successfully (from cache or freshly computed)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "team_id": {
                      "type": "string",
                      "description": "The team ID echoed from the path parameter."
                    },
                    "cache": {
                      "type": "object",
                      "properties": {
                        "hit": {
                          "type": "boolean",
                          "description": "True if the response was served from the stats cache, false if freshly computed."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The authenticated user is not a member of the team or lacks sufficient role",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required, no valid bearer token present",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1TeamsIdStats"
      }
    },
    "/api/v1/user/current-team": {
      "patch": {
        "tags": [
          "user"
        ],
        "summary": "Switch the signed-in user's active team context",
        "description": "Updates the authenticated user's `current_team_id` field, which controls which team\nscope the user is acting within for subsequent requests.\n\nPassing a valid team UUID sets that team as the active context. The user must already\nbe a member of the specified team; if they are not, the request is rejected with a 403.\n\nPassing `null` (or omitting `team_id`) clears the active team context, placing the user\nin their personal scope. Any value that is neither a string nor `null` is rejected with\na 400.\n\nAuthentication is required. The handler reads `data.user.id` from middleware-populated\nsession data, which implies a valid bearer JWT must accompany the request. No explicit\nrate limits are enforced at the handler level.\n\nSide effects: the `users` table row for the authenticated user is updated in place,\nsetting `current_team_id` and refreshing `updated_at` to the current Unix timestamp.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "team_id": {
                    "description": "UUID of the team to switch into, or `null` to clear the active team context and revert to personal scope. If omitted, the context is also cleared.\n",
                    "oneOf": [
                      {
                        "type": "string",
                        "description": "UUID of a team the authenticated user is a member of"
                      },
                      {
                        "type": "null",
                        "description": "Clears the current team context"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Active team context updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "current_team_id": {
                      "description": "The new active team UUID, or null if the context was cleared",
                      "oneOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The provided team_id value is not a string or null",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_team_id"
                      ]
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The authenticated user is not a member of the specified team",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "not_team_member"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "An unexpected server-side error occurred",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "patchV1UserCurrentTeam"
      }
    },
    "/api/v1/user/export": {
      "get": {
        "tags": [
          "user"
        ],
        "summary": "Export complete account data as a ZIP archive",
        "description": "Returns a ZIP archive containing the authenticated user's complete account\ndata. The archive includes three files: `codes.csv` (every dynamic QR code\never created for the account, including grace-period and expired codes),\n`scans.csv` (aggregated daily scan counts per code, country, and device\ntype), `README.txt` (a column guide and re-import instructions), and\n`MANIFEST.json` (a portable reconstitution manifest with format version,\nper-file SHA-256 digests, shortcode derivation parameters, and account\nmetadata per Abundera patent provisional QR-05).\n\nAuthentication is required. The handler reads `data.user` which is\npopulated upstream by session middleware; unauthenticated requests will\nbe rejected before the handler runs.\n\nThe user's data is fetched from the shard database determined by\n`dataDbForUser`. Both the codes and scans queries are scoped strictly to\nthe authenticated user's `user_id`.\n\nAs a side effect, a GDPR audit-log entry is written fire-and-forget to\n`env.DB` recording the user ID, email, action (`export`), source\n(`self_service`), timestamp, code count, scan row count, and ZIP byte\nsize. A failure in this log write does not block delivery of the archive.\n\nThe response is not cacheable (`Cache-Control: no-store`). The\n`Content-Disposition` header sets a filename of the form\n`abundera-qr-pro-YYYY-MM-DD.zip` based on the UTC date at request time.\n\nNo rate limit is documented in the handler source, but the operation\nperforms two database queries and computes three SHA-256 digests before\nstreaming the ZIP, so callers should avoid issuing this request in tight\nloops.\n",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "ZIP archive containing MANIFEST.json, codes.csv, scans.csv, and README.txt for the authenticated user.\n",
            "headers": {
              "Content-Disposition": {
                "description": "Attachment filename in the form `attachment; filename=\"abundera-qr-pro-YYYY-MM-DD.zip\"`.\n",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Always `no-store`.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated. The session middleware rejected the request before the handler was invoked."
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1UserExport"
      }
    },
    "/api/v1/user/me": {
      "delete": {
        "tags": [
          "user"
        ],
        "summary": "Soft-delete the authenticated user's account",
        "description": "Marks the authenticated user's account as pending deletion by setting\n`plan_status` to `pending_delete` and recording `delete_requested_at`.\nThe account is not immediately removed; a scheduled daily sweeper in the\nredirect worker performs the hard delete 30 days after the request,\npurging QR codes, scans, API keys, the user row, and tombstoning every\nassociated KV entry.\n\nThe caller must supply `{ \"confirm\": \"DELETE\" }` in the JSON request body\nas an explicit acknowledgement. Omitting or misspelling this field returns\na 400 error and leaves the account untouched.\n\nStripe subscription cancellation is not performed by this endpoint and\nshould be completed via the Customer Portal prior to deletion (or will be\nhandled independently via webhook).\n\nA GDPR audit-log entry is written asynchronously (fire-and-forget) with\naction `delete` and source `self_service`, recording the scheduled purge\ntimestamp. A second audit entry for `hard_delete` is written by the daily\nsweeper after the 30-day hold expires.\n\nRequires a valid bearer JWT. The resolved user is expected to be present\non `data.user` as populated by upstream authentication middleware.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "confirm"
                ],
                "properties": {
                  "confirm": {
                    "type": "string",
                    "enum": [
                      "DELETE"
                    ],
                    "description": "Explicit deletion acknowledgement. Must be the exact string \"DELETE\" or the request will be rejected with 400.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Account successfully marked for deletion",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending_delete"
                      ],
                      "description": "Indicates the account is now in the pending-delete state."
                    },
                    "delete_requested_at": {
                      "type": "integer",
                      "description": "Unix timestamp (seconds) when the deletion was requested."
                    },
                    "purge_after": {
                      "type": "integer",
                      "description": "Unix timestamp (seconds) after which the hard delete will be executed by the scheduled sweeper (delete_requested_at + 30 days).\n"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Confirmation field missing or incorrect",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "confirm_required"
                      ],
                      "description": "Returned when the confirm field is absent or not equal to \"DELETE\"."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "deleteV1UserMe"
      }
    },
    "/api/v1/user/scan-usage": {
      "get": {
        "tags": [
          "user"
        ],
        "summary": "Get current month's scan usage for the authenticated user",
        "description": "Returns scan usage statistics for the currently authenticated user for the\npresent calendar month. The month is determined server-side in UTC and\nformatted as `YYYY-MM`.\n\nThe scan count is read from a shared KV namespace where the key\n`mscan:<user_id>:<YYYY-MM>` is incremented by the `qr-redirect-worker`\neach time one of the user's QR codes is scanned. A separate sentinel key\n`mscan-sent:<user_id>:<YYYY-MM>` is checked to determine whether a\ncap-crossed notification has already been dispatched for this month.\n\nThe `cap` field reflects the plan's advertised monthly scan limit as\ndefined in the server-side plan configuration. A `null` cap indicates an\nunlimited plan. The `over` flag is `true` when the cap is non-null and the\ncurrent count meets or exceeds it.\n\nAuthentication is required. The handler reads `data.user` which is\npopulated by upstream middleware that validates the bearer JWT. Requests\nwithout a valid token will be rejected before reaching this handler.\n\nThis endpoint has no side effects; it performs read-only KV lookups. If\nthe KV namespace is unavailable the handler recovers gracefully and returns\na count of `0` with `over: false`.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Scan usage retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "month",
                    "plan",
                    "count",
                    "cap",
                    "over",
                    "crossed_notification_flag"
                  ],
                  "properties": {
                    "month": {
                      "type": "string",
                      "description": "The current calendar month in `YYYY-MM` format, determined server-side in UTC.\n",
                      "example": "2025-05"
                    },
                    "plan": {
                      "type": "string",
                      "description": "The authenticated user's current plan identifier.",
                      "example": "pro"
                    },
                    "count": {
                      "type": "integer",
                      "description": "Number of scans recorded for the user during the current month. Returns `0` if no scans have been recorded or if the KV namespace is unavailable.\n",
                      "minimum": 0
                    },
                    "cap": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "The plan's advertised monthly scan limit. `null` indicates the plan has no enforced cap (unlimited).\n",
                      "minimum": 0
                    },
                    "over": {
                      "type": "boolean",
                      "description": "`true` if the user's scan count meets or exceeds the plan cap this month. Always `false` when `cap` is `null`.\n"
                    },
                    "crossed_notification_flag": {
                      "type": "boolean",
                      "description": "`true` if a cap-crossed notification sentinel key exists in KV for this user and month, indicating a notification was previously dispatched.\n"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required, no valid bearer token was provided"
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1UserScanUsage"
      }
    },
    "/api/v1/user/scope-stats": {
      "get": {
        "tags": [
          "user"
        ],
        "summary": "Retrieve usage stats for the caller's current scope",
        "description": "Returns aggregated usage statistics for the authenticated user's active scope. The\nscope is resolved automatically: if the user has a `current_team_id` and a valid team\nmembership that scope is used (type `team`); otherwise the user's personal workspace\nscope is used (type `user`). This means the dashboard can make a single call without\nneeding to branch on scope resolution itself.\n\nAuthentication is required. The handler reads `data.user` which is populated by an\nupstream authentication middleware that validates a bearer JWT. Requests without a\nvalid token will be rejected before the handler is invoked.\n\nThe endpoint implements a read-through cache introduced in migration 019. On a cache\nhit the pre-computed stats object is returned immediately along with `cache.hit: true`.\nOn a miss, live stats are computed from the appropriate shard database, the result is\nreturned to the caller with `cache.hit: false`, and the cache is written back\nasynchronously (via `waitUntil` where available) so subsequent callers benefit from\nthe cached value. No rate limiting or quota enforcement is applied inside this handler.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Stats for the resolved scope, with scope metadata and cache status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "scope": {
                      "type": "object",
                      "description": "Describes the resolved scope for this response",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "team",
                            "user"
                          ],
                          "description": "Whether stats are for a team or a personal workspace"
                        },
                        "team_id": {
                          "type": "string",
                          "description": "Present only when type is \"team\"; the resolved team identifier"
                        },
                        "role": {
                          "type": "string",
                          "description": "Present only when type is \"team\"; the caller's role within the team"
                        },
                        "workspace_label": {
                          "type": "string",
                          "nullable": true,
                          "description": "Present only when type is \"user\"; the user's workspace label, or null"
                        }
                      }
                    },
                    "cache": {
                      "type": "object",
                      "description": "Cache metadata for the response",
                      "properties": {
                        "hit": {
                          "type": "boolean",
                          "description": "True if the response was served from the stats cache, false if freshly computed"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server-side error during scope resolution or stats computation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1UserScopeStats"
      }
    },
    "/api/v1/user/weather-overlay": {
      "get": {
        "tags": [
          "user/weather-overlay"
        ],
        "summary": "Correlate daily scan volume with weather for top countries",
        "description": "Returns a time-series overlay of daily QR-code scan counts alongside daily\nmean temperature (\u00b0C) and total precipitation (mm) for the scope's top-3\ncountries by scan volume over the requested window.\n\nThe handler resolves the caller's team or personal scope, queries the shard\ndatabase for the top-3 countries ranked by total scans in the window, then\nfetches historical weather from the open-meteo free archive API\n(archive-api.open-meteo.com) using fixed centroid coordinates for each\ncountry. Weather data is cached in KV under a per-(country, date-range) key\nwith a 24-hour TTL; only countries present in the built-in centroid table\nare eligible for weather data.\n\nFor each country, Pearson correlation coefficients are computed between the\ndaily scan series and each of the temperature and precipitation series.\nCountries whose ISO code is not in the centroid table are still returned but\nwith `available: false` and no weather or correlation fields.\n\nAuthentication is required; the handler reads `data.user` which is populated\nby upstream bearer-token middleware. Open-meteo is called without a customer\nAPI key; its free tier allows up to 10 000 calls per day and receives only\n(latitude, longitude, date range), no user-identifying information.\n\nThe `days` window is clamped to the range [7, 90]. If no scan data exists\nwith country information in the window, the response body contains\n`available: false` with reason `no_country_data` at the top level.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Number of trailing calendar days to include in the analysis window. Clamped to the range [7, 90]. Defaults to 30.\n",
            "schema": {
              "type": "integer",
              "minimum": 7,
              "maximum": 90,
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Weather-overlay data returned. The top-level `available` flag is `false` when no country scan data exists; otherwise `true` and the `days` and `countries` arrays are present.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "available": {
                      "type": "boolean",
                      "description": "`false` when there is no scan data with country information for the requested window; `true` otherwise.\n"
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "no_country_data"
                      ],
                      "description": "Present only when `available` is `false`. Indicates why no overlay could be produced.\n"
                    },
                    "days": {
                      "type": "array",
                      "description": "Ordered list of calendar dates (YYYY-MM-DD) covering the requested window, newest last. Present only when `available` is `true`.\n",
                      "items": {
                        "type": "string",
                        "format": "date"
                      }
                    },
                    "countries": {
                      "type": "array",
                      "description": "Up to three country entries ranked by descending total scan volume. Present only when `available` is `true`.\n",
                      "items": {
                        "type": "object",
                        "properties": {
                          "iso": {
                            "type": "string",
                            "description": "ISO 3166-1 alpha-2 country code."
                          },
                          "total_scans": {
                            "type": "integer",
                            "description": "Total scan count for this country over the window.\n"
                          },
                          "daily_scans": {
                            "type": "array",
                            "description": "Scan count for each day in the `days` array (zero-filled for days with no scans).\n",
                            "items": {
                              "type": "integer"
                            }
                          },
                          "daily_temp_c": {
                            "type": "array",
                            "description": "Daily mean temperature in degrees Celsius, aligned to the `days` array. `null` for days where open-meteo returned no value. Absent when `available` is `false` for this country.\n",
                            "items": {
                              "type": [
                                "number",
                                "null"
                              ]
                            }
                          },
                          "daily_precip_mm": {
                            "type": "array",
                            "description": "Daily total precipitation in millimetres, aligned to the `days` array. Treated as 0 when open-meteo returns `null`. Absent when `available` is `false` for this country.\n",
                            "items": {
                              "type": [
                                "number",
                                "null"
                              ]
                            }
                          },
                          "correlation": {
                            "type": "object",
                            "description": "Pearson correlation coefficients between each weather series and the daily scan series. Absent when `available` is `false` for this country.\n",
                            "properties": {
                              "temp_r": {
                                "type": [
                                  "number",
                                  "null"
                                ],
                                "description": "Pearson r between daily temperature and daily scans. `null` when fewer than three finite paired observations are available.\n"
                              },
                              "precip_r": {
                                "type": [
                                  "number",
                                  "null"
                                ],
                                "description": "Pearson r between daily precipitation and daily scans. `null` when fewer than three finite paired observations are available.\n"
                              }
                            }
                          },
                          "available": {
                            "type": "boolean",
                            "description": "`false` when weather data could not be fetched for this country (e.g. its ISO code is not in the centroid table or the open-meteo request failed). When `false`, the `daily_temp_c`, `daily_precip_mm`, and `correlation` fields are absent.\n"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1UserWeatherOverlay"
      }
    },
    "/api/v1/user/workspace-label": {
      "patch": {
        "tags": [
          "user"
        ],
        "summary": "Set or clear the user's virtual-workspace display label",
        "description": "Updates the authenticated user's personal workspace display label, the name\nshown in place of a team name when the user operates in personal (non-team)\nscope. No team row is created; the value is stored directly on the user record.\n\nPassing a non-empty string sets the label (trimmed, capped at 120 characters).\nPassing `null`, an empty string `\"\"`, or a whitespace-only string clears the\nlabel back to the default \"My workspace\" behaviour by setting the column to\n`NULL`.\n\nRequires a valid bearer JWT. The handler reads `data.user.id` to identify the\nauthenticated user, so requests without a valid session will be rejected by\nupstream middleware before reaching this handler.\n\nNo explicit rate limit is enforced within this handler beyond what the platform\napplies globally. The only side effect is an `UPDATE` to the `users` table\n(`workspace_label` and `updated_at` columns).\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workspace_label": {
                    "description": "The desired display label for the user's personal workspace.\nPass `null` or `\"\"` to clear the label and revert to the default.\nWhitespace-only strings are treated as a clear. Maximum 120\ncharacters after trimming. Must be a string (or null) if present.\n",
                    "oneOf": [
                      {
                        "type": "string",
                        "maxLength": 120
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Label updated or cleared successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "workspace_label": {
                      "description": "The stored label value, or null if cleared.",
                      "oneOf": [
                        {
                          "type": "string",
                          "maxLength": 120
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error, workspace_label value is invalid",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_workspace_label"
                      ]
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "must_be_string",
                        "too_long"
                      ]
                    },
                    "max": {
                      "type": "integer",
                      "description": "Maximum allowed length; present only when reason is too_long.",
                      "example": 120
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "patchV1UserWorkspaceLabel"
      }
    },
    "/api/v1/user/workspace-stats": {
      "get": {
        "tags": [
          "user"
        ],
        "summary": "Get personal workspace statistics for the signed-in user",
        "description": "Returns a rollup of QR code and scan statistics scoped to the authenticated\nuser's personal workspace, that is, codes where no team is assigned\n(team_id IS NULL). This mirrors the shape returned by the team stats\nendpoint but applies only to the user's own codes.\n\nAuthentication is required. The handler reads the resolved `data.user`\nobject, which is populated upstream by bearer-token middleware. Requests\nwithout a valid JWT will be rejected before reaching this handler.\n\nThe response includes all scan and code metrics produced by the shared\n`statsForScope` utility, supplemented by the user's `workspace_label` if\none has been set on the account. No member list is included because a\npersonal workspace has no team members.\n\nNo write side-effects are produced by this endpoint. Rate limits and quotas\nare governed by the user's plan, as passed to the underlying stats query.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Personal workspace statistics returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "workspace_label": {
                      "type": "string",
                      "nullable": true,
                      "description": "Human-readable label for the user's personal workspace, or null if none has been set.\n"
                    },
                    "total_codes": {
                      "type": "integer",
                      "description": "Total number of QR codes in the personal workspace."
                    },
                    "active_codes": {
                      "type": "integer",
                      "description": "Number of QR codes that are currently active."
                    },
                    "total_scans": {
                      "type": "integer",
                      "description": "Total scan events recorded across all personal codes."
                    },
                    "unique_scans": {
                      "type": "integer",
                      "description": "Number of scans from distinct visitors."
                    },
                    "scans_today": {
                      "type": "integer",
                      "description": "Scan count for the current calendar day."
                    },
                    "scans_this_week": {
                      "type": "integer",
                      "description": "Scan count for the current calendar week."
                    },
                    "scans_this_month": {
                      "type": "integer",
                      "description": "Scan count for the current calendar month."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1UserWorkspaceStats"
      }
    },
    "/api/v1/verify/phone/confirm": {
      "post": {
        "tags": [
          "verify-phone"
        ],
        "summary": "Confirm phone OTP and mark the account as phone-verified",
        "description": "Second leg of the Twilio Verify SMS OTP flow. The caller submits the E.164\nphone number and the 6-digit (or 4\u201310 digit) code that was delivered by SMS\nduring the `/api/verify/phone/start` step. The handler validates both inputs,\nenforces a US-only geo-gate via the `CF-IPCountry` header, then asks Twilio\nVerify whether the supplied code is approved for that number.\n\nOn a successful Twilio check the handler:\n- Computes `SHA-256(phone_e164)` to produce a `phone_hash`.\n- Rejects the request with `409` if any *other* already-verified account\n  carries the same hash, preventing one physical number from unlocking\n  multiple accounts.\n- Writes `phone_verified = 1`, `phone_verified_at`, `phone_hash`, and\n  `phone_last4` to the authenticated user's record.\n- Appends a `phone.verified` entry to the audit log.\n\nCompleting this step unlocks the Free-tier 3-code generation ceiling\n(equivalent to the Stripe $0 SetupIntent path) as evaluated by\n`codesLimitFor()` in `lib/plan.js`.\n\n**Authentication**: a valid bearer JWT is required; the handler reads\n`data.user` injected by upstream auth middleware.\n\n**Rate limiting**: a per-user sliding window of 10 confirm attempts per\nhour is enforced in addition to Twilio Verify's own attempt cap for the\noutstanding verification SID.\n\n**Geo restriction**: requests whose `CF-IPCountry` header is not `US` are\nrejected with `400 country_not_supported`, mirroring the restriction in the\nstart step.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "phone_e164",
                  "code"
                ],
                "properties": {
                  "phone_e164": {
                    "type": "string",
                    "pattern": "^\\+[1-9]\\d{7,14}$",
                    "description": "The phone number in E.164 format (e.g. `+12125550100`) that was used in the preceding start step.\n"
                  },
                  "code": {
                    "type": "string",
                    "pattern": "^\\d{4,10}$",
                    "description": "The numeric OTP code (4\u201310 digits) delivered to the phone via SMS by Twilio Verify.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Phone number successfully verified",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "phone_last4",
                    "phone_verified_at"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "phone_last4": {
                      "type": "string",
                      "description": "Last four digits of the verified phone number."
                    },
                    "phone_verified_at": {
                      "type": "integer",
                      "description": "Unix epoch timestamp (seconds) at which verification was recorded."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failure, unsupported country, already-verified account, or Twilio Verify did not approve the code.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_phone",
                        "invalid_code",
                        "country_not_supported",
                        "already_verified",
                        "code_not_approved"
                      ],
                      "description": "Machine-readable error token."
                    },
                    "country": {
                      "type": "string",
                      "description": "Reflected `CF-IPCountry` value; present only when `error` is `country_not_supported`.\n"
                    },
                    "phone_last4": {
                      "type": "string",
                      "description": "Last four digits of the already-verified number; present only when `error` is `already_verified`.\n"
                    },
                    "status": {
                      "type": "string",
                      "description": "Twilio Verify status string; present only when `error` is `code_not_approved`.\n"
                    },
                    "detail": {
                      "type": "string",
                      "description": "Additional error detail from Twilio Verify; present only when `error` is `code_not_approved`.\n"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The phone number is already registered to another verified account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "phone_already_registered"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-user confirm attempt rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "retry_after"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "rate_limited"
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds the caller must wait before retrying."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1VerifyPhoneConfirm"
      }
    },
    "/api/v1/verify/phone/start": {
      "post": {
        "tags": [
          "verify-phone"
        ],
        "summary": "Start a Twilio Verify OTP flow for a US phone number",
        "description": "Initiates the first leg of the phone-number OTP verification flow by sending an SMS via\nTwilio Verify. The caller must be an authenticated user (bearer JWT required); the endpoint\nreads `data.user` from the middleware-populated request context.\n\nSix layered checks are applied in order, each designed to reject bad requests before\nincurring cost from the next tier:\n\n1. **Regex prefilter**, validates E.164 format and rejects non-US, toll-free, and premium\n   numbers at zero cost.\n2. **IP-country gate**, rejects requests where the `CF-IPCountry` header is not `US`.\n3. **Per-user rate limit**, at most 3 verification starts per user per hour, enforced via\n   Cloudflare KV.\n4. **Per-phone-hash rate limit**, at most 5 starts per unique phone number (stored as a\n   SHA-256 hash) per day, preventing phone-exhaustion attacks across multiple accounts.\n5. **Twilio Lookup lineTypeIntelligence**, checks the number's line type ($0.008, cached\n   90 days in KV by phone hash). VoIP and unsupported line types are rejected here.\n6. **Twilio Verify send**, the billable SMS dispatch (~$0.008).\n\nOn success, a `phone.verify_start` event is written to the audit log. The phone number is\nnever stored in plaintext; only its SHA-256 hash is persisted in KV. If the user has\nalready completed verification (`phone_verified = 1`), the request is rejected immediately\nwithout consuming any rate-limit quota.\n\nA successful downstream confirmation (POST /api/verify/phone/confirm) sets\n`users.phone_verified = 1`, which raises the Free-tier code-generation ceiling from 1 to 3.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "phone_e164"
                ],
                "properties": {
                  "phone_e164": {
                    "type": "string",
                    "description": "The phone number to verify in E.164 format. Must be a US mobile or landline number; toll-free, premium, non-US, VoIP, and malformed values are rejected.\n",
                    "example": "+12065551234"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification started; SMS dispatched via Twilio Verify",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "sid",
                    "status"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "sid": {
                      "type": "string",
                      "description": "Twilio Verify Service SID for the initiated verification resource"
                    },
                    "status": {
                      "type": "string",
                      "description": "Twilio-reported verification status; defaults to \"pending\" when absent",
                      "example": "pending"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request rejected due to invalid phone number, unsupported country or line type, already-verified user, or a failed prefilter check\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "country_not_supported",
                        "already_verified",
                        "number_not_us",
                        "line_type_not_supported",
                        "prefilter_error"
                      ],
                      "description": "Machine-readable error code"
                    },
                    "country": {
                      "type": "string",
                      "description": "ISO 3166-1 alpha-2 country code; present when error is `country_not_supported` or `number_not_us`\n"
                    },
                    "phone_last4": {
                      "type": "string",
                      "nullable": true,
                      "description": "Last four digits of the already-verified phone; present when error is `already_verified`"
                    },
                    "line_type": {
                      "type": "string",
                      "description": "Twilio-reported line type; present when error is `line_type_not_supported`"
                    },
                    "detail": {
                      "type": "string",
                      "description": "Additional human-readable context when available"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded, per-user (3/hour) or per-phone-hash (5/day)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "rate_limited",
                        "phone_rate_limited"
                      ],
                      "description": "`rate_limited`, per-user hourly bucket exhausted; `phone_rate_limited`, per-phone daily bucket exhausted\n"
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds until the rate-limit window resets"
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "Upstream Twilio API error (Lookup or Verify send failed)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "lookup_failed",
                        "verify_start_failed"
                      ],
                      "description": "Indicates which Twilio call failed"
                    },
                    "detail": {
                      "type": "string",
                      "nullable": true,
                      "description": "Error message or code forwarded from the Twilio API response"
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1VerifyPhoneStart"
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "tags": [
          "webhooks"
        ],
        "summary": "List webhooks for the current scope",
        "description": "Returns all webhooks registered under the caller's resolved code scope\n(personal or team, determined server-side from the authenticated user\nrecord). Each webhook entry includes its target URL, subscribed event\ntypes, active flag, optional description, and last-delivery metadata.\n\nThe response also includes the full list of supported event type strings\nso clients can populate a UI without a separate discovery call.\n\nAuthentication is required. The handler reads `data.user`, which is\npopulated by upstream JWT bearer-token middleware. Requests without a\nvalid bearer token will be rejected before reaching this handler.\n\nThe signing secret is never returned by this endpoint; it is only\navailable at creation time.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook list retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Unique identifier for the webhook."
                          },
                          "url": {
                            "type": "string",
                            "description": "The HTTPS endpoint that receives webhook deliveries."
                          },
                          "events": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "List of event type strings this webhook is subscribed to."
                          },
                          "active": {
                            "type": "boolean",
                            "description": "Whether the webhook is currently enabled for deliveries."
                          },
                          "description": {
                            "type": "string",
                            "nullable": true,
                            "description": "Optional human-readable label for the webhook."
                          },
                          "last_delivery_status": {
                            "type": "string",
                            "nullable": true,
                            "description": "HTTP status code or error label from the most recent delivery attempt."
                          },
                          "last_delivery_at": {
                            "type": "string",
                            "nullable": true,
                            "description": "ISO 8601 timestamp of the most recent delivery attempt."
                          },
                          "created_at": {
                            "type": "string",
                            "description": "ISO 8601 timestamp of when the webhook was created."
                          }
                        }
                      }
                    },
                    "available_events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Complete list of event type strings supported by the platform, drawn from the WEBHOOK_EVENT_TYPES constant.\n"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "internal"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getV1Webhooks"
      },
      "post": {
        "tags": [
          "webhooks"
        ],
        "summary": "Create a new webhook for the current scope",
        "description": "Registers a new webhook endpoint under the caller's resolved code scope.\nThe scope (personal or team) is determined server-side from the\nauthenticated user record.\n\nThe request body must supply a target URL and at least a list of event\ntypes to subscribe to. An optional human-readable description may also\nbe provided.\n\nOn success the response includes the full webhook record **plus the raw\nsigning secret**, which is returned exactly once at creation time and\nis never reflected back by any subsequent API call. Callers must\nrecord this secret immediately and use it to verify the\n`X-Abundera-Signature` header on incoming deliveries. This matches the\nStripe / GitHub webhook security pattern.\n\nAuthentication is required. The handler reads `data.user`, which is\npopulated by upstream JWT bearer-token middleware. Requests without a\nvalid bearer token will be rejected before reaching this handler.\n\nValidation errors thrown by the `createWebhook` helper (e.g. invalid\nURL, unrecognised event types) are propagated with whatever HTTP status\nthe error carries; unrecognised errors fall back to 500.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "The HTTPS endpoint that will receive webhook POST deliveries. Must be a valid, reachable URL.\n"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "One or more event type strings to subscribe to. Valid values are those listed in the WEBHOOK_EVENT_TYPES constant, available from GET /api/webhooks.\n"
                  },
                  "description": {
                    "type": "string",
                    "description": "Optional human-readable label to identify the webhook's purpose. Not used for routing or filtering.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created; signing secret included in this response only",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The full webhook record as returned by createWebhook, including the one-time signing secret. Exact properties mirror the database row produced by the helper.\n",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Unique identifier for the newly created webhook."
                    },
                    "url": {
                      "type": "string",
                      "description": "The target URL registered for deliveries."
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Event types the webhook is subscribed to."
                    },
                    "active": {
                      "type": "boolean",
                      "description": "Whether the webhook is enabled; true immediately after creation."
                    },
                    "description": {
                      "type": "string",
                      "nullable": true,
                      "description": "The optional description supplied at creation time."
                    },
                    "secret": {
                      "type": "string",
                      "description": "Raw HMAC signing secret. Returned once at creation only. The caller must store this value securely; it cannot be retrieved again.\n"
                    },
                    "created_at": {
                      "type": "string",
                      "description": "ISO 8601 timestamp of creation."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation or bad-request error raised by the createWebhook helper",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message from the thrown error."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Error message, or the string \"internal\" if none was available."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "postV1Webhooks"
      }
    },
    "/api/v1/webhooks/{id}": {
      "delete": {
        "tags": [
          "webhooks"
        ],
        "summary": "Delete a webhook",
        "description": "Permanently delete a webhook from the caller's resolved code scope.\n\nDelivery to the URL stops immediately. Past delivery-log rows for this\nwebhook remain in the database (for audit purposes) but are no longer\nreachable from the API.\n\nAuthentication is required. The handler reads `data.user`, which is\npopulated by upstream JWT bearer-token middleware. Requests without a\nvalid bearer token will be rejected before reaching this handler.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Webhook identifier returned by POST /api/webhooks."
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found in caller's scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "deleteV1WebhooksId"
      }
    },
    "/p/{token}": {
      "get": {
        "tags": [
          "p"
        ],
        "summary": "Serve the public shared-stats shell HTML page",
        "description": "Returns a self-contained HTML shell page for a public share link identified by the\npath token. The page itself contains no scan data; it is a minimal client-side shell\nthat reads the token from `location.pathname` and issues a subsequent browser-side\n`fetch` to `/api/p/<token>/stats` to load live statistics.\n\nNo authentication is required. The endpoint is intentionally public so that\nrecipients of a share link can view aggregate scan statistics without logging in.\nThe owner of the share can revoke access at any time; revocation is enforced by\nthe downstream `/api/p/<token>/stats` endpoint (which returns 410), not here.\n\nThe response is always HTTP 200 regardless of whether the token is valid. Token\nvalidity is determined client-side after the stats API responds. The HTML is\ninlined in the function rather than fetched from the origin to avoid same-origin\nfetch loops on Cloudflare Pages.\n\nNo cookies, bearer tokens, or service secrets are read. No side effects are\nproduced. The response carries `Cache-Control: no-store` to prevent proxies and\nbrowsers from caching the shell, and `X-Content-Type-Options: nosniff` as a\ndefence-in-depth header.\n",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The opaque share token that identifies a public stats share. The token is passed through to the client script, which uses it to call `/api/p/<token>/stats`. The handler itself does not validate or look up the token.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "HTML shell page returned successfully",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string",
                  "description": "A complete HTML document. The page renders a loading state on arrival and then populates the title, subtitle, and heatmap sections asynchronously via a fetch to /api/p/<token>/stats."
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": false,
                      "description": "False on error."
                    },
                    "error": {
                      "type": "string",
                      "description": "Machine-readable error code."
                    },
                    "retry_after": {
                      "type": "integer",
                      "description": "Seconds to wait before retry. Present on 429."
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getPToken",
        "security": []
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ServiceSecret": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Service-Secret",
        "description": "Service-to-service shared-secret header."
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ApiKey",
        "description": "Long-lived API key."
      }
    }
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": ".well-known"
    },
    {
      "name": "admin"
    },
    {
      "name": "analytics"
    },
    {
      "name": "audit"
    },
    {
      "name": "auth"
    },
    {
      "name": "billing"
    },
    {
      "name": "byo"
    },
    {
      "name": "codes"
    },
    {
      "name": "config"
    },
    {
      "name": "groups"
    },
    {
      "name": "health"
    },
    {
      "name": "invites"
    },
    {
      "name": "keepalive"
    },
    {
      "name": "keybound"
    },
    {
      "name": "org"
    },
    {
      "name": "org-logo"
    },
    {
      "name": "otp"
    },
    {
      "name": "p"
    },
    {
      "name": "p-token-stats"
    },
    {
      "name": "push"
    },
    {
      "name": "push-prefs"
    },
    {
      "name": "push-subscriptions"
    },
    {
      "name": "sales-lead"
    },
    {
      "name": "stats"
    },
    {
      "name": "status"
    },
    {
      "name": "support"
    },
    {
      "name": "teams"
    },
    {
      "name": "user"
    },
    {
      "name": "user/weather-overlay"
    },
    {
      "name": "verify-phone"
    },
    {
      "name": "webhooks"
    }
  ]
}
