{"openapi":"3.1.0","info":{"title":"KernelScan API","description":"Public REST API for KernelScan — the Linux kernel CVE analyzer. Upload a kernel .config and get a CycloneDX VEX report of affecting CVEs, query the CVE database, and manage product analyses. Authenticate with `Authorization: Bearer ks_live_<key>` (create one at /api/auth/api-keys). The same surface is also available over MCP at /mcp/.","version":"1.0.0","contact":{"name":"KernelScan Support","url":"https://kernelscan.io/docs","email":"support@kernelscan.io"},"termsOfService":"https://kernelscan.io/agb"},"paths":{"/api/auth/register":{"post":{"tags":["auth"],"summary":"Register","operationId":"register_api_auth_register_post","responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Register Api Auth Register Post"}}}}}}},"/api/auth/request-invitation":{"post":{"tags":["auth"],"summary":"Request Invitation","description":"Public application form. Returns 202 generically — does not leak whether\nthe email is already a user, already has a pending request, etc.","operationId":"request_invitation_api_auth_request_invitation_post","responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Request Invitation Api Auth Request Invitation Post"}}}}}}},"/api/auth/invitations":{"get":{"tags":["auth"],"summary":"List My Invitations","operationId":"list_my_invitations_api_auth_invitations_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List My Invitations Api Auth Invitations Get"}}}}}},"post":{"tags":["auth"],"summary":"Create Invitation","operationId":"create_invitation_api_auth_invitations_post","responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Invitation Api Auth Invitations Post"}}}}}}},"/api/auth/invitations/accept":{"post":{"tags":["auth"],"summary":"Accept Team Invitation","description":"Seat the logged-in caller into the team a code invites them to.\n\nThe counterpart of register()'s team branch for an email that is already an\naccount (backend#323 slice 2). Same code rows, same email-bound lookup —\nthe difference is that the account exists, so the seating goes through\n:func:`app.services.team_service.add_member` exactly as the admin console's\ndoes: products follow when the move empties the caller's personal team, and\nthe seat limit is enforced under the team-row lock in the same transaction\nas the seat write (other *pending* invites never block an accept).","operationId":"accept_team_invitation_api_auth_invitations_accept_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Accept Team Invitation Api Auth Invitations Accept Post"}}}}}}},"/api/auth/login":{"post":{"tags":["auth"],"summary":"Login","operationId":"login_api_auth_login_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Login Api Auth Login Post"}}}}}}},"/api/auth/refresh":{"post":{"tags":["auth"],"summary":"Refresh","operationId":"refresh_api_auth_refresh_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Refresh Api Auth Refresh Post"}}}}}}},"/api/auth/logout":{"post":{"tags":["auth"],"summary":"Logout","description":"Close the user's active session, invalidate issued tokens, emit an event.\n\nBumping ``token_version`` makes this a logout-*all*: every access/refresh\ntoken minted before this call (on any device) fails the 'tv' check in\n:func:`app.auth.get_optional_user` / the refresh endpoint (backend#288).","operationId":"logout_api_auth_logout_post","responses":{"204":{"description":"Successful Response"}}}},"/api/auth/verify-email":{"post":{"tags":["auth"],"summary":"Verify Email","operationId":"verify_email_api_auth_verify_email_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Verify Email Api Auth Verify Email Post"}}}}}}},"/api/auth/resend-verification":{"post":{"tags":["auth"],"summary":"Resend Verification","description":"Mail a fresh confirmation token to the signed-in account's address.\n\nRegistration was the only place that ever sent one, which left an\nunverified account with nothing to do but hunt for a mail that may be days\nold — and signing in again does not help, since login never asked for\nverification in the first place. Anything that locks a viewer out on\n``is_email_verified`` needs this to point at.\n\nDeliberately NOT the enumeration-proof shape ``forgot-password`` uses: the\ncaller is already authenticated, so there is no address to confirm or deny\nthe existence of, and the honest 429 is worth more than a fake 200. The\naddress is the account's own — never taken from the request — so this\ncannot be turned into a mailer for someone else's inbox.","operationId":"resend_verification_api_auth_resend_verification_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Resend Verification Api Auth Resend Verification Post"}}}}}}},"/api/auth/forgot-password":{"post":{"tags":["auth"],"summary":"Forgot Password","operationId":"forgot_password_api_auth_forgot_password_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Forgot Password Api Auth Forgot Password Post"}}}}}}},"/api/auth/reset-password":{"post":{"tags":["auth"],"summary":"Reset Password","operationId":"reset_password_api_auth_reset_password_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Reset Password Api Auth Reset Password Post"}}}}}}},"/api/auth/me":{"get":{"tags":["auth"],"summary":"Me","operationId":"me_api_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Me Api Auth Me Get"}}}}}}},"/api/auth/newsletter":{"put":{"tags":["auth"],"summary":"Set Newsletter Pref","description":"Give or withdraw consent to product news.\n\n``require_user``, not ``require_verified_user``: withdrawal must be at least\nas easy as giving consent (Art. 7(3)), and an unverified account that ticked\nthe box at signup has to be able to take it back. One toggle, no\nconfirmation step, no reason asked.","operationId":"set_newsletter_pref_api_auth_newsletter_put","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Set Newsletter Pref Api Auth Newsletter Put"}}}}}}},"/api/auth/request-tier":{"post":{"tags":["auth"],"summary":"Request Tier","operationId":"request_tier_api_auth_request_tier_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Request Tier Api Auth Request Tier Post"}}}}}}},"/api/auth/api-keys":{"get":{"tags":["auth"],"summary":"List Api Keys","operationId":"list_api_keys_api_auth_api_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Api Keys Api Auth Api Keys Get"}}}}}},"post":{"tags":["auth"],"summary":"Create Api Key","operationId":"create_api_key_api_auth_api_keys_post","responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Api Key Api Auth Api Keys Post"}}}}}}},"/api/auth/account":{"delete":{"tags":["auth"],"summary":"Delete Account","operationId":"delete_account_api_auth_account_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Delete Account Api Auth Account Delete"}}}}}}},"/api/auth/api-keys/{key_id}":{"delete":{"tags":["auth"],"summary":"Revoke Api Key","operationId":"revoke_api_key_api_auth_api_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Revoke Api Key Api Auth Api Keys  Key Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/api-keys/{key_id}/permanent":{"delete":{"tags":["auth"],"summary":"Purge Api Key","description":"Hard-delete a revoked API key, removing the audit row entirely.\n\nSoft revocation (`DELETE /api-keys/{id}`) is the default — it preserves\nthe row so prefix and last-used timestamp stay visible for audit. This\nendpoint physically removes the row, but only for keys that are already\nrevoked, to prevent accidental loss of an active credential.","operationId":"purge_api_key_api_auth_api_keys__key_id__permanent_delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Purge Api Key Api Auth Api Keys  Key Id  Permanent Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/feed-url-token":{"get":{"tags":["auth"],"summary":"Get Feed Url Token","description":"Return metadata for the user's active feed URL token, or 404 if none.\n\nThe raw token is **never** returned here — it is only revealed at creation\nor rotation time.","operationId":"get_feed_url_token_api_auth_feed_url_token_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Feed Url Token Api Auth Feed Url Token Get"}}}}}},"post":{"tags":["auth"],"summary":"Create Or Rotate Feed Url Token","description":"Mint a new feed URL token, soft-revoking any existing active one.\n\nOptional JSON body ``{\"series\": [\"mainline\", \"6.6\", …]}`` scopes the feed\nto those kernel series (backend#272). Each requested series is validated\nagainst the caller's plan via :func:`_feed_series_options` (the same\nresolved set analytics uses); free is locked to ``mainline``. Omitting\n``series`` leaves the token unscoped (paid keeps the full feed; free is\nclamped to mainline at request time).\n\nReturns the raw token exactly once; subsequent GETs only show metadata.","operationId":"create_or_rotate_feed_url_token_api_auth_feed_url_token_post","responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Or Rotate Feed Url Token Api Auth Feed Url Token Post"}}}}}},"delete":{"tags":["auth"],"summary":"Revoke Feed Url Token","description":"Soft-revoke the user's active feed URL token. 404 if none.","operationId":"revoke_feed_url_token_api_auth_feed_url_token_delete","responses":{"204":{"description":"Successful Response"}}}},"/api/auth/totp/setup":{"post":{"tags":["auth"],"summary":"Totp Setup","description":"Begin TOTP enrollment. Returns a fresh secret + otpauth URI.\n\nCalling this while a half-finished secret exists (totp_enabled=0,\nsecret!=NULL) **rotates** to a new secret — simplest UX and the old\nsecret was never confirmed.","operationId":"totp_setup_api_auth_totp_setup_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Totp Setup Api Auth Totp Setup Post"}}}}}}},"/api/auth/totp/confirm":{"post":{"tags":["auth"],"summary":"Totp Confirm","description":"Finalize TOTP enrollment. Verifies a code, flips enabled, returns backup codes.","operationId":"totp_confirm_api_auth_totp_confirm_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Totp Confirm Api Auth Totp Confirm Post"}}}}}}},"/api/auth/login/totp":{"post":{"tags":["auth"],"summary":"Login Totp","description":"Exchange a one-shot MFA challenge token + a TOTP/backup code for access tokens.","operationId":"login_totp_api_auth_login_totp_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Login Totp Api Auth Login Totp Post"}}}}}}},"/api/auth/totp/disable":{"post":{"tags":["auth"],"summary":"Totp Disable","description":"Disable TOTP. Requires password + a valid TOTP code or backup code.","operationId":"totp_disable_api_auth_totp_disable_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Totp Disable Api Auth Totp Disable Post"}}}}}}},"/api/auth/totp/backup-codes/regenerate":{"post":{"tags":["auth"],"summary":"Totp Regenerate Backup Codes","description":"Replace the user's backup-code set. Requires password + valid TOTP code.","operationId":"totp_regenerate_backup_codes_api_auth_totp_backup_codes_regenerate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Totp Regenerate Backup Codes Api Auth Totp Backup Codes Regenerate Post"}}}}}}},"/api/auth/totp/forgot":{"post":{"tags":["auth"],"summary":"Totp Forgot","description":"Email-recovery for a lost authenticator. Requires email AND current password.\n\nAlways returns 200 — and always runs bcrypt against either the real hash\nor a dummy — so neither the existence of the account nor whether 2FA is\non can be inferred from response timing.","operationId":"totp_forgot_api_auth_totp_forgot_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Totp Forgot Api Auth Totp Forgot Post"}}}}}}},"/api/auth/totp/reset":{"post":{"tags":["auth"],"summary":"Totp Reset","description":"Consume a one-time reset token (from email) and disable TOTP on the account.","operationId":"totp_reset_api_auth_totp_reset_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Totp Reset Api Auth Totp Reset Post"}}}}}}},"/api/health":{"get":{"tags":["analysis"],"summary":"Health","operationId":"health_api_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Health Api Health Get"}}}}}}},"/api/promo":{"get":{"tags":["analysis"],"summary":"Promo Status","description":"Public flag the register form gates its plan choice on.\n\nThe signup offer only exists through a Stripe checkout, so it is live\nexactly when a checkout would actually succeed: self-serve billing\nswitched on AND at least one paid tier with a monthly price configured —\nthe same gates ``start_checkout`` enforces, so the marketing copy and\nthe real offer can never diverge. ``tiers`` lists which paid plans the\nregister form may reveal its card for (the form shows those and no\nothers); a tier without a price never appears, because the flag must\nnot advertise a plan that tier's checkout would refuse. Every paid card\nadditionally needs the trial campaign to be open\n(``signup_trial_being_offered``: its card copy IS the 30-day offer), and\n``days`` is the length of that signup trial. Fails closed: any error\nmeans no choice is offered and the form registers a plain free account.","operationId":"promo_status_api_promo_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Promo Status Api Promo Get"}}}}}}},"/api/stats/public":{"get":{"tags":["analysis"],"summary":"Public Stats","operationId":"public_stats_api_stats_public_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Public Stats Api Stats Public Get"}}}}}}},"/api/versions":{"get":{"tags":["analysis"],"summary":"Versions","operationId":"versions_api_versions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array","title":"Response Versions Api Versions Get"}}}}}}},"/api/analyze":{"post":{"tags":["analysis"],"summary":"Analyze","operationId":"analyze_api_analyze_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_analyze_api_analyze_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Analyze Api Analyze Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}":{"get":{"tags":["analysis"],"summary":"Job Status","operationId":"job_status_api_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Job Status Api Jobs  Job Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jobs/{job_id}/push-to-dt":{"post":{"tags":["analysis"],"summary":"Push Job To Dt","description":"Manually push (or re-push) the VEX for a completed job to DT.\n\nUseful as a retry path when auto-push failed transiently, and as the\nonly path for jobs that completed before the customer enabled\nauto-push. Ownership is checked: callers see only their own jobs\n(admins see all).","operationId":"push_job_to_dt_api_jobs__job_id__push_to_dt_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Push Job To Dt Api Jobs  Job Id  Push To Dt Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/download/{job_id}":{"get":{"tags":["analysis"],"summary":"Download Vex","operationId":"download_vex_api_download__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/kernel-releases":{"get":{"tags":["analysis"],"summary":"Kernel Releases","description":"Known releases of an overlay tree for one series. Public and read-only:\ngit tags and public fix data. ``complete`` is true only when the tag list\nfrom the tree's git tags is stored and readable; otherwise the list holds\nthe fix-data releases only, and a client must not conclude from it that a\nrelease does not exist (nor that a lone entry is the only one).","operationId":"kernel_releases_api_kernel_releases_get","parameters":[{"name":"flavor","in":"query","required":true,"schema":{"type":"string","title":"Flavor"}},{"name":"series","in":"query","required":true,"schema":{"type":"string","title":"Series"}},{"name":"rt","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Rt"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Kernel Releases Api Kernel Releases Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/cves":{"get":{"tags":["cves"],"summary":"Cve List","operationId":"cve_list_api_cves_get","parameters":[{"name":"kernel_version","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kernel Version"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"needs_assessment","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"If true, return only CVEs the auditor has not yet processed (ai_assessed_at IS NULL). NVD-scored CVEs are included so the auditor can enrich them with factors + prose.","title":"Needs Assessment"},"description":"If true, return only CVEs the auditor has not yet processed (ai_assessed_at IS NULL). NVD-scored CVEs are included so the auditor can enrich them with factors + prose."},{"name":"severity","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by effective severity: CRITICAL, HIGH, MEDIUM, LOW","title":"Severity"},"description":"Filter by effective severity: CRITICAL, HIGH, MEDIUM, LOW"},{"name":"cvss_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":10.0,"minimum":0.0},{"type":"null"}],"description":"Minimum effective CVSS score","title":"Cvss Min"},"description":"Minimum effective CVSS score"},{"name":"cvss_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":10.0,"minimum":0.0},{"type":"null"}],"description":"Maximum effective CVSS score","title":"Cvss Max"},"description":"Maximum effective CVSS score"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cve List Api Cves Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/cves/featured":{"get":{"tags":["cves"],"summary":"Cves Featured","description":"Latest high-severity, AI-assessed CVEs — fully detailed, no auth required.\n\nDrives the homepage \"Recent High-Severity CVEs\" section and is the\nsingle source of truth for what SSR /cve/{id} pages may expose publicly.","operationId":"cves_featured_api_cves_featured_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Cves Featured Api Cves Featured Get"}}}}}}},"/api/cves/{cve_id}/timeline":{"get":{"tags":["cves"],"summary":"Cve Timeline","description":"CVE lifecycle timeline (backend#368) — threat and kernel-code events.\n\nA separate, lazily-loaded endpoint rather than a block inside\n``/public``: the timeline needs the sighting table and every fix row, and\njoining that into the CVE payload would make each of the ~20 rows on a list\npage pay for a detail-only visualisation. A detail view opens, this fires\nonce.\n\nVisibility follows the CVE browser (see _can_view_cve_detail): anonymous\ncallers get public CVEs only, signed-in users everything their plan's feed\nwindow shows. Anything else is a 404, never a 403, so the endpoint cannot be\nused to probe which CVEs exist outside the caller's window.","operationId":"cve_timeline_api_cves__cve_id__timeline_get","parameters":[{"name":"cve_id","in":"path","required":true,"schema":{"type":"string","title":"Cve Id"}},{"name":"branch","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"description":"Product kernel series, e.g. \"6.6\" — floats that lane to the top in product context. Cosmetic ordering only; it never changes which events are returned.","title":"Branch"},"description":"Product kernel series, e.g. \"6.6\" — floats that lane to the top in product context. Cosmetic ordering only; it never changes which events are returned."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cve Timeline Api Cves  Cve Id  Timeline Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/cves/{cve_id}/public":{"get":{"tags":["cves"],"summary":"Cve Public Detail","description":"Full details of a single CVE if it qualifies for public exposure.\n\nReturns 404 for any CVE that doesn't qualify — we don't want to reveal\nwhether a CVE exists behind the paywall via a different status code.","operationId":"cve_public_detail_api_cves__cve_id__public_get","parameters":[{"name":"cve_id","in":"path","required":true,"schema":{"type":"string","title":"Cve Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cve Public Detail Api Cves  Cve Id  Public Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/showcase":{"get":{"tags":["samples"],"summary":"Showcase","description":"Return showcase data: same Switch 2400 config shown two ways.\n\n- config_scan: plain .config → VEX (version + config filtering only)\n- product: same config + security factors → AI contextual triage","operationId":"showcase_api_showcase_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Showcase Api Showcase Get"}}}}}}},"/api/samples/{distro_slug}/{arch}/vex":{"get":{"tags":["samples"],"summary":"Sample Vex","operationId":"sample_vex_api_samples__distro_slug___arch__vex_get","parameters":[{"name":"distro_slug","in":"path","required":true,"schema":{"type":"string","title":"Distro Slug"}},{"name":"arch","in":"path","required":true,"schema":{"type":"string","title":"Arch"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/samples/{distro_slug}/{arch}/cves":{"get":{"tags":["samples"],"summary":"Sample Cves","description":"Return CVE list for a sample, grouped by analysis status.","operationId":"sample_cves_api_samples__distro_slug___arch__cves_get","parameters":[{"name":"distro_slug","in":"path","required":true,"schema":{"type":"string","title":"Distro Slug"}},{"name":"arch","in":"path","required":true,"schema":{"type":"string","title":"Arch"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Sample Cves Api Samples  Distro Slug   Arch  Cves Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/products":{"get":{"tags":["products"],"summary":"List Products","operationId":"list_products_api_products_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response List Products Api Products Get"}}}}}},"post":{"tags":["products"],"summary":"Create Product","operationId":"create_product_api_products_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Product Api Products Post"}}}}}}},"/api/products/{product_id}":{"get":{"tags":["products"],"summary":"Get Product","operationId":"get_product_api_products__product_id__get","parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"string","title":"Product Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Product Api Products  Product Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["products"],"summary":"Update Product","operationId":"update_product_api_products__product_id__put","parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"string","title":"Product Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Product Api Products  Product Id  Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["products"],"summary":"Delete Product","operationId":"delete_product_api_products__product_id__delete","parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"string","title":"Product Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Product Api Products  Product Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/configs/uploads":{"post":{"tags":["products"],"summary":"Upload Config","description":"Stage a kernel .config for later consumption by create_product /\nupdate_product (REST or MCP).\n\nThis is the out-of-band path the LLM-driven MCP flow uses: the caller\nPOSTs the file here over HTTP (no token budget), keeps the returned\n``config_upload_id``, then passes it into ``create_product`` /\n``update_product`` as ``config_upload_id`` instead of inlining the\nconfig text. Uploads are per-user, single-use, and expire after 30\nminutes.","operationId":"upload_config_api_configs_uploads_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_config_api_configs_uploads_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Upload Config Api Configs Uploads Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/products/{product_id}/alerts":{"get":{"tags":["products"],"summary":"Get Product Alerts","operationId":"get_product_alerts_api_products__product_id__alerts_get","parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"string","title":"Product Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Product Alerts Api Products  Product Id  Alerts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["products"],"summary":"Update Product Alerts","operationId":"update_product_alerts_api_products__product_id__alerts_put","parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"string","title":"Product Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Product Alerts Api Products  Product Id  Alerts Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/products/{product_id}/vex":{"get":{"tags":["products"],"summary":"Product Vex","operationId":"product_vex_api_products__product_id__vex_get","parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"string","title":"Product Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/teams/me":{"get":{"tags":["teams"],"summary":"Get My Team","description":"The caller's team and its members. Any member, not just team admins.\n\nA team admin (or global admin) additionally gets ``pending_invitations``\nand ``seats``; for members those keys are absent — the shape contract is\ndocumented on :func:`app.services.team_service.get_team_overview`.","operationId":"get_my_team_api_teams_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get My Team Api Teams Me Get"}}}}}},"patch":{"tags":["teams"],"summary":"Update My Team","operationId":"update_my_team_api_teams_me_patch","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Update My Team Api Teams Me Patch"}}}}}}},"/api/teams/me/members/{user_id}":{"delete":{"tags":["teams"],"summary":"Remove My Team Member","description":"Unseat a member. Their products stay with the team; they get their own.\n\nNot a deletion — the account survives on a fresh personal team, which is why\nthis is not gated behind a password confirmation the way\n``DELETE /api/auth/account`` is. What it does destroy is the removed member's\n*access* to the team's products, so it is a team-admin action and is logged.","operationId":"remove_my_team_member_api_teams_me_members__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":{"type":"object","additionalProperties":true,"title":"Response Remove My Team Member Api Teams Me Members  User Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/teams/me/invitations":{"post":{"tags":["teams"],"summary":"Create Team Invitation","description":"Invite someone into the caller's team by email.\n\nTwo deliberate differences from the personal ``POST /api/auth/invitations``:\n\n* **No ``email_is_registered`` 409.** A personal invite is only a way to\n  *register*, so inviting an existing user is a dead code. A team invite is\n  a seat: an existing account accepts the very same code while logged in\n  (``POST /api/auth/invitations/accept``) and moves in, products following.\n* **No ``PER_USER_TOTAL_QUOTA``.** That cap throttles viral registration\n  growth; a team invite is bounded by something better — the seats the\n  team's plan actually has (the ``ensure_seat_available`` call below).\n\nMember-only on purpose: ``invited_team_role`` is pinned to ``team_member``\nhere, promotions stay a staff action in the admin console.","operationId":"create_team_invitation_api_teams_me_invitations_post","responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Team Invitation Api Teams Me Invitations Post"}}}}}}},"/api/teams/me/invitations/{invitation_id}/revoke":{"post":{"tags":["teams"],"summary":"Revoke Team Invitation","description":"Withdraw a pending invite and free its seat.\n\n404 for any code that is not THIS team's — other teams' codes and personal\ninvites (``team_id`` NULL) alike: confirming a foreign invitation id exists\nis the same disclosure the cross-user product and job routes refuse, and\npersonal invites keep their existing admin-only revoke. 409 once accepted:\nthe invitee is a member now, and unseating members is\n``DELETE /api/teams/me/members/{id}``, which has rules of its own.","operationId":"revoke_team_invitation_api_teams_me_invitations__invitation_id__revoke_post","parameters":[{"name":"invitation_id","in":"path","required":true,"schema":{"type":"string","title":"Invitation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Revoke Team Invitation Api Teams Me Invitations  Invitation Id  Revoke Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/checkout":{"post":{"tags":["billing"],"summary":"Start Checkout","description":"Create a Stripe Checkout session and return its hosted URL.\n\nRequires the paid-order legal data the upgrade form already collects\n(``account_type`` + ``country``, §§ 13/14 BGB and the VAT jurisdiction). It\ndoes not re-collect them: rejecting with a pointer at the existing form\nkeeps ONE place where that data is captured, which is also where\nbackend#145 will attach the § 312j/§ 356 V surface.","operationId":"start_checkout_api_billing_checkout_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Start Checkout Api Billing Checkout Post"}}}}}}},"/api/billing/checkout/abandon":{"post":{"tags":["billing"],"summary":"Abandon Signup Checkout","description":"Turn an explicitly cancelled paid signup into an ordinary Free account.","operationId":"abandon_signup_checkout_api_billing_checkout_abandon_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Abandon Signup Checkout Api Billing Checkout Abandon Post"}}}}}}},"/api/billing/change-plan":{"post":{"tags":["billing"],"summary":"Change Plan","description":"Open Stripe's hosted plan-change flow for an active subscription.\n\nThis route changes nothing itself. Stripe's portal shows the exact price,\ntax and proration before confirmation, then the normal subscription webhook\napplies the resulting tier. It is management of an existing contract and\ntherefore remains available when the new-sales switch is off.","operationId":"change_plan_api_billing_change_plan_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Change Plan Api Billing Change Plan Post"}}}}}}},"/api/billing/portal":{"post":{"tags":["billing"],"summary":"Open Portal","description":"Return a one-time URL into Stripe's hosted billing portal.\n\nConfigured-only, not sales-gated: the portal is where an existing customer\nmanages or ends what they are already paying for.","operationId":"open_portal_api_billing_portal_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Open Portal Api Billing Portal Post"}}}}}}},"/api/billing/cancel":{"post":{"tags":["billing"],"summary":"Cancel Subscription","description":"Stop the subscription renewing; the plan runs to the end of the period.\n\nDeliberately not an immediate downgrade. The period is paid for, and taking\nthe plan away the moment someone clicks cancel would be charging for time\nthen withholding it. The drop to free happens when Stripe fires\n``customer.subscription.deleted`` at the period end, on the same webhook\npath as any other ending — one implementation, not two.\n\n**This is the only path that cancels anything automatically.** The § 312k\nBGB Kündigungsbutton (``app/routes/cancellation.py``, backend#372) is\nlogin-free by law and therefore records a declaration and hands it to\nsupport instead — an unauthenticated claim on an email address is not\nevidence of who is asking. Here ``require_team_admin`` establishes identity\nand authority together, which is what makes acting immediately safe.","operationId":"cancel_subscription_api_billing_cancel_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Cancel Subscription Api Billing Cancel Post"}}}}}}},"/api/billing/resume":{"post":{"tags":["billing"],"summary":"Resume Subscription","description":"Call off a scheduled cancellation, while the period is still running.\n\nThe counterpart to cancelling at period end: until that date arrives\nnothing has actually happened, so undoing it is a flag flip rather than a\nnew purchase. After the period ends there is no subscription to resume and\nthe customer buys again through checkout.","operationId":"resume_subscription_api_billing_resume_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Resume Subscription Api Billing Resume Post"}}}}}}},"/api/billing/subscription":{"get":{"tags":["billing"],"summary":"Get Subscription State","description":"The account's current subscription, for the account page.\n\nAny member, not just team admins: what plan the team is on is not a secret\nfrom the people sharing it. Not gated on the self-serve flag either — an\naccount that already has a subscription must always be able to see it.","operationId":"get_subscription_state_api_billing_subscription_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Subscription State Api Billing Subscription Get"}}}}}}},"/api/billing/webhook":{"post":{"tags":["billing"],"summary":"Stripe Webhook","description":"Receive a Stripe webhook.\n\nUnauthenticated by necessity — Stripe has no credential to present — so the\nHMAC signature over the raw body IS the authentication, and it is verified\nbefore the payload is parsed, let alone acted on.\n\nAlways answers 200 once the signature checks out, including for events we\ndo not handle and for a subscription we cannot resolve to a team. A non-2xx\nmakes Stripe retry for three days and eventually disable the endpoint;\nneither an unknown event type nor an orphaned customer is fixed by\nredelivery, and both are already in the log.","operationId":"stripe_webhook_api_billing_webhook_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Stripe Webhook Api Billing Webhook Post"}}}}}}},"/api/cancellation":{"post":{"tags":["billing"],"summary":"Submit Cancellation","description":"Accept a cancellation declaration from the login-free page.\n\nValidation is limited to what the *form* needs to be a valid declaration —\na type, an address to look up, and a reason when one is legally required.\nEverything beyond that is recorded as typed, because § 312k Abs. 2 asks the\nconsumer for identifying details and it is not our place to reject their\nanswer.\n\nA session, when the browser has one, is recorded alongside as evidence of\nwho sent it. It takes no part in any decision.","operationId":"submit_cancellation_api_cancellation_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Submit Cancellation Api Cancellation Post"}}}}}}},"/api/security-factors":{"get":{"tags":["security-factors"],"summary":"List Security Factors","operationId":"list_security_factors_api_security_factors_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Security Factors Api Security Factors Get"}}}}}}},"/api/products/{product_id}/factors":{"get":{"tags":["security-factors"],"summary":"Get Product Factors","operationId":"get_product_factors_api_products__product_id__factors_get","parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"string","title":"Product Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Product Factors Api Products  Product Id  Factors Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/products/{product_id}/factor-impact":{"get":{"summary":"Product Factor Impact","description":"Per-product: which CVEs are affected by factor context, assessment status.\n\nThe product's VEX holds the authoritative verdict; this endpoint explains it. While the\nattack-path engine is on (AppConfig ``attack_path_engine``), only the product's own Layer-2\nCVEs are reported, each as ``decided`` (engine) or, for the LLM fallback, ``assessed`` /\n``pending`` (really queued) / ``no_verdict``. Off: the legacy LLM view over every audited CVE.","operationId":"product_factor_impact_api_products__product_id__factor_impact_get","parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"string","title":"Product Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Product Factor Impact Api Products  Product Id  Factor Impact Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/cve-candidates/teaser":{"get":{"tags":["cve-candidates"],"summary":"Cve Candidates Teaser","description":"No auth. ``{\"available\": false}`` while no plan includes candidates, else\n``{\"available\": true, \"plan\", \"plans\", \"top\": [3], \"stats\"}``: the open\ncandidates' top three as complete examples (the detail record, which never\ncarries the threat model) and counts of what the plan gate hides.\nDeclared before ``/{candidate_id}``, which would otherwise take the path.","operationId":"cve_candidates_teaser_api_cve_candidates_teaser_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Cve Candidates Teaser Api Cve Candidates Teaser Get"}}}}}}},"/api/cve-candidates":{"get":{"tags":["cve-candidates"],"summary":"List Cve Candidates","operationId":"list_cve_candidates_api_cve_candidates_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","description":"candidate | promoted | closed_without_cve | all","default":"candidate","title":"Status"},"description":"candidate | promoted | closed_without_cve | all"},{"name":"severity","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CRITICAL, HIGH, MEDIUM, LOW","title":"Severity"},"description":"CRITICAL, HIGH, MEDIUM, LOW"},{"name":"stable_release","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"description":"A stable release that ships the fix, e.g. v7.2.9","title":"Stable Release"},"description":"A stable release that ships the fix, e.g. v7.2.9"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"description":"Free text: id, CVE id, name, subject, commit message or component","title":"Search"},"description":"Free text: id, CVE id, name, subject, commit message or component"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"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/cve-candidates/{candidate_id}":{"get":{"tags":["cve-candidates"],"summary":"Get Cve Candidate","operationId":"get_cve_candidate_api_cve_candidates__candidate_id__get","parameters":[{"name":"candidate_id","in":"path","required":true,"schema":{"type":"string","title":"Candidate Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/support/sales-enquiry":{"post":{"tags":["support"],"summary":"Submit Sales Enquiry","description":"Enterprise lead intake — the ONE destination for every \"talk to sales\".\n\nDeliberately not a ``/contact`` category. Enterprise is billed by invoice\nagainst a written contract and is the largest deal the product has; routing\nit through a support-triage dropdown put it in the same queue as a bug\nreport, with none of the fields a quote needs. The alert is subject-prefixed\n``[KernelScan SALES]`` so it is filterable even though both land in the same\nmailbox.\n\nUnauthenticated on purpose: a buyer evaluating the product has no account\nyet, and requiring one to ask for a price is the wrong order.","operationId":"submit_sales_enquiry_api_support_sales_enquiry_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Submit Sales Enquiry Api Support Sales Enquiry Post"}}}}}}},"/api/support/contact":{"post":{"tags":["support"],"summary":"Submit Contact","description":"Public contact form. Authenticated callers get their identity attached\nautomatically; anonymous callers must supply a reply email.","operationId":"submit_contact_api_support_contact_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Submit Contact Api Support Contact Post"}}}}}}},"/api/support/feedback":{"post":{"tags":["support"],"summary":"Submit Feedback","description":"Authenticated support / dispute form (used by /account and the\n``submit_support_report`` MCP tool's HTTP path). The user's id, email,\nrole and tier are attached server-side so the team can look the account\nup directly.","operationId":"submit_feedback_api_support_feedback_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Submit Feedback Api Support Feedback Post"}}}}}}},"/api/oauth/authorization-request":{"get":{"tags":["oauth"],"summary":"Describe Authorization Request","description":"What the consent page needs to render: who is asking, and for what.\n\nDeliberately unauthenticated: the page calls this before it knows whether\nanyone is signed in, and it reveals nothing secret — only a client name the\nclient chose for itself. Validating here means an invalid request is caught\nBEFORE the user logs in, rather than after.\n\n``client_kind`` tells the page how to present the requester: for ``cimd``\nthe proven ``client_host`` is the identity and ``client_name`` secondary;\nfor ``dcr`` everything is self-asserted and the page warns. Because this\nendpoint has no user, the DCR opt-in cannot be checked here — the page\nchecks ``oauth_dcr_enabled`` after sign-in for the explanation, and\nPOST /api/oauth/authorize is where it is enforced.","operationId":"describe_authorization_request_api_oauth_authorization_request_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/oauth/authorize":{"post":{"tags":["oauth"],"summary":"Grant Authorization","description":"Mint an authorization code for the signed-in user.\n\nAuthenticated with the user's own JWT (the consent page sends it from\nlocalStorage), so the identity being granted is unambiguous and no\ncross-site form can trigger it.\n\nThe code stores the redirect_uri exactly as presented (for a CIMD loopback\nredirect that includes the ephemeral port), and /oauth/token demands that\nsame string.","operationId":"grant_authorization_api_oauth_authorize_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/oauth/connections":{"get":{"tags":["oauth"],"summary":"List Connections","description":"Live OAuth grants for this account, so a user can see and cut them.\n\nA connector the user can't revoke is worse than the config-file key it\nreplaces — that one they could at least delete.","operationId":"list_connections_api_oauth_connections_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/oauth/connections/{client_id}":{"delete":{"tags":["oauth"],"summary":"Revoke Connection","description":"Revoke this client's tokens and pending codes in one serialized action.","operationId":"revoke_connection_api_oauth_connections__client_id__delete","parameters":[{"name":"client_id","in":"path","required":true,"schema":{"type":"string","title":"Client Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/oauth/dcr-setting":{"put":{"tags":["oauth"],"summary":"Set Dcr Setting","description":"Allow or forbid authorizing clients without a verified identity (backend#419).\n\nDeliberately NOT in ``OAUTH_REST_ALLOWLIST``: a connector must never be able\nto widen which connectors its user can authorize. Turning it off stops new\nDCR grants; existing connections stay listed on the account page, where the\nuser can revoke them.","operationId":"set_dcr_setting_api_oauth_dcr_setting_put","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"Body_analyze_api_analyze_post":{"properties":{"config_file":{"type":"string","format":"binary","title":"Config File"},"kernel_release":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kernel Release"}},"type":"object","required":["config_file"],"title":"Body_analyze_api_analyze_post"},"Body_upload_config_api_configs_uploads_post":{"properties":{"config_file":{"type":"string","format":"binary","title":"Config File"}},"type":"object","required":["config_file"],"title":"Body_upload_config_api_configs_uploads_post"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"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"}}},"servers":[{"url":"https://kernelscan.io"}],"externalDocs":{"description":"KernelScan documentation","url":"https://kernelscan.io/docs"},"tags":[{"name":"cves","description":"Search and read Linux kernel CVEs."},{"name":"cve-candidates","description":"Pre-CVE candidates: kernel fixes nominated for a CVE that is not published yet (Pro and Enterprise plans)."},{"name":"analysis","description":"Submit a kernel .config and retrieve its CycloneDX VEX report."},{"name":"products","description":"Manage product analyses (saved configs and their VEX)."},{"name":"security-factors","description":"Product security factors that contextualize CVE impact."},{"name":"samples","description":"Public sample / showcase analyses."},{"name":"auth","description":"Registration, login, API keys, and account management."},{"name":"support","description":"Contact and feedback endpoints."}]}