{"openapi":"3.1.0","info":{"title":"Verifiquemos API","description":"API de la plataforma Verifiquemos — verificación de cumplimiento (KYC/CDD) para el mercado guatemalteco. Multi-tenant con aislamiento por RLS. Autenticación JWT (RS256) y API keys.","contact":{"name":"Verifiquemos","email":"soporte@verifiquemos.com"},"version":"0.38.0"},"paths":{"/health":{"get":{"tags":["health"],"summary":"Health Check","description":"Liveness probe — returns ok if the FastAPI process is up.","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Check Health Get"}}}}}}},"/health/ready":{"get":{"tags":["health"],"summary":"Health Ready","description":"Readiness probe — critical dependencies (db, redis) must all be up.\n\nNo auth; intended for k8s / App Service readiness probes. Any failing\ndependency → 503.","operationId":"health_ready_health_ready_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Ready Health Ready Get"}}}}}}},"/health/detailed":{"get":{"tags":["health"],"summary":"Health Detailed","description":"Detailed health — per-dependency checks with timings (admin only).","operationId":"health_detailed_health_detailed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Detailed Health Detailed Get"}}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}]}},"/api/v1/admin/users/{user_id}/unlock":{"post":{"tags":["admin"],"summary":"Unlock User","description":"Clear lockout counters for a user. Requires admin role.\n\nResets failed_login_count to 0 and locked_until to NULL, then writes\na USER_ACCOUNT_UNLOCKED audit row. The target user must belong to the\nsame tenant as the admin.\n\nM50 review: verify_user_version is called inside the tenant session so a\nstolen JWT with stale user_version (e.g. admin rotated password since the\ntoken was minted) cannot unlock anyone. Pattern matches the other write\nendpoints (validations, credits, invitations, tenants).","operationId":"unlock_user_api_v1_admin_users__user_id__unlock_post","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Unlock User Api V1 Admin Users  User Id  Unlock Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/register":{"post":{"tags":["auth"],"summary":"Register","description":"Register a new user and tenant.","operationId":"register_api_v1_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/app__api__auth__AuthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/login":{"post":{"tags":["auth"],"summary":"Login","description":"Authenticate with email and password.\n\nFailed logins are always logged via the application logger. When the\nemail matches an existing user we ALSO write a ``user_login_failed``\naudit_log row under that user's tenant (M13 G14) so SIEM can join\nfailed attempts against the rest of the audit trail. When the email\nis unknown we have no tenant to attribute the row to (``audit_log``\nis RLS-scoped per tenant with ``tenant_id NOT NULL``), so those\nattempts stay logger-only -- forensics must lean on Application\nInsights for that subset.\n\nM50: Per-user account lockout. bcrypt runs FIRST (always) so locked\naccounts pay the same wall-time as unlocked ones — preserves M27's\nanti-enumeration guarantee. Only AFTER verify do we check lockout and\nreturn 429. Failed-counter advancement skips OAuth-only users\n(``password_hash IS NULL``) so an attacker cannot brute-force-lock\nan account that never had a password in the first place.","operationId":"login_api_v1_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/app__api__auth__AuthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/refresh":{"post":{"tags":["auth"],"summary":"Refresh","description":"Rotate refresh token and issue new access token.","operationId":"refresh_api_v1_auth_refresh_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/logout":{"post":{"tags":["auth"],"summary":"Logout","description":"Revoke a single refresh token.\n\nTenant-scoped: identity is known via the JWT, so RLS on\n``refresh_tokens`` enforces tenant isolation as a defense-in-depth\nlayer on top of the explicit ``user_id`` predicate.\n\nF1 fix (spec 2026-06-02): also purge the ``verifiquemos_app_mode``\nhttpOnly cookie. Without this the next login in the same browser\nwould inherit the prior session's mode — a tenancy violation when\ntwo analysts share a workstation.","operationId":"logout_api_v1_auth_logout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}]}},"/api/v1/auth/logout-all":{"post":{"tags":["auth"],"summary":"Logout All","description":"Revoke all refresh tokens for the current user.\n\nTenant-scoped: RLS ensures the bulk update can only touch rows\nowned by ``current_user.tenant_id``.\n\nF1 fix (spec 2026-06-02): also purge verifiquemos_app_mode cookie.\nOrdering matches ``logout`` for consistency; same caveat applies — the\nSet-Cookie clear ships only on the happy path. FastAPI drops the\nconfigured Response if the endpoint raises, so a failed revoke leaves\nthe cookie untouched server-side. The frontend's onSettled cleanup\ncovers that case from the client side.","operationId":"logout_all_api_v1_auth_logout_all_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}]}},"/api/v1/auth/change-password":{"post":{"tags":["auth"],"summary":"Change Password","description":"Change authenticated user's password (M25/G6/D33).\n\nFlow:\n1. Validate new password strength (>=12 chars + zxcvbn score >=3) -> 422.\n2. Tenant-scoped session: fetch user, bcrypt-verify current password.\n   - Mismatch -> 401 ``{code: invalid_credentials}`` + USER_PASSWORD_CHANGE_FAILED audit.\n3. Update password_hash, revoke ALL active refresh_tokens, emit\n   USER_PASSWORD_CHANGED audit.\n4. Return MessageResponse (HTTP 200; not 204, because apiClient calls\n   ``res.json()`` unconditionally).\n\nRate-limited 5/min per user_id (falls back to IP for invalid tokens).\nRevokes ALL active refresh tokens -- \"except current\" is not implementable\nbecause the endpoint only receives current_password + new_password, not\nthe refresh-token handle. Access tokens remain valid until natural expiry.\n\nTODO(M-MFA): require step-up challenge when MFA is implemented.","operationId":"change_password_api_v1_auth_change_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}]}},"/api/v1/auth/me":{"get":{"tags":["auth"],"summary":"Me","description":"Return the current user profile and tenant info.\n\nTenant-scoped: ``users`` is RLS-protected and ``tenants`` is\nlooked up by primary key, which the user's tenant_id pins to\ntheir own tenant row.","operationId":"me_api_v1_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}]}},"/api/v1/auth/forgot-password":{"post":{"tags":["auth"],"summary":"Forgot Password","description":"Issue a single-use password-reset token for the given email.\n\nEmail-enumeration prevention: the response is identical whether or\nnot the address corresponds to a registered user. The actual side\neffect (token row + outbound email) only happens when the address\nmatches an active user, so there is no observable signal in the API\nresponse itself.\n\nRuns on the admin pool because the email -> user lookup MUST happen\nbefore any tenant context is established (that is the whole point\nof the flow). Token rows do not carry tenant_id and are not\nRLS-scoped — see migration 005.","operationId":"forgot_password_api_v1_auth_forgot_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/reset-password":{"post":{"tags":["auth"],"summary":"Reset Password","description":"Consume a password-reset token and set the user's new password.\n\nOnce successfully consumed, the token is marked ``used_at`` (single\nuse) and ALL of the user's existing refresh tokens are revoked --\nthis is the moment to invalidate sessions that may have been\nissued to an attacker if the password reset is reactive to an\naccount compromise.\n\nRuns on the admin pool because the user's tenant is not known\nbefore the token is verified (and the refresh-token revocation\nneeds to operate across tenant boundaries safely; the WHERE\n``user_id`` clause is the explicit scope).","operationId":"reset_password_api_v1_auth_reset_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/password/set-request":{"post":{"tags":["auth"],"summary":"Password Set Request","description":"Solicitar un email para establecer contraseña por primera vez.\n\nPer spec §5.6. Solo para usuarios OAuth-only (sin identity 'password').\nSi el usuario ya tiene password (i.e. ya hay una identity 'password'),\ndevolvemos 409 — para cambiarla, debe usar el flujo /reset-password.","operationId":"password_set_request_api_v1_auth_password_set_request_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPasswordRequestResponse"}}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}]}},"/api/v1/auth/password/set-confirm":{"post":{"tags":["auth"],"summary":"Password Set Confirm","description":"Consumir el token de set-password, hashear password, crear identity password.\n\nPer spec §5.6. El token DEBE tener kind='set'. Tras consumir:\n- UPDATE users.password_hash\n- INSERT user_identities (provider='password', provider_user_id='self')\n- mark token used_at\nA diferencia de /reset-password, NO revocamos refresh tokens — el usuario\nno perdió control de la cuenta; está agregando un método.","operationId":"password_set_confirm_api_v1_auth_password_set_confirm_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPasswordConfirmRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/credits/balance":{"get":{"tags":["credits"],"summary":"Get Balance","description":"Obtener el saldo de créditos del tenant actual.","operationId":"get_balance_api_v1_credits_balance_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BalanceResponse"}}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}]}},"/api/v1/credits/history":{"get":{"tags":["credits"],"summary":"Get History","description":"Obtener el historial de movimientos de créditos (paginado).","operationId":"get_history_api_v1_credits_history_get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/credits/purchase":{"post":{"tags":["credits"],"summary":"Purchase Credits","description":"Comprar un paquete de créditos.\n\nIdempotency, in layers:\n\n1. **HTTP header** ``Idempotency-Key`` (Redis, 24h TTL via\n   :func:`app.deps.idempotency.check_idempotency`) — replays the cached\n   response on retries that already produced a 2xx.\n2. **Payment intent** (this endpoint, M28 follow-up): a PENDING\n   ``ExternalPaymentLog`` row is committed in its own transaction\n   BEFORE Cybersource is called. The partial unique index\n   ``(tenant_id, payment_reference_id)`` (migration 026) makes a\n   second INSERT fail with IntegrityError, so a retry after a\n   crashed authorization can branch on the stored status instead of\n   re-charging the card.\n3. **Ledger guard** (existing): partial unique index on\n   ``credit_ledger(tenant_id, payment_reference_id) WHERE\n   payment_reference_id IS NOT NULL`` blocks duplicate ledger rows\n   from a concurrent winner of the slow path.\n\nBalance update is an atomic\n``UPDATE ... SET credit_balance = credit_balance + :amount RETURNING ...``.\nNo ``SELECT FOR UPDATE`` is needed because layer 3 makes only one\nconcurrent caller's ``+:amount`` survive.","operationId":"purchase_credits_api_v1_credits_purchase_post","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseResponse"}}}},"400":{"description":"Card or billing data missing when CYBERSOURCE_ENABLED=true.","content":{"application/json":{"example":{"detail":"Datos de tarjeta y facturación son requeridos. Complete todos los campos del formulario."}}}},"402":{"description":"Card declined by Cybersource.","content":{"application/json":{"example":{"detail":{"code":"payment_declined","message":"Tarjeta rechazada por el emisor."}}}}},"501":{"description":"Cybersource processing not available (CYBERSOURCE_ENABLED=false in production).","content":{"application/json":{"example":{"detail":"Procesamiento de pagos no disponible. Contacte soporte."}}}},"502":{"description":"Cybersource transport or API error.","content":{"application/json":{"example":{"detail":"Error procesando el pago. Intente más tarde."}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/credits/refund":{"post":{"tags":["credits"],"summary":"Refund Credit","description":"Reembolsar 1 crédito por una validación fallida (solo administradores).","operationId":"refund_credit_api_v1_credits_refund_post","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/validations/batch/template":{"get":{"tags":["batch"],"summary":"Batch Template","description":"Download the batch-submission CSV template.","operationId":"batch_template_api_v1_validations_batch_template_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}]}},"/api/v1/validations/batch":{"post":{"tags":["batch"],"summary":"Submit Batch","description":"Submit a batch of clients for validation.","operationId":"submit_batch_api_v1_validations_batch_post","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_submit_batch_api_v1_validations_batch_post"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCreatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["batch"],"summary":"List Batches","description":"List the tenant's batch jobs, newest first. RLS scopes to tenant.","operationId":"list_batches_api_v1_validations_batch_get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/validations/batch/{batch_id}":{"get":{"tags":["batch"],"summary":"Get Batch","description":"Get one batch job with its items. RLS returns 404 cross-tenant.","operationId":"get_batch_api_v1_validations_batch__batch_id__get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"batch_id","in":"path","required":true,"schema":{"type":"string","title":"Batch Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/validations/batch/{batch_id}/cancel":{"post":{"tags":["batch"],"summary":"Cancel Batch","description":"Cascade-cancel a batch: the job and all non-terminal items.\n\nIdempotent — a second cancel returns already_terminal=true.\n\nD22: Mutations always return Cache-Control: no-store on every\n200 branch, including the idempotent already-terminal case.","operationId":"cancel_batch_api_v1_validations_batch__batch_id__cancel_post","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"batch_id","in":"path","required":true,"schema":{"type":"string","title":"Batch Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCancelResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/validations":{"post":{"tags":["validations"],"summary":"Create Validation","description":"Crear una nueva validación.\n\nTwo-phase flow (T15): the validation row is inserted with NULL\ndocument URLs *before* any blob upload, so a successful upload is\nalways referenced by a row -- preventing orphaned blobs in the\ncommon failure modes (DB rollback after upload, request timeout\nmid-flight, etc). The companion taskiq cron job\n:func:`app.maintenance.orphan_blob_gc.gc_orphan_blobs` mops up\nblobs that slip through (e.g., process kill between phases).\n\nPhases:\n\n1. Read + size-check uploads (HTTP-413 fail-fast, no DB / blob writes).\n2. Tenant transaction: stale-JWT guard, atomic credit debit, ledger\n   row, validation row INSERT (NULL urls), audit row. Commits.\n3. Blob uploads (HTTP I/O, OUTSIDE any DB transaction).\n4. On upload failure: mark row ``failed`` and refund the credit;\n   respond 500. On success: UPDATE the row with the URLs.\n5. Enqueue the taskiq pipeline job.\n\nRequires at least 1 credit available.","operationId":"create_validation_api_v1_validations_post","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_create_validation_api_v1_validations_post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationCreatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["validations"],"summary":"List Validations","description":"Listar validaciones del tenant con filtros y paginación.","operationId":"list_validations_api_v1_validations_get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"description":"Max 100 por página","default":20,"title":"Limit"},"description":"Max 100 por página"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"risk_level","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Risk Level"}},{"name":"cui","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cui"}},{"name":"date_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Date From"}},{"name":"date_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Date To"}},{"name":"app_mode","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filtra por modo de aplicación. 'initial' incluye filas legacy con app_mode NULL; 'update' exige match exacto. Cuando se envía, ignora la cookie de modo activo.","title":"App Mode"},"description":"Filtra por modo de aplicación. 'initial' incluye filas legacy con app_mode NULL; 'update' exige match exacto. Cuando se envía, ignora la cookie de modo activo."},{"name":"naturaleza","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filtra por naturaleza_cliente exacta (persona_individual | persona_juridica).","title":"Naturaleza"},"description":"Filtra por naturaleza_cliente exacta (persona_individual | persona_juridica)."},{"name":"score_min","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Score total mínimo (inclusivo).","title":"Score Min"},"description":"Score total mínimo (inclusivo)."},{"name":"score_max","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Score total máximo (inclusivo).","title":"Score Max"},"description":"Score total máximo (inclusivo)."},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filtra por el usuario que creó la validación.","title":"User Id"},"description":"Filtra por el usuario que creó la validación."},{"name":"segment_country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filtra por país de origen de fondos (ISO 3 chars).","title":"Segment Country"},"description":"Filtra por país de origen de fondos (ISO 3 chars)."},{"name":"activity_risk","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filtra por clasificación de riesgo de actividad (low | medium | high).","title":"Activity Risk"},"description":"Filtra por clasificación de riesgo de actividad (low | medium | high)."},{"name":"list_match","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filtra a validaciones con un match positivo en la lista indicada (OFAC, ONU, ENGEL, PEP, PROVEEDORES).","title":"List Match"},"description":"Filtra a validaciones con un match positivo en la lista indicada (OFAC, ONU, ENGEL, PEP, PROVEEDORES)."},{"name":"special_condition","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filtra por condición especial activa (pep | cpe | ong | fideicomiso). Lee consolidated_special_conditions.","title":"Special Condition"},"description":"Filtra por condición especial activa (pep | cpe | ong | fideicomiso). Lee consolidated_special_conditions."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/validations/{validation_id}":{"get":{"tags":["validations"],"summary":"Get Validation","description":"Obtener detalle de una validación.\n\nD22 cache headers:\n- status='processing' → private, no-store (in-flight, defense-in-depth)\n- status terminal     → private, no-cache (revalida pero cache local privado)","operationId":"get_validation_api_v1_validations__validation_id__get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"validation_id","in":"path","required":true,"schema":{"type":"string","title":"Validation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/validations/{validation_id}/report":{"get":{"tags":["validations"],"summary":"Get Validation Report","description":"Obtener el reporte PDF de una validación.","operationId":"get_validation_report_api_v1_validations__validation_id__report_get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"validation_id","in":"path","required":true,"schema":{"type":"string","title":"Validation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["validations"],"summary":"Regenerate Validation Report","description":"Enqueue an async PDF re-render for a completed validation.\n\nOnly validations in 'completed' or 'completed_partial' status are\neligible. Returns 202 with a job_id. The client should poll\nGET /validations/{id} until report_pdf_url changes (or, observe\nthat GET /report stops returning Cache-Control: no-store).\nConflict (409) if another regen is already in flight.\n\nAI recommendation is NOT recomputed.","operationId":"regenerate_validation_report_api_v1_validations__validation_id__report_post","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"validation_id","in":"path","required":true,"schema":{"type":"string","title":"Validation Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportRegenRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportRegenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/validations/{validation_id}/cancel":{"post":{"tags":["validations"],"summary":"Cancel Validation","description":"Cancel a non-terminal validation. Idempotent via Idempotency-Key.\n\nAtomic: a single UPDATE refuses to overwrite terminal statuses. No\ncredit refund — the customer chose to cancel and the analyst time was\nconsumed.","operationId":"cancel_validation_api_v1_validations__validation_id__cancel_post","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"validation_id","in":"path","required":true,"schema":{"type":"string","title":"Validation Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationCancelRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationCancelResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/dashboard/strategic":{"get":{"tags":["dashboard"],"summary":"Get Strategic Dashboard","description":"Strategic dashboard: snapshot-first, live fallback.\n\nA hit on ``dashboard_strategic_snapshot`` for (tenant_id, period)\nreturns immediately with ``source=\"snapshot\"``. A miss computes all\nnine KPIs live, upserts the result, and returns ``source=\"live\"`` so\nthe next request will hit the snapshot path.","operationId":"get_strategic_dashboard_api_v1_dashboard_strategic_get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"30d","title":"Period"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrategicResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/dashboard/operational":{"get":{"tags":["dashboard"],"summary":"Get Operational Dashboard","description":"Operational dashboard: always live (no snapshot path).\n\nThe operational view is cheap to recompute (six COUNT/GROUP BY queries\non indexed columns) and benefits from being fresh — analysts watch\nconsumption counters update as their team works. The snapshot+cron\nmachinery used by the strategic dashboard is therefore not wired here.","operationId":"get_operational_dashboard_api_v1_dashboard_operational_get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"30d","title":"Period"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/dashboard/recompute":{"post":{"tags":["dashboard","admin"],"summary":"Recompute Strategic Snapshot","description":"Enqueue a strategic-snapshot refresh for the caller's tenant.\n\nThe period is validated against the same allow-list the dashboard\nendpoints use (``7d`` / ``30d`` / ``90d`` / ``1y`` / ``all``).\n\n``tenant_id`` matches the spec contract (§6.4) so callers can target a\ntenant explicitly. The system currently has no super-admin role, so an\nexplicit ``tenant_id`` that differs from the caller's tenant is rejected\nwith 403. If a super-admin role is introduced later, relax the check\nhere. On success the response carries ``job_id``.","operationId":"recompute_strategic_snapshot_api_v1_admin_dashboard_recompute_post","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"30d","title":"Period"}},{"name":"tenant_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Recompute Strategic Snapshot Api V1 Admin Dashboard Recompute Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/dashboard/strategic/export":{"get":{"tags":["dashboard","export"],"summary":"Export Strategic","operationId":"export_strategic_api_v1_dashboard_strategic_export_get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"30d","title":"Period"}},{"name":"format","in":"query","required":false,"schema":{"enum":["csv","pdf"],"type":"string","default":"csv","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/dashboard/operational/export":{"get":{"tags":["dashboard","export"],"summary":"Export Operational","operationId":"export_operational_api_v1_dashboard_operational_export_get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"30d","title":"Period"}},{"name":"format","in":"query","required":false,"schema":{"enum":["csv","pdf"],"type":"string","default":"csv","title":"Format"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/app-mode":{"post":{"tags":["app-mode"],"summary":"Set App Mode","description":"Set the active app mode for this session.\n\nWrites the httpOnly cookie ``verifiquemos_app_mode`` and audits the\nchange. The middleware reads this cookie on subsequent requests.","operationId":"set_app_mode_api_v1_app_mode_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppModeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppModeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}]}},"/api/v1/lists":{"get":{"tags":["lists"],"summary":"Get Lists Status","description":"Per-list record counts + most recent ETL job.","operationId":"get_lists_status_api_v1_lists_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListsStatusResponse"}}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}]}},"/api/v1/etl-jobs/{job_id}":{"get":{"tags":["lists"],"summary":"Get Etl Job","description":"Look up a single ETL job by id.","operationId":"get_etl_job_api_v1_etl_jobs__job_id__get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ETLJobSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/lists/etl":{"post":{"tags":["lists"],"summary":"Trigger Etl","description":"Trigger an OFAC/ONU sanctions-list refresh (admin only).\n\nEnqueues run_etl on the worker. The runner downloads the source XML,\nparses, and atomically swaps the table. Engel/PEP/Proveedores are\nupload-based — use POST /lists/upload for those.","operationId":"trigger_etl_api_v1_lists_etl_post","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerETLRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerETLResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/lists/upload":{"post":{"tags":["lists"],"summary":"Upload List File","description":"Upload an Engel PDF / Proveedores XLSX / PEP CSV and trigger ETL.\n\nAdmin only. Validates file size and (where the format has magic\nbytes) MIME. Enqueues run_etl with the file content; the worker\nparses and atomically swaps the table.","operationId":"upload_list_file_api_v1_lists_upload_post","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_list_file_api_v1_lists_upload_post"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerETLResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/countries":{"get":{"tags":["countries"],"summary":"Get Countries","operationId":"get_countries_api_v1_countries_get","parameters":[{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Substring search (case-insensitive).","title":"Search"},"description":"Substring search (case-insensitive)."},{"name":"code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact alpha-3 lookup (case-insensitive).","title":"Code"},"description":"Exact alpha-3 lookup (case-insensitive)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CountriesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/countries/{code_alpha3}/sanctions":{"get":{"tags":["countries"],"summary":"Get Country Sanctions","operationId":"get_country_sanctions_api_v1_countries__code_alpha3__sanctions_get","parameters":[{"name":"code_alpha3","in":"path","required":true,"schema":{"type":"string","title":"Code Alpha3"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CountrySanctionsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tenants/{tenant_id}":{"patch":{"tags":["tenants"],"summary":"Update Tenant","description":"Actualizar los settings del tenant (solo el nombre / razón social).\n\nEl `plan` y el `credit_balance` se gestionan vía facturación, no aquí.","operationId":"update_tenant_api_v1_tenants__tenant_id__patch","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tenants/{tenant_id}/users":{"get":{"tags":["tenants"],"summary":"List Tenant Users","description":"Listar los usuarios del tenant con paginación.","operationId":"list_tenant_users_api_v1_tenants__tenant_id__users_get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"description":"Máx 100 por página","default":20,"title":"Limit"},"description":"Máx 100 por página"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tenants/{tenant_id}/users/{user_id}/role":{"patch":{"tags":["tenants"],"summary":"Update User Role","description":"Cambiar el rol de un usuario del tenant.\n\nUn admin no puede cambiar su propio rol (evita auto-bloqueo).","operationId":"update_user_role_api_v1_tenants__tenant_id__users__user_id__role_patch","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoleUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tenants/{tenant_id}/api-keys":{"post":{"tags":["tenants"],"summary":"Create Api Key","description":"Emitir una nueva API key. La clave completa se devuelve solo aquí.","operationId":"create_api_key_api_v1_tenants__tenant_id__api_keys_post","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["tenants"],"summary":"List Api Keys","description":"Listar las API keys activas del tenant. Nunca expone la clave completa.","operationId":"list_api_keys_api_v1_tenants__tenant_id__api_keys_get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tenants/{tenant_id}/api-keys/{key_id}":{"delete":{"tags":["tenants"],"summary":"Revoke Api Key","description":"Revocar (soft-delete) una API key.","operationId":"revoke_api_key_api_v1_tenants__tenant_id__api_keys__key_id__delete","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Revoke Api Key Api V1 Tenants  Tenant Id  Api Keys  Key Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tenants/{tenant_id}/settings/ip-allowlist":{"get":{"tags":["tenants"],"summary":"Get Ip Allowlist","description":"Consultar la configuración de IP allow-list del tenant.","operationId":"get_ip_allowlist_api_v1_tenants__tenant_id__settings_ip_allowlist_get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IpAllowlistSettings"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["tenants"],"summary":"Update Ip Allowlist","description":"Actualizar la configuración de IP allow-list del tenant (upsert).","operationId":"update_ip_allowlist_api_v1_tenants__tenant_id__settings_ip_allowlist_put","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IpAllowlistSettings"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IpAllowlistSettings"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tenants/{tenant_id}/settings/password-policy":{"get":{"tags":["tenants"],"summary":"Get Password Policy","description":"Consultar la política de contraseñas del tenant (admin-only).","operationId":"get_password_policy_api_v1_tenants__tenant_id__settings_password_policy_get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordPolicySettings"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["tenants"],"summary":"Update Password Policy","description":"Actualizar la política de contraseñas del tenant (upsert, admin-only).","operationId":"update_password_policy_api_v1_tenants__tenant_id__settings_password_policy_put","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordPolicySettings"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordPolicySettings"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/audit-log":{"get":{"tags":["audit"],"summary":"Query Audit Log","description":"Consultar el log de auditoría del tenant con filtros y paginación.\n\nRLS restringe los resultados al tenant del usuario autenticado.","operationId":"query_audit_log_api_v1_audit_log_get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"description":"Máx 200 por página","default":50,"title":"Limit"},"description":"Máx 200 por página"},{"name":"action","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action"}},{"name":"entity_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Type"}},{"name":"entity_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"date_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Date From"}},{"name":"date_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Date To"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditLogResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tenants/{tenant_id}/invites":{"post":{"tags":["invitations"],"summary":"Create Invite","description":"Invitar a una persona a unirse al tenant. La invitación expira en 7 días.","operationId":"create_invite_api_v1_tenants__tenant_id__invites_post","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["invitations"],"summary":"List Invites","description":"Listar las invitaciones pendientes (no aceptadas, no expiradas).","operationId":"list_invites_api_v1_tenants__tenant_id__invites_get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tenants/{tenant_id}/invites/{invite_id}":{"delete":{"tags":["invitations"],"summary":"Revoke Invite","description":"Revocar una invitación pendiente (la elimina).","operationId":"revoke_invite_api_v1_tenants__tenant_id__invites__invite_id__delete","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"invite_id","in":"path","required":true,"schema":{"type":"string","title":"Invite Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Revoke Invite Api V1 Tenants  Tenant Id  Invites  Invite Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/invites/{token}":{"get":{"tags":["invitations"],"summary":"Preview Invite","description":"Previsualizar una invitación por su token (sin autenticar).","operationId":"preview_invite_api_v1_invites__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitePreviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/invites/{token}/accept":{"post":{"tags":["invitations"],"summary":"Accept Invite","description":"Aceptar una invitación: crea el usuario y lo une al tenant (sin autenticar).","operationId":"accept_invite_api_v1_invites__token__accept_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteAcceptRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteAcceptResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/oauth/{provider}/start":{"get":{"tags":["oauth"],"summary":"Oauth Start","description":"Inicia el flujo OAuth — redirige al IdP con state/PKCE/nonce.","operationId":"oauth_start_api_v1_auth_oauth__provider__start_get","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","title":"Provider"}},{"name":"intent","in":"query","required":false,"schema":{"enum":["login","signup","link","invite"],"type":"string","default":"login","title":"Intent"}},{"name":"next","in":"query","required":false,"schema":{"type":"string","default":"/dashboard","title":"Next"}},{"name":"invitation_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invitation Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Oauth Start Api V1 Auth Oauth  Provider  Start Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/oauth/{provider}/callback":{"get":{"tags":["oauth"],"summary":"Oauth Callback","description":"Recibe el code del IdP, intercambia tokens, ejecuta el árbol de decisión.\n\nSpec §5.1 (identity existe → login), §5.2 (email nuevo → signup intent),\n§5.3 caso A (email existe + password → link intent), §5.3 caso B (email\nexiste + sólo OAuth → bloqueo).","operationId":"oauth_callback_api_v1_auth_oauth__provider__callback_get","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","title":"Provider"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Oauth Callback Api V1 Auth Oauth  Provider  Callback Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/oauth/bridge-claim":{"post":{"tags":["oauth"],"summary":"Oauth Bridge Claim","description":"Verify bridge nonce to bind the OAuth-initiating browser to the consumer.\n\nThe /oauth-bridge frontend page extracts ``n`` from the URL fragment and\nPOSTs it here. The server verifies that the submitted nonce matches the\n``oauth_bridge_nonce`` HttpOnly cookie (which only the original browser\nhas). On success the cookie is deleted (single-use). On failure → 400.\n\nRate-limited to 20/minute per IP to prevent brute-force enumeration.","operationId":"oauth_bridge_claim_api_v1_auth_oauth_bridge_claim_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthBridgeClaimRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Response Oauth Bridge Claim Api V1 Auth Oauth Bridge Claim Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/oauth/signup-complete":{"post":{"tags":["oauth"],"summary":"Oauth Signup Complete","description":"Completar el signup OAuth iniciado en el callback.\n\nPer spec §5.2 step 7-9. El callback puso un ``signup_intent`` JWT en\nel fragment; el frontend pidió ``organization_name`` + ``tenant_type``\nal usuario y los manda acá junto con el JWT. Validamos JWT (firma +\nexp), consumimos el ``jti`` atómicamente contra ``oauth_consumed_jti``\n(defensa contra replay), creamos tenant + user (sin password_hash) +\nidentity (provider, sub) + starter credit ledger, emitimos tokens.","operationId":"oauth_signup_complete_api_v1_auth_oauth_signup_complete_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthSignupCompleteRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/app__api__oauth__AuthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/oauth/link-confirm":{"post":{"tags":["oauth"],"summary":"Oauth Link Confirm","description":"Vincular un proveedor a un usuario existente tras password challenge.\n\nPer spec §5.3 caso A. Validamos el ``link_intent`` JWT (firma + exp),\ncargamos el usuario, verificamos que el email del JWT siga coincidiendo\ncon ``user.email`` (defensa contra cambios concurrentes), verificamos el\npassword contra ``user.password_hash``. SI el password falla, escribimos\n``identity_link_password_failed`` y devolvemos 401 SIN consumir el jti\n(así un retry con password correcto sigue funcionando dentro de la\nventana del JWT). SI todo OK, consumimos el jti, creamos la identity,\nemitimos tokens, y auditamos ``identity_link_completed``.","operationId":"oauth_link_confirm_api_v1_auth_oauth_link_confirm_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthLinkConfirmRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/app__api__oauth__AuthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/identities":{"get":{"tags":["identities"],"summary":"List Identities","description":"Listar las identities del usuario actual.\n\nTenant-scoped session — RLS lo refuerza por tenant; el filtro\nexplícito por user_id lo restringe al usuario actual.","operationId":"list_identities_api_v1_auth_identities_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/app__api__oauth__IdentityResponse"},"type":"array","title":"Response List Identities Api V1 Auth Identities Get"}}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}]}},"/api/v1/auth/identities/{identity_id}":{"delete":{"tags":["identities"],"summary":"Unlink Identity","description":"Desvincular un proveedor del usuario actual.\n\nPer spec §5.5. Rechaza 409 si es la única identity del usuario\n(invariante: el usuario nunca puede quedarse sin método de\nautenticación). NO revoca refresh tokens — el usuario sigue siendo\nel mismo (excepción: si quiere force-logout, ya existe /logout-all).","operationId":"unlink_identity_api_v1_auth_identities__identity_id__delete","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"identity_id","in":"path","required":true,"schema":{"type":"string","title":"Identity Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row.","title":"Idempotency-Key"},"description":"ULID used to deduplicate retries of mutating requests. The server caches the response for 24h keyed on (tenant_id, user_id, method, path, key). Distinct from the `idempotency_key` body field on POST /credits/purchase, which guards the DB row."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Unlink Identity Api V1 Auth Identities  Identity Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/security/csp-report":{"post":{"tags":["security"],"summary":"Csp Report","description":"Receive CSP violation reports.\n\nAccepts Content-Type: application/csp-report (W3C CSP) and application/json.\nSamples persistence per CSP_REPORT_SAMPLING_RATE setting.\nAlways returns 204 — even when sampling skips persistence or body is malformed\n(browsers don't retry CSP reports on non-2xx).","operationId":"csp_report_api_v1_security_csp_report_post","responses":{"204":{"description":"Successful Response"}}}},"/api/v1/security/csp-reports":{"get":{"tags":["security"],"summary":"List Csp Reports","description":"Admin-only paginated list of recent CSP violations.\n\nCSP reports are stored under SYSTEM_TENANT — admins see all violations globally.","operationId":"list_csp_reports_api_v1_security_csp_reports_get","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Csp Reports Api V1 Security Csp Reports Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AcceptedUserResponse":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"name":{"type":"string","title":"Name"},"role":{"type":"string","title":"Role"}},"type":"object","required":["id","email","name","role"],"title":"AcceptedUserResponse"},"ActionsByUser":{"properties":{"user_id":{"type":"string","title":"User Id"},"full_name":{"type":"string","title":"Full Name"},"initial_count":{"type":"integer","title":"Initial Count"},"update_count":{"type":"integer","title":"Update Count"}},"type":"object","required":["user_id","full_name","initial_count","update_count"],"title":"ActionsByUser"},"AmlAlertsKpi":{"properties":{"alerts_index":{"type":"number","title":"Alerts Index"},"from_list_hits":{"type":"integer","title":"From List Hits"},"from_negative_internet":{"type":"integer","title":"From Negative Internet"}},"type":"object","required":["alerts_index","from_list_hits","from_negative_internet"],"title":"AmlAlertsKpi"},"ApiKeyCreateRequest":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"}},"type":"object","required":["name"],"title":"ApiKeyCreateRequest"},"ApiKeyCreatedResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"key":{"type":"string","title":"Key"},"key_prefix":{"type":"string","title":"Key Prefix"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","name","key","key_prefix","is_active","created_at"],"title":"ApiKeyCreatedResponse"},"ApiKeyListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ApiKeySummary"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"ApiKeyListResponse"},"ApiKeySummary":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"key_prefix":{"type":"string","title":"Key Prefix"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","name","key_prefix","last_used_at","is_active","created_at"],"title":"ApiKeySummary"},"AppModeRequest":{"properties":{"mode":{"type":"string","enum":["initial","update"],"title":"Mode"}},"type":"object","required":["mode"],"title":"AppModeRequest"},"AppModeResponse":{"properties":{"mode":{"type":"string","title":"Mode"}},"type":"object","required":["mode"],"title":"AppModeResponse"},"AuditEntry":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"},"action":{"type":"string","title":"Action"},"entity_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Type"},"entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Id"},"ip_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ip Address"},"details":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Details"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","user_id","action","entity_type","entity_id","ip_address","details","created_at"],"title":"AuditEntry"},"AuditLogResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AuditEntry"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"limit":{"type":"integer","title":"Limit"}},"type":"object","required":["items","total","page","limit"],"title":"AuditLogResponse"},"BalanceResponse":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id"},"credit_balance":{"type":"integer","title":"Credit Balance"},"plan":{"type":"string","title":"Plan"}},"type":"object","required":["tenant_id","credit_balance","plan"],"title":"BalanceResponse"},"BatchCancelResponse":{"properties":{"batch_id":{"type":"string","title":"Batch Id"},"status":{"type":"string","title":"Status"},"already_terminal":{"type":"boolean","title":"Already Terminal"}},"type":"object","required":["batch_id","status","already_terminal"],"title":"BatchCancelResponse"},"BatchCreatedResponse":{"properties":{"batch_id":{"type":"string","title":"Batch Id"},"total_rows":{"type":"integer","title":"Total Rows"},"status":{"type":"string","title":"Status"}},"type":"object","required":["batch_id","total_rows","status"],"title":"BatchCreatedResponse"},"BatchDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"status":{"type":"string","title":"Status"},"total_rows":{"type":"integer","title":"Total Rows"},"completed_count":{"type":"integer","title":"Completed Count"},"failed_count":{"type":"integer","title":"Failed Count"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"items":{"items":{"$ref":"#/components/schemas/BatchItemResponse"},"type":"array","title":"Items"}},"type":"object","required":["id","status","total_rows","completed_count","failed_count","created_at","items"],"title":"BatchDetailResponse"},"BatchItemResponse":{"properties":{"id":{"type":"string","title":"Id"},"row_number":{"type":"integer","title":"Row Number"},"dpi_filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dpi Filename"},"validation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Validation Id"},"status":{"type":"string","title":"Status"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["id","row_number","status"],"title":"BatchItemResponse"},"BatchListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/BatchSummary"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"limit":{"type":"integer","title":"Limit"}},"type":"object","required":["items","total","page","limit"],"title":"BatchListResponse"},"BatchSummary":{"properties":{"id":{"type":"string","title":"Id"},"status":{"type":"string","title":"Status"},"total_rows":{"type":"integer","title":"Total Rows"},"completed_count":{"type":"integer","title":"Completed Count"},"failed_count":{"type":"integer","title":"Failed Count"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"}},"type":"object","required":["id","status","total_rows","completed_count","failed_count","created_at"],"title":"BatchSummary"},"BillingInput":{"properties":{"firstname":{"type":"string","maxLength":60,"minLength":1,"title":"Firstname"},"lastname":{"type":"string","maxLength":60,"minLength":1,"title":"Lastname"},"address1":{"type":"string","maxLength":120,"minLength":1,"title":"Address1"},"locality":{"type":"string","maxLength":60,"minLength":1,"title":"Locality"},"administrative_area":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Administrative Area"},"postal_code":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Postal Code"},"country":{"type":"string","maxLength":2,"minLength":2,"title":"Country","default":"GT"},"email":{"type":"string","maxLength":255,"minLength":3,"title":"Email"},"phone_number":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Phone Number"}},"type":"object","required":["firstname","lastname","address1","locality","email"],"title":"BillingInput","description":"Billing address — required by Cybersource for AVS verification.\n\nDefaults to country=GT since that is the verifiquemos primary market.\nThe frontend pre-populates these from the user's tenant profile."},"Body_create_validation_api_v1_validations_post":{"properties":{"dpi_file":{"type":"string","contentMediaType":"application/octet-stream","title":"Dpi File"},"naturaleza_cliente":{"type":"string","title":"Naturaleza Cliente"},"pais_origen_fondos":{"type":"string","title":"Pais Origen Fondos","default":"GTM"},"pais_destino_fondos":{"type":"string","title":"Pais Destino Fondos","default":"GTM"},"feic_file":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"Feic File"},"feic_anexo_file":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"Feic Anexo File"},"feic_data_manual":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Feic Data Manual"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["dpi_file","naturaleza_cliente"],"title":"Body_create_validation_api_v1_validations_post"},"Body_submit_batch_api_v1_validations_batch_post":{"properties":{"csv_file":{"type":"string","contentMediaType":"application/octet-stream","title":"Csv File"},"zip_file":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"Zip File"}},"type":"object","required":["csv_file"],"title":"Body_submit_batch_api_v1_validations_batch_post"},"Body_upload_list_file_api_v1_lists_upload_post":{"properties":{"list_type":{"type":"string","title":"List Type"},"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["list_type","file"],"title":"Body_upload_list_file_api_v1_lists_upload_post"},"CardInput":{"properties":{"name_on_card":{"type":"string","maxLength":100,"minLength":2,"title":"Name On Card","description":"Cardholder name as printed on the card.","examples":["JUAN PEREZ"]},"card_number":{"type":"string","maxLength":19,"minLength":13,"title":"Card Number","description":"Primary account number (PAN). Spaces and dashes are stripped server-side before Luhn validation. Never logged or persisted."},"card_expiration":{"type":"string","format":"date","title":"Card Expiration","description":"Card expiration date in ISO 8601 (YYYY-MM-DD). The card is valid until the LAST day of the stated month; pass the 1st.","examples":["2027-09-01"]},"card_cvs":{"type":"string","maxLength":4,"minLength":3,"pattern":"^\\d{3,4}$","title":"Card Cvs","description":"Card security code (CVV/CID). 3 digits on Visa/MC, 4 on Amex. Never logged or persisted."}},"type":"object","required":["name_on_card","card_number","card_expiration","card_cvs"],"title":"CardInput","description":"Credit card data submitted from the purchase form.\n\nCard data never persists — it travels from this schema directly into\nthe Cybersource API payload. The ExternalPaymentLog deliberately does\nNOT capture card_number or card_cvs (see app/api/credits.py purchase\nflow, Task 7)."},"ChangePasswordRequest":{"properties":{"current_password":{"type":"string","maxLength":128,"minLength":1,"title":"Current Password"},"new_password":{"type":"string","maxLength":128,"minLength":1,"title":"New Password"}},"type":"object","required":["current_password","new_password"],"title":"ChangePasswordRequest"},"ConsolidatedConditionsOut":{"properties":{"pep":{"$ref":"#/components/schemas/SpecialConditionFlagOut"},"cpe":{"$ref":"#/components/schemas/SpecialConditionFlagOut"},"ong":{"type":"boolean","title":"Ong"},"fideicomiso":{"type":"boolean","title":"Fideicomiso"}},"type":"object","required":["pep","cpe","ong","fideicomiso"],"title":"ConsolidatedConditionsOut"},"ConsumptionByUser":{"properties":{"user_id":{"type":"string","title":"User Id"},"full_name":{"type":"string","title":"Full Name"},"total":{"type":"integer","title":"Total"},"by_status":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Status"},"first_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"First At"},"last_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last At"}},"type":"object","required":["user_id","full_name","total","by_status","first_at","last_at"],"title":"ConsumptionByUser"},"ConsumptionTimePoint":{"properties":{"date":{"type":"string","title":"Date"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["date","count"],"title":"ConsumptionTimePoint"},"CountriesResponse":{"properties":{"countries":{"items":{"$ref":"#/components/schemas/CountryOut"},"type":"array","title":"Countries"}},"type":"object","required":["countries"],"title":"CountriesResponse"},"CountryOut":{"properties":{"code":{"type":"string","title":"Code"},"name":{"type":"string","title":"Name"}},"type":"object","required":["code","name"],"title":"CountryOut"},"CountrySanctionsOut":{"properties":{"country_code":{"type":"string","title":"Country Code"},"country_name":{"type":"string","title":"Country Name"},"sanctions":{"items":{"$ref":"#/components/schemas/SanctionItem"},"type":"array","title":"Sanctions"}},"type":"object","required":["country_code","country_name","sanctions"],"title":"CountrySanctionsOut"},"Coverage":{"properties":{"total_validations":{"type":"integer","title":"Total Validations"},"with_income":{"type":"integer","title":"With Income"},"without_income":{"type":"integer","title":"Without Income"}},"type":"object","required":["total_validations","with_income","without_income"],"title":"Coverage"},"ETLJobSummary":{"properties":{"id":{"type":"string","title":"Id"},"list_type":{"type":"string","title":"List Type"},"status":{"type":"string","title":"Status"},"records_imported":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Records Imported"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"}},"type":"object","required":["id","list_type","status"],"title":"ETLJobSummary"},"ExposedValueKpi":{"properties":{"total_qtz":{"type":"number","title":"Total Qtz"}},"type":"object","required":["total_qtz"],"title":"ExposedValueKpi"},"ForgotPasswordRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"ForgotPasswordRequest"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HistoryResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/LedgerEntryResponse"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"limit":{"type":"integer","title":"Limit"}},"type":"object","required":["items","total","page","limit"],"title":"HistoryResponse"},"IncomeBucket":{"properties":{"label":{"type":"string","title":"Label"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["label","count"],"title":"IncomeBucket"},"InternetResultResponse":{"properties":{"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"snippet":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snippet"},"sentiment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentiment"},"risk_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Risk Level"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},"type":"object","title":"InternetResultResponse"},"InviteAcceptRequest":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"password":{"type":"string","minLength":8,"title":"Password"}},"type":"object","required":["name","password"],"title":"InviteAcceptRequest"},"InviteAcceptResponse":{"properties":{"message":{"type":"string","title":"Message"},"user":{"$ref":"#/components/schemas/AcceptedUserResponse"}},"type":"object","required":["message","user"],"title":"InviteAcceptResponse"},"InviteCreateRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"role":{"type":"string","enum":["admin","analyst"],"title":"Role"}},"type":"object","required":["email","role"],"title":"InviteCreateRequest"},"InviteListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/InviteSummary"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"InviteListResponse"},"InvitePreviewResponse":{"properties":{"email":{"type":"string","title":"Email"},"role":{"type":"string","title":"Role"},"tenant_name":{"type":"string","title":"Tenant Name"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"type":"object","required":["email","role","tenant_name","expires_at"],"title":"InvitePreviewResponse"},"InviteSummary":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"role":{"type":"string","title":"Role"},"invited_by":{"type":"string","title":"Invited By"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"accepted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Accepted At"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","email","role","invited_by","expires_at","accepted_at","created_at"],"title":"InviteSummary"},"IpAllowlistSettings":{"properties":{"ip_allowlist":{"items":{"type":"string"},"type":"array","title":"Ip Allowlist"},"ip_enforcement_mode":{"type":"string","enum":["disabled","audit_only","enforce"],"title":"Ip Enforcement Mode"}},"type":"object","required":["ip_allowlist","ip_enforcement_mode"],"title":"IpAllowlistSettings"},"LedgerEntryResponse":{"properties":{"id":{"type":"string","title":"Id"},"delta":{"type":"integer","title":"Delta"},"balance_after":{"type":"integer","title":"Balance After"},"reason":{"type":"string","title":"Reason"},"validation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Validation Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","delta","balance_after","reason","created_at"],"title":"LedgerEntryResponse"},"ListMatchCount":{"properties":{"list_type":{"type":"string","title":"List Type"},"distinct_clients":{"type":"integer","title":"Distinct Clients"}},"type":"object","required":["list_type","distinct_clients"],"title":"ListMatchCount"},"ListMatchSummaryOut":{"properties":{"matched_name":{"type":"string","title":"Matched Name"},"similarity":{"type":"integer","title":"Similarity"},"reference":{"type":"string","title":"Reference"}},"type":"object","required":["matched_name","similarity","reference"],"title":"ListMatchSummaryOut"},"ListResultResponse":{"properties":{"list_type":{"type":"string","title":"List Type"},"found":{"type":"boolean","title":"Found"},"match_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Match Type"},"match_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Match Score"},"match_details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Match Details"}},"type":"object","required":["list_type","found"],"title":"ListResultResponse"},"ListStatus":{"properties":{"list_type":{"type":"string","title":"List Type"},"record_count":{"type":"integer","title":"Record Count"},"last_etl_job":{"anyOf":[{"$ref":"#/components/schemas/ETLJobSummary"},{"type":"null"}]}},"type":"object","required":["list_type","record_count"],"title":"ListStatus"},"ListsStatusResponse":{"properties":{"lists":{"items":{"$ref":"#/components/schemas/ListStatus"},"type":"array","title":"Lists"}},"type":"object","required":["lists"],"title":"ListsStatusResponse"},"LoginRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"LoginRequest"},"LogoutRequest":{"properties":{"refresh_token":{"type":"string","title":"Refresh Token"}},"type":"object","required":["refresh_token"],"title":"LogoutRequest"},"MeResponse":{"properties":{"user":{"$ref":"#/components/schemas/UserResponse"},"tenant":{"$ref":"#/components/schemas/TenantResponse"}},"type":"object","required":["user","tenant"],"title":"MeResponse"},"MessageResponse":{"properties":{"message":{"type":"string","title":"Message"}},"type":"object","required":["message"],"title":"MessageResponse"},"NaturalezaKpi":{"properties":{"persona_individual":{"type":"integer","title":"Persona Individual"},"persona_juridica":{"type":"integer","title":"Persona Juridica"},"no_clasificado":{"type":"integer","title":"No Clasificado"}},"type":"object","required":["persona_individual","persona_juridica","no_clasificado"],"title":"NaturalezaKpi"},"OAuthBridgeClaimRequest":{"properties":{"nonce":{"type":"string","maxLength":128,"minLength":1,"title":"Nonce"}},"type":"object","required":["nonce"],"title":"OAuthBridgeClaimRequest"},"OAuthLinkConfirmRequest":{"properties":{"intent_jwt":{"type":"string","minLength":1,"title":"Intent Jwt"},"password":{"type":"string","minLength":1,"title":"Password"}},"type":"object","required":["intent_jwt","password"],"title":"OAuthLinkConfirmRequest"},"OAuthSignupCompleteRequest":{"properties":{"intent_jwt":{"type":"string","minLength":1,"title":"Intent Jwt"},"organization_name":{"type":"string","maxLength":255,"minLength":1,"title":"Organization Name"},"tenant_type":{"type":"string","enum":["individual","juridica"],"title":"Tenant Type"}},"type":"object","required":["intent_jwt","organization_name","tenant_type"],"title":"OAuthSignupCompleteRequest"},"OperationalResponse":{"properties":{"period":{"type":"string","title":"Period"},"computed_at":{"type":"string","format":"date-time","title":"Computed At"},"source":{"type":"string","const":"live","title":"Source"},"consumption_over_time":{"items":{"$ref":"#/components/schemas/ConsumptionTimePoint"},"type":"array","title":"Consumption Over Time"},"consumption_by_user":{"items":{"$ref":"#/components/schemas/ConsumptionByUser"},"type":"array","title":"Consumption By User"},"naturaleza_breakdown":{"$ref":"#/components/schemas/NaturalezaKpi"},"score_distribution":{"items":{"$ref":"#/components/schemas/ScoreBucket"},"type":"array","title":"Score Distribution"},"actions_by_user":{"items":{"$ref":"#/components/schemas/ActionsByUser"},"type":"array","title":"Actions By User"},"list_matches":{"items":{"$ref":"#/components/schemas/ListMatchCount"},"type":"array","title":"List Matches"}},"type":"object","required":["period","computed_at","source","consumption_over_time","consumption_by_user","naturaleza_breakdown","score_distribution","actions_by_user","list_matches"],"title":"OperationalResponse"},"PasswordPolicySettings":{"properties":{"password_min_length":{"type":"integer","maximum":128.0,"minimum":8.0,"title":"Password Min Length"},"password_min_zxcvbn_score":{"type":"integer","maximum":4.0,"minimum":0.0,"title":"Password Min Zxcvbn Score"},"password_history_n":{"type":"integer","maximum":24.0,"minimum":0.0,"title":"Password History N"},"password_max_age_days":{"type":"integer","maximum":730.0,"minimum":0.0,"title":"Password Max Age Days"}},"type":"object","required":["password_min_length","password_min_zxcvbn_score","password_history_n","password_max_age_days"],"title":"PasswordPolicySettings"},"PurchaseRequest":{"properties":{"package":{"type":"string","enum":["10","50","100"],"title":"Package"},"idempotency_key":{"type":"string","title":"Idempotency Key"},"card":{"anyOf":[{"$ref":"#/components/schemas/CardInput"},{"type":"null"}]},"billing":{"anyOf":[{"$ref":"#/components/schemas/BillingInput"},{"type":"null"}]}},"type":"object","required":["package","idempotency_key"],"title":"PurchaseRequest"},"PurchaseResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"credits_added":{"type":"integer","title":"Credits Added"},"new_balance":{"type":"integer","title":"New Balance"},"idempotent_replay":{"type":"boolean","title":"Idempotent Replay","default":false}},"type":"object","required":["success","credits_added","new_balance"],"title":"PurchaseResponse"},"RefreshRequest":{"properties":{"refresh_token":{"type":"string","title":"Refresh Token"}},"type":"object","required":["refresh_token"],"title":"RefreshRequest"},"RefundRequest":{"properties":{"validation_id":{"type":"string","title":"Validation Id"}},"type":"object","required":["validation_id"],"title":"RefundRequest"},"RefundResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"new_balance":{"type":"integer","title":"New Balance"}},"type":"object","required":["success","new_balance"],"title":"RefundResponse"},"RegisterRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","minLength":8,"title":"Password"},"name":{"type":"string","title":"Name"},"organization_name":{"type":"string","title":"Organization Name"}},"type":"object","required":["email","password","name","organization_name"],"title":"RegisterRequest"},"ReportRegenRequest":{"properties":{},"type":"object","title":"ReportRegenRequest","description":"Empty request body, present for future option extensibility."},"ReportRegenResponse":{"properties":{"validation_id":{"type":"string","title":"Validation Id"},"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"}},"type":"object","required":["validation_id","job_id","status"],"title":"ReportRegenResponse"},"ReportResponse":{"properties":{"report_url":{"type":"string","title":"Report Url"}},"type":"object","required":["report_url"],"title":"ReportResponse"},"ResetPasswordRequest":{"properties":{"token":{"type":"string","minLength":1,"title":"Token"},"new_password":{"type":"string","minLength":8,"title":"New Password"}},"type":"object","required":["token","new_password"],"title":"ResetPasswordRequest"},"RiskAggregateKpi":{"properties":{"aggregate_index":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Aggregate Index"},"pct_high_risk":{"type":"number","title":"Pct High Risk"}},"type":"object","required":["aggregate_index","pct_high_risk"],"title":"RiskAggregateKpi"},"RiskExposureKpi":{"properties":{"exposure_index":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Exposure Index"},"band":{"anyOf":[{"$ref":"#/components/schemas/SemaforoBand"},{"type":"null"}]}},"type":"object","required":["exposure_index","band"],"title":"RiskExposureKpi"},"RiskTrendPoint":{"properties":{"date":{"type":"string","title":"Date"},"aggregate_index":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Aggregate Index"},"sample_size":{"type":"integer","title":"Sample Size"}},"type":"object","required":["date","aggregate_index","sample_size"],"title":"RiskTrendPoint"},"RoleUpdateRequest":{"properties":{"role":{"type":"string","enum":["admin","analyst"],"title":"Role"}},"type":"object","required":["role"],"title":"RoleUpdateRequest"},"SanctionItem":{"properties":{"category":{"type":"string","title":"Category"}},"type":"object","required":["category"],"title":"SanctionItem"},"ScoreBucket":{"properties":{"range":{"type":"string","title":"Range"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["range","count"],"title":"ScoreBucket"},"ScoreFactorResponse":{"properties":{"factor_key":{"type":"string","title":"Factor Key"},"label":{"type":"string","title":"Label"},"raw_score":{"type":"integer","title":"Raw Score"},"max_score":{"type":"integer","title":"Max Score"}},"type":"object","required":["factor_key","label","raw_score","max_score"],"title":"ScoreFactorResponse"},"SegmentBreakdown":{"properties":{"by_country":{"items":{"$ref":"#/components/schemas/SegmentRow"},"type":"array","title":"By Country"},"by_activity":{"items":{"$ref":"#/components/schemas/SegmentRow"},"type":"array","title":"By Activity"},"by_naturaleza":{"items":{"$ref":"#/components/schemas/SegmentRow"},"type":"array","title":"By Naturaleza"}},"type":"object","required":["by_country","by_activity","by_naturaleza"],"title":"SegmentBreakdown"},"SegmentRow":{"properties":{"key":{"type":"string","title":"Key"},"total":{"type":"integer","title":"Total"},"high_risk_count":{"type":"integer","title":"High Risk Count"},"pct_high":{"type":"number","title":"Pct High"}},"type":"object","required":["key","total","high_risk_count","pct_high"],"title":"SegmentRow"},"SemaforoBand":{"type":"string","enum":["green","yellow","red"],"title":"SemaforoBand"},"SetPasswordConfirmRequest":{"properties":{"token":{"type":"string","minLength":1,"title":"Token"},"new_password":{"type":"string","minLength":8,"title":"New Password"}},"type":"object","required":["token","new_password"],"title":"SetPasswordConfirmRequest"},"SetPasswordRequestResponse":{"properties":{"message":{"type":"string","title":"Message"}},"type":"object","required":["message"],"title":"SetPasswordRequestResponse"},"SpecialConditionFlag":{"properties":{"flag":{"type":"string","enum":["pep","cpe","ong","fideicomiso"],"title":"Flag"},"count_true":{"type":"integer","title":"Count True"},"pct":{"type":"number","title":"Pct"}},"type":"object","required":["flag","count_true","pct"],"title":"SpecialConditionFlag"},"SpecialConditionFlagOut":{"properties":{"value":{"type":"boolean","title":"Value"},"declared":{"type":"boolean","title":"Declared"},"discrepancy":{"type":"boolean","title":"Discrepancy"},"list_match":{"anyOf":[{"$ref":"#/components/schemas/ListMatchSummaryOut"},{"type":"null"}]}},"type":"object","required":["value","declared","discrepancy"],"title":"SpecialConditionFlagOut"},"StrategicResponse":{"properties":{"period":{"type":"string","title":"Period"},"computed_at":{"type":"string","format":"date-time","title":"Computed At"},"source":{"type":"string","enum":["snapshot","live"],"title":"Source"},"coverage":{"$ref":"#/components/schemas/Coverage"},"risk_aggregate":{"$ref":"#/components/schemas/RiskAggregateKpi"},"risk_exposure":{"$ref":"#/components/schemas/RiskExposureKpi"},"risk_evolution":{"items":{"$ref":"#/components/schemas/RiskTrendPoint"},"type":"array","title":"Risk Evolution"},"risk_by_segment":{"$ref":"#/components/schemas/SegmentBreakdown"},"income_concentration":{"items":{"$ref":"#/components/schemas/IncomeBucket"},"type":"array","title":"Income Concentration"},"special_conditions":{"items":{"$ref":"#/components/schemas/SpecialConditionFlag"},"type":"array","title":"Special Conditions"},"exposed_value":{"$ref":"#/components/schemas/ExposedValueKpi"},"aml_alerts":{"$ref":"#/components/schemas/AmlAlertsKpi"}},"type":"object","required":["period","computed_at","source","coverage","risk_aggregate","risk_exposure","risk_evolution","risk_by_segment","income_concentration","special_conditions","exposed_value","aml_alerts"],"title":"StrategicResponse"},"TenantResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"plan":{"type":"string","title":"Plan"},"credit_balance":{"type":"integer","title":"Credit Balance"}},"type":"object","required":["id","name","plan","credit_balance"],"title":"TenantResponse"},"TenantUpdateRequest":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"}},"type":"object","required":["name"],"title":"TenantUpdateRequest"},"TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"refresh_token":{"type":"string","title":"Refresh Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"}},"type":"object","required":["access_token","refresh_token"],"title":"TokenResponse"},"TriggerETLRequest":{"properties":{"list_type":{"type":"string","enum":["OFAC","ONU"],"title":"List Type"}},"type":"object","required":["list_type"],"title":"TriggerETLRequest"},"TriggerETLResponse":{"properties":{"list_type":{"type":"string","title":"List Type"},"status":{"type":"string","title":"Status"},"job_id":{"type":"string","title":"Job Id"}},"type":"object","required":["list_type","status","job_id"],"title":"TriggerETLResponse"},"UserListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/UserSummary"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"limit":{"type":"integer","title":"Limit"}},"type":"object","required":["items","total","page","limit"],"title":"UserListResponse"},"UserResponse":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"name":{"type":"string","title":"Name"},"role":{"type":"string","title":"Role"}},"type":"object","required":["id","email","name","role"],"title":"UserResponse"},"UserSummary":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"name":{"type":"string","title":"Name"},"role":{"type":"string","title":"Role"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","email","name","role","is_active"],"title":"UserSummary"},"ValidationCancelRequest":{"properties":{"reason":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Reason"}},"type":"object","title":"ValidationCancelRequest"},"ValidationCancelResponse":{"properties":{"id":{"type":"string","title":"Id"},"status":{"type":"string","title":"Status"},"cancelled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cancelled At"},"cancel_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cancel Reason"},"already_terminal":{"type":"boolean","title":"Already Terminal"}},"type":"object","required":["id","status","already_terminal"],"title":"ValidationCancelResponse"},"ValidationCreatedResponse":{"properties":{"id":{"type":"string","title":"Id"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","status","created_at"],"title":"ValidationCreatedResponse"},"ValidationDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"cui":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cui"},"client_full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Full Name"},"status":{"type":"string","title":"Status"},"risk_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Risk Level"},"naturaleza_cliente":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Naturaleza Cliente"},"pais_origen_fondos":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pais Origen Fondos"},"pais_destino_fondos":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pais Destino Fondos"},"score_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Score Total"},"ai_recommendation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Recommendation"},"current_step":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Step"},"degraded_steps":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Degraded Steps"},"error_code":{"anyOf":[{"type":"string","enum":["UPLOAD_FAILED","VALIDATION_OCR_FAILED","VALIDATION_LISTS_FAILED","VALIDATION_STEP_FAILED","VALIDATION_TIMEOUT","STALE_PROCESSING_TIMEOUT","VALIDATION_PIPELINE_CRASH","DPI_MISSING_IN_ZIP","BATCH_TOO_LARGE"]},{"type":"null"}],"title":"Error Code"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"client_photo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Photo Url"},"feic_data":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Feic Data"},"feic_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Feic Source"},"identity":{"anyOf":[{"$ref":"#/components/schemas/app__api__validations__IdentityResponse"},{"type":"null"}]},"score_factors":{"items":{"$ref":"#/components/schemas/ScoreFactorResponse"},"type":"array","title":"Score Factors","default":[]},"computed_risk_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Computed Risk Level"},"overrides_applied":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Overrides Applied"},"internet_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Internet Summary"},"internet_risk_alerts":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Internet Risk Alerts"},"internet_relevant_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Internet Relevant Count"},"internet_negative_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Internet Negative Count"},"list_results":{"items":{"$ref":"#/components/schemas/ListResultResponse"},"type":"array","title":"List Results","default":[]},"internet_results":{"items":{"$ref":"#/components/schemas/InternetResultResponse"},"type":"array","title":"Internet Results","default":[]},"ocr_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Ocr Data"},"renap_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Renap Data"},"consolidated_special_conditions":{"anyOf":[{"$ref":"#/components/schemas/ConsolidatedConditionsOut"},{"type":"null"}]}},"type":"object","required":["id","status","created_at"],"title":"ValidationDetailResponse"},"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"},"ValidationListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ValidationSummaryResponse"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"limit":{"type":"integer","title":"Limit"}},"type":"object","required":["items","total","page","limit"],"title":"ValidationListResponse"},"ValidationSummaryResponse":{"properties":{"id":{"type":"string","title":"Id"},"cui":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cui"},"client_full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Full Name"},"status":{"type":"string","title":"Status"},"risk_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Risk Level"},"naturaleza_cliente":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Naturaleza Cliente"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","status","created_at"],"title":"ValidationSummaryResponse"},"app__api__auth__AuthResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"refresh_token":{"type":"string","title":"Refresh Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"},"user":{"$ref":"#/components/schemas/UserResponse"},"tenant":{"$ref":"#/components/schemas/TenantResponse"},"password_expired":{"type":"boolean","title":"Password Expired","default":false}},"type":"object","required":["access_token","refresh_token","user","tenant"],"title":"AuthResponse"},"app__api__oauth__AuthResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"refresh_token":{"type":"string","title":"Refresh Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"},"user":{"$ref":"#/components/schemas/UserResponse"},"tenant":{"$ref":"#/components/schemas/TenantResponse"}},"type":"object","required":["access_token","refresh_token","user","tenant"],"title":"AuthResponse"},"app__api__oauth__IdentityResponse":{"properties":{"id":{"type":"string","title":"Id"},"provider":{"type":"string","title":"Provider"},"email_at_link":{"type":"string","title":"Email At Link"},"linked_at":{"type":"string","format":"date-time","title":"Linked At"},"last_login_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Login At"}},"type":"object","required":["id","provider","email_at_link","linked_at","last_login_at"],"title":"IdentityResponse"},"app__api__validations__IdentityResponse":{"properties":{"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"data_match":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Data Match"},"face_match":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Face Match"},"face_same":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Face Same"},"face_reasoning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Face Reasoning"}},"type":"object","title":"IdentityResponse"}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT access token issued by POST /api/v1/auth/login. Send as `Authorization: Bearer <token>`. Lifetime 15 minutes; refresh via POST /api/v1/auth/refresh."},"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key","description":"Server-issued API key (prefix `vfq_`). Manage via POST /api/v1/tenants/{tenant_id}/api-keys. Inherits the tenant's admin role. Cannot be rotated automatically — revoke and issue a new one."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$"},"description":"ULID identifier (26 chars, Crockford base32) that lets the client retry this request safely. The server caches the 2xx response for 24 hours; a second request with the same key on the same path returns the original response with header `Idempotent-Replay: true`. Generate one new ULID per user-intent, NOT per network retry."}},"headers":{"XRequestId":{"schema":{"type":"string"},"description":"Server-generated request identifier (hex). Echoes the inbound `X-Request-ID` when supplied, otherwise generated. Returned on every response — use to correlate client and server logs."},"XRateLimitLimit":{"schema":{"type":"integer"},"description":"Maximum number of requests allowed per window. Windows are per-route (e.g. login is 5/minute, list endpoints are 100/minute). The exact limit for the endpoint that returned this header is the integer value."},"XRateLimitRemaining":{"schema":{"type":"integer"},"description":"Requests remaining in the current per-route window before slowapi starts returning 429. Use with X-RateLimit-Reset to compute the wait time."},"XRateLimitReset":{"schema":{"type":"integer"},"description":"Unix epoch seconds when the rate-limit window resets and `X-RateLimit-Remaining` returns to `X-RateLimit-Limit`."}},"responses":{"RateLimited":{"description":"Too Many Requests — the per-route or global rate limit was exceeded. Retry after the `Retry-After` interval.","headers":{"XRateLimitLimit":{"$ref":"#/components/headers/XRateLimitLimit"},"XRateLimitRemaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"XRateLimitReset":{"$ref":"#/components/headers/XRateLimitReset"},"XRequestId":{"$ref":"#/components/headers/XRequestId"},"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before the next request is accepted (RFC 6585 §4). Absent on non-429 responses."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Rate limit exceeded: 5 per 1 minute"}}}}}},"Unauthorized":{"description":"Unauthenticated — missing, invalid, or expired credentials.","headers":{"XRateLimitLimit":{"$ref":"#/components/headers/XRateLimitLimit"},"XRateLimitRemaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"XRateLimitReset":{"$ref":"#/components/headers/XRateLimitReset"},"XRequestId":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"Success200":{"description":"Successful response (generic). See individual operations for the exact schema.","headers":{"XRateLimitLimit":{"$ref":"#/components/headers/XRateLimitLimit"},"XRateLimitRemaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"XRateLimitReset":{"$ref":"#/components/headers/XRateLimitReset"},"XRequestId":{"$ref":"#/components/headers/XRequestId"}}}}}}