{"openapi":"3.1.0","info":{"title":"SyteCheck API","description":"\nThe SyteCheck public API runs comprehensive, scored audits of any website —\nSEO, accessibility, performance, security headers, and more.\n\n## Authentication\n\nAll `/api/v1` routes accept either a session JWT or a personal **API key** via the\n`Authorization: Bearer <token>` header. Generate API keys from your account settings\nor the `POST /api/v1/keys` endpoint. A getting-started guide, client examples, and an\nMCP server live at <https://github.com/BertPC/sytecheck-mcp>.\n\n## Plans and quotas\n\nEvery account has a monthly scan quota, a set of analysis categories, and a concurrency\nlimit set by its plan. **The API itself is available on every plan, including Free** —\nan API key confers no allowance of its own, reaching the same quota and the same\ncategories as the plan it belongs to. Exceeding the\nmonthly quota returns `429 Too Many Requests` with a `Retry-After` header giving the\ntime until the calendar month rolls over. Requesting a category your plan does not\ninclude returns `403 Forbidden`. A separate daily burst cap also applies as abuse and\ninfrastructure protection. Current per-plan limits come from `GET /api/v1/billing/plans`\nand `GET /users/me/usage` — do not hard-code them.\n\n## Webhooks\n\nPass a `webhook_url` when submitting a scan to receive the completed result payload\nby HTTP POST as soon as analysis finishes; delivery is retried on failure. Webhooks\nrequire the **Agency** plan — on any other plan the field is refused with `403` and\nthe scan should be submitted without it, then polled or streamed for completion.\n","version":"1.0.0"},"paths":{"/health":{"get":{"tags":["health"],"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Health Get"}}}}}}},"/api/v1/config":{"get":{"tags":["meta","meta"],"summary":"Get public configuration","description":"Return client configuration needed before signing in, including the IP addresses\nour scanners originate from — useful if you need to allow-list them so scans of\nyour own site are not blocked.","operationId":"public_config_api_v1_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicConfigResponse"}}}}}}},"/auth/register":{"post":{"tags":["auth"],"summary":"Register","operationId":"register_auth_register_post","parameters":[{"name":"accept-language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"description":"Create an account and return access and refresh tokens. A verification email is sent; scans require a verified address."}},"/auth/login":{"post":{"tags":["auth"],"summary":"Sign in","operationId":"login_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"description":"Exchange an email and password for access and refresh tokens."}},"/auth/refresh":{"post":{"tags":["auth"],"summary":"Refresh tokens","operationId":"refresh_auth_refresh_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"description":"Exchange a refresh token for a new access token. Refresh tokens rotate on use."}},"/auth/password-reset/request":{"post":{"tags":["auth"],"summary":"Request a password reset","operationId":"request_password_reset_auth_password_reset_request_post","parameters":[{"name":"accept-language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetRequestBody"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"description":"Send a password reset link. Always succeeds, whether or not the address has an account."}},"/auth/password-reset/confirm":{"post":{"tags":["auth"],"summary":"Complete a password reset","description":"Set a new password using the token from a reset link.","operationId":"confirm_password_reset_auth_password_reset_confirm_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetConfirmBody"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/accept-invitation":{"post":{"tags":["auth"],"summary":"Accept an invitation","description":"Set a password using an invitation token and sign in. The address is already\nconfirmed, so no separate verification step is needed.\n\nUnknown, expired, and already-used tokens are all refused identically, so this\ncannot be used to discover which addresses have accounts.","operationId":"accept_invitation_auth_accept_invitation_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptInvitationBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/verify-email":{"post":{"tags":["auth"],"summary":"Verify an email address","description":"Confirm an email address using the token from a verification link.","operationId":"verify_email_auth_verify_email_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailVerificationConfirmBody"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/verify-email/resend":{"post":{"tags":["auth"],"summary":"Resend the verification email","description":"Send a fresh verification link to the account's address.","operationId":"resend_email_verification_auth_verify_email_resend_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"accept-language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/change-email":{"post":{"tags":["auth"],"summary":"Change the account's email address","description":"Begin moving the account to a new email address. Returns `202`: nothing has\nchanged yet. A confirmation link is sent to the new address and the change is\napplied only when that link is used, so a mistyped address cannot lock you out.\n\nAccounts with a password must re-enter it.","operationId":"change_email_auth_change_email_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"accept-language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeEmailRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/change-password":{"post":{"tags":["auth"],"summary":"Change the password","description":"Set a new password, re-authenticating with the current one.","operationId":"change_password_auth_change_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/auth/google":{"post":{"tags":["auth"],"summary":"Sign in with Google","description":"Exchange a Google ID token for access and refresh tokens, creating an account if needed.","operationId":"google_auth_auth_google_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAuthRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/users/me":{"get":{"tags":["users"],"summary":"Get the current account","operationId":"get_me_users_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}}},"security":[{"HTTPBearer":[]}],"description":"Return the authenticated account's profile and preferences."},"delete":{"tags":["users"],"summary":"Delete the account","description":"Permanently erase the account and everything belonging to it: scans, reports,\nscreenshots, and API keys. This cannot be undone.\n\nCancel any active subscription first; this answers `409` while one is live.","operationId":"delete_me_users_me_delete","responses":{"204":{"description":"Successful Response"}},"security":[{"HTTPBearer":[]}]},"patch":{"tags":["users"],"summary":"Update preferences","description":"Update the account's preferences. Email addresses cannot be changed here — use\n`POST /auth/change-email`, which confirms the new address first.","operationId":"update_me_users_me_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserUpdateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/users/me/usage":{"get":{"tags":["users"],"summary":"Get plan usage and limits","description":"Return the plan's limits and consumption so far: scans used and remaining this\ncalendar month, when the quota resets, which categories are available, and any\none-off scan credits.\n\nQuota runs on the calendar month in UTC and does not roll over.","operationId":"get_my_usage_users_me_usage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanUsageResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/users/me/webhook-secret":{"get":{"tags":["users"],"summary":"Get the webhook signing secret","description":"Return the secret used to verify scan webhook deliveries. Unlike an API key it\ncan be read as often as needed — it verifies that a delivery came from us and\ngrants no access on its own.\n\nRequires an interactive session and a plan that includes webhooks.","operationId":"get_my_webhook_secret_users_me_webhook_secret_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSecretResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/users/me/webhook-secret/rotate":{"post":{"tags":["users"],"summary":"Rotate the webhook signing secret","description":"Issue a new webhook signing secret. **The previous secret stops working\nimmediately — there is no overlap window**, so update your receiver first.\n\nRequires an interactive session and a plan that includes webhooks.","operationId":"rotate_my_webhook_secret_users_me_webhook_secret_rotate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSecretResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/users/me/export":{"get":{"tags":["users"],"summary":"Export all account data","description":"Download everything held about the account as one JSON document: the profile,\nevery scan (including deleted scans not yet permanently removed) with its full\nresults, screenshot links, API-key metadata, and subscriptions. Password and key\nhashes are never included. Screenshot links expire after an hour.\n\nThe body is streamed. A file that does not end in `]}` was cut off by an error\nand should be requested again.\n\nRequires an interactive session. Available on every plan.","operationId":"export_me_users_me_export_get","responses":{"200":{"description":"The export, as JSON.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountExportResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/scans/categories":{"get":{"tags":["scans"],"summary":"List scan categories","description":"Return every analysis category with its description and its weight in the\noverall score, plus an `included` flag saying whether your plan may run it.\n\nRequesting a category your plan withholds is refused rather than silently\nskipped, so check this before passing `categories` to a scan.","operationId":"list_categories_api_v1_scans_categories_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoriesResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/scans":{"get":{"tags":["scans"],"summary":"List scans","description":"Return your scans, newest first, with pagination. Filter by `q` (matches the URL\nor label) and by `status`. Deleted scans are excluded.","operationId":"list_scans_api_v1_scans_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page number (1-indexed).","default":1,"title":"Page"},"description":"Page number (1-indexed)."},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Results per page (1–100).","default":20,"title":"Page Size"},"description":"Results per page (1–100)."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Case-insensitive search over URL and label.","title":"Q"},"description":"Case-insensitive search over URL and label."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by scan status (queued, scanning, summarizing, complete, failed).","title":"Status"},"description":"Filter by scan status (queued, scanning, summarizing, complete, failed)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["scans"],"summary":"Submit a scan","description":"Queue a website scan and return immediately with its id and a `queued` status.\n\nA scan opens a real browser, runs a Lighthouse audit, and has screenshots\nreviewed by a vision model, so it takes a few minutes rather than seconds. Poll\n`GET /api/v1/scans/{scan_id}`, subscribe to `GET /api/v1/scans/{scan_id}/stream`,\nor supply a `webhook_url` to be notified on completion.\n\n**Each scan draws on your monthly quota.** Omit `categories` to run everything\nyour plan includes; request specific ones with `GET /api/v1/scans/categories`.\n\nRefusals are distinguishable and worth handling separately:\n\n- `403` — your plan does not include a requested category, webhooks, or the\n  scan credit you asked to spend. Retrying will not help.\n- `422` — the URL or a category id is not valid.\n- `429` — monthly quota, concurrent scan limit, or daily burst cap. A\n  `Retry-After` header says when to try again.\n\nA scan that ends `failed` — its URL unreachable, or every category errored or\nblocked — is voided: it is marked with `quota_refunded_at`, costs no quota, and\nreturns any spent credit.","operationId":"create_scan_api_v1_scans_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Language for report content (e.g. 'es', 'pt-BR'). Falls back to the Accept-Language header, then English.","title":"Lang"},"description":"Language for report content (e.g. 'es', 'pt-BR'). Falls back to the Accept-Language header, then English."},{"name":"accept-language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/scans/trends":{"get":{"tags":["scans"],"summary":"Get score trends","description":"Return score history grouped by URL for sites you have scanned more than once —\nthe data behind \"is this site improving?\". Each URL returns its most recent\npoints in chronological order.","operationId":"get_trends_api_v1_scans_trends_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrendsResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/scans/recent-duplicate":{"get":{"tags":["scans"],"summary":"Check for a recent scan of a URL","description":"Report whether this URL was scanned very recently, so you can offer the existing\nreport instead of spending quota on a near-identical one.\n\nAdvisory only — it never blocks a scan.","operationId":"recent_duplicate_api_v1_scans_recent_duplicate_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"url","in":"query","required":true,"schema":{"type":"string","description":"The URL about to be scanned.","title":"Url"},"description":"The URL about to be scanned."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecentDuplicateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/scans/{scan_id}":{"get":{"tags":["scans"],"summary":"Get a scan","description":"Return one scan with its per-category results and screenshot URLs.\n\nPoll this to follow progress: `status` moves `queued` → `scanning` →\n`summarizing` → `complete`, or to `failed`. Screenshot URLs are pre-signed and\nshort-lived — fetch them promptly rather than storing them.\n\nAnswers `404` for a scan that does not exist **and** for one belonging to another\naccount, so scan ids cannot be enumerated.","operationId":"get_scan_api_v1_scans__scan_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"integer","title":"Scan Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["scans"],"summary":"Rename a scan","description":"Update a scan's label. Send `{\"label\": null}` to clear it. Omitted fields are\nleft unchanged.","operationId":"update_scan_api_v1_scans__scan_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"integer","title":"Scan Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["scans"],"summary":"Delete a scan","description":"Delete a scan. It disappears from every listing immediately and its stored\nresults and screenshots are removed permanently after a short grace period.\n\n**Deleting does not restore quota.** A scan that ran has already been paid for.","operationId":"delete_scan_api_v1_scans__scan_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"integer","title":"Scan Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/scans/{scan_id}/report":{"get":{"tags":["scans"],"summary":"Get a scan report","description":"Return the full report: an executive summary with recommended actions, a scored\nbreakdown, and per-category findings.\n\nEach finding carries a recommendation, except where the account's plan withholds\nit: a finding with `detail_locked: true` reports what is wrong (`title`, `detail`,\n`severity`) with an empty `recommendation` and no `snippet`. The free plan locks\naccessibility fixes this way; every paid plan, and any scan bought with a one-off\nscan credit, returns them in full.\n\nAvailable once the scan is finished; answers `409` while it is still running.\n\nReport text is stored in English and translated on demand — pass `?lang=de` or\nsend an `Accept-Language` header. A translation failure falls back to English\nrather than erroring.\n\nEach category carries its own status. `pass`, `warn` and `fail` reflect its\nscore; `error` means the analysis failed; `blocked` means the site's bot\nprotection refused automated access. Both `error` and `blocked` carry a null\nscore and are excluded from the overall score rather than counted as failures.","operationId":"get_scan_report_api_v1_scans__scan_id__report_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"integer","title":"Scan Id"}},{"name":"lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Language for report content (e.g. 'es', 'pt-BR'). Falls back to the Accept-Language header, then English.","title":"Lang"},"description":"Language for report content (e.g. 'es', 'pt-BR'). Falls back to the Accept-Language header, then English."},{"name":"accept-language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanReportResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/scans/{scan_id}/report/pdf":{"get":{"tags":["scans"],"summary":"Download a report as PDF","description":"Return the report as a formatted PDF suitable for sending to a client.\n\nRequires a plan that includes PDF export, and a finished scan. Supports the same\n`?lang=` and `Accept-Language` localization as the JSON report.","operationId":"download_scan_report_pdf_api_v1_scans__scan_id__report_pdf_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"integer","title":"Scan Id"}},{"name":"lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Language for report content (e.g. 'es', 'pt-BR'). Falls back to the Accept-Language header, then English.","title":"Lang"},"description":"Language for report content (e.g. 'es', 'pt-BR'). Falls back to the Accept-Language header, then English."},{"name":"accept-language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/scans/{scan_id}/stream":{"get":{"tags":["scans"],"summary":"Stream scan progress","description":"A Server-Sent Events stream emitting a `status` event on each transition and a\nfinal `done` event when the scan finishes.\n\nThe connection closes with a `timeout` event after five minutes, so treat this as\na fast path and keep polling as the fallback for long scans.","operationId":"stream_scan_status_api_v1_scans__scan_id__stream_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"integer","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/v1/keys":{"get":{"tags":["api-keys"],"summary":"List API keys","description":"List your API keys with their scopes and usage counts. Secrets are never\nreturned. Requires an interactive session.","operationId":"list_api_keys_api_v1_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyListResponse"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["api-keys"],"summary":"Create an API key","description":"Create an API key for programmatic access. **The secret is returned once, in this\nresponse, and is never retrievable again.**\n\nRequires an interactive session — an API key cannot create further keys.\n\nChoose the narrowest `scopes` that work: `scans:read` alone cannot create or\ndelete scans, which makes it the safer credential for anything unattended.","operationId":"create_api_key_api_v1_keys_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/keys/{key_id}":{"delete":{"tags":["api-keys"],"summary":"Revoke an API key","description":"Revoke an API key. It stops working immediately and permanently; there is no way\nto restore it. Requires an interactive session.","operationId":"revoke_api_key_api_v1_keys__key_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"integer","title":"Key Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/billing/plans":{"get":{"tags":["billing"],"summary":"List available plans","description":"Return every plan with its price, monthly scan allowance, included categories,\nretention window, and concurrency limit. No authentication required.\n\nThis is the authoritative source for plan limits; do not hard-code them.","operationId":"get_public_plans_api_v1_billing_plans_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPlansResponse"}}}}}}},"/api/v1/billing/subscription":{"get":{"tags":["billing"],"summary":"Get subscription state","description":"Return the account's current subscription, plan, and renewal state.","operationId":"get_subscription_api_v1_billing_subscription_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/billing/checkout":{"post":{"tags":["billing"],"summary":"Start a checkout","description":"Begin a checkout for a plan or a one-off scan, returning a transaction to complete in the browser.","operationId":"create_checkout_api_v1_billing_checkout_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"accept-language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/billing/portal":{"get":{"tags":["billing"],"summary":"Get customer portal links","description":"Return links to the payment provider's portal for managing payment methods and invoices.","operationId":"get_portal_api_v1_billing_portal_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/billing/subscription/change":{"post":{"tags":["billing"],"summary":"Change plan","description":"Move the subscription to a different plan or billing period.","operationId":"change_subscription_api_v1_billing_subscription_change_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionChangeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/billing/subscription/cancel":{"post":{"tags":["billing"],"summary":"Cancel the subscription","description":"Cancel the subscription at the end of the current billing period. Access\ncontinues until then, and scan history is kept for a grace period afterwards\nrather than being deleted immediately.","operationId":"cancel_subscription_api_v1_billing_subscription_cancel_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/billing/withdraw":{"post":{"tags":["billing"],"summary":"Withdraw from a recent purchase","description":"Exercise a statutory right of withdrawal on a recent purchase, where one applies.","operationId":"withdraw_from_contract_api_v1_billing_withdraw_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionResponse"}}}}},"security":[{"HTTPBearer":[]}]}}},"components":{"schemas":{"AcceptInvitationBody":{"properties":{"token":{"type":"string","title":"Token"},"password":{"type":"string","maxLength":128,"minLength":12,"title":"Password"}},"type":"object","required":["token","password"],"title":"AcceptInvitationBody","description":"Take possession of an admin-created account by setting its first password."},"AccountExportResponse":{"properties":{"exported_at":{"type":"string","format":"date-time","title":"Exported At"},"screenshot_urls_expire_in_seconds":{"type":"integer","title":"Screenshot Urls Expire In Seconds","description":"Screenshot links stop working after this long; export again for fresh ones."},"account":{"$ref":"#/components/schemas/ExportAccount"},"scans":{"items":{"$ref":"#/components/schemas/ExportScan"},"type":"array","title":"Scans"},"api_keys":{"items":{"$ref":"#/components/schemas/ExportApiKey"},"type":"array","title":"Api Keys"},"subscriptions":{"items":{"$ref":"#/components/schemas/ExportSubscription"},"type":"array","title":"Subscriptions"},"milestones":{"items":{"$ref":"#/components/schemas/ExportMilestone"},"type":"array","title":"Milestones"},"offer_codes":{"items":{"$ref":"#/components/schemas/ExportOfferCode"},"type":"array","title":"Offer Codes"},"lifecycle_emails":{"items":{"$ref":"#/components/schemas/ExportLifecycleEmail"},"type":"array","title":"Lifecycle Emails"}},"type":"object","required":["exported_at","screenshot_urls_expire_in_seconds","account","scans","api_keys","subscriptions","milestones","offer_codes","lifecycle_emails"],"title":"AccountExportResponse","description":"Everything SyteCheck holds about the caller, as one machine-readable document."},"ApiKeyCreate":{"properties":{"label":{"type":"string","maxLength":255,"minLength":1,"title":"Label","description":"Human-readable name to identify this key (e.g. 'CI pipeline').","examples":["Production integration"]},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"What this key may do: `scans:read`, `scans:write`. Defaults to all of them. A read-only key cannot spend scan quota, which is the safer choice for anything that runs unattended. Scopes never widen what a key can reach: account, billing, and key-management routes are closed to every key.","examples":[["scans:read","scans:write"],["scans:read"]]}},"type":"object","required":["label"],"title":"ApiKeyCreate","description":"Body for POST /api/v1/keys."},"ApiKeyCreatedResponse":{"properties":{"id":{"type":"integer","title":"Id"},"label":{"type":"string","title":"Label"},"key_prefix":{"type":"string","title":"Key Prefix"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"What this key may do."},"is_active":{"type":"boolean","title":"Is Active"},"request_count":{"type":"integer","title":"Request Count"},"scan_count":{"type":"integer","title":"Scan Count"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At"},"api_key":{"type":"string","title":"Api Key","description":"The secret key. Store it now; it is never shown again."}},"type":"object","required":["id","label","key_prefix","is_active","request_count","scan_count","created_at","last_used_at","revoked_at","api_key"],"title":"ApiKeyCreatedResponse","description":"Returned only by key creation — includes the raw secret, shown once."},"ApiKeyListResponse":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/ApiKeyResponse"},"type":"array","title":"Keys"}},"type":"object","required":["keys"],"title":"ApiKeyListResponse","description":"All of the caller's API keys, newest first."},"ApiKeyResponse":{"properties":{"id":{"type":"integer","title":"Id"},"label":{"type":"string","title":"Label"},"key_prefix":{"type":"string","title":"Key Prefix"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"What this key may do."},"is_active":{"type":"boolean","title":"Is Active"},"request_count":{"type":"integer","title":"Request Count"},"scan_count":{"type":"integer","title":"Scan Count"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At"}},"type":"object","required":["id","label","key_prefix","is_active","request_count","scan_count","created_at","last_used_at","revoked_at"],"title":"ApiKeyResponse","description":"An API key as shown in listings — never includes the secret value."},"CategoriesResponse":{"properties":{"categories":{"items":{"$ref":"#/components/schemas/CategoryInfo"},"type":"array","title":"Categories"},"plan":{"type":"string","title":"Plan","default":"free"}},"type":"object","required":["categories"],"title":"CategoriesResponse","description":"Response for GET /api/v1/scans/categories."},"CategoryInfo":{"properties":{"id":{"type":"string","title":"Id"},"description":{"type":"string","title":"Description"},"weight":{"type":"number","title":"Weight"},"included":{"type":"boolean","title":"Included","default":true}},"type":"object","required":["id","description","weight"],"title":"CategoryInfo","description":"Description of a single analysis category."},"CategoryReportResponse":{"properties":{"category":{"type":"string","title":"Category"},"status":{"type":"string","enum":["pass","warn","fail","error","blocked"],"title":"Status"},"score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score"},"summary":{"type":"string","title":"Summary"},"findings":{"items":{"$ref":"#/components/schemas/FindingResponse"},"type":"array","title":"Findings"},"metrics":{"items":{"$ref":"#/components/schemas/MetricReport"},"type":"array","title":"Metrics","default":[]}},"type":"object","required":["category","status","score","summary","findings"],"title":"CategoryReportResponse","description":"Fully parsed result for one analysis category."},"ChangeEmailRequest":{"properties":{"new_email":{"type":"string","format":"email","title":"New Email"},"current_password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Password","description":"The account's current password. Required when the account has a password."},"google_credential":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Google Credential","description":"A fresh Google ID token from Sign In With Google, for the Google account linked to this one. Required when the account has no password."}},"type":"object","required":["new_email"],"title":"ChangeEmailRequest","description":"Request to move the account to a different email address.\n\nThe address is not applied here — a confirmation link is sent to it, and only\nclicking that link updates the account (see ``POST /auth/change-email``)."},"ChangePasswordRequest":{"properties":{"current_password":{"type":"string","title":"Current Password"},"new_password":{"type":"string","maxLength":128,"minLength":12,"title":"New Password"}},"type":"object","required":["current_password","new_password"],"title":"ChangePasswordRequest"},"CheckoutRequest":{"properties":{"product":{"type":"string","enum":["subscription","one_off_scan"],"title":"Product","default":"subscription"},"plan":{"anyOf":[{"type":"string","enum":["free","pro","agency"]},{"type":"null"}],"title":"Plan","description":"Tier to subscribe to: \"pro\" or \"agency\"."},"period":{"type":"string","enum":["monthly","annual"],"title":"Period","description":"Billing period: \"monthly\" or \"annual\".","default":"monthly"}},"type":"object","title":"CheckoutRequest","description":"What the customer wants to buy.\n\nEither a subscription (`plan` + `period`) or the one-off audit\n(`product=\"one_off_scan\"`). Prices are never sent by the client — the server\nresolves the Paddle price id from the plan table, so a tampered request buys\nnothing unexpected."},"CheckoutResponse":{"properties":{"transaction_id":{"type":"string","title":"Transaction Id"}},"type":"object","required":["transaction_id"],"title":"CheckoutResponse","description":"A Paddle transaction to hand to `Paddle.Checkout.open({ transactionId })`.\n\nThe browser gets only this opaque id. The account the payment credits is set\nserver-side in the transaction's `custom_data`, so it cannot be nominated by\nthe client."},"EmailVerificationConfirmBody":{"properties":{"token":{"type":"string","title":"Token"}},"type":"object","required":["token"],"title":"EmailVerificationConfirmBody"},"ExecutiveSummary":{"properties":{"summary":{"type":"string","title":"Summary"},"top_actions":{"items":{"type":"string"},"type":"array","title":"Top Actions"}},"type":"object","required":["summary","top_actions"],"title":"ExecutiveSummary","description":"Claude-generated plain-English overview and prioritised actions for a scan."},"ExportAccount":{"properties":{"id":{"type":"integer","title":"Id"},"email":{"type":"string","title":"Email"},"email_verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Email Verified At"},"google_account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Google Account Id"},"tier":{"type":"string","title":"Tier"},"preferences":{"additionalProperties":true,"type":"object","title":"Preferences"},"scan_credits":{"type":"integer","title":"Scan Credits"},"scan_credit_purchased_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scan Credit Purchased At"},"plan_downgraded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Plan Downgraded At"},"billing_customer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Billing Customer Id"},"signup_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signup Source"},"signup_source_detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signup Source Detail"},"signup_attribution":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Signup Attribution"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","email","email_verified_at","google_account_id","tier","preferences","scan_credits","scan_credit_purchased_at","plan_downgraded_at","billing_customer_id","signup_source","signup_source_detail","signup_attribution","created_at","updated_at"],"title":"ExportAccount","description":"The account row, minus credential material and internal counters."},"ExportApiKey":{"properties":{"label":{"type":"string","title":"Label"},"key_prefix":{"type":"string","title":"Key Prefix"},"scopes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scopes"},"request_count":{"type":"integer","title":"Request Count"},"scan_count":{"type":"integer","title":"Scan Count"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At"}},"type":"object","required":["label","key_prefix","scopes","request_count","scan_count","created_at","last_used_at","revoked_at"],"title":"ExportApiKey","description":"API key metadata. The secret itself is never stored, so it cannot be exported."},"ExportLifecycleEmail":{"properties":{"kind":{"type":"string","title":"Kind"},"about_site":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"About Site"},"sent_at":{"type":"string","format":"date-time","title":"Sent At"}},"type":"object","required":["kind","about_site","sent_at"],"title":"ExportLifecycleEmail","description":"A follow-up email we sent after sign-up or a scan."},"ExportMilestone":{"properties":{"milestone":{"type":"string","title":"Milestone"},"occurred_at":{"type":"string","format":"date-time","title":"Occurred At"}},"type":"object","required":["milestone","occurred_at"],"title":"ExportMilestone","description":"When the account first reached a step such as its first scan or first purchase."},"ExportOfferCode":{"properties":{"credits":{"type":"integer","title":"Credits"},"claimed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Claimed At"},"credited_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Credited At"}},"type":"object","required":["credits","claimed_at","credited_at"],"title":"ExportOfferCode","description":"An outreach offer code this account redeemed. The code itself is never kept."},"ExportScan":{"properties":{"id":{"type":"integer","title":"Id"},"url":{"type":"string","title":"Url"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"status":{"type":"string","title":"Status"},"categories_requested":{"items":{"type":"string"},"type":"array","title":"Categories Requested"},"overall_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Overall Score"},"executive_summary":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Executive Summary"},"webhook_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Url"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"used_scan_credit":{"type":"boolean","title":"Used Scan Credit"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"deleted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deleted At"},"results":{"items":{"$ref":"#/components/schemas/ExportScanResult"},"type":"array","title":"Results"},"screenshots":{"items":{"$ref":"#/components/schemas/ExportScreenshot"},"type":"array","title":"Screenshots"}},"type":"object","required":["id","url","label","status","categories_requested","overall_score","executive_summary","webhook_url","error_message","used_scan_credit","created_at","completed_at","deleted_at","results","screenshots"],"title":"ExportScan","description":"A scan and everything recorded about it."},"ExportScanResult":{"properties":{"category":{"type":"string","title":"Category"},"status":{"type":"string","title":"Status"},"score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score"},"completed_at":{"type":"string","format":"date-time","title":"Completed At"},"result":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Result"}},"type":"object","required":["category","status","score","completed_at","result"],"title":"ExportScanResult","description":"One category's result, including the full analyzer output we retain."},"ExportScreenshot":{"properties":{"breakpoint":{"type":"string","title":"Breakpoint"},"captured_at":{"type":"string","format":"date-time","title":"Captured At"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"}},"type":"object","required":["breakpoint","captured_at","url"],"title":"ExportScreenshot","description":"A screenshot, reachable for a limited time through a signed URL.\n\n``url`` is None if the link could not be signed; export again to retry."},"ExportSubscription":{"properties":{"tier":{"type":"string","title":"Tier"},"billing_period":{"type":"string","title":"Billing Period"},"status":{"type":"string","title":"Status"},"current_period_end":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Current Period End"},"cancel_at_period_end":{"type":"boolean","title":"Cancel At Period End"}},"type":"object","required":["tier","billing_period","status","current_period_end","cancel_at_period_end"],"title":"ExportSubscription","description":"Our record of a subscription. Payment records are held by Paddle, not us."},"FindingResponse":{"properties":{"id":{"type":"string","title":"Id"},"severity":{"type":"string","enum":["critical","warning","info"],"title":"Severity"},"title":{"type":"string","title":"Title"},"detail":{"type":"string","title":"Detail"},"recommendation":{"type":"string","title":"Recommendation"},"snippet":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snippet"},"detail_locked":{"type":"boolean","title":"Detail Locked","default":false}},"type":"object","required":["id","severity","title","detail","recommendation"],"title":"FindingResponse","description":"One finding within a category report."},"GoogleAuthRequest":{"properties":{"credential":{"type":"string","title":"Credential"},"attribution":{"anyOf":[{"$ref":"#/components/schemas/SignupAttribution"},{"type":"null"}]},"offer_code":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Offer Code"}},"type":"object","required":["credential"],"title":"GoogleAuthRequest"},"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"},"turnstile_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Turnstile Token"}},"type":"object","required":["email","password"],"title":"LoginRequest"},"MetricReport":{"properties":{"id":{"type":"string","title":"Id"},"label":{"type":"string","title":"Label"},"value":{"type":"string","title":"Value"},"rating":{"anyOf":[{"type":"string","enum":["good","needs-improvement","poor"]},{"type":"null"}],"title":"Rating"}},"type":"object","required":["id","label","value"],"title":"MetricReport","description":"One measured metric within a category report.\n\nDistinct from a finding: a finding is something to fix, a metric is simply\nsomething measured. A category can report metrics with no findings at all —\nwhich is the point, since it lets a clean report still show its working.\nCurrently only the ``performance`` category populates these (its Core Web\nVitals and timing metrics); other categories send an empty list."},"PasswordResetConfirmBody":{"properties":{"token":{"type":"string","title":"Token"},"new_password":{"type":"string","maxLength":128,"minLength":12,"title":"New Password"}},"type":"object","required":["token","new_password"],"title":"PasswordResetConfirmBody"},"PasswordResetRequestBody":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"turnstile_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Turnstile Token"}},"type":"object","required":["email"],"title":"PasswordResetRequestBody"},"PlanOption":{"properties":{"tier":{"type":"string","enum":["free","pro","agency"],"title":"Tier"},"name":{"type":"string","title":"Name"},"monthly_price_usd":{"type":"string","title":"Monthly Price Usd"},"annual_price_usd":{"type":"string","title":"Annual Price Usd"},"monthly_scans":{"type":"integer","title":"Monthly Scans"},"pdf_export":{"type":"boolean","title":"Pdf Export"},"public_api":{"type":"boolean","title":"Public Api"},"webhooks":{"type":"boolean","title":"Webhooks"},"retention_days":{"type":"integer","title":"Retention Days"},"concurrent_scans":{"type":"integer","title":"Concurrent Scans"}},"type":"object","required":["tier","name","monthly_price_usd","annual_price_usd","monthly_scans","pdf_export","public_api","webhooks","retention_days","concurrent_scans"],"title":"PlanOption","description":"One buyable plan, for rendering the plan picker."},"PlanUsageResponse":{"properties":{"tier":{"type":"string","title":"Tier","description":"Billing tier: \"free\", \"pro\", or \"agency\"."},"plan_name":{"type":"string","title":"Plan Name","description":"Display name of the plan, e.g. \"Pro\"."},"scans_used":{"type":"integer","title":"Scans Used","description":"Scans created in the current calendar month (UTC)."},"scans_limit":{"type":"integer","title":"Scans Limit","description":"Scans the plan permits per calendar month."},"scans_remaining":{"type":"integer","title":"Scans Remaining","description":"Scans left before the monthly cap is reached."},"period_start":{"type":"string","format":"date-time","title":"Period Start","description":"First instant of the current quota month (UTC)."},"period_end":{"type":"string","format":"date-time","title":"Period End","description":"When the quota resets (UTC)."},"categories":{"items":{"type":"string"},"type":"array","title":"Categories","description":"Analysis categories this plan may run."},"pdf_export":{"type":"boolean","title":"Pdf Export","description":"Whether PDF report download is included."},"public_api":{"type":"boolean","title":"Public Api","description":"Whether API keys may be minted and used."},"webhooks":{"type":"boolean","title":"Webhooks","description":"Whether a scan may request webhook delivery."},"retention_days":{"type":"integer","title":"Retention Days","description":"How long completed scans are kept."},"concurrent_scans":{"type":"integer","title":"Concurrent Scans","description":"Maximum simultaneously running scans."},"scan_credits":{"type":"integer","title":"Scan Credits","description":"Unspent one-off scan credits. Each buys a single all-category scan outside the monthly quota, and is only spent on explicit request.","default":0},"retention_grace_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Retention Grace Until","description":"When the extended retention granted after a plan downgrade ends. Until then scans are kept as though the account were still on its previous plan; after it, `retention_days` applies and older scans may be permanently deleted. Null when no grace period is running."},"one_off_offer_price_usd":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"One Off Offer Price Usd","description":"A one-time reduced price for the one-off scan, applied automatically at checkout until `one_off_offer_expires_at`. Null when there is no offer."},"one_off_offer_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"One Off Offer Expires At","description":"When the one-time one-off scan price ends."}},"type":"object","required":["tier","plan_name","scans_used","scans_limit","scans_remaining","period_start","period_end","categories","pdf_export","public_api","webhooks","retention_days","concurrent_scans"],"title":"PlanUsageResponse","description":"The caller's plan entitlements and their usage against them (Phase 11 B3).\n\nExists because the limits were previously invisible: a user only discovered\nthe monthly cap by being 429'd. The SPA reads this to show remaining scans,\nlabel the categories a plan withholds, and decide whether to offer an\nupgrade prompt."},"PortalResponse":{"properties":{"overview_url":{"type":"string","title":"Overview Url"},"update_payment_method_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Update Payment Method Url"},"cancel_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cancel Url"}},"type":"object","required":["overview_url"],"title":"PortalResponse","description":"Short-lived authenticated links into Paddle's customer portal.\n\nMinted per request and never stored — the token appended to each link is\ntemporary by design."},"PublicConfigResponse":{"properties":{"scanner_egress_ips":{"items":{"type":"string"},"type":"array","title":"Scanner Egress Ips"},"billing_enabled":{"type":"boolean","title":"Billing Enabled"},"paddle_client_token":{"type":"string","title":"Paddle Client Token"},"paddle_environment":{"type":"string","title":"Paddle Environment"},"paddle_js_url":{"type":"string","title":"Paddle Js Url"}},"type":"object","required":["scanner_egress_ips","billing_enabled","paddle_client_token","paddle_environment","paddle_js_url"],"title":"PublicConfigResponse","description":"Runtime config the web UI reads on load."},"PublicPlan":{"properties":{"tier":{"type":"string","enum":["free","pro","agency"],"title":"Tier"},"name":{"type":"string","title":"Name"},"monthly_price_usd":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Monthly Price Usd"},"annual_price_usd":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Annual Price Usd"},"monthly_scans":{"type":"integer","title":"Monthly Scans"},"categories":{"items":{"type":"string"},"type":"array","title":"Categories"},"pdf_export":{"type":"boolean","title":"Pdf Export"},"public_api":{"type":"boolean","title":"Public Api"},"webhooks":{"type":"boolean","title":"Webhooks"},"retention_days":{"type":"integer","title":"Retention Days"},"concurrent_scans":{"type":"integer","title":"Concurrent Scans"},"paddle_price_id_monthly":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paddle Price Id Monthly"},"paddle_price_id_annual":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paddle Price Id Annual"}},"type":"object","required":["tier","name","monthly_scans","categories","pdf_export","public_api","webhooks","retention_days","concurrent_scans"],"title":"PublicPlan","description":"One plan as the public pricing page renders it.\n\nDeliberately a separate shape from :class:`PlanOption` rather than a loosened\none. That model serves the authenticated plan picker, where every entry is\nbuyable and every price is therefore non-null; this one has to carry **Free**,\nwhich is the price anchor the pricing page is built around.\n\nIt also carries the Paddle price ids, which `PlanOption` has no reason to.\nThey are not secrets — a price id is a catalog pointer, and the client token\nthat dereferences it already ships via ``GET /api/v1/config`` — but they are\n*configuration* (they differ between the sandbox and production catalogs),\nwhich is why they come from the server rather than a `VITE_` build variable.\nThe browser needs them to ask Paddle for a localized, tax-inclusive price."},"PublicPlansResponse":{"properties":{"billing_enabled":{"type":"boolean","title":"Billing Enabled"},"plans":{"items":{"$ref":"#/components/schemas/PublicPlan"},"type":"array","title":"Plans"},"one_off_scan_price_usd":{"type":"string","title":"One Off Scan Price Usd"},"one_off_scan_paddle_price_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"One Off Scan Paddle Price Id"},"annual_months_charged":{"type":"integer","title":"Annual Months Charged"}},"type":"object","required":["billing_enabled","plans","one_off_scan_price_usd","annual_months_charged"],"title":"PublicPlansResponse","description":"The full plan matrix, for the unauthenticated pricing page."},"RecentDuplicateResponse":{"properties":{"found":{"type":"boolean","title":"Found"},"scan_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Scan Id"},"last_scan_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Scan At"},"minutes_ago":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Minutes Ago"},"cooldown_minutes":{"type":"integer","title":"Cooldown Minutes"}},"type":"object","required":["found","cooldown_minutes"],"title":"RecentDuplicateResponse","description":"Result of the pre-submit recent-duplicate check for GET /scans/recent-duplicate.\n\nAdvisory only — reports whether the caller scanned the same website within the\nwarning window so the UI can caution against tripping the site's WAF. It never\nblocks submission."},"RefreshRequest":{"properties":{"refresh_token":{"type":"string","title":"Refresh Token"}},"type":"object","required":["refresh_token"],"title":"RefreshRequest"},"RegisterRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","maxLength":128,"minLength":12,"title":"Password"},"attribution":{"anyOf":[{"$ref":"#/components/schemas/SignupAttribution"},{"type":"null"}]},"offer_code":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Offer Code"},"turnstile_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Turnstile Token"}},"type":"object","required":["email","password"],"title":"RegisterRequest"},"ScanCreate":{"properties":{"url":{"type":"string","maxLength":2083,"minLength":1,"format":"uri","title":"Url"},"label":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Label"},"categories":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Categories","description":"Analysis categories to run. Omit to run every category included in your plan. Valid values: \"html\", \"seo\", \"links\", \"visual\", \"ai_visibility\", \"accessibility\", \"performance\", \"security_headers\", \"responsive\", \"content_freshness\".","examples":[["html","seo","links","visual","ai_visibility","accessibility","performance","security_headers","responsive","content_freshness"],["seo","security_headers","performance"]]},"webhook_url":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Webhook Url","description":"Optional URL to receive a POST with the completed scan payload once analysis finishes. Delivery is retried on failure."},"use_scan_credit":{"type":"boolean","title":"Use Scan Credit","description":"Spend one purchased scan credit on this scan instead of your monthly quota. Runs every analysis category regardless of your plan. Credits are never spent unless you set this.","default":false}},"type":"object","required":["url"],"title":"ScanCreate","description":"Body for POST /api/v1/scans.","example":{"categories":["seo","security_headers","performance"],"label":"Quick SEO + security check","url":"https://example.com","webhook_url":"https://your-app.example.com/hooks/scan-complete"}},"ScanDetailResponse":{"properties":{"id":{"type":"integer","title":"Id"},"url":{"type":"string","title":"Url"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"status":{"type":"string","enum":["queued","scanning","summarizing","complete","failed"],"title":"Status"},"categories_requested":{"items":{"type":"string"},"type":"array","title":"Categories Requested"},"overall_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Overall Score"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"webhook_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Url"},"quota_refunded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Quota Refunded At"},"used_scan_credit":{"type":"boolean","title":"Used Scan Credit","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"results":{"items":{"$ref":"#/components/schemas/ScanResultResponse"},"type":"array","title":"Results"},"screenshots":{"items":{"$ref":"#/components/schemas/ScreenshotResponse"},"type":"array","title":"Screenshots","default":[]},"score_breakdown":{"items":{"$ref":"#/components/schemas/ScoreBreakdownItem"},"type":"array","title":"Score Breakdown","default":[]}},"type":"object","required":["id","url","label","status","categories_requested","overall_score","created_at","completed_at","results"],"title":"ScanDetailResponse","description":"Scan detail — includes per-category results, score breakdown, and screenshots."},"ScanListResponse":{"properties":{"scans":{"items":{"$ref":"#/components/schemas/ScanResponse"},"type":"array","title":"Scans"},"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"page_size":{"type":"integer","title":"Page Size"}},"type":"object","required":["scans","total","page","page_size"],"title":"ScanListResponse","description":"Paginated scan history for the authenticated user."},"ScanReportResponse":{"properties":{"id":{"type":"integer","title":"Id"},"url":{"type":"string","title":"Url"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"status":{"type":"string","enum":["queued","scanning","summarizing","complete","failed"],"title":"Status"},"overall_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Overall Score"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"score_breakdown":{"items":{"$ref":"#/components/schemas/ScoreBreakdownItem"},"type":"array","title":"Score Breakdown"},"screenshots":{"items":{"$ref":"#/components/schemas/ScreenshotResponse"},"type":"array","title":"Screenshots"},"categories":{"items":{"$ref":"#/components/schemas/CategoryReportResponse"},"type":"array","title":"Categories"},"executive_summary":{"anyOf":[{"$ref":"#/components/schemas/ExecutiveSummary"},{"type":"null"}]}},"type":"object","required":["id","url","label","status","overall_score","created_at","completed_at","score_breakdown","screenshots","categories"],"title":"ScanReportResponse","description":"Full structured report for a completed scan, suitable for rendering or PDF export."},"ScanResponse":{"properties":{"id":{"type":"integer","title":"Id"},"url":{"type":"string","title":"Url"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"status":{"type":"string","enum":["queued","scanning","summarizing","complete","failed"],"title":"Status"},"categories_requested":{"items":{"type":"string"},"type":"array","title":"Categories Requested"},"overall_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Overall Score"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"webhook_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Url"},"quota_refunded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Quota Refunded At"},"used_scan_credit":{"type":"boolean","title":"Used Scan Credit","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"}},"type":"object","required":["id","url","label","status","categories_requested","overall_score","created_at","completed_at"],"title":"ScanResponse","description":"Scan summary — returned by POST and in the scan history list."},"ScanResultResponse":{"properties":{"category":{"type":"string","title":"Category"},"status":{"type":"string","enum":["pass","warn","fail","error","blocked"],"title":"Status"},"score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score"},"raw_result":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Raw Result"}},"type":"object","required":["category","status","score","raw_result"],"title":"ScanResultResponse","description":"One category's result inside a scan detail response."},"ScanUpdate":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Label"}},"type":"object","title":"ScanUpdate","description":"Body for PATCH /api/v1/scans/:id — currently only the label is editable."},"ScoreBreakdownItem":{"properties":{"category":{"type":"string","title":"Category"},"score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score"},"weight":{"type":"number","title":"Weight"}},"type":"object","required":["category","score","weight"],"title":"ScoreBreakdownItem","description":"Score and weight for one analysis category."},"ScreenshotResponse":{"properties":{"breakpoint":{"type":"string","enum":["mobile","tablet","desktop"],"title":"Breakpoint"},"storage_key":{"type":"string","title":"Storage Key"},"signed_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signed Url"}},"type":"object","required":["breakpoint","storage_key"],"title":"ScreenshotResponse","description":"One screenshot entry — storage key plus an optional pre-signed URL."},"SignupAttribution":{"properties":{"referrer_host":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Referrer Host"},"utm_source":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Utm Source"},"utm_medium":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Utm Medium"},"utm_campaign":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Utm Campaign"},"landing_path":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Landing Path"}},"type":"object","title":"SignupAttribution","description":"What the browser saw on the visit that led to sign-up.\n\nCaptured by the SPA on landing (``frontend/src/utils/attribution.ts``) and sent\nwith the registration request. Every field is optional: a direct visit has no\nreferrer and most links carry no UTM tags."},"SubscriptionChangeRequest":{"properties":{"plan":{"type":"string","enum":["free","pro","agency"],"title":"Plan"},"period":{"type":"string","enum":["monthly","annual"],"title":"Period","default":"monthly"}},"type":"object","required":["plan"],"title":"SubscriptionChangeRequest","description":"Move an existing subscription to a different plan or billing period."},"SubscriptionResponse":{"properties":{"tier":{"type":"string","title":"Tier"},"plan_name":{"type":"string","title":"Plan Name"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"subscription_tier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subscription Tier"},"billing_period":{"anyOf":[{"type":"string","enum":["monthly","annual"]},{"type":"null"}],"title":"Billing Period"},"current_period_end":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Current Period End"},"cancel_at_period_end":{"type":"boolean","title":"Cancel At Period End","default":false},"scan_credits":{"type":"integer","title":"Scan Credits","default":0},"withdrawal_available_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Withdrawal Available Until"},"retention_grace_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Retention Grace Until"},"billing_enabled":{"type":"boolean","title":"Billing Enabled","default":true},"plans":{"items":{"$ref":"#/components/schemas/PlanOption"},"type":"array","title":"Plans"}},"type":"object","required":["tier","plan_name"],"title":"SubscriptionResponse","description":"The caller's billing state, for the account billing page."},"TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"refresh_token":{"type":"string","title":"Refresh Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"},"offer_status":{"anyOf":[{"type":"string","enum":["claimed","invalid"]},{"type":"null"}],"title":"Offer Status"}},"type":"object","required":["access_token","refresh_token"],"title":"TokenResponse"},"TrendPoint":{"properties":{"scan_id":{"type":"integer","title":"Scan Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score"}},"type":"object","required":["scan_id","created_at","score"],"title":"TrendPoint","description":"One point in a URL's score history."},"TrendsResponse":{"properties":{"trends":{"items":{"$ref":"#/components/schemas/UrlTrend"},"type":"array","title":"Trends"}},"type":"object","required":["trends"],"title":"TrendsResponse","description":"Per-URL score history for the authenticated user's completed scans."},"UrlTrend":{"properties":{"url":{"type":"string","title":"Url"},"points":{"items":{"$ref":"#/components/schemas/TrendPoint"},"type":"array","title":"Points"}},"type":"object","required":["url","points"],"title":"UrlTrend","description":"Overall-score history for a single URL the user has scanned."},"UserPreferences":{"properties":{"default_categories":{"items":{"type":"string"},"type":"array","title":"Default Categories","description":"Analysis categories run by default when none are specified in a scan request. Valid values: \"html\", \"seo\", \"links\", \"visual\", \"ai_visibility\", \"accessibility\", \"performance\", \"security_headers\", \"responsive\", \"content_freshness\"."},"default_breakpoints":{"items":{"type":"string"},"type":"array","title":"Default Breakpoints","description":"Screenshot breakpoints used by default. Valid values: \"mobile\" (375 px), \"tablet\" (768 px), \"desktop\" (1440 px)."},"email_notifications":{"type":"boolean","title":"Email Notifications","description":"Send an email when a scan completes.","default":false},"dark_mode":{"type":"boolean","title":"Dark Mode","description":"Use the dark colour scheme in the web UI.","default":false},"onboarding_dismissed":{"type":"boolean","title":"Onboarding Dismissed","description":"Whether the user has dismissed the first-login onboarding walkthrough.","default":false},"dismissed_announcement_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Dismissed Announcement Id","description":"The in-app announcement the user last dismissed; a newer one shows again."},"product_emails":{"type":"boolean","title":"Product Emails","description":"Occasional follow-up emails after your first scans, including a one-time offer.","default":true}},"type":"object","title":"UserPreferences"},"UserResponse":{"properties":{"id":{"type":"integer","title":"Id"},"email":{"type":"string","format":"email","title":"Email"},"tier":{"type":"string","title":"Tier"},"role":{"type":"string","title":"Role"},"email_verified":{"type":"boolean","title":"Email Verified"},"has_password":{"type":"boolean","title":"Has Password"},"preferences":{"$ref":"#/components/schemas/UserPreferences"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","email","tier","role","email_verified","has_password","preferences","created_at","updated_at"],"title":"UserResponse"},"UserUpdateRequest":{"properties":{"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email","description":"Deprecated and rejected with 400. Changing an email address requires confirming the new one is reachable, so it goes through POST /auth/change-email instead."},"preferences":{"anyOf":[{"$ref":"#/components/schemas/UserPreferences"},{"type":"null"}]},"signup_source":{"anyOf":[{"type":"string","enum":["search_google","search_bing","search_duckduckgo","search_other","ai_chatgpt","ai_claude","ai_perplexity","ai_gemini","ai_copilot","ai_other","social","friend","article","directory","ascent","other"]},{"type":"null"}],"title":"Signup Source","description":"Where the user heard about SyteCheck."},"signup_source_detail":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Signup Source Detail","description":"Free text accompanying an \"other\" answer."}},"type":"object","title":"UserUpdateRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WebhookSecretResponse":{"properties":{"secret":{"type":"string","title":"Secret","description":"The signing secret, as `whsec_<hex>`. Configure it in the receiver that verifies the `X-SyteCheck-Signature` header."},"version":{"type":"integer","title":"Version","description":"Rotation counter. Rotating issues a new secret and immediately invalidates the previous one, so update the receiver first."},"signature_max_age_seconds":{"type":"integer","title":"Signature Max Age Seconds","description":"How old a signed timestamp may be before a receiver should reject the delivery as a replay."}},"type":"object","required":["secret","version","signature_max_age_seconds"],"title":"WebhookSecretResponse","description":"An account's outbound webhook signing secret.\n\nReturned in full on every read, unlike an API key, and the difference is\ndeliberate. A key is a *bearer* credential — anyone holding it can act as the\naccount, so it is stored hashed and shown once. This secret grants no access\nto anything: it only lets the holder verify that a delivery came from us. The\ncost of showing it again is therefore low, and the cost of *not* showing it is\nhigh — a customer who loses it would otherwise have to rotate, breaking a\nworking integration to recover a value we can simply re-derive."}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}},"tags":[{"name":"scans","description":"Submit scans, poll status, and retrieve reports."},{"name":"api-keys","description":"Create, list, and revoke API keys for programmatic access."},{"name":"users","description":"Manage the authenticated user's profile and preferences."},{"name":"auth","description":"Registration, login, token refresh, and password reset."},{"name":"billing","description":"Checkout, subscription management, and the billing portal."}]}