{"openapi":"3.1.0","info":{"title":"WhoisGenius","description":"Domain intelligence platform with AI-powered operator attribution Responses are JSON by default; request the compact, token-efficient TOON format with the `Accept: application/toon` header.","version":"0.1.0"},"paths":{"/health":{"get":{"tags":["health"],"summary":"Health Check","description":"Basic health check -- always returns ok if the service is running.","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Health Check Health Get"}}}}}}},"/version":{"get":{"tags":["health"],"summary":"Version","description":"Build provenance probe (SRE W3-7).\n\nReports the git SHA this image was built from (injected as GIT_SHA into the\ntask definition at deploy time). The deploy pipeline polls this endpoint\nuntil the reported SHA matches the commit being deployed, replacing the\nad-hoc ``curl /health`` warmup loops — a 200 from a still-draining OLD task\ncan no longer satisfy readiness and mask a regression or trigger a false\nrollback. Exposes only a commit hash (no internal architecture details).","operationId":"version_version_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Version Version Get"}}}}}}},"/health/ready":{"get":{"tags":["health"],"summary":"Readiness Check","description":"Readiness check -- verifies database and Redis connectivity.\n\nReturns only aggregate status publicly.  Individual collector names\nand their enabled/disabled state are intentionally omitted to avoid\nleaking internal architecture details to unauthenticated callers.","operationId":"readiness_check_health_ready_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Readiness Check Health Ready Get"}}}}}}},"/health/auth":{"get":{"tags":["health"],"summary":"Auth Pipeline Check","description":"Auth pipeline health check -- tests JWKS fetch, DB, full auth readiness.\n\nExercises the real JWKS HTTP endpoint that ``decode_cognito_token`` uses\n(not just the boto3 ``describe_user_pool`` that ``/health/ready`` tests).\nThis catches NAT Gateway latency, JWKS endpoint unreachability, and\nDNS resolution failures that would cause ``Failed to fetch`` in browsers.","operationId":"auth_pipeline_check_health_auth_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Auth Pipeline Check Health Auth Get"}}}}}}},"/auth/verify-token":{"post":{"tags":["authentication"],"summary":"Verify magic link token (server-side)","description":"Verify a magic link token server-side via Cognito admin API.\n\nRate-limited to 5 attempts per minute per IP to prevent brute-force.","operationId":"verify_token_auth_verify_token_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyTokenRequest"}}},"required":true},"responses":{"200":{"description":"Cognito tokens on success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_VerifyTokenResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/auth/me":{"get":{"tags":["authentication"],"summary":"Get current user profile","description":"Return the currently authenticated user's profile.\n\nRequires a valid Cognito JWT Bearer token.","operationId":"get_me_auth_me_get","responses":{"200":{"description":"Current authenticated user's profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_MeResponse_"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}},"security":[{"HTTPBearer":[]}]},"delete":{"tags":["account"],"summary":"Close account (GDPR erasure)","description":"Close the authenticated account immediately.\n\nIn one transaction: deactivates the account, revokes every API key and\nBYOK key (they stop authenticating instantly), anonymizes PII in place,\ncancels any live subscription immediately, and disables the identity at\nthe IdP. Analysis history and the credit ledger are retained in\nanonymized form. The same email can register a fresh account immediately.","operationId":"delete_me_auth_me_delete","responses":{"200":{"description":"Deletion confirmation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_dict_str__Any__"}}}},"401":{"description":"Not authenticated (closed accounts cannot authenticate)"},"410":{"description":"Account already closed"}},"security":[{"HTTPBearer":[]}]}},"/auth/keys":{"get":{"tags":["authentication"],"summary":"List active API keys","description":"List all active API keys for the authenticated user.\n\nRaw key values are never included in list responses.","operationId":"list_api_keys_auth_keys_get","responses":{"200":{"description":"List of API keys (without raw values)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ApiKeyListResponse_"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["authentication"],"summary":"Create a new API key","description":"Create a named API key for the authenticated user.\n\nThe raw key value is returned exactly once in this response.\nIt is never stored and cannot be retrieved again.\nEnforces key limits: free accounts get ``api_key_limit_free``; legacy\nsubscription tiers keep their caps; any user with at least one completed\ncredit-pack purchase is boosted to ``api_key_limit_paid``.","operationId":"create_api_key_auth_keys_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyRequest"}}},"required":true},"responses":{"200":{"description":"API key with raw value (shown once)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ApiKeyResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}},"security":[{"HTTPBearer":[]}]}},"/auth/session-key":{"post":{"tags":["authentication"],"summary":"Mint a Try It session key","description":"Mint (or rotate) the in-app \"Try It\" playground key for this user.\n\nThe dashboard/landing Try It panel calls this with the logged-in user's\nCognito JWT, so the panel never depends on a copy-once raw key stashed in\nthe browser. Each call revokes the prior playground key and returns a fresh\nraw value.\n\nAbuse controls (the playground adds NO new spend budget):\n  * Minting is capped per user (``session_key_mint_per_hour``).\n  * The key is excluded from the per-tier key cap and hidden from GET /keys.\n  * Analyses made with it consume the user's normal monthly quota and are\n    rate-limited per user_id at ``playground_rate_limit_per_hour`` so\n    re-minting cannot reset the hourly window (enforced in deps.py).","operationId":"create_session_key_auth_session_key_post","responses":{"200":{"description":"Short-lived playground API key (raw value, shown each call)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_ApiKeyResponse_"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}},"security":[{"HTTPBearer":[]}]}},"/auth/keys/{key_id}":{"delete":{"tags":["authentication"],"summary":"Revoke an API key","description":"Revoke an API key by ID.\n\nOnly the key owner can revoke their own keys. Sets is_active=False\nand records the revocation timestamp.","operationId":"revoke_api_key_auth_keys__key_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"200":{"description":"Revocation confirmation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_dict_str__Any__"}}}},"404":{"description":"API key not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/auth/email-preferences":{"get":{"tags":["authentication"],"summary":"Get email preferences","description":"Return the current user's email notification preferences.\n\nAll default to true (opted-in) when the key is absent.","operationId":"get_email_preferences_auth_email_preferences_get","responses":{"200":{"description":"Current email notification preferences","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_EmailPreferencesResponse_"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}},"security":[{"HTTPBearer":[]}]},"patch":{"tags":["authentication"],"summary":"Update email preferences","description":"Update email notification preferences (merge semantics).\n\nOnly provided fields are updated; omitted fields keep their current\nvalue. Use ``{\"digest_enabled\": false}`` to unsubscribe from digests, or\n``{\"lifecycle_enabled\": false}`` to opt out of lifecycle campaigns.","operationId":"update_email_preferences_auth_email_preferences_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailPreferencesUpdate"}}},"required":true},"responses":{"200":{"description":"Updated email notification preferences","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_EmailPreferencesResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}},"security":[{"HTTPBearer":[]}]}},"/auth/me/export":{"get":{"tags":["account"],"summary":"Export account data (GDPR)","description":"Export all data held for the authenticated user (data portability).\n\nAvailable on ALL tiers. The bundle contains the profile, API-key and\nBYOK-key metadata (never raw secrets or ciphertext), job history with\ntier-filtered results, and the credit ledger. Rate-limited per user.","operationId":"export_me_auth_me_export_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Full user-scoped data bundle as a JSON attachment","content":{"application/json":{"schema":{}}}},"401":{"description":"Not authenticated"},"429":{"description":"Export rate limit exceeded"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/checkout":{"post":{"tags":["billing"],"summary":"Create Stripe Checkout Session","description":"Legacy subscription checkout — discontinued.\n\nNew subscription sales ended with the move to one-time credit packs\n(2026-08-09 decision). Existing subscribers are unaffected: the Stripe\nwebhook and customer portal continue to manage their plans. New\npurchases go through POST /billing/credits/checkout.","operationId":"create_checkout_billing_checkout_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/CheckoutRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_CheckoutResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/billing/portal":{"get":{"tags":["billing"],"summary":"Get Stripe Customer Portal URL","description":"Create a Stripe Customer Portal session for subscription management.\n\nReturns a portal URL where the user can manage their subscription,\nupdate payment methods, or cancel. Requires an active Stripe customer.","operationId":"get_portal_billing_portal_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"return_url","in":"query","required":true,"schema":{"type":"string","title":"Return Url"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PortalResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/billing/subscription":{"get":{"tags":["billing"],"summary":"Get current subscription info","description":"Return the current subscription details for the authenticated user.\n\nIncludes tier, subscription status, and Stripe IDs.","operationId":"get_subscription_billing_subscription_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_SubscriptionInfo_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/billing/credits/checkout":{"post":{"tags":["billing"],"summary":"Create credit pack checkout session","description":"Create a Stripe Checkout Session for a one-time credit pack purchase.\n\nMaps the requested pack to a Stripe price ID and creates a payment-mode\ncheckout session. Returns the checkout URL for client-side redirect.","operationId":"create_credit_checkout_billing_credits_checkout_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/CreditCheckoutRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_CreditCheckoutResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/billing/credits":{"get":{"tags":["billing"],"summary":"Get credit balance","description":"Return the current credit balance and subscription status for the user.\n\nThe has_active_subscription field indicates whether the user has a paid\nsubscription that covers analyses without requiring credits.","operationId":"get_credits_billing_credits_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_CreditBalance_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/billing/ledger":{"get":{"tags":["billing"],"summary":"List credit ledger movements","description":"Return a page of the user's credit-ledger movements, newest first.\n\nEach entry is one grant/debit/refund/follower-free event with the signed\ndelta, reason, and resulting balance -- the legible audit trail behind the\nbalance (Dissent 1). User-scoped; never exposes another account's rows.\n``limit``/``offset`` page the journal so a long history can be fully\nretrieved for reconciliation (B-18).","operationId":"get_credit_ledger_billing_ledger_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Max rows to return (1-500).","default":50,"title":"Limit"},"description":"Max rows to return (1-500)."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Rows to skip for pagination.","default":0,"title":"Offset"},"description":"Rows to skip for pagination."},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_list_LedgerEntry__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/billing/auto-recharge":{"get":{"tags":["billing"],"summary":"Get auto-recharge settings","description":"Return the user's credit auto-recharge settings (phase 999.1).","operationId":"get_auto_recharge_billing_auto_recharge_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_AutoRechargeSettings_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}},"put":{"tags":["billing"],"summary":"Update auto-recharge settings","description":"Update the user's auto-recharge settings.\n\nEnabling requires a saved card (add one via POST /billing/auto-recharge/setup);\nan enable-without-card attempt or invalid input returns 400.","operationId":"update_auto_recharge_billing_auto_recharge_put","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/AutoRechargeUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_AutoRechargeSettings_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/billing/auto-recharge/setup":{"post":{"tags":["billing"],"summary":"Save a card for auto-recharge","description":"Open a hosted Stripe Checkout (mode=setup) to save a reusable card.\n\nReturns the checkout URL for client redirect. On completion the setup webhook\npersists the PaymentMethod onto the user's config; the user can then enable\nauto-recharge via PUT /billing/auto-recharge.","operationId":"setup_auto_recharge_card_billing_auto_recharge_setup_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/AutoRechargeSetupRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_AutoRechargeSetupResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/billing/usage":{"get":{"tags":["billing"],"summary":"Get usage dashboard","description":"Return monthly consumption, remaining quota, and projected overage.\n\nPer PRICE-05: Shows current consumption, remaining quota, billing\nperiod dates, and projected overage cost based on current usage rate.","operationId":"get_usage_dashboard_billing_usage_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_UsageDashboard_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/account/llm-keys":{"get":{"tags":["byok"],"summary":"List BYOK LLM API keys","description":"List all active BYOK LLM API keys for the authenticated user.\n\nNever includes raw or encrypted key values in the response.","operationId":"list_byok_keys_account_llm_keys_get","responses":{"200":{"description":"Active keys for the authenticated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_BYOKKeyListResponse_"}}}},"403":{"description":"Tier insufficient — Team or higher required"},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["byok"],"summary":"Save a BYOK LLM API key","description":"Save (create or update) an encrypted BYOK LLM API key.\n\nValidates the key via a test inference call before persisting.\nOne key per provider per user -- saving for an existing provider\nreplaces the previous key.","operationId":"save_byok_key_account_llm_keys_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveBYOKKeyRequest"}}},"required":true},"responses":{"200":{"description":"Saved key details (never includes raw key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_BYOKKeyResponse_"}}}},"400":{"description":"Unsupported provider"},"403":{"description":"Tier insufficient — Team or higher required"},"422":{"description":"Key validation failed (invalid/expired key)"},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}},"security":[{"HTTPBearer":[]}]}},"/account/llm-keys/{provider}":{"delete":{"tags":["byok"],"summary":"Delete a BYOK LLM API key","description":"Soft-delete a BYOK LLM API key by provider name.\n\nReturns 204 on success, 404 if no active key exists for the given provider.","operationId":"delete_byok_key_account_llm_keys__provider__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","title":"Provider"}}],"responses":{"204":{"description":"Deletion confirmation"},"403":{"description":"Tier insufficient — Team or higher required"},"404":{"description":"No active key found for this provider"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/analyze":{"post":{"tags":["analysis"],"summary":"Submit domain for analysis","description":"Submit a single domain for analysis.\n\nCreates an analysis job and returns 202 with a job_id for polling.\nDeduplicates against active jobs unless force_refresh is True.\nAn audit log entry is written in the background.\n\nSet ``wait=true`` for a one-call flow: the request is held briefly and\nreturns the result inline (HTTP 200) when the analysis resolves, or HTTP 202\nwith the job_id to poll if it is still running when the wait budget elapses.\n\nSet ``depth=\"quick\"`` for the honest fast path: the fast collector subset\n(dns/whois/http_probe/asn/rdns) runs inline under a hard deadline and the\nterminal result comes back as HTTP 200 (same body shape as GET /jobs/{id},\nplus job_id). Quick never runs vision/LLM/reverse-WHOIS/metered third-party\nenrichment -- it is the fast honest answer; deep is the full one. Quick\ncannot be combined with wait=true or force_refresh (400), costs the same\n1 credit as deep, and refunds on a failed outcome.\n\nQuota enforcement: each query increments a monthly Redis counter.\nFree tier users are hard-blocked at their monthly quota\n(``settings.quota_free``). Pro/Team users exceeding quota are billed\noverage via Stripe metered billing. Falls back to credit deduction if\noverage billing unavailable.","operationId":"submit_analysis_analyze_post","parameters":[{"name":"wait","in":"query","required":false,"schema":{"type":"boolean","description":"When true, hold the response for up to ~20 seconds and return the result inline once ready (same body as GET /jobs/{job_id}) with HTTP 200. If the analysis is still running when the wait budget is spent, return HTTP 202 with the job_id so you can poll GET /jobs/{job_id}. Default false returns 202 immediately.","default":false,"title":"Wait"},"description":"When true, hold the response for up to ~20 seconds and return the result inline once ready (same body as GET /jobs/{job_id}) with HTTP 200. If the analysis is still running when the wait budget is spent, return HTTP 202 with the job_id so you can poll GET /jobs/{job_id}. Default false returns 202 immediately."},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}},{"name":"X-LLM-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Llm-Api-Key"}},{"name":"X-LLM-Provider","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Llm-Provider"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitRequest"}}}},"responses":{"202":{"description":"Analysis job accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_SubmitResponse_"}}}},"200":{"description":"wait=true or depth=quick: analysis resolved inline within the request (depth=quick always answers 200 with the terminal result; wait=true answers 200 only when the job resolved inside the wait budget)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_JobStatusResponse_"}}}},"400":{"description":"Invalid depth combination (quick + wait/force_refresh)"},"422":{"description":"Invalid domain format"},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/analyze/batch":{"post":{"tags":["analysis"],"summary":"Submit batch domain analysis","description":"Submit multiple domains for analysis in a single request.\n\nProcesses each domain independently -- partial failures do not abort\nthe batch. Returns 202 if all succeed, 207 if some fail, 422 if all fail.\nEach accepted domain gets an audit log entry in the background.\n\nQuota enforcement: each domain in the batch gets its own atomic Redis\nINCR via check_and_increment. Free tier users are hard-blocked at quota.\nPaid tier overage is allowed and billed via periodic InvoiceItem.\n\nWith ``depth=quick`` every domain runs through the inline fast path\ninstead (see POST /analyze): 200 when all resolve, 207 partial, 422 when\nall fail. Quick batches are capped at QUICK_BATCH_MAX_DOMAINS.","operationId":"submit_batch_analysis_analyze_batch_post","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/BatchSubmitRequest"}}}},"responses":{"202":{"description":"Batch submission results","content":{"application/json":{"schema":{}}}},"200":{"description":"depth=quick only: every domain resolved inline"},"207":{"description":"Partial success -- some domains failed"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/analyze/bulk":{"post":{"tags":["analysis"],"summary":"Submit bulk domain analysis (results emailed)","description":"Submit a large domain list for asynchronous analysis with email delivery.\n\nUnlike /analyze/batch (<=50 domains, processed in-request), a bulk upload\nonly persists a BulkJob row and dispatches a worker task: the per-domain\ngate -> dispatch -> settle sequence runs in ``process_bulk_job`` with the\nsame post-dispatch billing settlement as the batch pass 3, and when every\nchild job is terminal the hygiene tick flips the row to ``exporting`` so\nthe export task renders CSV/JSON, uploads to S3, and emails presigned\nlinks to the account address. Invalid domains are recorded as per-item\nerrors on the bulk job (batch precedent), not a whole-request 422.","operationId":"submit_bulk_analysis_analyze_bulk_post","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/BulkSubmitRequest"}}}},"responses":{"202":{"description":"Bulk submission accepted","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/jobs":{"get":{"tags":["jobs"],"summary":"List jobs","description":"List the authenticated user's analysis jobs, newest first.\n\nAccepts both Cognito JWT (dashboard) and API key auth. ``duration_ms`` is\nmeasured to ``completed_at``; for failed jobs persisted before\n``completed_at`` was stamped on failure it falls back to ``updated_at``.\nDeclared before ``/{job_id}`` so the literal path wins.","operationId":"list_jobs_jobs_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Max jobs to return (1-100).","default":20,"title":"Limit"},"description":"Max jobs to return (1-100)."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Rows to skip for pagination.","default":0,"title":"Offset"},"description":"Rows to skip for pagination."},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_JobListResponse_"}}}},"401":{"description":"Missing or invalid credentials"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/jobs/{job_id}":{"get":{"tags":["jobs"],"summary":"Get job status","description":"Poll a job's current status.\n\nReturns the current job state (Postgres-first, with live progress\noverlaid from the Redis hash). When the job is completed or partial, the\nscoring_result from PostgreSQL is included in the response for immediate\naccess to results.","operationId":"get_job_status_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_JobStatusResponse_"}}}},"404":{"description":"Job not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/jobs/{job_id}/signals":{"get":{"tags":["jobs"],"summary":"Get per-signal breakdown","description":"Get the per-entity signal breakdown for a completed job.\n\nProvides full transparency into how entities were scored. Each entity\nincludes its individual signal contributions and cross-signal correlations\nfrom the scoring engine output stored in the AnalysisRun JSONB.\n\nAvailable to all tiers (output is no longer tier-gated). Returns 404 if\nthe job does not exist, 409 if the analysis is still in progress, and the\nfull signal breakdown when complete. Internal signal weights are never\nincluded in the response schema.","operationId":"get_job_signals_jobs__job_id__signals_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_SignalsResponse_"}}}},"404":{"description":"Job not found"},"409":{"description":"Analysis not yet complete"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/jobs/{job_id}/subdomains":{"get":{"tags":["jobs"],"summary":"Get discovered subdomains","description":"Get subdomains discovered for the domain in this job.\n\nTriggers subdomain enumeration on first call (CT SAN extraction + DNS brute-force).\nSubsequent calls within the cache TTL return cached results.\n\nReturns 404 if the job does not exist, 409 if the analysis is still in progress.","operationId":"get_job_subdomains_jobs__job_id__subdomains_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_SubdomainsResponse_"}}}},"404":{"description":"Job not found"},"409":{"description":"Analysis not yet complete"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/jobs/{job_id}/certificate":{"get":{"tags":["jobs"],"summary":"Get attribution certificate","description":"Get a signed attribution certificate for a completed job.\n\nReturns a deterministic, HMAC-signed certificate containing the full\nanalysis results, chain-of-evidence, and cryptographic metadata for\nindependent verification.\n\nImplements generate-once/serve-forever: the first request creates and\npersists the certificate, subsequent requests return the stored version.\n\nReturns 404 if the job does not exist, 409 if the analysis is still\nin progress, and the signed certificate envelope when complete.","operationId":"get_job_certificate_jobs__job_id__certificate_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"format","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Response format: 'pdf', 'csv', or 'json'","title":"Format"},"description":"Response format: 'pdf', 'csv', or 'json'"},{"name":"accept","in":"header","required":false,"schema":{"type":"string","default":"application/json","title":"Accept"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Certificate as JSON envelope or PDF document","content":{"application/json":{"schema":{}},"application/pdf":{}}},"404":{"description":"Job not found or no scoring data"},"409":{"description":"Analysis not yet complete"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/webhooks/{job_id}/status":{"get":{"tags":["webhooks"],"summary":"Get webhook delivery status for a job","description":"Query the delivery status of a webhook callback for a specific job.","operationId":"get_webhook_status_webhooks__job_id__status_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Webhook delivery status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_WebhookStatusResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/webhooks/secret":{"post":{"tags":["webhooks"],"summary":"Generate or rotate webhook secret","description":"Generate a new webhook secret for HMAC-SHA256 signing.\n\nThis serves as both 'create' (first time) and 'rotate' (subsequent calls).\nThe previous secret is immediately invalidated.","operationId":"create_webhook_secret_webhooks_secret_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"New webhook secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_WebhookSecretResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}},"get":{"tags":["webhooks"],"summary":"Get current webhook secret","description":"Retrieve the user's current webhook secret.","operationId":"get_webhook_secret_webhooks_secret_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Current webhook secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_WebhookSecretResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/webhooks/dead-letter/{delivery_id}/retry":{"post":{"tags":["webhooks"],"summary":"Re-enqueue a dead-lettered webhook delivery","description":"Reset a dead-lettered delivery to pending and re-enqueue delivery.\n\nOnly deliveries belonging to the authenticated user's jobs can be retried.","operationId":"retry_dead_letter_webhook_webhooks_dead_letter__delivery_id__retry_post","parameters":[{"name":"delivery_id","in":"path","required":true,"schema":{"type":"string","title":"Delivery Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Redelivery confirmation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_dict_str__Any__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/webhooks/dead-letter":{"get":{"tags":["webhooks"],"summary":"List dead-lettered webhook deliveries","description":"List dead-lettered webhook deliveries for the authenticated user.","operationId":"list_dead_letter_webhooks_webhooks_dead_letter_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Dead-lettered deliveries for reconciliation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_list_dict_str__Any___"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/domains/{domain}/history":{"get":{"tags":["domains"],"summary":"Get domain analysis history","description":"Get chronological analysis history for a domain.\n\nReturns paginated analysis runs with entity attribution, confidence\nscores, and diffs between consecutive runs showing entity, registrar,\nnameserver, and confidence changes.\n\nSupports cursor-based pagination via the ``before`` parameter (ISO 8601\ntimestamp from ``next_cursor`` in a previous response).\n\nWhen ``include_external`` is True, merges pre-WhoisGenius ownership\nhistory from the WHOIS History API into the timeline. External entries\nare labeled ``source: external`` and only include records that predate\nthe earliest local analysis. Costs 50 credits per call.\n\nReturns 404 if the domain has never been analyzed.","operationId":"get_domain_history_domains__domain__history_get","parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string","title":"Domain"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"before","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cursor: ISO 8601 timestamp for pagination","title":"Before"},"description":"Cursor: ISO 8601 timestamp for pagination"},{"name":"include_external","in":"query","required":false,"schema":{"type":"boolean","description":"Include pre-WhoisGenius ownership history from WHOIS History API (50 credits)","default":false,"title":"Include External"},"description":"Include pre-WhoisGenius ownership history from WHOIS History API (50 credits)"},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_HistoryResponse_"}}}},"404":{"description":"Domain not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/domains/{domain}/portfolio":{"get":{"tags":["domains"],"summary":"Get domain entity portfolio","description":"Discover related domains via the top entity's Reverse WHOIS portfolio.\n\nLooks up the most recent analysis for the given domain, extracts the\ntop-ranked entity, and queries the Reverse WHOIS API\nto find all domains associated with that entity.\n\nReturns HTTP 404 if the domain has not been analyzed or has no entity.\nReturns HTTP 503 when the Reverse WHOIS service is unavailable.\n\nRequires API key authentication via X-API-Key header.","operationId":"get_domain_portfolio_domains__domain__portfolio_get","parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string","title":"Domain"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_DomainPortfolioResponse_"}}}},"404":{"description":"Domain not analyzed or no entity found"},"503":{"description":"Reverse WHOIS service unavailable"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/entities/search":{"get":{"tags":["entities"],"summary":"Search entities by name","description":"Search entities by name with relevance ranking.\n\nReturns entities matching the query ranked by relevance (exact > prefix\n> substring), with associated domains and per-domain attribution\nconfidence from entity signals.\n\nQuery is normalized using the same function used to store entity names,\nso searches like \"Google LLC\" and \"google\" both match the same entities.\n\nWhen ``include_portfolio`` is true, top 5 results are enriched with a\n``portfolio_count`` field from Reverse WHOIS preview mode (free, adds\n1-3 seconds latency per entity).\n\nRequires API key authentication via X-API-Key header.","operationId":"search_entities_entities_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":255,"description":"Entity name search query","title":"Q"},"description":"Entity name search query"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"include_portfolio","in":"query","required":false,"schema":{"type":"boolean","description":"Include portfolio count from Reverse WHOIS (adds latency)","default":false,"title":"Include Portfolio"},"description":"Include portfolio count from Reverse WHOIS (adds latency)"},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_EntitySearchResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/entities/{name}/portfolio":{"get":{"tags":["entities"],"summary":"Get entity domain portfolio","description":"Discover all domains associated with an entity via Reverse WHOIS.\n\nQueries the Reverse WHOIS API to find domains registered\nto or associated with the given entity name. Results are capped at 50\ndomains and cached for 7 days.\n\nReturns HTTP 503 when the Reverse WHOIS service is unavailable or\nnot configured. This endpoint is independent of the main analysis\npipeline and never degrades analysis performance.\n\nRequires API key authentication via X-API-Key header.","operationId":"get_entity_portfolio_entities__name__portfolio_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","minLength":2,"maxLength":255,"description":"Entity name to query (2-255 chars, no control characters)","title":"Name"},"description":"Entity name to query (2-255 chars, no control characters)"},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_PortfolioResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/correlate":{"post":{"tags":["correlation"],"summary":"Submit cross-domain correlation","description":"Submit 2-100 domains for cross-domain correlation analysis.\n\nCreates a background job to analyze uncached domains, compute pairwise\ncorrelation scores, detect clusters, and generate LLM summaries.\n\nCorrelation is a credit-billed product available to any account with\nsufficient credits (no subscription tier required — Gap G, Option A).\n\nCredit billing: 1 credit per uncached domain + 1 for the correlation\ncompute, debited as ONE job-keyed charge (single settlement; exactly-once\nvia the ``charge:{job_id}`` ledger idempotency key). The charge is refunded\nvia the keyed settle path if dispatch fails or the job ends ``failed``.","operationId":"submit_correlation_correlate_post","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/CorrelateRequest"}}}},"responses":{"202":{"description":"Correlation job accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_CorrelateResponse_"}}}},"402":{"description":"Insufficient credits"},"422":{"description":"Invalid request (domain count out of range)"},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/correlate/{correlation_id}":{"get":{"tags":["correlation"],"summary":"Get correlation job status","description":"Poll the status of a correlation job.\n\nReturns the current job status. When status is \"completed\", the full\ncorrelation result (clusters, pairwise scores, summaries) is included.","operationId":"get_correlation_status_correlate__correlation_id__get","parameters":[{"name":"correlation_id","in":"path","required":true,"schema":{"type":"string","title":"Correlation Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Correlation job status and result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_CorrelateStatusResponse_"}}}},"404":{"description":"Correlation job not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/schemas/templates":{"post":{"tags":["schema-templates"],"summary":"Create a schema template","description":"Create a new named schema template with validated field paths.\n\nMutating route: consumes a rate-limit slot (W2-15 / data-endpoints-01).","operationId":"create_template_schemas_templates_post","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/CreateTemplateRequest"}}}},"responses":{"200":{"description":"Created template","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_TemplateResponse_"}}}},"403":{"description":"Tier limit exceeded"},"409":{"description":"Template name already exists"},"422":{"description":"Invalid field paths"},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}},"get":{"tags":["schema-templates"],"summary":"List schema templates","description":"List all templates visible to the authenticated user (own + built-ins).","operationId":"list_templates_schemas_templates_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"User templates plus all built-ins","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_TemplateListResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/schemas/templates/{template_id}":{"get":{"tags":["schema-templates"],"summary":"Get a schema template","description":"Get a single template by ID if owned by the user or built-in.","operationId":"get_template_schemas_templates__template_id__get","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Single template details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_TemplateResponse_"}}}},"404":{"description":"Template not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}},"put":{"tags":["schema-templates"],"summary":"Update a schema template","description":"Update an existing user-owned template with re-validated fields.\n\nMutating route: consumes a rate-limit slot (W2-15 / data-endpoints-01).","operationId":"update_template_schemas_templates__template_id__put","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}},{"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/UpdateTemplateRequest"}}}},"responses":{"200":{"description":"Updated template","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiResponse_TemplateResponse_"}}}},"403":{"description":"Cannot modify built-in template or not owned"},"404":{"description":"Template not found"},"422":{"description":"Invalid field paths"},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}},"delete":{"tags":["schema-templates"],"summary":"Delete a schema template","description":"Delete a user-owned template. Built-in templates cannot be deleted.\n\nMutating route: consumes a rate-limit slot (W2-15 / data-endpoints-01).","operationId":"delete_template_schemas_templates__template_id__delete","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"204":{"description":"Deletion confirmation"},"403":{"description":"Cannot delete built-in template"},"404":{"description":"Template not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}},"/mcp":{"post":{"tags":["mcp"],"summary":"MCP tool server (JSON-RPC 2.0)","description":"Streamable-HTTP MCP endpoint implementing JSON-RPC 2.0.\n\nSupports three MCP methods:\n\n* ``initialize`` — capability handshake (stateless; ignores clientInfo)\n* ``tools/list`` — enumerate available tools\n* ``tools/call`` — invoke ``analyze_domain`` or ``correlate_domains``\n\nAuthentication is required for ``tools/call`` (valid ``X-API-Key``);\n``initialize`` and ``tools/list`` are unauthenticated.  Errors are\nreturned as JSON-RPC error objects (HTTP 200) rather than raising\nHTTPException, so the envelope middleware never wraps them.\n\nTo use with Claude::\n\n    claude mcp add --transport http whoisgenius http://localhost:8000/mcp\n\nOr for the hosted API::\n\n    claude mcp add --transport http whoisgenius https://api.whoisgeni.us/mcp","operationId":"mcp_endpoint_mcp_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"JSON-RPC response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthorized — missing or invalid API key/JWT.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"null"},"error":{"type":"object","properties":{"code":{"type":"string","example":"UNAUTHORIZED"},"message":{"type":"string"}}},"meta":{"type":"null"}}}}}}}}}},"components":{"schemas":{"AnalysisCharge":{"properties":{"credits":{"type":"integer","title":"Credits"},"reason":{"type":"string","enum":["analysis","overage","recently_analyzed_free","uncharged"],"title":"Reason"}},"type":"object","required":["credits","reason"],"title":"AnalysisCharge","description":"What this submission cost, for legible billing (Dissent 1).\n\n``credits`` is the number debited (0 for a free dedup-follower or an\nuncharged race). ``reason`` is the API-facing explanation -- note it is\nNOT the ledger's ``LedgerReason`` (a follower bills 0 here as\n``recently_analyzed_free`` while the ledger row is ``follower_free``)."},"ApiKeyListResponse":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/ApiKeyResponse"},"type":"array","title":"Keys"}},"type":"object","required":["keys"],"title":"ApiKeyListResponse","description":"Response body listing all API keys for a user."},"ApiKeyResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"key_prefix":{"type":"string","title":"Key Prefix"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"is_active":{"type":"boolean","title":"Is Active"},"raw_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raw Key"}},"type":"object","required":["id","name","key_prefix","created_at","is_active"],"title":"ApiKeyResponse","description":"Response body for a single API key.\n\n``raw_key`` is only populated on creation (shown once, never stored)."},"ApiResponse_ApiKeyListResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/ApiKeyListResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[ApiKeyListResponse]"},"ApiResponse_ApiKeyResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/ApiKeyResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[ApiKeyResponse]"},"ApiResponse_AutoRechargeSettings_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/AutoRechargeSettings"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[AutoRechargeSettings]"},"ApiResponse_AutoRechargeSetupResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/AutoRechargeSetupResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[AutoRechargeSetupResponse]"},"ApiResponse_BYOKKeyListResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/BYOKKeyListResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[BYOKKeyListResponse]"},"ApiResponse_BYOKKeyResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/BYOKKeyResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[BYOKKeyResponse]"},"ApiResponse_CheckoutResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/CheckoutResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[CheckoutResponse]"},"ApiResponse_CorrelateResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/CorrelateResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[CorrelateResponse]"},"ApiResponse_CorrelateStatusResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/CorrelateStatusResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[CorrelateStatusResponse]"},"ApiResponse_CreditBalance_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/CreditBalance"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[CreditBalance]"},"ApiResponse_CreditCheckoutResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/CreditCheckoutResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[CreditCheckoutResponse]"},"ApiResponse_DomainPortfolioResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/DomainPortfolioResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[DomainPortfolioResponse]"},"ApiResponse_EmailPreferencesResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/EmailPreferencesResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[EmailPreferencesResponse]"},"ApiResponse_EntitySearchResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/EntitySearchResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[EntitySearchResponse]"},"ApiResponse_HistoryResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/HistoryResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[HistoryResponse]"},"ApiResponse_JobListResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/JobListResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[JobListResponse]"},"ApiResponse_JobStatusResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/JobStatusResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[JobStatusResponse]"},"ApiResponse_MeResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/MeResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[MeResponse]"},"ApiResponse_PortalResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/PortalResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[PortalResponse]"},"ApiResponse_PortfolioResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/PortfolioResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[PortfolioResponse]"},"ApiResponse_SignalsResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/SignalsResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[SignalsResponse]"},"ApiResponse_SubdomainsResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/SubdomainsResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[SubdomainsResponse]"},"ApiResponse_SubmitResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/SubmitResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[SubmitResponse]"},"ApiResponse_SubscriptionInfo_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/SubscriptionInfo"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[SubscriptionInfo]"},"ApiResponse_TemplateListResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/TemplateListResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[TemplateListResponse]"},"ApiResponse_TemplateResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/TemplateResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[TemplateResponse]"},"ApiResponse_UsageDashboard_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/UsageDashboard"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[UsageDashboard]"},"ApiResponse_VerifyTokenResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/VerifyTokenResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[VerifyTokenResponse]"},"ApiResponse_WebhookSecretResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/WebhookSecretResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[WebhookSecretResponse]"},"ApiResponse_WebhookStatusResponse_":{"properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/WebhookStatusResponse"},{"type":"null"}]},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[WebhookStatusResponse]"},"ApiResponse_dict_str__Any__":{"properties":{"data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Data"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[dict[str, Any]]"},"ApiResponse_list_LedgerEntry__":{"properties":{"data":{"anyOf":[{"items":{"$ref":"#/components/schemas/LedgerEntry"},"type":"array"},{"type":"null"}],"title":"Data"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[list[LedgerEntry]]"},"ApiResponse_list_dict_str__Any___":{"properties":{"data":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Data"},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"}},"type":"object","title":"ApiResponse[list[dict[str, Any]]]"},"AutoRechargeSettings":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"threshold_credits":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Threshold Credits"},"pack":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pack"},"has_saved_card":{"type":"boolean","title":"Has Saved Card"},"disabled_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disabled Reason"},"monthly_cap_usd":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Monthly Cap Usd"},"month_spent_usd":{"type":"integer","title":"Month Spent Usd"}},"type":"object","required":["enabled","threshold_credits","pack","has_saved_card","disabled_reason","monthly_cap_usd","month_spent_usd"],"title":"AutoRechargeSettings","description":"Current auto-recharge (credit auto-buy) settings for a user."},"AutoRechargeSetupRequest":{"properties":{"success_url":{"type":"string","title":"Success Url"},"cancel_url":{"type":"string","title":"Cancel Url"}},"type":"object","required":["success_url","cancel_url"],"title":"AutoRechargeSetupRequest","description":"Request body to open a hosted Checkout for saving an auto-recharge card."},"AutoRechargeSetupResponse":{"properties":{"checkout_url":{"type":"string","title":"Checkout Url"},"session_id":{"type":"string","title":"Session Id"}},"type":"object","required":["checkout_url","session_id"],"title":"AutoRechargeSetupResponse","description":"Response body with the hosted setup-Checkout URL."},"AutoRechargeUpdateRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"threshold_credits":{"type":"integer","minimum":0.0,"title":"Threshold Credits","description":"Top up when balance drops below this."},"pack":{"type":"string","enum":["100_credits","500_credits","2000_credits"],"title":"Pack"},"monthly_cap_usd":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Monthly Cap Usd","description":"Optional monthly USD spend ceiling."}},"type":"object","required":["enabled","threshold_credits","pack"],"title":"AutoRechargeUpdateRequest","description":"Request body to update auto-recharge settings.\n\nEnabling requires a saved card (enforced server-side -> 400 otherwise)."},"BYOKKeyListResponse":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/BYOKKeyResponse"},"type":"array","title":"Keys"}},"type":"object","required":["keys"],"title":"BYOKKeyListResponse","description":"Response body listing all BYOK keys for a user."},"BYOKKeyResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"provider":{"type":"string","title":"Provider"},"key_prefix":{"type":"string","title":"Key Prefix"},"is_active":{"type":"boolean","title":"Is Active"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"last_validated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Validated At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","provider","key_prefix","is_active","created_at","updated_at"],"title":"BYOKKeyResponse","description":"Response body for a single BYOK key. Never includes the raw or encrypted key."},"BatchSubmitRequest":{"properties":{"domains":{"items":{"type":"string"},"type":"array","maxItems":50,"minItems":1,"title":"Domains"},"depth":{"type":"string","enum":["deep","quick"],"title":"Depth","description":"Analysis depth, applied to every domain in the batch. 'deep' (default) queues full async analyses (202/207/422). 'quick' runs each domain through the inline fast path (same semantics as POST /analyze depth=quick) and is capped at 10 domains per batch (422 above) so the request stays under the edge timeout; it cannot be combined with force_refresh (400).","default":"deep"},"force_refresh":{"type":"boolean","title":"Force Refresh","default":false},"callback_url":{"anyOf":[{"type":"string","maxLength":2048,"pattern":"^https?://"},{"type":"null"}],"title":"Callback Url","description":"Optional URL to receive webhook POSTs when each analysis completes"},"output_schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Output Schema","description":"Template ID for response projection (mutually exclusive with output_fields)"},"output_fields":{"anyOf":[{"$ref":"#/components/schemas/OutputFieldsSpec"},{"type":"null"}],"description":"Inline field spec for response projection (mutually exclusive with output_schema)"}},"type":"object","required":["domains"],"title":"BatchSubmitRequest","description":"Request body for submitting multiple domains for analysis.\n\nAttributes:\n    domains: List of domain names to analyze (1-50)."},"BulkSubmitRequest":{"properties":{"domains":{"items":{"type":"string"},"type":"array","maxItems":500,"minItems":1,"title":"Domains"},"force_refresh":{"type":"boolean","title":"Force Refresh","default":false}},"type":"object","required":["domains"],"title":"BulkSubmitRequest","description":"Request body for a bulk domain upload (results delivered by email).\n\nAttributes:\n    domains: List of domain names to analyze (1-500; the operator-tunable\n        cap ``settings.bulk_max_domains`` is enforced by the handler).\n    force_refresh: Skip deduplication, force fresh analysis runs."},"CheckoutRequest":{"properties":{"tier":{"type":"string","enum":["pro","team"],"title":"Tier"},"billing_period":{"type":"string","enum":["monthly","annual"],"title":"Billing Period","default":"monthly"},"success_url":{"type":"string","title":"Success Url"},"cancel_url":{"type":"string","title":"Cancel Url"}},"type":"object","required":["tier","success_url","cancel_url"],"title":"CheckoutRequest","description":"Request body for creating a Stripe Checkout Session."},"CheckoutResponse":{"properties":{"checkout_url":{"type":"string","title":"Checkout Url"},"session_id":{"type":"string","title":"Session Id"}},"type":"object","required":["checkout_url","session_id"],"title":"CheckoutResponse","description":"Response body after creating a Stripe Checkout Session."},"ClusterEdge":{"properties":{"from_domain":{"type":"string","title":"From Domain"},"to_domain":{"type":"string","title":"To Domain"},"reason":{"type":"string","title":"Reason"}},"type":"object","required":["from_domain","to_domain","reason"],"title":"ClusterEdge","description":"A directed, reason-annotated hop in a cluster's edge list (R3).\n\nExpresses the pairwise evidence that caused two domains to be placed in\nthe same cluster as a human-readable ``{from_domain, to_domain, reason}``\ntriple. Each signal match between a pair produces one edge entry so the\ncluster is explainable hop-by-hop (e.g. domain_a -> domain_b :\n``\"shared analytics_id G-XXXX\"``).\n\n``from_domain`` / ``to_domain`` are ordered lexicographically (the lower\ncomes first) so edges are deterministic and deduplication is trivial."},"ClusterEvidence":{"properties":{"mean_pairwise_score":{"type":"number","title":"Mean Pairwise Score","default":0.0},"corroboration_bonus":{"type":"number","title":"Corroboration Bonus","default":0.0}},"type":"object","title":"ClusterEvidence","description":"Evidence ledger for one cluster's confidence score.\n\nSplits the reported confidence into the two components that\n``ClusterBuilder._compute_confidence`` has always computed and then\ndiscarded: the mean intra-cluster pairwise score and the capped\ncorroboration bonus for distinct owner-relation signal types."},"CorrelateRequest":{"properties":{"domains":{"items":{"type":"string"},"type":"array","maxItems":100,"minItems":2,"title":"Domains"},"callback_url":{"anyOf":[{"type":"string","maxLength":2048,"pattern":"^https?://"},{"type":"null"}],"title":"Callback Url","description":"Optional URL to receive a webhook POST when correlation completes"}},"type":"object","required":["domains"],"title":"CorrelateRequest","description":"Request body for submitting a cross-domain correlation analysis.\n\nAttributes:\n    domains: List of domain names to correlate (2-100).\n    callback_url: Optional webhook URL for completion notification.","example":{"callback_url":"https://example.com/webhook","domains":["example.com","example.org"]}},"CorrelateResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"correlation_id":{"type":"string","title":"Correlation Id"},"poll_url":{"type":"string","title":"Poll Url"},"domain_count":{"type":"integer","title":"Domain Count"},"domains_cached":{"type":"integer","title":"Domains Cached"},"domains_to_analyze":{"type":"integer","title":"Domains To Analyze"},"credits_charged":{"type":"integer","title":"Credits Charged"},"status":{"type":"string","title":"Status","default":"pending"}},"type":"object","required":["job_id","correlation_id","poll_url","domain_count","domains_cached","domains_to_analyze","credits_charged"],"title":"CorrelateResponse","description":"Response after submitting a correlation analysis (202 Accepted).\n\nAttributes:\n    job_id: Unique correlation job identifier for polling status.\n    correlation_id: Alias of ``job_id`` -- the value to pass to\n        ``GET /correlate/{correlation_id}``.\n    poll_url: Relative URL to poll for status/results. Correlation jobs are\n        NOT retrievable from ``GET /jobs/{id}`` (that serves single-domain\n        analysis only); this points at the correct endpoint.\n    domain_count: Number of domains submitted.\n    domains_cached: Number of domains with fully cached signals.\n    domains_to_analyze: Number of domains needing fresh analysis.\n    credits_charged: Total credits deducted for this request.\n    status: Initial job status (always \"pending\").","example":{"correlation_id":"3f6b8c2a-7a1d-4e5f-9b0c-2d4e6f8a0b1c","credits_charged":4,"domain_count":7,"domains_cached":4,"domains_to_analyze":3,"job_id":"3f6b8c2a-7a1d-4e5f-9b0c-2d4e6f8a0b1c","poll_url":"/correlate/3f6b8c2a-7a1d-4e5f-9b0c-2d4e6f8a0b1c","status":"pending"}},"CorrelateStatusResponse":{"properties":{"correlation_id":{"type":"string","title":"Correlation Id"},"status":{"type":"string","title":"Status"},"domain_count":{"type":"integer","title":"Domain Count"},"result":{"anyOf":[{"$ref":"#/components/schemas/CorrelationResult"},{"type":"null"}]},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"phase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phase"}},"type":"object","required":["correlation_id","status","domain_count"],"title":"CorrelateStatusResponse","description":"Response for polling correlation job status.\n\nAttributes:\n    correlation_id: The correlation job identifier.\n    status: Current job status (pending | processing | completed | failed).\n        Always converges on a terminal value: a job that outlives its budget\n        is reported ``failed`` rather than ``processing`` forever (#363).\n    domain_count: Number of domains in the correlation.\n    result: Correlation result when status is \"completed\".\n    error: Error message when status is \"failed\".\n    phase: Coarse progress hint while non-terminal (``queued`` ->\n        ``analyzing`` -> ``correlating`` -> ``summarizing``). Cosmetic only,\n        and distinct from ``status`` -- it lets a poller show real progress\n        instead of an opaque \"processing\", and distinguishes a job still\n        waiting for a worker (``queued``) from one being worked on.","example":{"correlation_id":"3f6b8c2a-7a1d-4e5f-9b0c-2d4e6f8a0b1c","domain_count":7,"status":"completed"}},"CorrelationCluster":{"properties":{"cluster_id":{"type":"string","title":"Cluster Id"},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence"},"domain_count":{"type":"integer","title":"Domain Count"},"domains":{"items":{"type":"string"},"type":"array","title":"Domains"},"shared_signals":{"items":{"$ref":"#/components/schemas/SharedSignal"},"type":"array","title":"Shared Signals"},"edges":{"items":{"$ref":"#/components/schemas/ClusterEdge"},"type":"array","title":"Edges"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"evidence":{"anyOf":[{"$ref":"#/components/schemas/ClusterEvidence"},{"type":"null"}]}},"type":"object","required":["cluster_id","confidence","domain_count","domains","shared_signals"],"title":"CorrelationCluster","description":"A group of domains with high pairwise correlation.\n\nDomains within a cluster share sufficient pairwise correlation to be grouped.\nIncludes per-cluster confidence and shared signal evidence for transparency."},"CorrelationResult":{"properties":{"correlation_id":{"type":"string","title":"Correlation Id"},"domain_count":{"type":"integer","title":"Domain Count"},"analyzed_at":{"type":"string","title":"Analyzed At"},"clusters":{"items":{"$ref":"#/components/schemas/CorrelationCluster"},"type":"array","title":"Clusters"},"unclustered_domains":{"items":{"type":"string"},"type":"array","title":"Unclustered Domains"},"pairwise":{"items":{"$ref":"#/components/schemas/PairwiseScore"},"type":"array","title":"Pairwise"},"correlation_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Correlation Summary"},"excluded_domains":{"items":{"type":"string"},"type":"array","title":"Excluded Domains"},"degraded":{"type":"boolean","title":"Degraded","default":false}},"type":"object","required":["correlation_id","domain_count","analyzed_at","clusters","unclustered_domains","pairwise"],"title":"CorrelationResult","description":"Top-level correlation engine output.\n\nContains clusters, pairwise scores, and unclustered domains for a\ncross-domain correlation analysis. Matches the design partner spec\nshape (Example 3 in output_examples.md)."},"CreateApiKeyRequest":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"}},"type":"object","required":["name"],"title":"CreateApiKeyRequest","description":"Request body for creating a new API key."},"CreateTemplateRequest":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Description"},"mode":{"type":"string","enum":["include","exclude"],"title":"Mode"},"fields":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Fields"}},"type":"object","required":["name","mode","fields"],"title":"CreateTemplateRequest","description":"Request body for creating a new schema template."},"CreditBalance":{"properties":{"balance":{"type":"integer","title":"Balance"},"has_active_subscription":{"type":"boolean","title":"Has Active Subscription"}},"type":"object","required":["balance","has_active_subscription"],"title":"CreditBalance","description":"Current credit balance and subscription status for a user."},"CreditCheckoutRequest":{"properties":{"pack":{"type":"string","enum":["100_credits","500_credits","2000_credits"],"title":"Pack"},"success_url":{"type":"string","title":"Success Url"},"cancel_url":{"type":"string","title":"Cancel Url"}},"type":"object","required":["pack","success_url","cancel_url"],"title":"CreditCheckoutRequest","description":"Request body for creating a Stripe Checkout Session for credit packs."},"CreditCheckoutResponse":{"properties":{"checkout_url":{"type":"string","title":"Checkout Url"},"session_id":{"type":"string","title":"Session Id"}},"type":"object","required":["checkout_url","session_id"],"title":"CreditCheckoutResponse","description":"Response body after creating a credit pack checkout session."},"DomainAttribution":{"properties":{"domain":{"type":"string","title":"Domain"},"domain_id":{"type":"string","title":"Domain Id"},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence"},"signal_count":{"type":"integer","title":"Signal Count"},"last_seen":{"type":"string","title":"Last Seen"}},"type":"object","required":["domain","domain_id","confidence","signal_count","last_seen"],"title":"DomainAttribution","description":"A domain linked to an entity with attribution confidence.\n\nAttributes:\n    domain: Domain name (e.g., \"example.com\").\n    domain_id: UUID of the domain record.\n    confidence: Maximum attribution confidence for this entity-domain pair.\n    signal_count: Number of distinct signals linking entity to domain.\n    last_seen: ISO 8601 timestamp of the most recent signal."},"DomainPortfolioResponse":{"properties":{"domain":{"type":"string","title":"Domain"},"entity_name":{"type":"string","title":"Entity Name"},"portfolio_count":{"type":"integer","title":"Portfolio Count"},"domains":{"items":{"$ref":"#/components/schemas/PortfolioDomain"},"type":"array","title":"Domains"},"cached":{"type":"boolean","title":"Cached"}},"type":"object","required":["domain","entity_name","portfolio_count","domains","cached"],"title":"DomainPortfolioResponse","description":"Portfolio of domains owned by the same entity as the queried domain.\n\nAttributes:\n    domain: The queried domain name.\n    entity_name: The top entity found for this domain from scoring.\n    portfolio_count: Total domains in portfolio (may exceed len(domains)).\n    domains: Up to 50 domains from the entity's Reverse WHOIS portfolio.\n    cached: Whether the result was served from cache."},"EmailPreferencesResponse":{"properties":{"digest_enabled":{"type":"boolean","title":"Digest Enabled"},"changelog_enabled":{"type":"boolean","title":"Changelog Enabled"},"lifecycle_enabled":{"type":"boolean","title":"Lifecycle Enabled"}},"type":"object","required":["digest_enabled","changelog_enabled","lifecycle_enabled"],"title":"EmailPreferencesResponse","description":"Response body showing current email preferences."},"EmailPreferencesUpdate":{"properties":{"digest_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Digest Enabled"},"changelog_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Changelog Enabled"},"lifecycle_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Lifecycle Enabled"}},"type":"object","title":"EmailPreferencesUpdate","description":"Request body for PATCH /auth/email-preferences."},"EntityMatch":{"properties":{"entity_id":{"type":"string","title":"Entity Id"},"name":{"type":"string","title":"Name"},"display_name":{"type":"string","title":"Display Name"},"entity_type":{"type":"string","title":"Entity Type"},"domain_count":{"type":"integer","title":"Domain Count"},"domains":{"items":{"$ref":"#/components/schemas/DomainAttribution"},"type":"array","title":"Domains"},"relevance":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Relevance"},"portfolio_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Portfolio Count"}},"type":"object","required":["entity_id","name","display_name","entity_type","domain_count","relevance"],"title":"EntityMatch","description":"A single entity matching the search query.\n\nAttributes:\n    entity_id: UUID of the entity record.\n    name: Normalized canonical entity name.\n    display_name: Human-readable entity name (first alias or name).\n    entity_type: Entity classification (organization, person, unknown).\n    domain_count: Total number of associated domains.\n    domains: Top domains by confidence (capped at 10 in search results).\n    relevance: Search relevance score (1.0=exact, 0.8=prefix, 0.5=substring).\n    portfolio_count: Domain count from Reverse WHOIS preview (None when unavailable)."},"EntitySearchResponse":{"properties":{"query":{"type":"string","title":"Query"},"total":{"type":"integer","title":"Total"},"entities":{"items":{"$ref":"#/components/schemas/EntityMatch"},"type":"array","title":"Entities"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"has_more":{"type":"boolean","title":"Has More"}},"type":"object","required":["query","total","entities","limit","offset","has_more"],"title":"EntitySearchResponse","description":"Paginated entity search results.\n\nAttributes:\n    query: Original search query string.\n    total: Total number of matching entities.\n    entities: List of matched entities with relevance and domains.\n    limit: Maximum results per page.\n    offset: Number of results skipped.\n    has_more: Whether more results exist beyond this page."},"EntitySignalBreakdown":{"properties":{"entity":{"$ref":"#/components/schemas/EntitySummary"},"signals":{"items":{"$ref":"#/components/schemas/SignalDetail"},"type":"array","title":"Signals"},"correlations":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Correlations"}},"type":"object","required":["entity","signals"],"title":"EntitySignalBreakdown","description":"Full signal breakdown for a single entity.\n\nAttributes:\n    entity: Summary of the entity being examined.\n    signals: List of individual signal contributions.\n    correlations: Cross-signal correlation evidence."},"EntitySummary":{"properties":{"name":{"type":"string","title":"Name"},"display_name":{"type":"string","title":"Display Name"},"entity_type":{"type":"string","title":"Entity Type"},"confidence":{"type":"number","title":"Confidence"}},"type":"object","required":["name","display_name","entity_type","confidence"],"title":"EntitySummary","description":"Compact entity representation for signal breakdown responses.\n\nAttributes:\n    name: Canonical normalized entity name.\n    display_name: Best raw entity name preserving original casing.\n    entity_type: Classification (organization, person, unknown).\n    confidence: Computed confidence score from 0.0 to 1.0."},"ErrorDetail":{"properties":{"code":{"type":"string","title":"Code"},"message":{"type":"string","title":"Message"},"details":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Details"}},"type":"object","required":["code","message"],"title":"ErrorDetail","description":"Structured error information for API error responses.\n\nAttributes:\n    code: Machine-readable error code (e.g. \"JOB_NOT_FOUND\").\n    message: Human-readable error description.\n    details: Optional additional context for debugging.","example":{"code":"JOB_NOT_FOUND","message":"No job found with the given ID."}},"EvidenceSignal":{"properties":{"signal_type":{"type":"string","title":"Signal Type"},"value":{"type":"string","title":"Value"},"strength_bucket":{"type":"string","enum":["strong","moderate","weak"],"title":"Strength Bucket"}},"type":"object","required":["signal_type","value","strength_bucket"],"title":"EvidenceSignal","description":"One matched signal in the pairwise evidence ledger.\n\nCarries the signal type, the concrete matched value, and a coarse\nstrength bucket -- never the raw scoring weight (opsec: exact weights\nmust not appear in user-facing output)."},"FieldChange":{"properties":{"from":{"type":"string","title":"From"},"to":{"type":"string","title":"To"}},"type":"object","required":["from","to"],"title":"FieldChange","description":"A single field that changed between consecutive runs.\n\nAttributes:\n    from_value: Previous value (serialized as \"from\").\n    to_value: Current value (serialized as \"to\")."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HistoryEntry":{"properties":{"analysis_run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Analysis Run Id"},"completed_at":{"type":"string","title":"Completed At"},"status":{"type":"string","title":"Status"},"entity":{"type":"string","title":"Entity"},"entity_type":{"type":"string","title":"Entity Type"},"confidence":{"type":"number","title":"Confidence"},"registrar":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Registrar"},"nameservers":{"items":{"type":"string"},"type":"array","title":"Nameservers"},"verdict":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verdict"},"verdict_operator":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verdict Operator"},"diff":{"anyOf":[{"$ref":"#/components/schemas/RunDiff"},{"type":"null"}]},"source":{"type":"string","title":"Source","default":"local"}},"type":"object","required":["completed_at","status","entity","entity_type","confidence"],"title":"HistoryEntry","description":"A single analysis run in the domain's history timeline.\n\nAttributes:\n    analysis_run_id: UUID of the analysis run, or None for external entries.\n    completed_at: ISO 8601 timestamp when the run completed.\n    status: Run status (completed, partial, or historical).\n    entity: Top attributed entity display name.\n    entity_type: Entity classification (organization, person, unknown).\n    confidence: Top entity confidence score (0.0 to 1.0).\n    registrar: Domain registrar from WHOIS data.\n    nameservers: Sorted list of nameservers.\n    verdict: Attribution verdict tier for this run, or None for runs\n        persisted before verdicts existed and external entries.\n    verdict_operator: Operator named by the verdict, when present.\n    diff: Changes from the previous run, or None for the first run.\n    source: Source of this entry: \"local\" (WhoisGenius analysis) or\n        \"external\" (upstream WHOIS History provider)."},"HistoryResponse":{"properties":{"domain":{"type":"string","title":"Domain"},"total_runs":{"type":"integer","title":"Total Runs"},"entries":{"items":{"$ref":"#/components/schemas/HistoryEntry"},"type":"array","title":"Entries"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"has_more":{"type":"boolean","title":"Has More","default":false}},"type":"object","required":["domain","total_runs","entries"],"title":"HistoryResponse","description":"Paginated history of analysis runs for a domain.\n\nAttributes:\n    domain: The domain name queried.\n    total_runs: Total number of completed/partial runs for this domain.\n    entries: List of history entries (newest first).\n    next_cursor: Cursor for the next page (completed_at ISO timestamp).\n    has_more: Whether more entries exist beyond this page."},"JobListItem":{"properties":{"job_id":{"type":"string","title":"Job Id"},"domain":{"type":"string","title":"Domain"},"status":{"type":"string","enum":["pending","processing","completed","failed","partial"],"title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms"}},"type":"object","required":["job_id","domain","status","created_at"],"title":"JobListItem","description":"A single job in the per-user job history list.\n\nAttributes:\n    job_id: Unique job identifier.\n    domain: Domain that was analyzed.\n    status: Current job status (pending, processing, completed, failed, partial).\n    created_at: When the job was submitted.\n    completed_at: When the job reached a terminal state, or None while in flight.\n    duration_ms: Wall-clock time from submission to ``completed_at`` (falling\n        back to ``updated_at`` for failed jobs persisted before the fix that\n        stamped ``completed_at``), in milliseconds."},"JobListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/JobListItem"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["total","limit","offset"],"title":"JobListResponse","description":"Paginated per-user job history, newest first.\n\nAttributes:\n    items: The page of jobs.\n    total: Total number of jobs owned by the caller.\n    limit: Page size used for this response.\n    offset: Rows skipped for this response."},"JobStatusResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","enum":["pending","processing","completed","failed","partial"],"title":"Status"},"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain"},"phase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phase"},"collectors_done":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Collectors Done"},"collectors_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Collectors Total"},"analysis_run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Analysis Run Id"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At"},"scoring_result":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Scoring Result"}},"type":"object","required":["job_id","status"],"title":"JobStatusResponse","description":"Response for polling a job's current status.\n\nWhen the job is completed, ``scoring_result`` contains the full\nScoringResult dict from the scoring engine.\n\nWhen LLM enrichment is enabled and the ambiguity gate triggers,\nthe scoring_result dict includes additional fields:\nenriched_conclusion, entity_resolution_notes, arbitration_reasoning.\nThese fields are null/absent when LLM was not invoked.\n\nAttributes:\n    job_id: Unique job identifier.\n    status: Current job status (pending, processing, completed, failed).\n    domain: Domain being analyzed.\n    collectors_done: Number of collectors that have finished.\n    collectors_total: Total number of collectors in the pipeline.\n    analysis_run_id: Database analysis run ID.\n    started_at: ISO 8601 timestamp when the job started processing.\n    completed_at: ISO 8601 timestamp when the job finished.\n    scoring_result: Full scoring result dict when completed. May include\n        enriched_conclusion, entity_resolution_notes, and\n        arbitration_reasoning when LLM enrichment was invoked.","example":{"analysis_run_id":"660e8400-e29b-41d4-a716-446655440001","collectors_done":3,"collectors_total":7,"domain":"example.com","job_id":"550e8400-e29b-41d4-a716-446655440000","started_at":"2026-03-18T12:00:00Z","status":"processing"}},"LedgerEntry":{"properties":{"id":{"type":"string","title":"Id"},"delta":{"type":"integer","title":"Delta"},"reason":{"type":"string","title":"Reason"},"balance_after":{"type":"integer","title":"Balance After"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","delta","reason","balance_after","created_at"],"title":"LedgerEntry","description":"A single credit-ledger movement for the credit-history view."},"MeResponse":{"properties":{"user_id":{"type":"string","format":"uuid","title":"User Id"},"email":{"type":"string","title":"Email"},"tier":{"type":"string","title":"Tier"},"cognito_sub":{"type":"string","title":"Cognito Sub"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["user_id","email","tier","cognito_sub","created_at"],"title":"MeResponse","description":"Response body for GET /auth/me -- current user profile."},"NameserverDiff":{"properties":{"added":{"items":{"type":"string"},"type":"array","title":"Added"},"removed":{"items":{"type":"string"},"type":"array","title":"Removed"}},"type":"object","title":"NameserverDiff","description":"Nameserver additions and removals between consecutive runs.\n\nAttributes:\n    added: Nameservers present in current but not previous run.\n    removed: Nameservers present in previous but not current run."},"OutputFieldsSpec":{"properties":{"mode":{"type":"string","enum":["include","exclude"],"title":"Mode"},"fields":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Fields"}},"type":"object","required":["mode","fields"],"title":"OutputFieldsSpec","description":"Inline field projection spec for response customization.\n\nSpecifies a mode (include or exclude) and a list of dot-notation field\npaths to apply as a filter on the scoring_result dict.\n\nAttributes:\n    mode: ``\"include\"`` to whitelist fields, ``\"exclude\"`` to blacklist.\n    fields: List of dot-notation field paths (min 1 required)."},"PairwiseEvidence":{"properties":{"signals":{"items":{"$ref":"#/components/schemas/EvidenceSignal"},"type":"array","title":"Signals"},"available_signal_count":{"type":"integer","title":"Available Signal Count","default":0},"adjustments":{"items":{"type":"string"},"type":"array","title":"Adjustments"},"both_cdn":{"type":"boolean","title":"Both Cdn","default":false},"score_pre_round":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Score Pre Round","default":0.0}},"type":"object","title":"PairwiseEvidence","description":"Evidence ledger for one pairwise score.\n\nThe public explainability contract for a pair: WHICH signals matched\n(type + value + strength bucket), how many signal types were available\nto compare (the dilution denominator, as a COUNT), and which scoring\nadjustments fired. Raw weight numbers are never exposed."},"PairwiseScore":{"properties":{"domain_a":{"type":"string","title":"Domain A"},"domain_b":{"type":"string","title":"Domain B"},"correlation":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Correlation"},"shared_signals":{"items":{"type":"string"},"type":"array","title":"Shared Signals"},"matched_signals":{"items":{"$ref":"#/components/schemas/PairwiseSignalMatch"},"type":"array","title":"Matched Signals"},"evidence":{"anyOf":[{"$ref":"#/components/schemas/PairwiseEvidence"},{"type":"null"}]}},"type":"object","required":["domain_a","domain_b","correlation","shared_signals"],"title":"PairwiseScore","description":"Pairwise correlation between two domains.\n\nCaptures the computed correlation score and the list of signal types\nthat contributed to it."},"PairwiseSignalMatch":{"properties":{"signal_type":{"type":"string","title":"Signal Type"},"value":{"type":"string","title":"Value"}},"type":"object","required":["signal_type","value"],"title":"PairwiseSignalMatch","description":"Enriched signal match record on a PairwiseScore (R3).\n\nCarries the signal type name and the matched value so callers can\nbuild human-readable edge reasons without re-inspecting raw collector\ndata. Deliberately omits the internal scoring weight (opsec: weights\nmust not appear in user-facing output)."},"PortalResponse":{"properties":{"portal_url":{"type":"string","title":"Portal Url"}},"type":"object","required":["portal_url"],"title":"PortalResponse","description":"Response body with Stripe Customer Portal URL."},"PortfolioDomain":{"properties":{"domain_name":{"type":"string","title":"Domain Name"}},"type":"object","required":["domain_name"],"title":"PortfolioDomain","description":"A single domain from Reverse WHOIS portfolio results.\n\nAttributes:\n    domain_name: Fully qualified domain name."},"PortfolioResponse":{"properties":{"entity_name":{"type":"string","title":"Entity Name"},"portfolio_count":{"type":"integer","title":"Portfolio Count"},"domains":{"items":{"$ref":"#/components/schemas/PortfolioDomain"},"type":"array","title":"Domains"},"cached":{"type":"boolean","title":"Cached"},"cache_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cache Expires At"}},"type":"object","required":["entity_name","portfolio_count","domains","cached"],"title":"PortfolioResponse","description":"Response for the entity portfolio endpoint.\n\nContains the domains associated with an entity via Reverse WHOIS API,\ncapped at 50 results.\n\nAttributes:\n    entity_name: The queried entity name.\n    portfolio_count: Total domain count (may exceed len(domains) if > 50).\n    domains: List of domains (up to 50).\n    cached: Whether the result was served from cache.\n    cache_expires_at: ISO 8601 timestamp when cache entry expires, if cached."},"RunDiff":{"properties":{"entity":{"anyOf":[{"$ref":"#/components/schemas/FieldChange"},{"type":"null"}]},"confidence_delta":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence Delta"},"registrar":{"anyOf":[{"$ref":"#/components/schemas/FieldChange"},{"type":"null"}]},"nameservers":{"anyOf":[{"$ref":"#/components/schemas/NameserverDiff"},{"type":"null"}]},"verdict":{"anyOf":[{"$ref":"#/components/schemas/VerdictChange"},{"type":"null"}]},"significance":{"type":"string","enum":["material","minor","none"],"title":"Significance","default":"none"},"significance_reasons":{"items":{"type":"string"},"type":"array","title":"Significance Reasons"}},"type":"object","title":"RunDiff","description":"Diff between this run and the previous run.\n\nAll change fields are None when unchanged. The entire diff is None for\nthe first run in a domain's history (no previous run to compare).\n\nAttributes:\n    entity: Entity attribution change (from/to display names).\n    confidence_delta: Change in top-entity confidence score.\n    registrar: Registrar change (from/to).\n    nameservers: Nameserver additions and removals.\n    verdict: Attribution verdict transition, when both runs have a verdict.\n    significance: Whether this change is worth acting on -- ``material``\n        (an operator/verdict/registrar/NS-migration event a monitor would\n        alert on), ``minor`` (a timeline detail), or ``none``.\n    significance_reasons: Ordered, human-readable reasons for the\n        significance classification (most significant first)."},"SaveBYOKKeyRequest":{"properties":{"provider":{"type":"string","title":"Provider","description":"LLM provider name: \"openai\", \"anthropic\", \"google\", \"mistral\", \"groq\""},"api_key":{"type":"string","minLength":1,"title":"Api Key","description":"Raw API key (will be encrypted server-side, never stored in plaintext)"}},"type":"object","required":["provider","api_key"],"title":"SaveBYOKKeyRequest","description":"Request body for saving (creating or updating) a BYOK LLM API key."},"SharedSignal":{"properties":{"signal_type":{"type":"string","title":"Signal Type"},"value":{"type":"string","title":"Value"},"domains_sharing":{"type":"integer","title":"Domains Sharing"},"all_domains":{"type":"boolean","title":"All Domains"},"relation":{"type":"string","enum":["owner","host","mixed"],"title":"Relation","default":"host"}},"type":"object","required":["signal_type","value","domains_sharing","all_domains"],"title":"SharedSignal","description":"A signal shared across domains in a cluster or pair.\n\nRepresents aggregate signal evidence within a correlation cluster,\ndocumenting which signals are shared by how many domains."},"SignalDetail":{"properties":{"signal_type":{"type":"string","title":"Signal Type"},"field":{"type":"string","title":"Field"},"raw_value":{"type":"string","title":"Raw Value"},"collector_confidence":{"type":"number","title":"Collector Confidence"},"contribution":{"type":"number","title":"Contribution"},"redacted":{"type":"boolean","title":"Redacted","default":false}},"type":"object","required":["signal_type","field","raw_value","collector_confidence","contribution"],"title":"SignalDetail","description":"Individual signal contribution within a signal breakdown.\n\nAttributes:\n    signal_type: Collector that produced this signal.\n    field: Specific field within the collector output.\n    raw_value: Original value from the collector.\n    collector_confidence: Collector's self-assessed data quality.\n    contribution: Effective contribution to entity score.\n    redacted: Whether the value was detected as privacy-protected."},"SignalsResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain"},"entities":{"items":{"$ref":"#/components/schemas/EntitySignalBreakdown"},"type":"array","title":"Entities"},"missing_signals":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Missing Signals"}},"type":"object","required":["job_id","entities"],"title":"SignalsResponse","description":"Top-level response for the signal breakdown endpoint.\n\nProvides full transparency into how entities were scored for\na given domain analysis.\n\nAttributes:\n    job_id: The job whose signals are being displayed.\n    domain: The domain that was analyzed.\n    entities: Per-entity signal breakdowns.\n    missing_signals: Signals that could not be collected."},"SubdomainEntry":{"properties":{"subdomain":{"type":"string","title":"Subdomain"},"source":{"type":"string","enum":["ct_san","dns_brute"],"title":"Source"}},"type":"object","required":["subdomain","source"],"title":"SubdomainEntry","description":"A single discovered subdomain with source attribution.\n\nAttributes:\n    subdomain: Fully qualified subdomain name (e.g., \"mail.example.com\").\n    source: Discovery method that found this subdomain.","example":{"source":"ct_san","subdomain":"mail.example.com"}},"SubdomainsResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"domain":{"type":"string","title":"Domain"},"subdomains":{"items":{"$ref":"#/components/schemas/SubdomainEntry"},"type":"array","title":"Subdomains"},"total":{"type":"integer","title":"Total"},"sources_used":{"items":{"type":"string"},"type":"array","title":"Sources Used"},"cached":{"type":"boolean","title":"Cached"},"wildcard_detected":{"type":"boolean","title":"Wildcard Detected"}},"type":"object","required":["job_id","domain","subdomains","total","sources_used","cached","wildcard_detected"],"title":"SubdomainsResponse","description":"Response for the subdomain enumeration endpoint.\n\nContains all discovered subdomains with per-entry source attribution,\ncaching metadata, and wildcard detection status.\n\nAttributes:\n    job_id: The job that triggered subdomain enumeration.\n    domain: The target domain whose subdomains were discovered.\n    subdomains: List of discovered subdomains with source attribution.\n    total: Total number of unique subdomains found.\n    sources_used: Which discovery methods were used (e.g., [\"ct_san\", \"dns_brute\"]).\n    cached: Whether the result was served from Redis cache.\n    wildcard_detected: Whether the domain has a wildcard DNS record.","example":{"cached":false,"domain":"example.com","job_id":"550e8400-e29b-41d4-a716-446655440000","sources_used":["ct_san","dns_brute"],"subdomains":[{"source":"ct_san","subdomain":"mail.example.com"},{"source":"dns_brute","subdomain":"api.example.com"}],"total":2,"wildcard_detected":false}},"SubmitRequest":{"properties":{"domain":{"type":"string","maxLength":512,"minLength":1,"title":"Domain","description":"Domain name to analyze (ASCII or internationalized)."},"display_domain":{"type":"string","title":"Display Domain","description":"Original IDN form of the domain (populated internally).","default":""},"depth":{"type":"string","enum":["deep","quick"],"title":"Depth","description":"Analysis depth. 'deep' (default) queues the full async pipeline (all collectors, vision, LLM enrichment, metered third-party enrichment) and returns 202 + job_id for polling. 'quick' runs the fast collector subset (dns/whois/http_probe/asn/rdns) inline under a hard deadline and returns 200 with the terminal result plus job_id; it never runs vision/LLM/reverse-WHOIS/metered enrichment. 'quick' cannot be combined with force_refresh or wait=true (400).","default":"deep"},"force_refresh":{"type":"boolean","title":"Force Refresh","default":false},"callback_url":{"anyOf":[{"type":"string","maxLength":2048,"pattern":"^https?://"},{"type":"null"}],"title":"Callback Url","description":"Optional URL to receive a webhook POST when analysis completes"},"output_schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Output Schema","description":"Template ID for response projection (mutually exclusive with output_fields)"},"output_fields":{"anyOf":[{"$ref":"#/components/schemas/OutputFieldsSpec"},{"type":"null"}],"description":"Inline field spec for response projection (mutually exclusive with output_schema)"}},"type":"object","required":["domain"],"title":"SubmitRequest","description":"Request body for submitting a single domain for analysis.\n\nAttributes:\n    domain: Fully qualified domain name to analyze.\n    force_refresh: Skip dedup and force a fresh analysis run.","example":{"callback_url":"https://example.com/webhook","domain":"example.com","force_refresh":false}},"SubmitResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"domain":{"type":"string","title":"Domain"},"display_domain":{"type":"string","title":"Display Domain","default":""},"status":{"type":"string","enum":["pending","processing","completed","failed","partial"],"title":"Status"},"deduplicated":{"type":"boolean","title":"Deduplicated","default":false},"analysis_run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Analysis Run Id"},"charge":{"anyOf":[{"$ref":"#/components/schemas/AnalysisCharge"},{"type":"null"}]}},"type":"object","required":["job_id","domain","status"],"title":"SubmitResponse","description":"Response after submitting a domain for analysis.\n\nAttributes:\n    job_id: Unique job identifier for polling status.\n    domain: The domain submitted (normalized).\n    status: Initial job status (typically \"pending\").\n    deduplicated: True if an existing active job was returned.\n    analysis_run_id: Database analysis run ID (None if deduplicated).","example":{"analysis_run_id":"660e8400-e29b-41d4-a716-446655440001","deduplicated":false,"display_domain":"example.com","domain":"example.com","job_id":"550e8400-e29b-41d4-a716-446655440000","status":"pending"}},"SubscriptionInfo":{"properties":{"tier":{"type":"string","title":"Tier"},"subscription_status":{"type":"string","title":"Subscription Status"},"stripe_subscription_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe Subscription Id"},"stripe_customer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe Customer Id"}},"type":"object","required":["tier","subscription_status","stripe_subscription_id","stripe_customer_id"],"title":"SubscriptionInfo","description":"Current subscription details for a user."},"TemplateListResponse":{"properties":{"templates":{"items":{"$ref":"#/components/schemas/TemplateResponse"},"type":"array","title":"Templates"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["templates","count"],"title":"TemplateListResponse","description":"Response body listing templates with count."},"TemplateResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"mode":{"type":"string","title":"Mode"},"fields":{"items":{"type":"string"},"type":"array","title":"Fields"},"is_builtin":{"type":"boolean","title":"Is Builtin"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","description","mode","fields","is_builtin","created_at","updated_at"],"title":"TemplateResponse","description":"Response body for a single schema template."},"UpdateTemplateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"mode":{"anyOf":[{"type":"string","enum":["include","exclude"]},{"type":"null"}],"title":"Mode"},"fields":{"anyOf":[{"items":{"type":"string"},"type":"array","minItems":1},{"type":"null"}],"title":"Fields"}},"type":"object","title":"UpdateTemplateRequest","description":"Request body for updating an existing schema template."},"UsageDashboard":{"properties":{"tier":{"type":"string","title":"Tier"},"count":{"type":"integer","title":"Count"},"quota":{"type":"integer","title":"Quota"},"percentage":{"type":"number","title":"Percentage"},"remaining":{"type":"integer","title":"Remaining"},"billing_period_start":{"type":"string","title":"Billing Period Start"},"billing_period_end":{"type":"string","title":"Billing Period End"},"projected_overage_cost":{"type":"number","title":"Projected Overage Cost"},"overage_rate":{"type":"number","title":"Overage Rate"},"credit_balance":{"type":"integer","title":"Credit Balance"},"has_active_subscription":{"type":"boolean","title":"Has Active Subscription"},"llm_query_count":{"type":"integer","title":"Llm Query Count","default":0},"llm_total_tokens":{"type":"integer","title":"Llm Total Tokens","default":0},"llm_estimated_cost_usd":{"type":"number","title":"Llm Estimated Cost Usd","default":0.0}},"type":"object","required":["tier","count","quota","percentage","remaining","billing_period_start","billing_period_end","projected_overage_cost","overage_rate","credit_balance","has_active_subscription"],"title":"UsageDashboard","description":"Usage dashboard showing monthly consumption and quota status."},"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"},"VerdictChange":{"properties":{"from":{"type":"string","title":"From"},"to":{"type":"string","title":"To"},"direction":{"type":"string","enum":["upgrade","downgrade","lateral"],"title":"Direction"},"operator":{"anyOf":[{"$ref":"#/components/schemas/FieldChange"},{"type":"null"}]}},"type":"object","required":["from","to","direction"],"title":"VerdictChange","description":"A change in the attribution verdict between consecutive runs.\n\nPopulated only when both runs carry an attribution verdict (runs\npersisted before verdicts existed have ``verdict=None`` and produce no\nVerdictChange). ``direction`` is relative to the tier ordinal:\n``upgrade`` when attribution strengthened, ``downgrade`` when it weakened,\n``lateral`` when the tier held but the operator identity changed.\n\nAttributes:\n    from_tier: Previous verdict tier (serialized as \"from\").\n    to_tier: Current verdict tier (serialized as \"to\").\n    direction: upgrade, downgrade, or lateral.\n    operator: Operator identity change, when the named operator differs."},"VerifyTokenRequest":{"properties":{"email":{"type":"string","minLength":1,"title":"Email"},"token":{"type":"string","minLength":1,"title":"Token"}},"type":"object","required":["email","token"],"title":"VerifyTokenRequest","description":"Request body for server-side magic link verification."},"VerifyTokenResponse":{"properties":{"id_token":{"type":"string","title":"Id Token"},"access_token":{"type":"string","title":"Access Token"},"refresh_token":{"type":"string","title":"Refresh Token"}},"type":"object","required":["id_token","access_token","refresh_token"],"title":"VerifyTokenResponse","description":"Cognito tokens returned after successful magic link verification."},"WebhookSecretResponse":{"properties":{"webhook_secret":{"type":"string","title":"Webhook Secret"},"message":{"type":"string","title":"Message"}},"type":"object","required":["webhook_secret","message"],"title":"WebhookSecretResponse","description":"Response after generating or retrieving a webhook secret."},"WebhookStatusResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"attempts":{"type":"integer","title":"Attempts"},"max_attempts":{"type":"integer","title":"Max Attempts"},"last_attempt_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Attempt At"},"last_response_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Response Code"},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error"},"delivered_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivered At"},"url":{"type":"string","title":"Url"}},"type":"object","required":["job_id","status","attempts","max_attempts","url"],"title":"WebhookStatusResponse","description":"Delivery status for a webhook callback."}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}