{"openapi":"3.1.0","info":{"title":"bouncecheck.email","description":"Email verification API with subscription management","version":"1.0.0"},"paths":{"/api/v1/auth/register":{"post":{"tags":["Authentication"],"summary":"Register","description":"Register a new user\n\nCreates a new user account with:\n- Email and password authentication\n- Free subscription plan\n- Default user settings","operationId":"register_api_v1_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserRegister"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/login":{"post":{"tags":["Authentication"],"summary":"Login","description":"Login with email and password\n\nReturns JWT access and refresh tokens","operationId":"login_api_v1_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserLogin"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Token"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/login/form":{"post":{"tags":["Authentication"],"summary":"Login Form","description":"Login with OAuth2 password flow (for Swagger UI)\n\nUses username field as email","operationId":"login_form_api_v1_auth_login_form_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_login_form_api_v1_auth_login_form_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Token"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/refresh":{"post":{"tags":["Authentication"],"summary":"Refresh Token","description":"Refresh access token using refresh token","operationId":"refresh_token_api_v1_auth_refresh_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenRefresh"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Token"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/me":{"get":{"tags":["Authentication"],"summary":"Get Me","description":"Get current authenticated user information","operationId":"get_me_api_v1_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/auth/change-password":{"post":{"tags":["Authentication"],"summary":"Change Password","description":"Change user password","operationId":"change_password_api_v1_auth_change_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePassword"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/auth/verify-email":{"post":{"tags":["Authentication"],"summary":"Verify Email","description":"Verify email address using verification token","operationId":"verify_email_api_v1_auth_verify_email_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/resend-verification":{"post":{"tags":["Authentication"],"summary":"Resend Verification","description":"Resend verification email","operationId":"resend_verification_api_v1_auth_resend_verification_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResendVerification"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/capture-context":{"post":{"tags":["Authentication"],"summary":"Capture Context","description":"Fill first-party context (country/device/source) from the *browser* request.\n\nFired client-side right after login (see webapp ContextCapture). This is the\nonly reliable way to capture these for Google-OAuth users: the /auth/google\ntoken exchange runs server-side (NextAuth), so it can't see the user's real\nIP/User-Agent, whereas this call is browser-direct through Cloudflare.\nIdempotent — only fills fields that are still empty, never overwrites.","operationId":"capture_context_api_v1_auth_capture_context_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ContextCapture"},{"type":"null"}],"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/auth/google":{"post":{"tags":["Authentication"],"summary":"Google Login","description":"Authenticate with Google OAuth\n\nVerifies Google ID token and creates/updates user account:\n- Email is automatically verified (Google handles verification)\n- Sends welcome email instead of verification email\n- Creates free subscription and default settings\n- Returns JWT access and refresh tokens","operationId":"google_login_api_v1_auth_google_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleLogin"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Token"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/forgot-password":{"post":{"tags":["Authentication"],"summary":"Forgot Password","description":"Request password reset email\n\nSends a password reset link to the user's email if the account exists.\nFor security, always returns success message regardless of whether email exists.","operationId":"forgot_password_api_v1_auth_forgot_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPassword"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/reset-password":{"post":{"tags":["Authentication"],"summary":"Reset Password","description":"Reset password using reset token\n\nValidates the reset token and updates the user's password.","operationId":"reset_password_api_v1_auth_reset_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPassword"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/account":{"delete":{"tags":["Authentication"],"summary":"Delete Account","description":"Permanently delete the authenticated user's account and all associated data.\n\nRequires re-typing the exact account email as confirmation. Irreversible:\ncancels any Stripe subscription, deletes the Stripe customer, removes all\ndata (invoices are retained — detached — for accounting), and emails a\nconfirmation. Afterwards the user's JWTs and API keys no longer work.","operationId":"delete_account_api_v1_auth_account_delete","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDeleteRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/users/profile":{"get":{"tags":["Users"],"summary":"Get Profile","description":"Get current user profile","operationId":"get_profile_api_v1_users_profile_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]},"put":{"tags":["Users"],"summary":"Update Profile","description":"Update user profile","operationId":"update_profile_api_v1_users_profile_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserUpdate"}}},"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":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/users/settings":{"get":{"tags":["Users"],"summary":"Get Settings","description":"Get user notification settings","operationId":"get_settings_api_v1_users_settings_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSettingsResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]},"put":{"tags":["Users"],"summary":"Update Settings","description":"Update user notification settings","operationId":"update_settings_api_v1_users_settings_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSettingsUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSettingsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/verification/public/verify":{"post":{"tags":["Verification"],"summary":"Verify Email Public","description":"Public endpoint for email verification on landing page\nLimited to 20 checks per IP address per day\nProtected by reCAPTCHA v3\nNo authentication required","operationId":"verify_email_public_api_v1_verification_public_verify_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicEmailVerifyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailVerifyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/verification/disposable-check/public":{"post":{"tags":["Verification"],"summary":"Disposable Check Public","description":"Public disposable-email check for the /tools/disposable-email-checker landing page.\n\n- No authentication required\n- 10 requests per day per IP (reuses PublicVerificationRateLimit table with\n  a separate key prefix so it doesn't conflict with /public/verify quota)\n- Optional reCAPTCHA v3 verification when configured","operationId":"disposable_check_public_api_v1_verification_disposable_check_public_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicDisposableCheckRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisposableCheckResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/verification/disposable-check":{"post":{"tags":["Verification"],"summary":"Disposable Check Authenticated","description":"Authenticated email-intelligence check (disposable + provider + mail infra\n+ registration + address classification).\n\n- Requires JWT or API key\n- Consumes 1 unit from the user's monthly plan quota\n- Records a DISPOSABLE-type history row so the credit spend is visible","operationId":"disposable_check_authenticated_api_v1_verification_disposable_check_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisposableCheckRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisposableCheckResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/verification/providers":{"get":{"tags":["Verification"],"summary":"List Disposable Providers","description":"Public index of known disposable-email providers (temp-mail services),\nranked by number of associated domains. Backs /tools/providers.","operationId":"list_disposable_providers_api_v1_verification_providers_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q"}},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"}},{"name":"min_domains","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Domains"}},{"name":"max_domains","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Domains"}},{"name":"updated_within_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Updated Within Days"}},{"name":"sort","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sort"}},{"name":"sort_dir","in":"query","required":false,"schema":{"type":"string","default":"desc","title":"Sort Dir"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/verification/providers/{slug}":{"get":{"tags":["Verification"],"summary":"Get Disposable Provider","description":"Public detail for one provider: its MX signatures and the domains we've\nobserved running on its infrastructure. Backs /tools/providers/[slug].","operationId":"get_disposable_provider_api_v1_verification_providers__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/verification/domains-index":{"get":{"tags":["Verification"],"summary":"List Domains Index","description":"Paginated list of every confirmed-disposable domain (plain DB read, no\nDNS/RDAP). Backs the /tools/domain sitemap generator — NOT a public page.\nOnly confirmed domains are exposed: those are exactly the ones whose\n/tools/domain/{domain} page has real signal and isn't noindex'd.","operationId":"list_domains_index_api_v1_verification_domains_index_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":500,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainIndexResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/verification/domain-lookup/{domain}":{"get":{"tags":["Verification"],"summary":"Domain Lookup Public","description":"Public domain-level lookup (UserCheck-style /domain/{domain} page). Backs\n/tools/domain/[domain].\n\nNo auth, no reCAPTCHA, no per-IP quota: this is a page-load (not a\nuser-submitted form), and detect_email_intelligence caches its verdict by\ndomain in Redis for 24h, so repeat/crawler traffic on the same domain is a\ncache read, not a fresh DNS/RDAP round-trip.","operationId":"domain_lookup_public_api_v1_verification_domain_lookup__domain__get","parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string","title":"Domain"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainLookupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/verification/single":{"post":{"tags":["Verification"],"summary":"Verify Single Email","description":"Verify a single email address\n\nRequires authentication via JWT token or API key","operationId":"verify_single_email_api_v1_verification_single_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailVerifyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailVerifyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/verification/sample-template":{"get":{"tags":["Verification"],"summary":"Download Sample Template","description":"Download a sample template file for bulk email verification\n\nArgs:\n    format: File format - 'csv' or 'xlsx' (default: csv)\n\nReturns:\n    StreamingResponse: Sample file download","operationId":"download_sample_template_api_v1_verification_sample_template_get","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","default":"csv","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/verification/bulk":{"post":{"tags":["Verification"],"summary":"Upload Bulk Verification","description":"Upload a CSV or Excel file for bulk email verification\n\nThe file should contain a column named 'email' with email addresses\nProcessing is done asynchronously via Celery","operationId":"upload_bulk_verification_api_v1_verification_bulk_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_bulk_verification_api_v1_verification_bulk_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/verification/bulk/list":{"post":{"tags":["Verification"],"summary":"Verify Email List","description":"Start a bulk verification from a typed / pasted list of addresses.\n\nThe same job, the same worker and the same billing as a file upload — the\nonly difference is how the addresses arrive. The list is written to a real\nCSV in UPLOAD_DIR because the job row is the whole contract with the worker:\n`process_bulk_verification` re-reads `file_path` on every run (including the\nresume sweep after a worker restart) and derives `result_path` from it, so a\njob with no file would crash into FAILED and never be retried.\n\nBad lines are skipped and counted rather than verified. A file upload sends\nits junk rows to the verifier, which answers 422, and those are stored\nInvalid and CHARGED; someone pasting from a spreadsheet gets the count back\ninstead of the bill. See `split_pasted_emails`.","operationId":"verify_email_list_api_v1_verification_bulk_list_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkListVerifyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/verification/bulk/{job_id}/continue":{"post":{"tags":["Verification"],"summary":"Continue Bulk Job","description":"Resume a bulk job that stopped because the credits ran out.\n\nThe worker skips every address already stored, so nothing is verified or\ncharged twice; it picks up the ones that were not checked. Needs credits —\nwithout any it would stop again at once.","operationId":"continue_bulk_job_api_v1_verification_bulk__job_id__continue_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"integer","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/v1/verification/status/{job_id}":{"get":{"tags":["Verification"],"summary":"Get Job Status","description":"Get status of a verification job","operationId":"get_job_status_api_v1_verification_status__job_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"integer","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationJobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/verification/results/{job_id}":{"get":{"tags":["Verification"],"summary":"Get Job Results","description":"Paginated + searchable + status-filterable results for the caller's own\ncompleted job. Returns a job header (counts, retryable-unknown, recheck\nstate) so the modal renders summaries and the retry action without loading\nevery row — bulk jobs run to tens of thousands of results.","operationId":"get_job_results_api_v1_verification_results__job_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"integer","title":"Job Id"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Per Page"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/verification/download/{job_id}":{"get":{"tags":["Verification"],"summary":"Download Results","description":"Download verification results as CSV.\n\nThe CSV is built from the rows verified so far rather than from the final\nresult file the worker only writes on completion, so this works even while a\njob is still processing (partial download) or if it failed midway.","operationId":"download_results_api_v1_verification_download__job_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"integer","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/v1/verification/jobs/{job_id}/recheck":{"get":{"tags":["Verification"],"summary":"Retry Unknown Status","description":"Whether a retry is currently queued/running for this job (UI polling).","operationId":"retry_unknown_status_api_v1_verification_jobs__job_id__recheck_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"integer","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Verification"],"summary":"Retry Unknown Results","description":"Re-run ONLY this job's Unknown (unchecked) emails.\n\nUnknown rows were never billed. The retry runs in the background; each\nemail is charged 1 credit only when its recheck completes — emails that\nstill can't be reached stay free. The customer gets an in-app\nnotification when results are updated.","operationId":"retry_unknown_results_api_v1_verification_jobs__job_id__recheck_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"integer","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/v1/history/jobs":{"get":{"tags":["History"],"summary":"Get Verification Jobs","description":"Get the current user's verification jobs, paginated + server-sorted.\n\nOnly the requested page is enriched, and the valid/invalid/email/disposable\ndata for that page is gathered in a few GROUPED queries (not the old\nper-job N+1), so the endpoint stays fast regardless of history size.\nReturns {jobs, total, page, per_page}.","operationId":"get_verification_jobs_api_v1_history_jobs_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":25,"title":"Per Page"}},{"name":"sort","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sort"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","default":"desc","title":"Order"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/history/jobs/by-email":{"get":{"tags":["History"],"summary":"Jobs Containing Email","description":"Return the ids of the current user's jobs whose verification results contain\nan email matching the query (substring, case-insensitive).\n\nLets the history UI answer \"which list is email X in?\" — it searches inside\nbulk lists (and single jobs) without loading every result client-side.","operationId":"jobs_containing_email_api_v1_history_jobs_by_email_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"email","in":"query","required":true,"schema":{"type":"string","minLength":1,"title":"Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/history":{"get":{"tags":["History"],"summary":"Get Verification History","description":"Get verification history with pagination and filters\n\nFilters:\n- job_type: Filter by job type (single, bulk)\n- status: Filter by status (pending, processing, completed, failed)\n- search: Search in file names","operationId":"get_verification_history_api_v1_history_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Page Size"}},{"name":"job_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/JobType"},{"type":"null"}],"title":"Job Type"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/JobStatus"},{"type":"null"}],"title":"Status"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/api-keys":{"get":{"tags":["API Keys"],"summary":"List Api Keys","description":"Get all API keys for current user","operationId":"list_api_keys_api_v1_api_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]},"post":{"tags":["API Keys"],"summary":"Create Api Key","description":"Create a new API key\n\nThe plain API key is only returned once during creation.\nStore it securely as it cannot be retrieved later.","operationId":"create_api_key_api_v1_api_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/APIKeyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/api-keys/{key_id}":{"put":{"tags":["API Keys"],"summary":"Update Api Key","description":"Update an API key (name or active status)","operationId":"update_api_key_api_v1_api_keys__key_id__put","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"integer","title":"Key Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/APIKeyUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/APIKeyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["API Keys"],"summary":"Delete Api Key","description":"Delete an API key","operationId":"delete_api_key_api_v1_api_keys__key_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"integer","title":"Key Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/subscriptions/plans":{"get":{"tags":["Subscriptions"],"summary":"Get Available Plans","description":"Get all available subscription plans","operationId":"get_available_plans_api_v1_subscriptions_plans_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/subscriptions/current":{"get":{"tags":["Subscriptions"],"summary":"Get Current Subscription","description":"Get current user subscription with full plan details","operationId":"get_current_subscription_api_v1_subscriptions_current_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/subscriptions/usage":{"get":{"tags":["Subscriptions"],"summary":"Get Usage Stats","description":"Get current usage statistics","operationId":"get_usage_stats_api_v1_subscriptions_usage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/subscriptions/upgrade":{"post":{"tags":["Subscriptions"],"summary":"Upgrade Subscription","description":"ADMIN ONLY. Set the caller's own plan with no payment.\n\nThis is the pre-Stripe stub — its own docstring used to say \"in production\nthis would integrate with a payment gateway\" — and it was still mounted\nbehind nothing but `get_current_user`. Any signed-in customer who found it\n(the client method sat in the JS bundle at lib/api.ts) could POST\n`{\"plan_id\": \"pro\"}` and get 20,000 credits a month plus an Invoice row\nmarked PAID. Worse, the row it leaves behind — paid plan, ACTIVE, no\n`stripe_subscription_id` — is exactly the shape `check_and_reset_monthly_usage`\ntreats as a hand-granted comp account, so it would then renew every 30 days\nfor ever, with nothing in Stripe to show for it.\n\nNever used in production: it writes an Invoice with no `stripe_invoice_id`,\nand all 54 invoices in the database carry a real `in_`/`pi_` id.\n\nNOT the customer upgrade path, which is\n`POST /subscriptions/create-checkout-session` -> Stripe Checkout ->\n`checkout.session.completed` -> `handle_checkout_session_completed`. That one\ntakes the money, sets `stripe_subscription_id`, carries the remaining credits\nover and cancels the old Stripe subscription. Nothing in the frontend calls\nthis endpoint, and an API key cannot reach it at all.\n\nKept rather than deleted because granting a plan by hand is a real\noperational need; admin-only makes it a privileged, deliberate act. It still\nchanges the ADMIN'S OWN subscription, so it does not stop an admin comping\nthemselves — which is the likeliest origin of the unlinked STARTER row on\nuser 227.","operationId":"upgrade_subscription_api_v1_subscriptions_upgrade_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionUpgrade"}}},"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":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/subscriptions/invoices":{"get":{"tags":["Subscriptions"],"summary":"List Invoices","description":"Get all invoices for current user","operationId":"list_invoices_api_v1_subscriptions_invoices_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoicesListResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/subscriptions/invoices/{invoice_id}/view":{"get":{"tags":["Subscriptions"],"summary":"Get Invoice View Url","description":"Get Stripe hosted invoice URL for viewing/downloading\n\nReturns the invoice URL where user can view and download the invoice PDF","operationId":"get_invoice_view_url_api_v1_subscriptions_invoices__invoice_id__view_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"invoice_id","in":"path","required":true,"schema":{"type":"integer","title":"Invoice Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/subscriptions/request-upgrade":{"post":{"tags":["Subscriptions"],"summary":"Request Upgrade","description":"Create an upgrade request\n\nUser will be contacted via email to complete the upgrade process","operationId":"request_upgrade_api_v1_subscriptions_request_upgrade_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpgradeRequestCreate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpgradeRequestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/subscriptions/validate-coupon":{"post":{"tags":["Subscriptions"],"summary":"Validate Coupon","description":"Pre-checkout coupon check for the billing page's promo input.\n\nAlways returns 200 with a `valid` flag so the UI has one response shape.\nThe preview is advisory — checkout endpoints re-resolve the code server-side\nand Session.create remains the final authority.","operationId":"validate_coupon_api_v1_subscriptions_validate_coupon_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CouponValidateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CouponValidateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/subscriptions/create-checkout":{"post":{"tags":["Subscriptions"],"summary":"Create Checkout Session","description":"Create a Stripe Checkout Session for subscription payment\n\nReturns a checkout URL where the user can complete payment","operationId":"create_checkout_session_api_v1_subscriptions_create_checkout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutRequest"}}},"required":true},"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"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/subscriptions/addon-packs":{"get":{"tags":["Subscriptions"],"summary":"List Addon Packs","description":"List the one-time add-on credit packs available for purchase.\n\nOnly packs with a configured Stripe Price ID are returned; the list is empty\nwhen add-on purchases aren't set up.","operationId":"list_addon_packs_api_v1_subscriptions_addon_packs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddonPacksResponse"}}}}}}},"/api/v1/subscriptions/create-addon-checkout":{"post":{"tags":["Subscriptions"],"summary":"Create Addon Checkout","description":"Create a Stripe Checkout Session for a one-time add-on credit pack.\n\nAvailable to STARTER and PRO users only (FREE must upgrade first). The\npurchased credits are persistent (never expire) and spent after the monthly\nquota — they're applied by the Stripe webhook on payment completion.","operationId":"create_addon_checkout_api_v1_subscriptions_create_addon_checkout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddonCheckoutRequest"}}},"required":true},"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"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/subscriptions/cancel":{"post":{"tags":["Subscriptions"],"summary":"Cancel Subscription","description":"Cancel subscription at end of billing period\n\nSubscription will remain active until the end of the current period,\nthen automatically downgrade to Free plan","operationId":"cancel_subscription_api_v1_subscriptions_cancel_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/subscriptions/downgrade-to-free":{"post":{"tags":["Subscriptions"],"summary":"Downgrade To Free","description":"Immediately downgrade to Free plan\n\nThis will cancel the Stripe subscription immediately and\ndowngrade the user to the Free plan","operationId":"downgrade_to_free_api_v1_subscriptions_downgrade_to_free_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/subscriptions/create-portal":{"post":{"tags":["Subscriptions"],"summary":"Create Customer Portal Session","description":"Create a Stripe Customer Portal session for managing subscriptions\n\nReturns a portal URL where users can manage their subscription,\npayment methods, and view invoices","operationId":"create_customer_portal_session_api_v1_subscriptions_create_portal_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerPortalResponse"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/stats/dashboard":{"get":{"tags":["Statistics"],"summary":"Get Dashboard Stats","description":"Get dashboard statistics for current user","operationId":"get_dashboard_stats_api_v1_stats_dashboard_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/notifications/":{"get":{"tags":["Notifications"],"summary":"Get Notifications","description":"Get user notifications","operationId":"get_notifications_api_v1_notifications__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"unread_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Unread Only"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationsListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/notifications/mark-read":{"post":{"tags":["Notifications"],"summary":"Mark As Read","description":"Mark notifications as read","operationId":"mark_as_read_api_v1_notifications_mark_read_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkAsReadRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/notifications/mark-all-read":{"post":{"tags":["Notifications"],"summary":"Mark All As Read","description":"Mark all notifications as read","operationId":"mark_all_as_read_api_v1_notifications_mark_all_read_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/notifications/{notification_id}":{"delete":{"tags":["Notifications"],"summary":"Delete Notification","description":"Delete a notification","operationId":"delete_notification_api_v1_notifications__notification_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"notification_id","in":"path","required":true,"schema":{"type":"integer","title":"Notification Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/contact/":{"post":{"tags":["Contact"],"summary":"Create Contact","description":"Create a new contact message (public endpoint, no authentication required)","operationId":"create_contact_api_v1_contact__post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Contact"],"summary":"Get Contacts","description":"Get all contact messages (admin only)","operationId":"get_contacts_api_v1_contact__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"skip","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Skip"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"status_filter","in":"query","required":false,"schema":{"$ref":"#/components/schemas/ContactStatus"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ContactResponse"},"title":"Response Get Contacts Api V1 Contact  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/contact/{contact_id}":{"get":{"tags":["Contact"],"summary":"Get Contact","description":"Get a specific contact message by ID (admin only)","operationId":"get_contact_api_v1_contact__contact_id__get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"integer","title":"Contact Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Contact"],"summary":"Update Contact Status","description":"Update contact message status (admin only)","operationId":"update_contact_status_api_v1_contact__contact_id__patch","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"integer","title":"Contact Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Contact"],"summary":"Delete Contact","description":"Delete a contact message (admin only)","operationId":"delete_contact_api_v1_contact__contact_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"integer","title":"Contact Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/webhooks/stripe":{"post":{"tags":["Webhooks"],"summary":"Stripe Webhook","description":"Handle Stripe webhook events\n\nSupported events:\n- checkout.session.completed: Activate subscription after successful payment\n- invoice.payment_succeeded: Create invoice record when payment succeeds\n- customer.subscription.deleted: Cancel subscription when deleted\n- customer.subscription.updated: Update subscription status","operationId":"stripe_webhook_api_v1_webhooks_stripe_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/admin/dashboard":{"get":{"tags":["Admin"],"summary":"Admin Dashboard","description":"Global growth KPIs (value + delta vs the previous equal-length window) and\na per-day time series. When both `start` and `end` are supplied (ISO date or\ndatetime; a date-only `end` is inclusive of that whole day) they define a\ncustom range; otherwise the last `days` days are used.","operationId":"admin_dashboard_api_v1_admin_dashboard_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"default":14,"title":"Days"}},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/daily-detail":{"get":{"tags":["Admin"],"summary":"Admin Daily Detail","description":"Who is behind one day's number in the dashboard's daily series.\n\n``kind`` selects the list:\n  - ``signups``   — users who registered that day\n  - ``new_paid``  — users whose FIRST-ever payment landed that day\n  - ``payments``  — every paid invoice that day (renewals + add-ons), i.e.\n                    the breakdown of that day's revenue\n\nThe day is intersected with the dashboard's window so the returned ``total``\nalways equals the number plotted on the chart — a preset range starts\nmid-day, so its first bucket is a partial day (``window.clamped``).\n\nCallers should echo ``/dashboard``'s reported ``window`` back as\n``start``/``end`` rather than re-sending ``days``: a preset window is\nanchored to ``utcnow()``, so re-resolving it later (a tab left open) shifts\nthe boundary buckets away from what the chart actually showed.","operationId":"admin_daily_detail_api_v1_admin_daily_detail_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"date","in":"query","required":true,"schema":{"type":"string","description":"Day to drill into (YYYY-MM-DD)","title":"Date"},"description":"Day to drill into (YYYY-MM-DD)"},{"name":"kind","in":"query","required":false,"schema":{"type":"string","description":"signups | new_paid | payments","default":"signups","title":"Kind"},"description":"signups | new_paid | payments"},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"default":14,"title":"Days"}},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":25,"title":"Per Page"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users":{"get":{"tags":["Admin"],"summary":"Admin Users","description":"Paginated global user list with plan, status, verify volume, revenue,\npayment dates (first = conversion, last = most recent) and signup date —\nfilterable (email search, plan, status) and sortable.","operationId":"admin_users_api_v1_admin_users_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"plan","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"}},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device"}},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"created_at","title":"Sort"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","default":"desc","title":"Order"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":25,"title":"Per Page"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/export":{"get":{"tags":["Admin"],"summary":"Admin Users Export","description":"Export the admin user list as CSV or XLSX.\n\nApplies the SAME filters and sort as ``GET /admin/users`` (it shares that\nendpoint's query, sort and row builders) but is deliberately NOT paginated:\nit returns every row matching the current filters, or every user when no\nfilter is set.","operationId":"admin_users_export_api_v1_admin_users_export_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"plan","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"}},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device"}},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"created_at","title":"Sort"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","default":"desc","title":"Order"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","pattern":"^(csv|xlsx)$","default":"csv","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/{user_id}":{"delete":{"tags":["Admin"],"summary":"Admin Delete User","description":"Admin-only: permanently delete a user and all their data (same purge as\nthe self-service flow — Stripe cleanup, data removal, retained invoices,\nconfirmation email). Used to fulfill deletion requests centrally.","operationId":"admin_delete_user_api_v1_admin_users__user_id__delete","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/jobs":{"get":{"tags":["Admin"],"summary":"Admin Jobs","description":"Paginated global verification-job list across all users, with owner email\nand per-status counts. Filters: ``search`` (owner email OR file name),\n``email`` (jobs whose results contain this address), ``type``, ``status``,\nand a ``start``/``end`` created-date range. Sortable + paginated like\nadmin_users.\n\n``filters`` carries the advanced conditions — a JSON array of\n``{field, op, value}`` joined by ``match`` (all|any) — which is where the\nnegative questions live (\"owner email NOT containing x\", \"job that does NOT\ninclude this address\"). See app/api/job_filters.py.\n\n``GET /admin/jobs/owners`` rolls this same filtered set up per owner, and\n``user_id`` is how the caller then drills back into one of those groups. It\nis a separate param rather than an advanced condition on purpose: a\ncondition would be joined by ``match``, so with ``match=any`` it would WIDEN\nthe result to \"these filters OR this owner\" instead of narrowing to one\nowner's jobs.","operationId":"admin_jobs_api_v1_admin_jobs_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"email","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"User Id"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End"}},{"name":"filters","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filters"}},{"name":"match","in":"query","required":false,"schema":{"type":"string","default":"all","title":"Match"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"created_at","title":"Sort"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","default":"desc","title":"Order"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":25,"title":"Per Page"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/jobs/owners":{"get":{"tags":["Admin"],"summary":"Admin Jobs By Owner","description":"The job list rolled up per owner: one row per user, with true totals over\nevery job that matches the filters.\n\nThis exists because grouping the flat list in the browser would only ever\ngroup the 25 jobs on the current page, so a heavy customer's totals would\nchange as the admin paged around — the numbers would look authoritative and\nbe wrong. Aggregating in SQL over the whole filtered set is the only version\nworth showing.\n\nTakes exactly the same filter params as ``GET /admin/jobs`` (shared through\n``_narrow_jobs``), so a grouped view and a flat view of the same filters\nalways describe the same set of jobs. ``total`` is the number of distinct\nowners, which is what the caller paginates over.","operationId":"admin_jobs_by_owner_api_v1_admin_jobs_owners_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"email","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End"}},{"name":"filters","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filters"}},{"name":"match","in":"query","required":false,"schema":{"type":"string","default":"all","title":"Match"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"last_created_at","title":"Sort"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","default":"desc","title":"Order"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":25,"title":"Per Page"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/jobs/{job_id}/results":{"get":{"tags":["Admin"],"summary":"Admin Job Results","description":"A job's per-email results (rich fields), paginated + searchable by email +\nfilterable by three-state status. Admin-scoped: no ownership filter and no\nCOMPLETED-only gate (partial/failed jobs are inspectable).","operationId":"admin_job_results_api_v1_admin_jobs__job_id__results_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"integer","title":"Job Id"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Per Page"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/jobs/{job_id}/download":{"get":{"tags":["Admin"],"summary":"Admin Job Download","description":"Download a job's results as CSV (admin-scoped — any job). Reuses the same\nDB-row CSV builder as the user download endpoint (partial-safe).","operationId":"admin_job_download_api_v1_admin_jobs__job_id__download_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"integer","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/v1/admin/jobs/{job_id}/recheck":{"post":{"tags":["Admin"],"summary":"Admin Job Recheck","description":"Re-run ONLY this job's Unknown (unchecked) emails. Admin-scoped, but the\nrecheck bills the JOB OWNER's subscription (matching the user flow's\neconomics — each email costs 1 credit only when its recheck completes).","operationId":"admin_job_recheck_api_v1_admin_jobs__job_id__recheck_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"integer","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/v1/admin/coupons":{"get":{"tags":["Admin"],"summary":"Admin List Coupons","description":"BounceCheck promotion codes (active and inactive) with usage: Stripe's\nown times_redeemed plus OUR granted-credit totals from coupon_redemptions.\nThe two legitimately differ (discount-only codes never grant; grants\npredate nothing but the table itself) — the UI labels them separately.","operationId":"admin_list_coupons_api_v1_admin_coupons_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]},"post":{"tags":["Admin"],"summary":"Admin Create Coupon","description":"Create a coupon + promotion code with the conventions the redemption\nflow expects (app marker, bonus metadata, credits-only nominal percent,\nUSD-only amount discounts). See stripe_service.create_bonus_promotion.","operationId":"admin_create_coupon_api_v1_admin_coupons_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCouponCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/admin/coupons/{promo_id}/active":{"post":{"tags":["Admin"],"summary":"Admin Set Coupon Active","description":"(De)activate one of OUR promotion codes. Foreign/unknown promo ids 404 —\nthe Stripe account is shared, and flipping the other store's live promo\nmust be impossible from here. Reactivation can fail at Stripe (expired or\nmaxed-out code, dead coupon) — that surfaces as a 400.","operationId":"admin_set_coupon_active_api_v1_admin_coupons__promo_id__active_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"promo_id","in":"path","required":true,"schema":{"type":"string","title":"Promo Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCouponActiveRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/coupons/{promo_id}/activity":{"get":{"tags":["Admin"],"summary":"Admin Coupon Activity","description":"Who used one promotion code, and who typed it and was refused.\n\nRedemptions are keyed by promotion_code_id (the object that granted); refused\nattempts by the code STRING, because a refused code may match no promotion\ncode at all and a recreated code has several ids (task 14402).","operationId":"admin_coupon_activity_api_v1_admin_coupons__promo_id__activity_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"promo_id","in":"path","required":true,"schema":{"type":"string","title":"Promo Id"}},{"name":"code","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Code"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/coupon-attempts":{"get":{"tags":["Admin"],"summary":"Admin Coupon Attempts","description":"Recent promo-code entries, newest first; default is the refused ones.","operationId":"admin_coupon_attempts_api_v1_admin_coupon_attempts_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"email","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Email"}},{"name":"outcome","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(rejected|valid|all)$"},{"type":"null"}],"default":"rejected","title":"Outcome"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/{user_id}/credit-grants":{"post":{"tags":["Admin"],"summary":"Admin Grant Credits","description":"Add add-on credits to a user by hand, leaving a ledger row.\n\nThe balance moves with a single relative UPDATE (same pattern as the coupon\ngrant in webhooks.py) so it cannot lose a concurrent webhook's credit; the\nledger row is written in the SAME transaction, so there is never a balance\nchange without its who/why, or the reverse.","operationId":"admin_grant_credits_api_v1_admin_users__user_id__credit_grants_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCreditGrantRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Admin"],"summary":"Admin List Credit Grants","operationId":"admin_list_credit_grants_api_v1_admin_users__user_id__credit_grants_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/proxies":{"get":{"tags":["Admin"],"summary":"Admin Proxies","description":"Paginated proxy inventory with each endpoint's per-adapter quota state.\n\nVerdict counts are an aggregate over proxy_adapter_quota, so sorting and\nfiltering on them needs the join rather than a column — that is what the\n`agg` subquery below is for. Passing `adapter` scopes both the counts and\nthe sort to that one provider, which is how you answer \"which IPs is\nemailverify actually getting verdicts from\".\n\nDefault sort stays `last_used_at` ascending: the order the rotation itself\nclaims in, so the top of page 1 is what gets used next.","operationId":"admin_proxies_api_v1_admin_proxies_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"proxy_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proxy Type"}},{"name":"enabled","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"}},{"name":"in_pool","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"In Pool"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"}},{"name":"adapter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Scope verdict counts to one adapter","title":"Adapter"},"description":"Scope verdict counts to one adapter"},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"exhausted | failing | idle | no_verdicts","title":"State"},"description":"exhausted | failing | idle | no_verdicts"},{"name":"min_verdicts","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"title":"Min Verdicts"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"last_used_at","title":"Sort"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","default":"asc","title":"Order"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Per Page"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/proxies/summary":{"get":{"tags":["Admin"],"summary":"Admin Proxies Summary","description":"Totals for the tab header: how much capacity actually exists right now.","operationId":"admin_proxies_summary_api_v1_admin_proxies_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/admin/proxies/{endpoint_id}/enabled":{"post":{"tags":["Admin"],"summary":"Admin Set Proxy Enabled","description":"Turn one endpoint on or off. Survives every resync — see the schema.","operationId":"admin_set_proxy_enabled_api_v1_admin_proxies__endpoint_id__enabled_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"integer","title":"Endpoint Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminProxyEnabledRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/proxies/resync":{"post":{"tags":["Admin"],"summary":"Admin Resync Proxies","description":"Sweep the provider now instead of waiting for the daily beat.\n\n`sync-proxy-pool` runs at 02:00 UTC, once a day (workers/celery_app.py).\nThis docstring used to say \"the 15-minute beat\", which is the cadence of\n`idle-sweep-unknown-results` — a different task entirely. The difference\nmatters when the pool is in trouble: at one sweep a day, a provider that\nrestores capacity at 03:00 is not picked up until the next morning, and\nthis button is how you avoid waiting.\n\nRun inline rather than dispatched: the caller wants the counts back, and a\nfull sweep was ~12s for both types when it was 4,000 v6 + 962 v4 (measured\n2026-08-27; v6 is empty at the provider as of 2026-09-16, so it is faster\nnow and will be slower again when that comes back).","operationId":"admin_resync_proxies_api_v1_admin_proxies_resync_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/admin/adapters":{"get":{"tags":["Admin"],"summary":"Admin Adapters","description":"Every registered adapter with its policy and what it has actually done.\n\nThe counters come from proxy_adapter_quota — the same rows the Proxies tab\nsorts on, so the two tabs cannot disagree about a provider's numbers.","operationId":"admin_adapters_api_v1_admin_adapters_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/admin/primary-verifier":{"get":{"tags":["Admin"],"summary":"Admin Primary Verifier","description":"Whether bounce.sidcorp.co verifies, or every check goes to the adapters.","operationId":"admin_primary_verifier_api_v1_admin_primary_verifier_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/admin/primary-verifier/enabled":{"post":{"tags":["Admin"],"summary":"Admin Set Primary Verifier Enabled","description":"Switch bounce.sidcorp.co off (proxy-only) or back on.\n\nSwitching it off is refused unless the proxy path can actually answer:\notherwise every single check becomes a 503 and every bulk row an Unknown,\nwhich is the outage this switch exists to route around.","operationId":"admin_set_primary_verifier_enabled_api_v1_admin_primary_verifier_enabled_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAdapterEnabledRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/admin/primary-verifier/routing":{"post":{"tags":["Admin"],"summary":"Admin Set Primary Verifier Routing","description":"Scope bounce.sidcorp.co to / away from mail hosts. An address it is not\nused for goes straight to the adapters, like proxy-only mode but per host.\nTakes effect on every worker within ~15s.","operationId":"admin_set_primary_verifier_routing_api_v1_admin_primary_verifier_routing_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPrimaryRoutingRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/admin/blocking-mx":{"get":{"tags":["Admin"],"summary":"Admin Blocking Mx","description":"MX suffixes whose Hard Bounces from bounce.sidcorp.co are re-checked over\nthe proxy pool, and whether that list is the measured default.","operationId":"admin_blocking_mx_api_v1_admin_blocking_mx_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"OAuth2PasswordBearer":[]}]},"post":{"tags":["Admin"],"summary":"Admin Set Blocking Mx","description":"Replace the blocking-MX list (null = back to the measured default).\nTakes effect on every worker within ~15s; no redeploy.","operationId":"admin_set_blocking_mx_api_v1_admin_blocking_mx_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminBlockingMxRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/admin/blocking-mx/probe":{"post":{"tags":["Admin"],"summary":"Admin Probe Blocking Mx","description":"Can bounce.sidcorp.co tell a real address from an invented one here?\n\nThis is the measurement that put Mimecast on the list: if the invented\naddress gets the same 550 as an address an adapter calls deliverable, the\ndomain's MX is refusing us, not the recipient. Calls bounce.sidcorp.co\ndirectly — never the proxy pool — because its answer is what is in question.","operationId":"admin_probe_blocking_mx_api_v1_admin_blocking_mx_probe_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminBlockingMxProbeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"OAuth2PasswordBearer":[]}]}},"/api/v1/admin/adapters/health":{"get":{"tags":["Admin"],"summary":"Admin Adapter Health","description":"Whether each adapter can still be trusted, per mail host.\n\nEvery routing rule we ship was found by hand, weeks after the provider\nstarted being wrong — opportify answering \"yes\" for every Yahoo address,\nbouncezero calling 14 known-live ones a bounce, tools530 returning invalid\nfor all of iCloud. They all look the same in the data: the answers for one\n(adapter, mail host) collapse onto a single outcome. This is that check,\nrun against rows we already store.","operationId":"admin_adapter_health_api_v1_admin_adapters_health_get","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"default":7,"title":"Days"}},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","description":"Recompute now instead of serving the daily snapshot (slow)","default":false,"title":"Refresh"},"description":"Recompute now instead of serving the daily snapshot (slow)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/adapters/{adapter_key}/routing":{"post":{"tags":["Admin"],"summary":"Admin Set Adapter Routing","description":"Override which mail hosts one adapter may answer for.\n\nThe adapter spec carries what was MEASURED; this carries what an operator\ndecided today. Sending a field as null clears the override and the\nmeasurement comes back — which is why the request has PATCH semantics\nrather than replacing the whole row.","operationId":"admin_set_adapter_routing_api_v1_admin_adapters__adapter_key__routing_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"adapter_key","in":"path","required":true,"schema":{"type":"string","title":"Adapter Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAdapterRoutingRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/adapters/{adapter_key}/enabled":{"post":{"tags":["Admin"],"summary":"Admin Set Adapter Enabled","description":"Turn one adapter on or off. Takes effect on the next re-check.","operationId":"admin_set_adapter_enabled_api_v1_admin_adapters__adapter_key__enabled_post","security":[{"OAuth2PasswordBearer":[]}],"parameters":[{"name":"adapter_key","in":"path","required":true,"schema":{"type":"string","title":"Adapter Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAdapterEnabledRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tools/dns-health-check/public":{"post":{"tags":["Tools"],"summary":"Dns Health Check Public","description":"Public DNS health check for the /tools/dns-health-checker landing page.\n\n- No authentication required\n- 10 requests per day per IP (PublicVerificationRateLimit table, \"dns:\" prefix)\n- Optional reCAPTCHA v3 verification when configured","operationId":"dns_health_check_public_api_v1_tools_dns_health_check_public_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicDnsHealthRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DnsHealthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/":{"get":{"summary":"Root","description":"API root endpoint","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/health":{"get":{"summary":"Health Check","description":"Health check endpoint for monitoring","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"APIKeyCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"}},"type":"object","required":["name"],"title":"APIKeyCreate","description":"Schema for creating an API key"},"APIKeyResponse":{"properties":{"id":{"type":"integer","title":"Id"},"user_id":{"type":"integer","title":"User Id"},"name":{"type":"string","title":"Name"},"key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"is_active":{"type":"integer","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","user_id","name","is_active","created_at"],"title":"APIKeyResponse","description":"Schema for API key response"},"APIKeyUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"is_active":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"APIKeyUpdate","description":"Schema for updating an API key"},"AccountDeleteRequest":{"properties":{"confirm_email":{"type":"string","format":"email","title":"Confirm Email"}},"type":"object","required":["confirm_email"],"title":"AccountDeleteRequest","description":"Schema for self-service account deletion — the user must retype their\naccount email to confirm this irreversible action."},"AddonCheckoutRequest":{"properties":{"pack_key":{"type":"string","title":"Pack Key","description":"Add-on pack key, e.g. 'addon_m'"},"coupon_code":{"anyOf":[{"type":"string","maxLength":200,"pattern":"^[A-Za-z0-9\\-_]+$"},{"type":"null"}],"title":"Coupon Code","description":"Optional Stripe promotion code (may discount and/or grant bonus credits)"}},"type":"object","required":["pack_key"],"title":"AddonCheckoutRequest","description":"Schema for creating a one-time add-on credit checkout session"},"AddonPackDetails":{"properties":{"key":{"type":"string","title":"Key"},"name":{"type":"string","title":"Name"},"credits":{"type":"integer","title":"Credits"},"price":{"type":"number","title":"Price"},"best_value":{"type":"boolean","title":"Best Value","default":false}},"type":"object","required":["key","name","credits","price"],"title":"AddonPackDetails","description":"Schema for a single add-on credit pack"},"AddonPacksResponse":{"properties":{"packs":{"items":{"$ref":"#/components/schemas/AddonPackDetails"},"type":"array","title":"Packs"}},"type":"object","required":["packs"],"title":"AddonPacksResponse","description":"Schema for the list of available add-on credit packs"},"AdminAdapterEnabledRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"notes":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Notes"}},"type":"object","required":["enabled"],"title":"AdminAdapterEnabledRequest","description":"Turn one fallback verification adapter on or off.\n\nWhich adapters exist is code; which are used is this. `notes` is the reason\n— worth capturing, because \"why is mailwarm off\" is the question someone\nasks six weeks later."},"AdminAdapterRoutingRequest":{"properties":{"mx_only":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Mx Only"},"mx_never":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Mx Never"},"mx_owner":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Mx Owner"},"priority":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":-100.0},{"type":"null"}],"title":"Priority"},"notes":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Notes"}},"type":"object","title":"AdminAdapterRoutingRequest","description":"Which mail hosts one adapter may answer for, and when it gets its turn.\n\nPATCH semantics: only fields actually PRESENT in the body are touched, and\nan explicit ``null`` CLEARS the override so the adapter spec's measured\ndefault comes back. \"No rule\" and \"back to what we measured\" are different\nintentions and the API has to be able to express both — otherwise a deny\nthat exists because of a measurement could be dropped but never restored.\nRead with ``model_fields_set``, never with ``is None``.\n\nThe suffix fields are CSV of MX hostname suffixes, matched against the END\nof the address's MX host. Keyed on MX rather than on a domain list because a\nhand-written list of 20 Yahoo domains missed 8 real variants in a 9,718-row\nprod sample, while one suffix (.yahoodns.net) covered 99.86% of it."},"AdminBlockingMxProbeRequest":{"properties":{"domain":{"type":"string","maxLength":253,"minLength":3,"title":"Domain"},"email":{"anyOf":[{"type":"string","maxLength":320},{"type":"null"}],"title":"Email"}},"type":"object","required":["domain"],"title":"AdminBlockingMxProbeRequest","description":"Ask bounce.sidcorp.co about a domain: an invented address, and\noptionally a real one, so an admin can see whether it can tell them apart\nBEFORE adding the domain's MX to the blocking list."},"AdminBlockingMxRequest":{"properties":{"suffixes":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Suffixes"},"notes":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Notes"}},"type":"object","title":"AdminBlockingMxRequest","description":"The MX suffixes whose Hard Bounces get re-checked over the proxy pool.\n\nCSV, replacing the list rather than adding to it. ``null`` clears the\nadmin's list so the env / measured default comes back; ``\"\"`` is an\nexplicit \"re-check nothing\" — two different intentions, as with adapter\nrouting."},"AdminCouponActiveRequest":{"properties":{"active":{"type":"boolean","title":"Active"}},"type":"object","required":["active"],"title":"AdminCouponActiveRequest","description":"Body for POST /admin/coupons/{promo_id}/active."},"AdminCouponCreateRequest":{"properties":{"code":{"type":"string","maxLength":200,"minLength":3,"pattern":"^[A-Za-z0-9]+$","title":"Code"},"bonus_credits":{"type":"integer","maximum":1000000.0,"minimum":0.0,"title":"Bonus Credits","default":0},"discount_type":{"type":"string","enum":["none","percent","amount"],"title":"Discount Type","default":"none"},"percent_off":{"anyOf":[{"type":"number","maximum":100.0,"exclusiveMinimum":0.0},{"type":"null"}],"title":"Percent Off"},"amount_off_usd_cents":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Amount Off Usd Cents"},"duration":{"type":"string","enum":["once","repeating","forever"],"title":"Duration","default":"once"},"duration_in_months":{"anyOf":[{"type":"integer","maximum":36.0,"minimum":1.0},{"type":"null"}],"title":"Duration In Months"},"max_redemptions":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Max Redemptions"},"expires_at":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expires At"},"first_time_transaction":{"type":"boolean","title":"First Time Transaction","default":false},"minimum_amount_usd_cents":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Minimum Amount Usd Cents"}},"type":"object","required":["code"],"title":"AdminCouponCreateRequest","description":"Create a Stripe coupon + promotion code from the admin Settings tab.\n\nThe credits-only path is the 2-field default (code + bonus_credits with\ndiscount_type=\"none\"); the endpoint then uses a nominal percent_off=0.01,\nwhich Stripe requires and checkout rounds to $0.00."},"AdminCreditGrantRequest":{"properties":{"credits":{"type":"integer","maximum":1000000.0,"minimum":1.0,"title":"Credits"},"reason":{"type":"string","maxLength":500,"minLength":3,"title":"Reason"},"reference":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Reference"}},"type":"object","required":["credits","reason"],"title":"AdminCreditGrantRequest","description":"Body for POST /admin/users/{user_id}/credit-grants — add-on credits only,\nnever negative (a correction downwards is a database job, deliberately not\none click)."},"AdminPrimaryRoutingRequest":{"properties":{"mx_only":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Mx Only"},"mx_never":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Mx Never"},"notes":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Notes"}},"type":"object","title":"AdminPrimaryRoutingRequest","description":"Which mail hosts bounce.sidcorp.co is used for. PATCH semantics like\nadapter routing: only fields present in the body change; null or \"\" clears\none. CSV of MX hostname suffixes (\".mimecast.com\")."},"AdminProxyEnabledRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled"}},"type":"object","required":["enabled"],"title":"AdminProxyEnabledRequest","description":"Turn one proxy endpoint on or off for the blocked-recheck rotation.\n\nThis is policy, kept apart from health: the sync task and the strike counter\nboth write `in_pool`, and neither may overwrite this flag. An endpoint an\nadmin switched off stays off through any number of resyncs."},"Body_login_form_api_v1_auth_login_form_post":{"properties":{"grant_type":{"anyOf":[{"type":"string","pattern":"^password$"},{"type":"null"}],"title":"Grant Type"},"username":{"type":"string","title":"Username"},"password":{"type":"string","format":"password","title":"Password"},"scope":{"type":"string","title":"Scope","default":""},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"format":"password","title":"Client Secret"}},"type":"object","required":["username","password"],"title":"Body_login_form_api_v1_auth_login_form_post"},"Body_upload_bulk_verification_api_v1_verification_bulk_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_bulk_verification_api_v1_verification_bulk_post"},"BulkListVerifyRequest":{"properties":{"emails":{"items":{"type":"string"},"type":"array","title":"Emails"}},"type":"object","required":["emails"],"title":"BulkListVerifyRequest","description":"Schema for a bulk job submitted as a typed/pasted list of addresses.\n\n`List[str]`, deliberately not `List[EmailStr]`: EmailStr would 422 the WHOLE\nrequest because one line was malformed, and the contract here is that bad\nlines are skipped and counted, not that the customer loses the other 99.\nEach line is classified individually in `split_pasted_emails`."},"ChangePassword":{"properties":{"current_password":{"type":"string","title":"Current Password"},"new_password":{"type":"string","maxLength":100,"minLength":8,"title":"New Password"}},"type":"object","required":["current_password","new_password"],"title":"ChangePassword","description":"Schema for changing password"},"CheckResult":{"properties":{"status":{"type":"string","title":"Status"},"value":{"anyOf":[{},{"type":"null"}],"title":"Value"},"message":{"type":"string","title":"Message"},"fix":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fix"}},"type":"object","required":["status","message"],"title":"CheckResult","description":"Result of a single DNS check."},"CheckoutRequest":{"properties":{"plan_id":{"type":"string","title":"Plan Id","description":"Plan ID to subscribe to (starter, pro)"},"coupon_code":{"anyOf":[{"type":"string","maxLength":200,"pattern":"^[A-Za-z0-9\\-_]+$"},{"type":"null"}],"title":"Coupon Code","description":"Optional Stripe promotion code (may discount and/or grant bonus credits)"}},"type":"object","required":["plan_id"],"title":"CheckoutRequest","description":"Schema for creating Stripe checkout session"},"CheckoutResponse":{"properties":{"checkout_url":{"type":"string","title":"Checkout Url","description":"Stripe checkout URL"},"checkout_session_id":{"type":"string","title":"Checkout Session Id","description":"Stripe checkout session ID"}},"type":"object","required":["checkout_url","checkout_session_id"],"title":"CheckoutResponse","description":"Schema for checkout session response"},"ContactCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"email":{"type":"string","format":"email","title":"Email"},"subject":{"type":"string","maxLength":255,"minLength":1,"title":"Subject"},"message":{"type":"string","maxLength":5000,"minLength":10,"title":"Message"}},"type":"object","required":["name","email","subject","message"],"title":"ContactCreate","description":"Schema for creating a contact message"},"ContactResponse":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"email":{"type":"string","format":"email","title":"Email"},"subject":{"type":"string","maxLength":255,"minLength":1,"title":"Subject"},"message":{"type":"string","maxLength":5000,"minLength":10,"title":"Message"},"id":{"type":"integer","title":"Id"},"status":{"$ref":"#/components/schemas/ContactStatus"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["name","email","subject","message","id","status","created_at","updated_at"],"title":"ContactResponse","description":"Schema for contact message response"},"ContactStatus":{"type":"string","enum":["new","read","in_progress","resolved","closed"],"title":"ContactStatus","description":"Contact message status enumeration"},"ContactUpdate":{"properties":{"status":{"anyOf":[{"$ref":"#/components/schemas/ContactStatus"},{"type":"null"}]}},"type":"object","title":"ContactUpdate","description":"Schema for updating contact message status"},"ContextCapture":{"properties":{"utm_source":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Utm Source"},"landing_page":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Landing Page"},"last_page":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Page"},"referrer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Referrer"}},"type":"object","title":"ContextCapture","description":"Optional body for the browser-side context ping (see /auth/capture-context)."},"CouponValidateRequest":{"properties":{"code":{"type":"string","maxLength":200,"minLength":1,"title":"Code"}},"type":"object","required":["code"],"title":"CouponValidateRequest","description":"Schema for pre-checkout coupon validation"},"CouponValidateResponse":{"properties":{"valid":{"type":"boolean","title":"Valid"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"},"promotion_code_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Promotion Code Id"},"coupon_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coupon Id"},"bonus_credits":{"type":"integer","title":"Bonus Credits","default":0},"percent_off":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Percent Off"},"amount_off":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Amount Off"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"duration":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Duration"},"minimum_amount":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Minimum Amount"}},"type":"object","required":["valid"],"title":"CouponValidateResponse","description":"Always HTTP 200; `valid` carries the verdict so the UI has one shape.\n\n`bonus_credits` is the amount the coupon's metadata grants on top of any\nnative Stripe discount (percent_off/amount_off may both be present too —\nhybrid coupons are supported)."},"CustomerPortalResponse":{"properties":{"portal_url":{"type":"string","title":"Portal Url","description":"Stripe customer portal URL"}},"type":"object","required":["portal_url"],"title":"CustomerPortalResponse","description":"Schema for customer portal session response"},"DisposableCheckRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"DisposableCheckRequest","description":"Schema for authenticated disposable-check request"},"DisposableCheckResponse":{"properties":{"email":{"type":"string","title":"Email"},"normalized_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Normalized Email"},"domain":{"type":"string","title":"Domain"},"is_disposable":{"type":"boolean","title":"Is Disposable"},"matched_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Matched Domain"},"matched_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Matched By"},"provider":{"anyOf":[{"$ref":"#/components/schemas/ProviderRef"},{"type":"null"}]},"mx":{"type":"boolean","title":"Mx","default":false},"mx_records":{"items":{"$ref":"#/components/schemas/MxRecord"},"type":"array","title":"Mx Records","default":[]},"mx_providers":{"items":{"$ref":"#/components/schemas/MxProvider"},"type":"array","title":"Mx Providers","default":[]},"a_records":{"items":{"type":"string"},"type":"array","title":"A Records","default":[]},"nameservers":{"items":{"type":"string"},"type":"array","title":"Nameservers","default":[]},"spf":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Spf"},"dmarc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dmarc"},"blocklisted":{"type":"boolean","title":"Blocklisted","default":false},"public_domain":{"type":"boolean","title":"Public Domain","default":false},"role_account":{"type":"boolean","title":"Role Account","default":false},"free_subdomain":{"type":"boolean","title":"Free Subdomain","default":false},"relay_domain":{"type":"boolean","title":"Relay Domain","default":false},"alias":{"type":"boolean","title":"Alias","default":false},"tld_trust":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tld Trust"},"registrar":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Registrar"},"registered_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Registered At"},"domain_age_in_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Domain Age In Days"},"did_you_mean":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Did You Mean"},"domain_info":{"anyOf":[{"$ref":"#/components/schemas/DomainInfo"},{"type":"null"}]},"checked_at":{"type":"string","format":"date-time","title":"Checked At"}},"type":"object","required":["email","domain","is_disposable","checked_at"],"title":"DisposableCheckResponse","description":"Full email-intelligence response (UserCheck-style).\n\nBackward-compatible: the original disposable fields are still present;\neverything else is additive and optional."},"DnsHealthResponse":{"properties":{"domain":{"type":"string","title":"Domain"},"score":{"type":"integer","title":"Score"},"grade":{"type":"string","title":"Grade"},"checks":{"additionalProperties":{"$ref":"#/components/schemas/CheckResult"},"type":"object","title":"Checks"},"issues":{"items":{"$ref":"#/components/schemas/DnsIssue"},"type":"array","title":"Issues","default":[]},"checked_at":{"type":"string","format":"date-time","title":"Checked At"}},"type":"object","required":["domain","score","grade","checks","checked_at"],"title":"DnsHealthResponse","description":"Full DNS health report."},"DnsIssue":{"properties":{"check":{"type":"string","title":"Check"},"severity":{"type":"string","title":"Severity"},"fix":{"type":"string","title":"Fix"}},"type":"object","required":["check","severity","fix"],"title":"DnsIssue","description":"An actionable issue surfaced from a failing/warning check."},"DomainIndexItem":{"properties":{"domain":{"type":"string","title":"Domain"},"last_seen":{"type":"string","format":"date-time","title":"Last Seen"}},"type":"object","required":["domain","last_seen"],"title":"DomainIndexItem"},"DomainIndexResponse":{"properties":{"domains":{"items":{"$ref":"#/components/schemas/DomainIndexItem"},"type":"array","title":"Domains","default":[]},"total":{"type":"integer","title":"Total"}},"type":"object","required":["total"],"title":"DomainIndexResponse","description":"Paginated list of every confirmed-disposable domain. Backs the\n/tools/domain sitemap — NOT a user-facing page."},"DomainInfo":{"properties":{"a_records":{"items":{"type":"string"},"type":"array","title":"A Records","default":[]},"mx_records":{"items":{"type":"string"},"type":"array","title":"Mx Records","default":[]},"nameservers":{"items":{"type":"string"},"type":"array","title":"Nameservers","default":[]},"registrar":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Registrar"},"registered_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Registered At"},"has_a":{"type":"boolean","title":"Has A","default":false}},"type":"object","title":"DomainInfo","description":"Live DNS/registration signals for a domain (UserCheck-style detail)."},"DomainLookupResponse":{"properties":{"domain":{"type":"string","title":"Domain"},"is_disposable":{"type":"boolean","title":"Is Disposable"},"matched_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Matched By"},"provider":{"anyOf":[{"$ref":"#/components/schemas/ProviderRef"},{"type":"null"}]},"mx":{"type":"boolean","title":"Mx","default":false},"mx_records":{"items":{"$ref":"#/components/schemas/MxRecord"},"type":"array","title":"Mx Records","default":[]},"mx_providers":{"items":{"$ref":"#/components/schemas/MxProvider"},"type":"array","title":"Mx Providers","default":[]},"a_records":{"items":{"type":"string"},"type":"array","title":"A Records","default":[]},"nameservers":{"items":{"type":"string"},"type":"array","title":"Nameservers","default":[]},"spf":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Spf"},"spf_checked":{"type":"boolean","title":"Spf Checked","default":true},"dmarc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dmarc"},"dmarc_checked":{"type":"boolean","title":"Dmarc Checked","default":true},"blocklisted":{"type":"boolean","title":"Blocklisted","default":false},"tld_trust":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tld Trust"},"registrar":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Registrar"},"registered_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Registered At"},"registration_checked":{"type":"boolean","title":"Registration Checked","default":true},"domain_age_in_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Domain Age In Days"},"provider_domain_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Provider Domain Count"},"related_domains":{"items":{"$ref":"#/components/schemas/ProviderDomainItem"},"type":"array","title":"Related Domains","default":[]},"checked_at":{"type":"string","format":"date-time","title":"Checked At"}},"type":"object","required":["domain","is_disposable","checked_at"],"title":"DomainLookupResponse","description":"Domain-level lookup (UserCheck-style domain page). Backs /tools/domain/[domain]."},"EmailVerifyRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"EmailVerifyRequest","description":"Schema for single email verification request"},"EmailVerifyResponse":{"properties":{"email":{"type":"string","title":"Email"},"is_valid":{"type":"boolean","title":"Is Valid"},"is_disposable":{"type":"boolean","title":"Is Disposable","default":false},"status":{"type":"string","title":"Status"},"message":{"type":"string","title":"Message"},"reason_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason Code"},"provider_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Reason"}},"type":"object","required":["email","is_valid","status","message"],"title":"EmailVerifyResponse","description":"Schema for single email verification response"},"ForgotPassword":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"ForgotPassword","description":"Schema for requesting password reset"},"GoogleLogin":{"properties":{"id_token":{"type":"string","title":"Id Token","description":"Google ID token from OAuth flow"}},"type":"object","required":["id_token"],"title":"GoogleLogin","description":"Schema for Google OAuth login"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"InvoiceResponse":{"properties":{"id":{"type":"integer","title":"Id"},"user_id":{"type":"integer","title":"User Id"},"invoice_number":{"type":"string","title":"Invoice Number"},"amount":{"type":"number","title":"Amount"},"status":{"$ref":"#/components/schemas/InvoiceStatus"},"plan_name":{"type":"string","title":"Plan Name"},"billing_date":{"type":"string","format":"date-time","title":"Billing Date"},"paid_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paid At"},"download_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Download Url"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","user_id","invoice_number","amount","status","plan_name","billing_date","created_at"],"title":"InvoiceResponse","description":"Schema for invoice response"},"InvoiceStatus":{"type":"string","enum":["paid","pending","failed","refunded"],"title":"InvoiceStatus","description":"Invoice status enumeration"},"InvoicesListResponse":{"properties":{"invoices":{"items":{"$ref":"#/components/schemas/InvoiceResponse"},"type":"array","title":"Invoices"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["invoices","total"],"title":"InvoicesListResponse","description":"Schema for invoices list response"},"JobStatus":{"type":"string","enum":["pending","processing","completed","failed"],"title":"JobStatus","description":"Verification job status enumeration"},"JobType":{"type":"string","enum":["single","bulk","disposable"],"title":"JobType","description":"Verification job type enumeration"},"MarkAsReadRequest":{"properties":{"notification_ids":{"items":{"type":"integer"},"type":"array","title":"Notification Ids"}},"type":"object","required":["notification_ids"],"title":"MarkAsReadRequest","description":"Schema for marking notifications as read"},"MxProvider":{"properties":{"slug":{"type":"string","title":"Slug"},"type":{"type":"string","title":"Type"},"grade":{"type":"string","title":"Grade"}},"type":"object","required":["slug","type","grade"],"title":"MxProvider","description":"A mail provider inferred from an MX host."},"MxRecord":{"properties":{"hostname":{"type":"string","title":"Hostname"},"priority":{"type":"integer","title":"Priority"}},"type":"object","required":["hostname","priority"],"title":"MxRecord","description":"An MX record (UserCheck-compatible shape)."},"NotificationResponse":{"properties":{"id":{"type":"integer","title":"Id"},"user_id":{"type":"integer","title":"User Id"},"type":{"$ref":"#/components/schemas/NotificationType"},"title":{"type":"string","title":"Title"},"message":{"type":"string","title":"Message"},"read":{"type":"boolean","title":"Read"},"link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Link"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"read_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Read At"}},"type":"object","required":["id","user_id","type","title","message","read","created_at"],"title":"NotificationResponse","description":"Schema for notification response"},"NotificationType":{"type":"string","enum":["success","info","warning","error"],"title":"NotificationType","description":"Notification types"},"NotificationsListResponse":{"properties":{"notifications":{"items":{"$ref":"#/components/schemas/NotificationResponse"},"type":"array","title":"Notifications"},"total":{"type":"integer","title":"Total"},"unread_count":{"type":"integer","title":"Unread Count"}},"type":"object","required":["notifications","total","unread_count"],"title":"NotificationsListResponse","description":"Schema for notifications list response"},"PlanDetails":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"price":{"type":"number","title":"Price"},"emails_limit":{"type":"integer","title":"Emails Limit"},"features":{"items":{"type":"string"},"type":"array","title":"Features"}},"type":"object","required":["id","name","price","emails_limit","features"],"title":"PlanDetails","description":"Schema for plan details"},"PlanType":{"type":"string","enum":["free","starter","pro"],"title":"PlanType","description":"Plan type enumeration"},"ProviderDetailResponse":{"properties":{"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"},"domain_count":{"type":"integer","title":"Domain Count"},"mx_host_suffixes":{"items":{"type":"string"},"type":"array","title":"Mx Host Suffixes","default":[]},"mx_ips":{"items":{"type":"string"},"type":"array","title":"Mx Ips","default":[]},"most_recent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Most Recent At"},"domains":{"items":{"$ref":"#/components/schemas/ProviderDomainItem"},"type":"array","title":"Domains","default":[]},"page":{"type":"integer","title":"Page"},"page_size":{"type":"integer","title":"Page Size"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["slug","name","domain_count","page","page_size","total"],"title":"ProviderDetailResponse"},"ProviderDomainItem":{"properties":{"domain":{"type":"string","title":"Domain"},"matched_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Matched By"},"mx_host":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mx Host"},"mx_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mx Ip"},"has_a":{"type":"boolean","title":"Has A","default":false},"first_seen":{"type":"string","format":"date-time","title":"First Seen"},"last_seen":{"type":"string","format":"date-time","title":"Last Seen"}},"type":"object","required":["domain","first_seen","last_seen"],"title":"ProviderDomainItem"},"ProviderListItem":{"properties":{"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"},"domain_count":{"type":"integer","title":"Domain Count"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"}},"type":"object","required":["slug","name","domain_count","updated_at"],"title":"ProviderListItem"},"ProviderListResponse":{"properties":{"providers":{"items":{"$ref":"#/components/schemas/ProviderListItem"},"type":"array","title":"Providers"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["providers","total"],"title":"ProviderListResponse"},"ProviderRef":{"properties":{"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"}},"type":"object","required":["slug","name"],"title":"ProviderRef","description":"Disposable provider a domain belongs to."},"PublicDisposableCheckRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"recaptcha_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recaptcha Token"}},"type":"object","required":["email"],"title":"PublicDisposableCheckRequest","description":"Schema for public disposable-check request with reCAPTCHA"},"PublicDnsHealthRequest":{"properties":{"domain":{"type":"string","maxLength":253,"minLength":1,"title":"Domain"},"recaptcha_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recaptcha Token"}},"type":"object","required":["domain"],"title":"PublicDnsHealthRequest","description":"Public DNS-health-check request (with optional reCAPTCHA token)."},"PublicEmailVerifyRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"recaptcha_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recaptcha Token"}},"type":"object","required":["email"],"title":"PublicEmailVerifyRequest","description":"Schema for public email verification request with reCAPTCHA"},"ResendVerification":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"ResendVerification","description":"Schema for resending verification email"},"ResetPassword":{"properties":{"token":{"type":"string","title":"Token"},"new_password":{"type":"string","maxLength":100,"minLength":8,"title":"New Password"}},"type":"object","required":["token","new_password"],"title":"ResetPassword","description":"Schema for resetting password with token"},"SubscriptionResponse":{"properties":{"id":{"type":"integer","title":"Id"},"user_id":{"type":"integer","title":"User Id"},"plan_id":{"$ref":"#/components/schemas/PlanType"},"status":{"$ref":"#/components/schemas/SubscriptionStatus"},"emails_limit":{"type":"integer","title":"Emails Limit"},"emails_used":{"type":"integer","title":"Emails Used"},"addon_credits":{"type":"integer","title":"Addon Credits","default":0},"effective_remaining":{"type":"integer","title":"Effective Remaining","default":0},"start_date":{"type":"string","format":"date-time","title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Date"},"renews_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Renews At"},"cancel_at_period_end":{"type":"boolean","title":"Cancel At Period End","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"plan":{"anyOf":[{"$ref":"#/components/schemas/PlanDetails"},{"type":"null"}]}},"type":"object","required":["id","user_id","plan_id","status","emails_limit","emails_used","start_date","created_at","updated_at"],"title":"SubscriptionResponse","description":"Schema for subscription response"},"SubscriptionStatus":{"type":"string","enum":["active","cancelled","expired","pending"],"title":"SubscriptionStatus","description":"Subscription status enumeration"},"SubscriptionUpgrade":{"properties":{"plan_id":{"$ref":"#/components/schemas/PlanType"}},"type":"object","required":["plan_id"],"title":"SubscriptionUpgrade","description":"Schema for upgrading subscription"},"Token":{"properties":{"access_token":{"type":"string","title":"Access Token"},"refresh_token":{"type":"string","title":"Refresh Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"}},"type":"object","required":["access_token","refresh_token"],"title":"Token","description":"Schema for JWT token response"},"TokenRefresh":{"properties":{"refresh_token":{"type":"string","title":"Refresh Token"}},"type":"object","required":["refresh_token"],"title":"TokenRefresh","description":"Schema for refreshing access token"},"UpgradeRequestCreate":{"properties":{"requested_plan":{"type":"string","title":"Requested Plan"}},"type":"object","required":["requested_plan"],"title":"UpgradeRequestCreate","description":"Schema for creating upgrade request"},"UpgradeRequestResponse":{"properties":{"id":{"type":"integer","title":"Id"},"user_id":{"type":"integer","title":"User Id"},"current_plan":{"type":"string","title":"Current Plan"},"requested_plan":{"type":"string","title":"Requested Plan"},"status":{"$ref":"#/components/schemas/UpgradeRequestStatus"},"user_email":{"type":"string","title":"User Email"},"user_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Name"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","user_id","current_plan","requested_plan","status","user_email","created_at","updated_at"],"title":"UpgradeRequestResponse","description":"Schema for upgrade request response"},"UpgradeRequestStatus":{"type":"string","enum":["pending","contacted","completed","cancelled"],"title":"UpgradeRequestStatus","description":"Upgrade request status enumeration"},"UsageResponse":{"properties":{"emails_used":{"type":"integer","title":"Emails Used"},"emails_limit":{"type":"integer","title":"Emails Limit"},"addon_credits":{"type":"integer","title":"Addon Credits","default":0},"effective_remaining":{"type":"integer","title":"Effective Remaining","default":0},"percentage_used":{"type":"number","title":"Percentage Used"},"resets_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resets At"}},"type":"object","required":["emails_used","emails_limit","percentage_used"],"title":"UsageResponse","description":"Schema for usage statistics"},"UserLogin":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"UserLogin","description":"Schema for user login"},"UserRegister":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","maxLength":100,"minLength":8,"title":"Password"},"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"utm_source":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Utm Source"},"landing_page":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Landing Page"},"last_page":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Page"},"referrer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Referrer"}},"type":"object","required":["email","password","name"],"title":"UserRegister","description":"Schema for user registration"},"UserResponse":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"id":{"type":"integer","title":"Id"},"role":{"$ref":"#/components/schemas/UserRole"},"is_active":{"type":"boolean","title":"Is Active"},"email_verified":{"type":"integer","title":"Email Verified"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["email","name","id","role","is_active","email_verified","created_at","updated_at"],"title":"UserResponse","description":"Schema for user response"},"UserRole":{"type":"string","enum":["user","admin"],"title":"UserRole","description":"User role enumeration"},"UserSettingsResponse":{"properties":{"id":{"type":"integer","title":"Id"},"user_id":{"type":"integer","title":"User Id"},"email_notifications":{"type":"boolean","title":"Email Notifications"},"weekly_report":{"type":"boolean","title":"Weekly Report"},"marketing_emails":{"type":"boolean","title":"Marketing Emails"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","user_id","email_notifications","weekly_report","marketing_emails","created_at","updated_at"],"title":"UserSettingsResponse","description":"Schema for user settings response"},"UserSettingsUpdate":{"properties":{"email_notifications":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Notifications"},"weekly_report":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Weekly Report"},"marketing_emails":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Marketing Emails"}},"type":"object","title":"UserSettingsUpdate","description":"Schema for updating user settings"},"UserUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email"}},"type":"object","title":"UserUpdate","description":"Schema for updating user profile"},"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"},"VerificationHistoryResponse":{"properties":{"jobs":{"items":{"$ref":"#/components/schemas/VerificationJobResponse"},"type":"array","title":"Jobs"},"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"page_size":{"type":"integer","title":"Page Size"}},"type":"object","required":["jobs","total","page","page_size"],"title":"VerificationHistoryResponse","description":"Schema for verification history response"},"VerificationJobResponse":{"properties":{"id":{"type":"integer","title":"Id"},"user_id":{"type":"integer","title":"User Id"},"type":{"$ref":"#/components/schemas/JobType"},"status":{"$ref":"#/components/schemas/JobStatus"},"total_emails":{"type":"integer","title":"Total Emails"},"processed_emails":{"type":"integer","title":"Processed Emails"},"valid_emails":{"type":"integer","title":"Valid Emails","default":0},"invalid_emails":{"type":"integer","title":"Invalid Emails","default":0},"file_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Path"},"result_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Result Path"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"stopped_for_quota":{"type":"boolean","title":"Stopped For Quota","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","user_id","type","status","total_emails","processed_emails","created_at"],"title":"VerificationJobResponse","description":"Schema for verification job response"}},"securitySchemes":{"OAuth2PasswordBearer":{"type":"oauth2","flows":{"password":{"scopes":{},"tokenUrl":"/api/v1/auth/login"}}},"HTTPBearer":{"type":"http","scheme":"bearer"}}}}