{"openapi":"3.1.0","info":{"title":"Are We Agent Ready API","version":"0.1.0"},"paths":{"/api/webhook/stripe":{"post":{"summary":"Stripe Webhook","description":"Stripe webhook handler. Signature verified via the emergent wrapper when\nSTRIPE_WEBHOOK_SECRET is set; falls back to an unverified parse in dev so\na half-configured webhook doesn't silently drop grants.","operationId":"stripe_webhook_api_webhook_stripe_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/auth/signup":{"post":{"summary":"Signup","operationId":"signup_api_auth_signup_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/login":{"post":{"summary":"Login","operationId":"login_api_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/me":{"get":{"summary":"Me","operationId":"me_api_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/auth/forgot-password":{"post":{"summary":"Forgot Password","description":"Always returns 200 regardless of whether the email exists — prevents\naccount enumeration. If the account exists, we issue a signed reset link\nand email it via Resend.","operationId":"forgot_password_api_auth_forgot_password_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/auth/reset-password":{"post":{"summary":"Reset Password","operationId":"reset_password_api_auth_reset_password_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/debug/email-config":{"get":{"summary":"Admin Email Config","description":"Diagnostic only: shows which sender address the backend is about to use\nand whether Resend looks configured. Admin+ auth. Never returns the key.","operationId":"admin_email_config_api_admin_debug_email_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/bootstrap/super-admin":{"post":{"summary":"Admin Bootstrap Super Admin","description":"Bootstrap / repair endpoint for provisioning the super admin on a new\ndeployment. Any admin (or super admin) can call this. Creates the user if\nmissing, promotes an existing user to `super_admin`, and issues a reset\nlink. Intentionally returns the reset URL in the response so the operator\nhas a belt-and-suspenders path when email deliverability is uncertain\n(e.g., DNS not yet verified on a new environment).","operationId":"admin_bootstrap_super_admin_api_admin_bootstrap_super_admin_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/users-list":{"get":{"summary":"Admin List Users","description":"Paginated user list for the admin UI. Regular admins and super admins both allowed.","operationId":"admin_list_users_api_admin_users_list_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Admin Create User","description":"Create a new user (any role). Super-admin only. Sends a welcome email with\na password-reset link so the new user sets their own password.","operationId":"admin_create_user_api_admin_users_list_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/users-list/{user_id}":{"patch":{"summary":"Admin Update User","description":"Change role/plan/name. Super-admin only.\n\nSafety rail: a super-admin cannot demote themselves. This prevents accidental\nlockout of the last super-admin account.","operationId":"admin_update_user_api_admin_users_list__user_id__patch","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Admin Delete User","operationId":"admin_delete_user_api_admin_users_list__user_id__delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users-list/{user_id}/send-reset":{"post":{"summary":"Admin Send Reset","description":"Trigger a password-reset email for any user (e.g., for a locked-out admin).","operationId":"admin_send_reset_api_admin_users_list__user_id__send_reset_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/scans":{"post":{"summary":"Create Scan","operationId":"create_scan_api_scans_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/quota":{"get":{"summary":"My Quota","description":"Return the current caller's plan + remaining scans. Works anonymously.","operationId":"my_quota_api_me_quota_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/public-share":{"head":{"summary":"Get Public Share Html","description":"Universal social-share endpoint for public SPA pages.\n\nGiven a `path` query param (e.g. `?path=/methodology`), returns a minimal\nSSR HTML document with per-page Open Graph + Twitter Card meta tags. If\nthe path looks like `/scan/:id`, delegates to the per-scan SSR renderer.\nOtherwise uses the `PUBLIC_PAGE_OG` registry (falling back to the Landing\nmetadata for unknown paths).\n\nIntended for use with a Cloudflare Transform Rule or similar edge rewrite\nthat sends social-crawler traffic here instead of to the static build\n(which can only serve the root `index.html` for SPA fallbacks).\n\nPublic (no auth). Accepts HEAD for crawlers that pre-flight OG images.","operationId":"get_public_share_html_api_public_share_head","parameters":[{"name":"path","in":"query","required":false,"schema":{"type":"string","default":"/","title":"Path"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"Get Public Share Html","description":"Universal social-share endpoint for public SPA pages.\n\nGiven a `path` query param (e.g. `?path=/methodology`), returns a minimal\nSSR HTML document with per-page Open Graph + Twitter Card meta tags. If\nthe path looks like `/scan/:id`, delegates to the per-scan SSR renderer.\nOtherwise uses the `PUBLIC_PAGE_OG` registry (falling back to the Landing\nmetadata for unknown paths).\n\nIntended for use with a Cloudflare Transform Rule or similar edge rewrite\nthat sends social-crawler traffic here instead of to the static build\n(which can only serve the root `index.html` for SPA fallbacks).\n\nPublic (no auth). Accepts HEAD for crawlers that pre-flight OG images.","operationId":"get_public_share_html_api_public_share_head","parameters":[{"name":"path","in":"query","required":false,"schema":{"type":"string","default":"/","title":"Path"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/scans/{scan_id}/share":{"head":{"summary":"Get Scan Share Html","description":"Shareable URL for a scan report — always returns SSR HTML with\ndynamic Open Graph + Twitter Card meta tags regardless of user agent.\n\nThis is the URL that the in-app \"Copy link\" button copies and that users\npaste into LinkedIn / Twitter / Slack / Facebook. Social crawlers see\nper-scan title/description and the dynamic scorecard image; humans see\na minimal landing page with a prominent \"View the full report\" button\nto the SPA page at `/scan/:id`.\n\nDeterministic behavior — no UA sniffing, no JS redirect trap for bots.\nPublic (no auth) because social crawlers can't pass credentials.","operationId":"get_scan_share_html_api_scans__scan_id__share_head","parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"string","title":"Scan Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"Get Scan Share Html","description":"Shareable URL for a scan report — always returns SSR HTML with\ndynamic Open Graph + Twitter Card meta tags regardless of user agent.\n\nThis is the URL that the in-app \"Copy link\" button copies and that users\npaste into LinkedIn / Twitter / Slack / Facebook. Social crawlers see\nper-scan title/description and the dynamic scorecard image; humans see\na minimal landing page with a prominent \"View the full report\" button\nto the SPA page at `/scan/:id`.\n\nDeterministic behavior — no UA sniffing, no JS redirect trap for bots.\nPublic (no auth) because social crawlers can't pass credentials.","operationId":"get_scan_share_html_api_scans__scan_id__share_head","parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"string","title":"Scan Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/scans/{scan_id}/og-image.png":{"head":{"summary":"Get Scan Og Image","description":"Dynamic Open Graph scorecard image (1200×630 PNG) for social shares.\nPublic (no auth) because social crawlers can't pass credentials — the\nscan document is minimal public data (score + classification + domain).","operationId":"get_scan_og_image_api_scans__scan_id__og_image_png_head","parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"string","title":"Scan Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"Get Scan Og Image","description":"Dynamic Open Graph scorecard image (1200×630 PNG) for social shares.\nPublic (no auth) because social crawlers can't pass credentials — the\nscan document is minimal public data (score + classification + domain).","operationId":"get_scan_og_image_api_scans__scan_id__og_image_png_head","parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"string","title":"Scan Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/scans/{scan_id}":{"get":{"summary":"Get Scan","operationId":"get_scan_api_scans__scan_id__get","parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"string","title":"Scan Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/scans/{scan_id}/coverage":{"get":{"summary":"Public Coverage","description":"Public, unauthenticated, shareable coverage snapshot. Only exposes non-PII data.","operationId":"public_coverage_api_scans__scan_id__coverage_get","parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"string","title":"Scan Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/scans/{scan_id}/unlock":{"post":{"summary":"Unlock Scan","operationId":"unlock_scan_api_scans__scan_id__unlock_post","parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"string","title":"Scan Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailUnlockRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/scans":{"get":{"summary":"My Scans","operationId":"my_scans_api_me_scans_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/scans/{scan_id}/save":{"post":{"summary":"Save Scan","description":"Attach an unclaimed scan to the current user.","operationId":"save_scan_api_me_scans__scan_id__save_post","parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"string","title":"Scan Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/dashboard":{"get":{"summary":"Dashboard","description":"Per-website or per-brand dashboard data.\n\nResolution order:\n  1. If `brand_id` is given, look it up and scope to brand.primary_url.\n  2. Else if `website` is given, scope to that normalized URL.\n  3. Else auto-select the user's most recently scanned website.","operationId":"dashboard_api_me_dashboard_get","parameters":[{"name":"website","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website"}},{"name":"brand_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/audits/catalog":{"get":{"summary":"Audits Catalog","description":"Future audit plugins. At launch, only the website scan is live; others are teasers.","operationId":"audits_catalog_api_audits_catalog_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/methodology":{"get":{"summary":"Methodology","description":"Machine-readable summary of the scoring framework. Referenced from\n`/.well-known/mcp.json` so agents can fetch methodology context before\ninterpreting scan findings.","operationId":"methodology_api_methodology_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/metrics":{"get":{"summary":"Admin Metrics","operationId":"admin_metrics_api_admin_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/pricing-metrics":{"get":{"summary":"Admin Pricing Metrics","description":"Revenue + conversion snapshot for the admin dashboard.\n\nNumbers here are computed on every request — OK while user/payment volume\nis small. Move to a cached daily rollup if this endpoint gets hot.","operationId":"admin_pricing_metrics_api_admin_pricing_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/users-list/{user_id}/grant-pro":{"post":{"summary":"Admin Grant Pro","description":"Explicit one-click grant: flip the user to plan=pro for a duration\n(default 365 days) with an audit-logged reason. Safe to call repeatedly —\nsimply extends the expiry from *now*.","operationId":"admin_grant_pro_api_admin_users_list__user_id__grant_pro_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users-list/{user_id}/revoke-pro":{"post":{"summary":"Admin Revoke Pro","description":"Revoke Pro — downgrades the user to free, clears expiry, zeroes brand\nslots. Logs the action + reason.","operationId":"admin_revoke_pro_api_admin_users_list__user_id__revoke_pro_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/audit-log":{"get":{"summary":"Admin Audit Log List","description":"Recent admin actions. Read-only for any admin; helpful for spotting\nwho granted/revoked Pro and why.","operationId":"admin_audit_log_list_api_admin_audit_log_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/scans":{"get":{"summary":"Admin Scans","description":"Paginated, filterable scan list for the admin console.","operationId":"admin_scans_api_admin_scans_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q"}},{"name":"site_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Type"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":25,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users/{user_id}/scans":{"get":{"summary":"Admin User Scans","description":"Scans belonging to a specific user — drilldown from the users tab.","operationId":"admin_user_scans_api_admin_users__user_id__scans_get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feedback":{"post":{"summary":"Submit Feedback","description":"Submit a feedback from the floating widget. Public — no auth required.\nAttaches the submitting user id when the caller is logged in. All four\nfields (name, email, page_url, message) are required.","operationId":"submit_feedback_api_feedback_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/feedback":{"get":{"summary":"Admin List Feedback","description":"List all feedback entries for the admin inbox. Most recent first.\nOptional `status` filter narrows to one workflow state.","operationId":"admin_list_feedback_api_admin_feedback_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/feedback/{feedback_id}":{"patch":{"summary":"Admin Update Feedback","description":"Update a feedback entry's status. Status is the only mutable field.","operationId":"admin_update_feedback_api_admin_feedback__feedback_id__patch","parameters":[{"name":"feedback_id","in":"path","required":true,"schema":{"type":"string","title":"Feedback Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/enterprise-leads":{"post":{"summary":"Submit Enterprise Lead","description":"Inbound lead from the \"Need more?\" form on /pro. Public — no auth.\n\nCaptured fields (name, email, company required; role, team_size,\nbrands_needed, use_case optional). Stores in `enterprise_leads` and\nemails the configured notify address.","operationId":"submit_enterprise_lead_api_enterprise_leads_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/":{"get":{"summary":"Root","operationId":"root_api__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/health":{"get":{"summary":"Health","operationId":"health_api_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/brands":{"get":{"summary":"List Brands","operationId":"list_brands_api_me_brands_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Create Brand","operationId":"create_brand_api_me_brands_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/brands/{brand_id}":{"patch":{"summary":"Update Brand","operationId":"update_brand_api_me_brands__brand_id__patch","parameters":[{"name":"brand_id","in":"path","required":true,"schema":{"type":"string","title":"Brand Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Brand","operationId":"delete_brand_api_me_brands__brand_id__delete","parameters":[{"name":"brand_id","in":"path","required":true,"schema":{"type":"string","title":"Brand Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/payments/packages":{"get":{"summary":"List Packages","operationId":"list_packages_api_payments_packages_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/scans/{scan_id}/actions":{"post":{"summary":"Trigger Action Scan","description":"Queue a Playwright-driven agent-action scan for a completed base scan.\n\nAccess: logged-in users on their own scans, admins, or any authenticated\naccount for public/anon scans. Gated behind a supported archetype\n(commerce / lead_gen) for Tier-1 MVP.\n\nAccepts an optional JSON body `{ \"archetype\": \"commerce\" | \"lead_gen\" }`\nto override automatic classification — useful when the primary type is\n`hybrid` but the user knows which audit plan they want to run.","operationId":"trigger_action_scan_api_scans__scan_id__actions_post","parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"string","title":"Scan Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"Get Action Scan","description":"Return the latest action-scan state + results for a scan.","operationId":"get_action_scan_api_scans__scan_id__actions_get","parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"string","title":"Scan Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/payments/checkout":{"post":{"summary":"Create Checkout","operationId":"create_checkout_api_payments_checkout_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/payments/status/{session_id}":{"get":{"summary":"Checkout Status","operationId":"checkout_status_api_payments_status__session_id__get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/payments/portal":{"post":{"summary":"Billing Portal","description":"Create a Stripe Customer Portal session so the user can update their\npayment method, download invoices, and (for true subscriptions) cancel.\n\nFor one-time annual payments the portal still works — it shows the past\ninvoices. When we migrate to proper subscriptions, cancellation will flow\nthrough the same URL without any app-side changes.","operationId":"billing_portal_api_payments_portal_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"EmailUnlockRequest":{"properties":{"scan_id":{"type":"string","title":"Scan Id"},"email":{"type":"string","format":"email","title":"Email"},"consent":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Consent","default":true}},"type":"object","required":["scan_id","email"],"title":"EmailUnlockRequest"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"LoginRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"LoginRequest"},"ScanRequest":{"properties":{"url":{"type":"string","title":"Url"},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email"},"competitor_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Competitor Url"}},"type":"object","required":["url"],"title":"ScanRequest"},"SignupRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","maxLength":128,"minLength":8,"title":"Password"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"}},"type":"object","required":["email","password"],"title":"SignupRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}