{
  "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": {
    "/api/client-error": {
      "post": {
        "tags": [
          "telemetry"
        ],
        "summary": "error",
        "description": "Receiver for browser-side error reports beaconed by\n`site/js/lib/error-reporter.js` (synced from abundera-shared).\n\nThin adopter of the shared createClientErrorHandler factory\n(abundera-shared/functions/lib/client-error.js). The parse \u2192 rate-limit\n(30/IP/min via QR_SHORTCODES) \u2192 clip \u2192 structured-log \u2192 204 core is\ncross-product; pro.qr's config is its KV binding, its privacy-first log\ncontext (device class + country, never raw UA/IP \u2014 ADR-0004), and a persist\nhook that writes the `request_errors` D1 table (the same feed the server-side\nreportError() writes to \u2014 one \"things broke today\" stream, not two).\n\nAuth: unauthenticated POST, same-origin, no CORS. Returns 204 fast (the\nbrowser sendBeacons and never reads the body).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Request body.",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "postClientError"
      }
    },
    "/api/stripe/webhook": {
      "post": {
        "tags": [
          "webhook"
        ],
        "summary": "POST /api/stripe/webhook",
        "description": "Public endpoint (allowlisted in _middleware.js). Stripe calls this with\nsigned events; we verify the signature, then drive plan_status transitions\nin D1.\n\nSupported events:\n  checkout.session.completed        , link Stripe customer to user (defensive backfill)\n  customer.subscription.created     , set plan + plan_status=active + sub id\n  customer.subscription.updated     , sync plan + plan_status from sub state\n  customer.subscription.deleted     , plan_status=grace, grace_until=now+90d,\n                                       all user codes \u2192 status='grace'\n  invoice.payment_failed (final)    , plan_status=past_due",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Request body.",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad request"
          },
          "500": {
            "description": "Internal server error"
          },
          "429": {
            "description": "Rate limited"
          }
        },
        "operationId": "postStripeWebhook"
      }
    }
  },
  "components": {
    "securitySchemes": {}
  },
  "tags": [
    {
      "name": "telemetry"
    },
    {
      "name": "webhook"
    }
  ]
}
