{"openapi":"3.1.0","info":{"title":"Aevum API","description":"Aevum is a personal-finance app that turns everyday spending into future savings — automatically. Every time you incur an expense, Aevum levies a small self-imposed \"consumption tax\" and transfers the proceeds into a dedicated savings account of your choice, building a provision for that same kind of expense down the road. Set budgets for the things that matter, and overspending adds a penalty on top — a sharper nudge to stay disciplined. It also auto-categorizes your transactions, generates smart expense analytics, and forecasts recurring bills, while the weekly tax ledger increments your savings pool. It's a budgeting tool with built-in self-accountability — a savings habit that quietly funds your future self.","version":"1.0.0"},"paths":{"/api/v1/auth/.well-known/jwks.json":{"get":{"tags":["Auth"],"summary":"Jwks","description":"Publish the ES256 signing key's public half as a JWK Set (RFC 7517).\n\n**Public, unauthenticated, un-DPoP'd** — a JWKS endpoint is meant to be\nfetched by any verifier. It carries only public key material (no ``d``), so\nthe future auth microservice (E5) can validate access/flow tokens with the\npublished key alone, never holding the signing secret. Served under the\nconventional ``/.well-known/`` path (prefixed by the router mount to\n``/api/v1/auth/.well-known/jwks.json``).","operationId":"jwks_api_v1_auth__well_known_jwks_json_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Jwks Api V1 Auth  Well Known Jwks Json Get"}}}}}}},"/api/v1/auth/register":{"post":{"tags":["Auth"],"summary":"Register","description":"Register a new user (profile + credentials) WITHOUT opening a session.\n\nPre-session email-verification gate (M-accounts): a verification OTP is\nemailed and a short-lived ``pending_token`` is returned; the FE routes to the\nverify page, and a real session is issued only once the code is confirmed\n(``POST /auth/verify-email``). Mirrors the new-device challenge — no usable\ncredential exists until the email is proven. ``request`` is kept for the\nrate-limiter.","operationId":"register_api_v1_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterChallengeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/login":{"post":{"tags":["Auth"],"summary":"Login","description":"Authenticate a user and open a session.","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/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/refresh":{"post":{"tags":["Auth"],"summary":"Refresh","description":"Rotate tokens using the refresh token from its ``httpOnly`` cookie (BFF).\n\nRetires the custom ``X-Refresh-Token`` header: the browser sends the refresh\ncookie automatically (JS never touched it), the rotated token is written back\nto the same cookie, and the fresh access token returns in the body for the FE\nto hold in memory. DPoP still gates the rotation via ``verify_request_dpop``.","operationId":"refresh_api_v1_auth_refresh_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}}}}},"/api/v1/auth/logout":{"post":{"tags":["Auth"],"summary":"Logout","description":"Revoke the current session and clear the refresh cookie.","operationId":"logout_api_v1_auth_logout_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/auth/security":{"get":{"tags":["Auth"],"summary":"Get Security Status","description":"Account-protection snapshot for Settings → Security (auth-domain read).\n\nA 1:1 wrapper over ``auth_services.get_security_state`` — the auth-owned\ntriple ``(has_recovery, two_factor_enabled, backup_codes_remaining)``. This\nis the single FE read for those signals; ``UserAuth`` state stays on auth\nroutes and never rides the users-module ``/me`` profile payload.","operationId":"get_security_status_api_v1_auth_security_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecurityStatusResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/auth/sessions":{"get":{"tags":["Auth"],"summary":"List Sessions","description":"List the caller's active sessions (Settings → Security).\n\n``is_current`` flags the session this request's token belongs to.","operationId":"list_sessions_api_v1_auth_sessions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/SessionInfo"},"type":"array","title":"Response List Sessions Api V1 Auth Sessions Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/auth/sessions/{session_id}":{"delete":{"tags":["Auth"],"summary":"Delete Session","description":"Revoke one of the caller's own sessions (404 if not theirs/missing).\n\nRevoking the current session is allowed — it logs this device out.","operationId":"delete_session_api_v1_auth_sessions__session_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/change-password":{"post":{"tags":["Auth"],"summary":"Change Password","description":"Update the password for the logged-in user.\n\nOn success the user's *other* sessions are signed out (this device stays\nlogged in) and a \"your password was changed\" intimation is emailed.","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":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/auth/change-email-request":{"post":{"tags":["Auth"],"summary":"Change Email Request","description":"Start an email change: re-authenticate, stage the request, and email an\nOTP to the **new** address plus a security intimation to the **source**.\n\nRe-auth is password + (a TOTP/backup ``code`` when 2FA is enabled). The OTP\nproves control of the new address; the source intimation guarantees the\nlegitimate owner always learns a change was set in motion.","operationId":"change_email_request_api_v1_auth_change_email_request_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailChangeRequestBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/auth/change-email-confirm":{"post":{"tags":["Auth"],"summary":"Change Email Confirm","description":"Confirm an email change with the emailed OTP: swap both identity columns,\nsign out other sessions, and intimate the source address of the outcome.","operationId":"change_email_confirm_api_v1_auth_change_email_confirm_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailChangeConfirmBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/auth/new-device/verify":{"post":{"tags":["New-device verification"],"summary":"Verify New Device","description":"Complete a new-device login: validate the pending token + emailed OTP,\nopen the session, and send an intimation email with a one-click revoke link.","operationId":"verify_new_device_api_v1_auth_new_device_verify_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewDeviceVerifyRequest"}}},"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/new-device/resend":{"post":{"tags":["New-device verification"],"summary":"Resend New Device Otp","description":"Re-issue a fresh code for an in-flight new-device challenge.","operationId":"resend_new_device_otp_api_v1_auth_new_device_resend_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewDeviceResendRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewDeviceChallengeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/new-device/revoke":{"post":{"tags":["New-device verification"],"summary":"Revoke New Device","description":"One-click revoke from the intimation email (unauthenticated, signed token).\n\nIdempotent: an already-revoked device still returns 204.","operationId":"revoke_new_device_api_v1_auth_new_device_revoke_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceRevokeRequest"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/devices":{"get":{"tags":["New-device verification"],"summary":"List Devices","description":"List the caller's trusted devices (Settings → Security).","operationId":"list_devices_api_v1_auth_devices_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/KnownDeviceInfo"},"type":"array","title":"Response List Devices Api V1 Auth Devices Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/auth/devices/{uid}":{"delete":{"tags":["New-device verification"],"summary":"Revoke Device","description":"Forget one of the caller's trusted devices (404 if not theirs).\n\nCascades the device's active session — that device must re-verify by OTP on\nits next login.","operationId":"revoke_device_api_v1_auth_devices__uid__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/recovery":{"get":{"tags":["Account Recovery"],"summary":"Get Recovery Config","description":"Return the logged-in user's security question.","operationId":"get_recovery_config_api_v1_auth_recovery_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoveryConfigResponse"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["Account Recovery"],"summary":"Update Recovery Config","description":"Set or update the logged-in user's security question and answer.","operationId":"update_recovery_config_api_v1_auth_recovery_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoveryQuestionCreate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/auth/recovery-question":{"post":{"tags":["Account Recovery"],"summary":"Lookup Recovery Question","description":"Look up the security question configured for an email.","operationId":"lookup_recovery_question_api_v1_auth_recovery_question_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoveryQuestionLookupRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/verify-answer":{"post":{"tags":["Account Recovery"],"summary":"Verify Recovery Answer","description":"Verify a security answer and return a password-reset token.","operationId":"verify_recovery_answer_api_v1_auth_verify_answer_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnswerVerifyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetTokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/forgot-password":{"post":{"tags":["Account Recovery"],"summary":"Forgot Password","description":"Trigger an OTP for password recovery. Always reports success.\n\nThe OTP email is queued (best-effort ``BackgroundTask``) only when the\naddress resolves to an account — an unknown email is silently a no-op, so the\n200 response never reveals whether the address is registered.","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":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/verify-otp":{"post":{"tags":["Account Recovery"],"summary":"Verify Otp","description":"Verify an OTP and return a password-reset token.","operationId":"verify_otp_api_v1_auth_verify_otp_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OTPVerifyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetTokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/reset-password-final":{"post":{"tags":["Account Recovery"],"summary":"Reset Password Final","description":"Reset the password with a valid reset token and open a fresh session.","operationId":"reset_password_final_api_v1_auth_reset_password_final_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordFinalRequest"}}},"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/oauth/{provider}/start":{"get":{"tags":["OAuth"],"summary":"Oauth Start","description":"Begin the OAuth flow — returns the provider authorize URL + signed tx.","operationId":"oauth_start_api_v1_auth_oauth__provider__start_get","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","title":"Provider"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthStartResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/oauth/{provider}/callback":{"post":{"tags":["OAuth"],"summary":"Oauth Callback","description":"Complete the OAuth flow: exchange the code, link-or-register, issue a\nsession — or return a 2FA challenge (the new-device OTP gate is skipped).","operationId":"oauth_callback_api_v1_auth_oauth__provider__callback_post","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","title":"Provider"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthCallbackRequest"}}}},"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/verify-email":{"post":{"tags":["Email verification"],"summary":"Verify Email","description":"Confirm ``(pending_token, otp)`` → mark email-verified → open the session.\n\nNo session is required (none exists yet) — the pending token IS the credential\nfor this step. Returns a real ``TokenResponse`` on success.","operationId":"verify_email_api_v1_auth_verify_email_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailVerifyRequest"}}},"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/verify-email/resend":{"post":{"tags":["Email verification"],"summary":"Resend Verify Email","description":"Re-issue + re-send the verification OTP; rotate the pending token.","operationId":"resend_verify_email_api_v1_auth_verify_email_resend_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailResendRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailResendResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/2fa/enroll":{"post":{"tags":["Two-factor authentication"],"summary":"Enroll 2Fa","description":"Begin TOTP enrollment: stage a secret and return it + the provisioning URI\n(the FE renders the QR). 2FA stays off until the first code is confirmed.","operationId":"enroll_2fa_api_v1_auth_2fa_enroll_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwoFactorEnrollResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/auth/2fa/verify-enroll":{"post":{"tags":["Two-factor authentication"],"summary":"Verify Enroll 2Fa","description":"Confirm the first TOTP code → enable 2FA and return the one-time backup\ncodes (shown exactly once).","operationId":"verify_enroll_2fa_api_v1_auth_2fa_verify_enroll_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwoFactorVerifyEnrollRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwoFactorEnrollResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/auth/2fa/disable":{"post":{"tags":["Two-factor authentication"],"summary":"Disable 2Fa","description":"Disable 2FA, re-confirming intent with the account password.","operationId":"disable_2fa_api_v1_auth_2fa_disable_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwoFactorDisableRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/auth/2fa/login-verify":{"post":{"tags":["Two-factor authentication"],"summary":"Login Verify 2Fa","description":"Complete a 2FA-gated login with a TOTP (or backup) code and open the\nsession. Reached after login / new-device verify / recovery reset returned a\n``two_factor_required`` challenge.","operationId":"login_verify_2fa_api_v1_auth_2fa_login_verify_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TwoFactorLoginVerifyRequest"}}},"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/users/me/data-reset":{"post":{"tags":["Users"],"summary":"Reset My Data","description":"Wipe all of the caller's domain data and restore the registration\ndefaults — a *clean restart* that keeps the account, credentials,\npreferences, and the current session intact. Password step-up (destructive).\n\nReturns the post-reset profile-card stats (all counts back to a fresh\naccount) as the confirmation payload.","operationId":"reset_my_data_api_v1_users_me_data_reset_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDataResetRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserStatsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/users/me":{"get":{"tags":["Users"],"summary":"Get Me","description":"Return the current user's profile (PII fields excluded).","operationId":"get_me_api_v1_users_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserEnvelope"}}}}},"security":[{"HTTPBearer":[]}]},"patch":{"tags":["Users"],"summary":"Update Me","description":"Apply a partial update to the current user's profile.","operationId":"update_me_api_v1_users_me_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/users/me/stats":{"get":{"tags":["Users"],"summary":"Get My Stats","description":"Return the current user's profile-card stats (metadata + counts).","operationId":"get_my_stats_api_v1_users_me_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserStatsResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/users/me/profile-image":{"post":{"tags":["Users"],"summary":"Upload Profile Image","description":"Upload a profile picture.\n\nThe bytes are decoded, resized, stripped of metadata, and re-encoded to\nWEBP before storage (the decode itself is the validation). Replaces any\nexisting image and deletes the previous upload's file.\n\nGated by the storage-aware ``config.profile_image_uploads_enabled`` — off\nwhere storage is ephemeral (e.g. a local-FS Render free tier); initials +\nportraits remain available. Same value the ``profile_image_upload`` capability\nadvertises, so the FE affordance and this gate stay in lockstep.","operationId":"upload_profile_image_api_v1_users_me_profile_image_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_profile_image_api_v1_users_me_profile_image_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]},"delete":{"tags":["Users"],"summary":"Delete Profile Image","description":"Remove the uploaded photo (a chosen portrait, if any, is untouched).\n\nIdempotent — clearing an already-empty photo is a no-op.","operationId":"delete_profile_image_api_v1_users_me_profile_image_delete","responses":{"204":{"description":"Successful Response"}},"security":[{"HTTPBearer":[]}]}},"/api/v1/users/me/portrait":{"put":{"tags":["Users"],"summary":"Set Portrait","description":"Choose a client-rendered portrait (style + seed); replaces any photo.","operationId":"set_portrait_api_v1_users_me_portrait_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortraitSelection"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]},"delete":{"tags":["Users"],"summary":"Delete Portrait","description":"Remove the chosen portrait (the photo, if any, is untouched).\n\nIdempotent — clearing an already-empty portrait is a no-op.","operationId":"delete_portrait_api_v1_users_me_portrait_delete","responses":{"204":{"description":"Successful Response"}},"security":[{"HTTPBearer":[]}]}},"/api/v1/users/me/delete":{"post":{"tags":["Users"],"summary":"Request Account Deletion","description":"Schedule the current account for deletion (two-phase soft delete).\n\nRe-verifies the password (step-up auth), sets the soft-delete marker,\ninvalidates every session (the caller is signed out everywhere), and emails\na signed cancel link. A weekly worker hard-deletes the account after the\ngrace window; the link reverses it any time before then.","operationId":"request_account_deletion_api_v1_users_me_delete_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDeletionRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDeletionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/users/me/delete/cancel":{"post":{"tags":["Users"],"summary":"Cancel Account Deletion","description":"Cancel a scheduled deletion via the signed link from the confirmation\nemail. Deliberately **unauthenticated** — the account is logged out while\npending deletion, so the signed token is the sole proof of intent.\n\nIdempotent on an already-active account; ``410`` once the account has been\npermanently purged, ``400`` for a bad/expired link.","operationId":"cancel_account_deletion_api_v1_users_me_delete_cancel_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDeletionCancelRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDeletionCancelResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/users/preferences":{"get":{"tags":["Users"],"summary":"Get Preferences","description":"Return the user's full preferences row (Preferences-tier settings + the\ntwo accessibility flags). The frontend dissects this across its preference\n/ accessibility stores. ``country`` is Profile-tier — read it from ``/me``.","operationId":"get_preferences_api_v1_users_preferences_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPreferencesResponse"}}}}},"security":[{"HTTPBearer":[]}]},"patch":{"tags":["Users"],"summary":"Update Preferences","description":"Apply a partial update to the user's preferences (any subset of fields).\n\nThe backend is source of truth — settings persist across devices and\nreinstalls. Validated values only; unknown enum / currency / timezone\nvalues are rejected with 422.","operationId":"update_preferences_api_v1_users_preferences_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPreferencesUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPreferencesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/onboarding":{"get":{"tags":["Onboarding"],"summary":"Get Onboarding State","description":"The current user's onboarding journey state.","operationId":"get_onboarding_state_api_v1_onboarding_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingStateResponse"}}}}},"security":[{"HTTPBearer":[]}]},"patch":{"tags":["Onboarding"],"summary":"Patch Onboarding State","description":"Update the user-controllable journey flags (tutorial seen / nudge\ndismissed / per-step checklist progress).","operationId":"patch_onboarding_state_api_v1_onboarding_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingStateUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingStateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/onboarding/profile-step":{"post":{"tags":["Onboarding"],"summary":"Complete Profile Step","description":"Complete or skip the onboarding profile section.\n\nWrites name/country (D7: ≥1 name when not skipping) and flips\n``profile_complete``; queues the welcome email (real name when present,\nelse the email local-part) on first resolution.","operationId":"complete_profile_step_api_v1_onboarding_profile_step_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileStepRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingStateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/onboarding/demo/seed":{"post":{"tags":["Onboarding"],"summary":"Seed Demo","description":"Seed the opt-in sample-data demo (flips ``demo_active``). Interactive\nteardown is the password-gated `POST /users/me/data-reset`; the background\nteardown is `POST /onboarding/demo/reset` (below).","operationId":"seed_demo_api_v1_onboarding_demo_seed_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingStateResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/onboarding/demo/reset":{"post":{"tags":["Onboarding"],"summary":"Reset Demo","description":"Password-less background teardown of the sample-data demo (M-accounts\nF4/F5). Used by the product tour to auto-reset on completion so the user is\nleft fresh. Safe without step-up auth: the seed is pristine-gated, so this\nonly ever unwinds a demo back to a pristine account. Refuses (409) if no\ndemo is active or if the account gained data beyond the sample set — the\ninteractive banner + ResetZone handle that case instead.","operationId":"reset_demo_api_v1_onboarding_demo_reset_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingStateResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/metadata/countries":{"get":{"tags":["Metadata"],"summary":"Get Countries","description":"List countries with phone code, default currency and timezone.","operationId":"get_countries_api_v1_metadata_countries_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CountriesResponse"}}}}}}},"/api/v1/metadata/currencies":{"get":{"tags":["Metadata"],"summary":"Get Currencies","description":"List currencies for dropdowns.","operationId":"get_currencies_api_v1_metadata_currencies_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CurrenciesResponse"}}}}}}},"/api/v1/metadata/timezones":{"get":{"tags":["Metadata"],"summary":"Get Timezones","description":"Full IANA timezone list with advisory standard/DST UTC offsets.","operationId":"get_timezones_api_v1_metadata_timezones_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimezonesResponse"}}}}}}},"/api/v1/metadata/branding":{"get":{"tags":["Metadata"],"summary":"Get Branding","description":"Return product brand identity (name/tagline/description/logo) for the FE.\n\nDeployment ``capabilities`` were decoupled to ``GET /metadata/capabilities``\n(T-capability-gating); this no longer carries them.","operationId":"get_branding_api_v1_metadata_branding_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandingResponse"}}}}}}},"/api/v1/metadata/capabilities":{"get":{"tags":["Metadata"],"summary":"Get Capabilities","description":"Two-tier deployment capability surface (``features`` / ``infrastructure``).\n\nThe BE source of truth for each optional integration's effective ``enabled``\nstate — the FE gates optional UI on ``features.*`` so a lean host hides an\naction rather than offering one that 403s. ``exclude_none`` keeps the payload\nto *registered* leaves only (an unregistered/reserved capability is absent, not\n``null``), matching the FE's optional reads (``features.oauth?.google``).","operationId":"get_capabilities_api_v1_metadata_capabilities_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CapabilitiesResponse"}}}}}}},"/api/v1/metadata/constants":{"get":{"tags":["Metadata"],"summary":"Get Constants","description":"Return system reference constants for frontend synchronization.","operationId":"get_constants_api_v1_metadata_constants_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SystemConstantsResponse"}}}}}}},"/api/v1/tags/":{"get":{"tags":["Tags"],"summary":"List Tags","description":"Return the tag tree visible to the caller (system + user-owned).","operationId":"list_tags_api_v1_tags__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagListResponse"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["Tags"],"summary":"Create Tag","description":"Create a user-scoped tag.","operationId":"create_tag_api_v1_tags__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagCreate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/tags/{tag_id}":{"patch":{"tags":["Tags"],"summary":"Update Tag","description":"Update a tag. Reserved tag name/type are read-only; aliases stay editable.","operationId":"update_tag_api_v1_tags__tag_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"tag_id","in":"path","required":true,"schema":{"type":"integer","title":"Tag Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Tags"],"summary":"Delete Tag","description":"Delete a user-owned tag. Reserved tags are read-only.","operationId":"delete_tag_api_v1_tags__tag_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"tag_id","in":"path","required":true,"schema":{"type":"integer","title":"Tag Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/registry/search":{"get":{"tags":["Registry"],"summary":"Search Registry","description":"Canonical counterparties matching ``q`` — what the user would get if they\nnamed one. Looser than the pipeline's matcher on purpose: a person reads this\nresult and decides, so recall beats precision (see ``registry_services.search``).","operationId":"search_registry_api_v1_registry_search_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","description":"Free-text name / alias fragment.","default":"","title":"Q"},"description":"Free-text name / alias fragment."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"default":10,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RegistryMatch"},"title":"Response Search Registry Api V1 Registry Search Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/transactions/":{"get":{"tags":["Transactions"],"summary":"List Transactions","description":"List transactions, or aggregate them by tag / beneficiary for one bucket.\n\nWith ``group_by`` set, returns a :class:`GroupedTransactionsResponse` read\njoin-free from the materialized aggregate read-model (T-aggregates-engine):\n``month=YYYY-MM`` selects a monthly bucket, ``period=week&date=YYYY-MM-DD`` a\nweekly one (default: current month). ``total_count`` / ``net_expense``\n(= total_debit − total_credit) replace the old ``frequency`` /\n``total_amount``; ``tag_type`` is included on tag groups. Without\n``group_by`` it returns the flat transaction list as before.","operationId":"list_transactions_api_v1_transactions__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Month"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Year"}},{"name":"tag_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tag Id"}},{"name":"debit_credit","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/DebitCredit"},{"type":"null"}],"title":"Debit Credit"}},{"name":"beneficiary_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Beneficiary Id"}},{"name":"group_by","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/GroupBy"},{"type":"null"}],"title":"Group By"}},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"enum":["week","month"],"title":"Period"}},{"name":"date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Anchor date (YYYY-MM-DD) for period=week grouping","title":"Date"},"description":"Anchor date (YYYY-MM-DD) for period=week grouping"},{"name":"sort_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"enum":["date","amount","total_count","net_expense","name"],"default":"date","title":"Sort By"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc","title":"Order"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":25,"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":{"anyOf":[{"$ref":"#/components/schemas/TransactionListResponse"},{"$ref":"#/components/schemas/GroupedTransactionsResponse"}],"title":"Response List Transactions Api V1 Transactions  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Transactions"],"summary":"Create Transaction","description":"Create a manual transaction.","operationId":"create_transaction_api_v1_transactions__post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"rule_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rule Id"}},{"name":"allow_duplicate","in":"query","required":false,"schema":{"type":"boolean","description":"Override a HIGH-confidence duplicate block (user confirmed 'add anyway').","default":false,"title":"Allow Duplicate"},"description":"Override a HIGH-confidence duplicate block (user confirmed 'add anyway')."},{"name":"allow_unlinked","in":"query","required":false,"schema":{"type":"boolean","description":"Override a SUSPECTED cross-leg block (user confirmed 'keep separate').","default":false,"title":"Allow Unlinked"},"description":"Override a SUSPECTED cross-leg block (user confirmed 'keep separate')."},{"name":"link_transfer_to","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Answer a SUSPECTED cross-leg block by LINKING: fold this entry and transaction #N into one transfer. Returns the surviving (debit) leg.","title":"Link Transfer To"},"description":"Answer a SUSPECTED cross-leg block by LINKING: fold this entry and transaction #N into one transfer. Returns the surviving (debit) leg."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionMutationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/transactions/{txn_id}":{"get":{"tags":["Transactions"],"summary":"Get Transaction","description":"Get a single transaction.","operationId":"get_transaction_api_v1_transactions__txn_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"txn_id","in":"path","required":true,"schema":{"type":"integer","title":"Txn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Transactions"],"summary":"Update Transaction","description":"Patch a transaction; statement-imported rows can only update notes/tags.","operationId":"update_transaction_api_v1_transactions__txn_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"txn_id","in":"path","required":true,"schema":{"type":"integer","title":"Txn Id"}},{"name":"rule_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rule Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionMutationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Transactions"],"summary":"Delete Transaction","description":"Soft-void a transaction (manual or statement) — the row is preserved with\n``voided_at`` set, not removed (T-treasury).","operationId":"delete_transaction_api_v1_transactions__txn_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"txn_id","in":"path","required":true,"schema":{"type":"integer","title":"Txn Id"}},{"name":"on_payment","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"On Payment"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/transactions/review-questions":{"get":{"tags":["Transactions"],"summary":"List Review Questions","description":"The user's open review questions — rows HELD rather than imported.\n\n**Two kinds, one queue.** A suspected TRANSFER leg (looks like the far side of a\nmove between the user's own accounts, but the counterparty handle isn't registered\nto any of them) and a suspected DUPLICATE (matches an existing row across ingestion\nevents, where duplication is possible and only the user can settle it).\n\nNeither is in the ledger. Neither is lost. Both wait for exactly one answer, and\npending rows are never purged — so an unanswered question cannot silently lose a\ntransaction.","operationId":"list_review_questions_api_v1_transactions_review_questions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Narrow to one kind: 'transfer' or 'duplicate'.","title":"Kind"},"description":"Narrow to one kind: 'transfer' or 'duplicate'."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ReviewQuestionResponse"},"title":"Response List Review Questions Api V1 Transactions Review Questions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/transactions/review-questions/{staged_uid}/transfer":{"post":{"tags":["Transactions"],"summary":"Answer Transfer Question","description":"Answer one held transfer question.","operationId":"answer_transfer_question_api_v1_transactions_review_questions__staged_uid__transfer_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"staged_uid","in":"path","required":true,"schema":{"type":"integer","title":"Staged Uid"}},{"name":"is_mine","in":"query","required":true,"schema":{"type":"boolean","description":"True: the counterparty IS one of the user's own accounts → register the identifier on it (so every future transfer between these two accounts auto-folds) and fold this pair into one transfer. False: the pair was a coincidence → promote the held row as an ordinary transaction.","title":"Is Mine"},"description":"True: the counterparty IS one of the user's own accounts → register the identifier on it (so every future transfer between these two accounts auto-folds) and fold this pair into one transfer. False: the pair was a coincidence → promote the held row as an ordinary transaction."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionMutationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/transactions/review-questions/{staged_uid}/duplicate":{"post":{"tags":["Transactions"],"summary":"Answer Duplicate Question","description":"Answer one held duplicate question.","operationId":"answer_duplicate_question_api_v1_transactions_review_questions__staged_uid__duplicate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"staged_uid","in":"path","required":true,"schema":{"type":"integer","title":"Staged Uid"}},{"name":"is_duplicate","in":"query","required":true,"schema":{"type":"boolean","description":"True: the user confirms this IS the same payment already in the ledger → discard the held row. False: two different payments that merely agree on amount, date and payee → import it. Only the user can settle this; the data cannot.","title":"Is Duplicate"},"description":"True: the user confirms this IS the same payment already in the ledger → discard the held row. False: two different payments that merely agree on amount, date and payee → import it. Only the user can settle this; the data cannot."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionMutationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/transactions/review-questions/duplicates/bulk":{"post":{"tags":["Transactions"],"summary":"Answer Duplicates Bulk","description":"Answer a whole import's duplicate queue at once.\n\nAn overlapping statement can hold 150 suspected duplicates. Asking 150 separate\nquestions would be worse than the auto-drop this replaced — it would train the user\nto bulk-dismiss, and a user who bulk-dismisses without looking has re-created the\nauto-drop with their own fingerprints on it. Grouping by import keeps it the user's\ndecision, made **once**.","operationId":"answer_duplicates_bulk_api_v1_transactions_review_questions_duplicates_bulk_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"query","required":true,"schema":{"type":"integer","description":"The import whose duplicate queue to answer.","title":"Job Id"},"description":"The import whose duplicate queue to answer."},{"name":"import_all","in":"query","required":true,"schema":{"type":"boolean","description":"True: import every held duplicate from this job (they are real, separate payments). False: discard them all (they are already in the ledger).","title":"Import All"},"description":"True: import every held duplicate from this job (they are real, separate payments). False: discard them all (they are already in the ledger)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkAnswerResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/transactions/{txn_id}/link-transfer":{"post":{"tags":["Transactions"],"summary":"Link Transfer","description":"Fold two already-landed transactions into ONE transfer (4d.3c).\n\nThe user's answer to a SUSPECTED cross-leg — from the statement-import review\n(``possible_transfer_detected``) or anywhere else two legs are spotted after the\nfact. The DEBIT leg survives carrying both accounts and is re-tagged; the credit\nleg is voided. Returns the survivor.","operationId":"link_transfer_api_v1_transactions__txn_id__link_transfer_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"txn_id","in":"path","required":true,"schema":{"type":"integer","title":"Txn Id"}},{"name":"counter_txn_uid","in":"query","required":true,"schema":{"type":"integer","description":"The other leg of the transfer (opposite direction).","title":"Counter Txn Uid"},"description":"The other leg of the transfer (opposite direction)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionMutationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/statement-uploads/parsers":{"get":{"tags":["Statement Upload"],"summary":"List Parsers","description":"The parser catalogue the frontend's picker uses.\n\nSame shape as the 422 ``available_parsers`` envelope; polled once at boot\n(``staleTime: Infinity``) so the picker is populated proactively, not only\nafter a low-confidence 422. Declared **before** ``/{job_id}`` so the literal\npath isn't captured by the int job-id route.","operationId":"list_parsers_api_v1_statement_uploads_parsers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ParserInfo"},"type":"array","title":"Response List Parsers Api V1 Statement Uploads Parsers Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/statement-uploads":{"post":{"tags":["Statement Upload"],"summary":"Upload Statement","description":"Accept a statement upload, queue background processing, return 202.\n\nThe file is hashed (409 on an exact re-upload) and a parser auto-detected\n(422 + ``available_parsers`` on low confidence) synchronously; the heavy\nparse/categorize/commit runs in a FastAPI ``BackgroundTask``.\n\n``parser_override`` (optional form field) is a registry class key the user\npicked — when present, detection is skipped and that parser is used (an\nunknown key → 422 + ``available_parsers``). The pipeline still falls back to\ndetection if the picked parser can't parse the file.\n\nGated by ``STATEMENT_UPLOAD_ENABLED`` — a safety valve for constrained hosts\nwhere in-process pdftotext/parsing could pressure memory.","operationId":"upload_statement_api_v1_statement_uploads_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_statement_api_v1_statement_uploads_post"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadAcceptedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/statement-uploads/{job_id}":{"get":{"tags":["Statement Upload"],"summary":"Get Upload Job","description":"Poll an upload job's progress / result.","operationId":"get_upload_job_api_v1_statement_uploads__job_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"integer","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/transactions/{txn_id}/manual-tags":{"post":{"tags":["Statement Upload"],"summary":"Manual Categorize Txn","description":"Manually (re)tag a statement-imported transaction.","operationId":"manual_categorize_txn_api_v1_transactions__txn_id__manual_tags_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"txn_id","in":"path","required":true,"schema":{"type":"integer","title":"Txn Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManualTagsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManualTagsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/beneficiaries/":{"get":{"tags":["Beneficiaries"],"summary":"List Beneficiaries","description":"List the user's visible beneficiaries (own + system-owned).","operationId":"list_beneficiaries_api_v1_beneficiaries__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/BeneficiaryResponse"},"type":"array","title":"Response List Beneficiaries Api V1 Beneficiaries  Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["Beneficiaries"],"summary":"Create Beneficiary","description":"Create a new beneficiary (with merchant or person detail).","operationId":"create_beneficiary_api_v1_beneficiaries__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeneficiaryCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeneficiaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/beneficiaries/check-alias":{"get":{"tags":["Beneficiaries"],"summary":"Check Alias","description":"Check whether an alias (or beneficiary name) is globally unique.","operationId":"check_alias_api_v1_beneficiaries_check_alias_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"alias","in":"query","required":true,"schema":{"type":"string","minLength":1,"title":"Alias"}},{"name":"exclude_uid","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Exclude Uid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AliasCheckResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/beneficiaries/merge":{"post":{"tags":["Beneficiaries"],"summary":"Merge Beneficiaries Route","description":"Merge ``source_uid`` into ``target_uid``.","operationId":"merge_beneficiaries_route_api_v1_beneficiaries_merge_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeneficiaryMergeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/beneficiaries/relationships":{"get":{"tags":["Beneficiaries"],"summary":"List Relationships","description":"List the standard relationship types for a ``Person`` beneficiary.","operationId":"list_relationships_api_v1_beneficiaries_relationships_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"string"},"type":"array","title":"Response List Relationships Api V1 Beneficiaries Relationships Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/beneficiaries/{uid}":{"get":{"tags":["Beneficiaries"],"summary":"Get Beneficiary","description":"Get a single beneficiary by uid.","operationId":"get_beneficiary_api_v1_beneficiaries__uid__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeneficiaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Beneficiaries"],"summary":"Update Beneficiary","description":"Update a beneficiary; may switch merchant/person specialisation.","operationId":"update_beneficiary_api_v1_beneficiaries__uid__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeneficiaryUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeneficiaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Beneficiaries"],"summary":"Delete Beneficiary","description":"Delete a beneficiary (merchant/person rows cascade).","operationId":"delete_beneficiary_api_v1_beneficiaries__uid__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/beneficiaries/{uid}/accounts":{"get":{"tags":["Beneficiaries"],"summary":"List Beneficiary Accounts","description":"List a beneficiary's own bank accounts.","operationId":"list_beneficiary_accounts_api_v1_beneficiaries__uid__accounts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/BeneficiaryAccountResponse"},"title":"Response List Beneficiary Accounts Api V1 Beneficiaries  Uid  Accounts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Beneficiaries"],"summary":"Create Beneficiary Account","description":"Record a bank account (VPA / last4) for the beneficiary.","operationId":"create_beneficiary_account_api_v1_beneficiaries__uid__accounts_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeneficiaryAccountCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeneficiaryAccountResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/beneficiaries/{uid}/accounts/{account_uid}":{"patch":{"tags":["Beneficiaries"],"summary":"Update Beneficiary Account","description":"Update a beneficiary account (identifier / type / label).","operationId":"update_beneficiary_account_api_v1_beneficiaries__uid__accounts__account_uid__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}},{"name":"account_uid","in":"path","required":true,"schema":{"type":"integer","title":"Account Uid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeneficiaryAccountUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeneficiaryAccountResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Beneficiaries"],"summary":"Delete Beneficiary Account","description":"Delete one of the beneficiary's bank accounts.","operationId":"delete_beneficiary_account_api_v1_beneficiaries__uid__accounts__account_uid__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}},{"name":"account_uid","in":"path","required":true,"schema":{"type":"integer","title":"Account Uid"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/bank-accounts/":{"get":{"tags":["Bank Accounts"],"summary":"List Bank Accounts","description":"List the user's bank accounts.","operationId":"list_bank_accounts_api_v1_bank_accounts__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/BankAccountResponse"},"type":"array","title":"Response List Bank Accounts Api V1 Bank Accounts  Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["Bank Accounts"],"summary":"Create Bank Account","description":"Create a bank account (optionally with inline identifiers).","operationId":"create_bank_account_api_v1_bank_accounts__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankAccountCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankAccountResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/bank-accounts/{uid}":{"get":{"tags":["Bank Accounts"],"summary":"Get Bank Account","description":"Get one of the user's bank accounts.","operationId":"get_bank_account_api_v1_bank_accounts__uid__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankAccountResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Bank Accounts"],"summary":"Update Bank Account","description":"Update a bank account (many committee accounts allowed — nothing to demote).","operationId":"update_bank_account_api_v1_bank_accounts__uid__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankAccountUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankAccountResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Bank Accounts"],"summary":"Delete Bank Account","description":"Delete a bank account (identifiers cascade).\n\n409 once any transaction is attributed to it — archive instead (see\n``BankAccountInUse``): deleting would strip the account out of its own history.","operationId":"delete_bank_account_api_v1_bank_accounts__uid__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/bank-accounts/{uid}/archived":{"patch":{"tags":["Bank Accounts"],"summary":"Set Archived","description":"Archive / un-archive an account — the escape hatch `delete` refuses to be.\n\nA closed account still anchored real transactions, and those still need its kind to\nstay classifiable. Archiving hides it without rewriting history.","operationId":"set_archived_api_v1_bank_accounts__uid__archived_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankAccountArchive"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankAccountResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/bank-accounts/{uid}/identifiers":{"post":{"tags":["Bank Accounts"],"summary":"Add Identifier","description":"Attach an identifier (UPI handle, …) to the user's bank account.","operationId":"add_identifier_api_v1_bank_accounts__uid__identifiers_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountIdentifierCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountIdentifierResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/bank-accounts/{uid}/identifiers/{identifier_uid}":{"delete":{"tags":["Bank Accounts"],"summary":"Delete Identifier","description":"Remove an identifier from the user's bank account.","operationId":"delete_identifier_api_v1_bank_accounts__uid__identifiers__identifier_uid__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}},{"name":"identifier_uid","in":"path","required":true,"schema":{"type":"integer","title":"Identifier Uid"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/categorization-rules/":{"get":{"tags":["Categorization Rules"],"summary":"List Categorization Rules","description":"List the current user's categorization rules (Decision 39: per-user).","operationId":"list_categorization_rules_api_v1_categorization_rules__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategorizationRuleListResponse"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["Categorization Rules"],"summary":"Create Categorization Rule","description":"Create a categorization rule (one per beneficiary).","operationId":"create_categorization_rule_api_v1_categorization_rules__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategorizationRuleCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategorizationRuleCreatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/categorization-rules/{uid}":{"put":{"tags":["Categorization Rules"],"summary":"Update Categorization Rule","description":"Patch a categorization rule. System rules accept notes/name updates.","operationId":"update_categorization_rule_api_v1_categorization_rules__uid__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategorizationRuleUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Categorization Rules"],"summary":"Delete Categorization Rule","description":"Delete a user-owned categorization rule.","operationId":"delete_categorization_rule_api_v1_categorization_rules__uid__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/categorization-rules/re-run":{"post":{"tags":["Categorization Rules"],"summary":"Re Run Rules","description":"Re-run every categorization rule across the user's statement transactions.","operationId":"re_run_rules_api_v1_categorization_rules_re_run_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/taxation-rules/":{"get":{"tags":["Taxation Rules"],"summary":"List Taxation Rules","description":"List effective taxation rules for the current user.","operationId":"list_taxation_rules_api_v1_taxation_rules__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"txn_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/TxnType"},{"type":"null"}],"title":"Txn Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaxationRuleListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/taxation-rules/{txn_type}":{"put":{"tags":["Taxation Rules"],"summary":"Upsert Taxation Rule","description":"Create or update the user's taxation rule for a txn_type.","operationId":"upsert_taxation_rule_api_v1_taxation_rules__txn_type__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"txn_type","in":"path","required":true,"schema":{"$ref":"#/components/schemas/TxnType"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaxationRuleUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaxationRuleUpsertResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/consumption-tax/bills":{"get":{"tags":["Consumption Tax"],"summary":"List Bills","operationId":"list_bills_api_v1_consumption_tax_bills_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillListResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/consumption-tax/bills/{bill_id}":{"get":{"tags":["Consumption Tax"],"summary":"Get Bill","operationId":"get_bill_api_v1_consumption_tax_bills__bill_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bill_id","in":"path","required":true,"schema":{"type":"integer","title":"Bill Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/consumption-tax/bills/{bill_id}/mark-paid":{"post":{"tags":["Consumption Tax"],"summary":"Mark Bill Paid","description":"Manually settle a bill — link a payment txn or override. Never creates a\ntransaction (the engine only reconciles existing txn data).","operationId":"mark_bill_paid_api_v1_consumption_tax_bills__bill_id__mark_paid_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bill_id","in":"path","required":true,"schema":{"type":"integer","title":"Bill Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkPaidRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkPaidResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/consumption-tax/pay":{"post":{"tags":["Consumption Tax"],"summary":"Pay Consumption Tax","description":"Affordance A — aggregate self-tax payment. Mints a synthetic\nconsumption_tax transfer (201 + the new txn uid) from the user-selected source\n(REGULAR) into the committee (tax-pot); the settlement hook FIFO-discharges\noutstanding bills. A probable retry surfaces a 409 (kind=duplicate) the user\nresolves by re-POSTing with ``allow_duplicate`` — they are the SoT.","operationId":"pay_consumption_tax_api_v1_consumption_tax_pay_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/consumption-tax/bills/{bill_id}/mark-unpaid":{"post":{"tags":["Consumption Tax"],"summary":"Mark Bill Unpaid","operationId":"mark_bill_unpaid_api_v1_consumption_tax_bills__bill_id__mark_unpaid_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bill_id","in":"path","required":true,"schema":{"type":"integer","title":"Bill Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkUnpaidResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/consumption-tax/bills/generate":{"post":{"tags":["Consumption Tax"],"summary":"Generate Bills","description":"Manually generate bills for a completed range. Manual-mode users only —\nauto-mode users' bills are owned by the weekly worker (409).","operationId":"generate_bills_api_v1_consumption_tax_bills_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsumptionTaxBillGenerateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillGenerateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/consumption-tax/admin/bills/generate":{"post":{"tags":["Consumption Tax (Admin)"],"summary":"Admin Generate Bills","description":"Generate bills on behalf of any user (ops/backfill). Bypasses the\nauto-mode guard, so it works for auto-enabled users too.","operationId":"admin_generate_bills_api_v1_consumption_tax_admin_bills_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminBillGenerateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillGenerateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/treasury/summary":{"get":{"tags":["Treasury"],"summary":"Get Treasury Summary","description":"Treasury summary for the dashboard hero — funded balance (cash set aside)\n+ provisioned (owed) + a weekly cumulative trend (default last 12 weeks).","operationId":"get_treasury_summary_api_v1_treasury_summary_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"weeks","in":"query","required":false,"schema":{"type":"integer","maximum":52,"minimum":1,"default":12,"title":"Weeks"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TreasurySummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/budget-limits/":{"get":{"tags":["Budget Limits"],"summary":"List Budget Limits","description":"List the user's budget limits (monthly only in MVP).","operationId":"list_budget_limits_api_v1_budget_limits__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"budget_period","in":"query","required":false,"schema":{"$ref":"#/components/schemas/PeriodType","default":"monthly"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BudgetLimitListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Budget Limits"],"summary":"Upsert Budget Limit","description":"Create or update a monthly budget limit for one tag.","operationId":"upsert_budget_limit_api_v1_budget_limits__post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BudgetLimitUpsert"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BudgetLimitEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/budget-limits/status":{"get":{"tags":["Budget Limits"],"summary":"Get Budget Status","description":"Detailed merged budget/tracker/limits report for the active month.","operationId":"get_budget_status_api_v1_budget_limits_status_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Month"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BudgetStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/budget-limits/{tag_id}":{"delete":{"tags":["Budget Limits"],"summary":"Delete Budget Limit","description":"Remove a monthly budget limit for one tag (204 on success).","operationId":"delete_budget_limit_api_v1_budget_limits__tag_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"tag_id","in":"path","required":true,"schema":{"type":"integer","title":"Tag Id"}},{"name":"budget_period","in":"query","required":false,"schema":{"$ref":"#/components/schemas/PeriodType","default":"monthly"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/expense-tracker/":{"get":{"tags":["Expense Tracker"],"summary":"Get Expense Trend","description":"Per-(tag, bucket) spend trend over the last ``n`` buckets of ``period_type``,\nending at ``end`` (defaults to today).","operationId":"get_expense_trend_api_v1_expense_tracker__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"period_type","in":"query","required":false,"schema":{"$ref":"#/components/schemas/PeriodType","default":"monthly"}},{"name":"n","in":"query","required":false,"schema":{"type":"integer","maximum":104,"minimum":1,"description":"Number of buckets back","default":12,"title":"N"},"description":"Number of buckets back"},{"name":"tag_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Limit the trend to a single tag","title":"Tag Id"},"description":"Limit the trend to a single tag"},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Anchor the window's end at the bucket containing this YYYY-MM-DD date (defaults to today) — lets the trend follow the page's month selector.","title":"End"},"description":"Anchor the window's end at the bucket containing this YYYY-MM-DD date (defaults to today) — lets the trend follow the page's month selector."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpenseTrendResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recurring/templates":{"get":{"tags":["Recurring"],"summary":"List Templates","description":"List the user's recurring templates (worker-detected + user-declared).","operationId":"list_templates_api_v1_recurring_templates_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/RecurringTemplateResponse"},"type":"array","title":"Response List Templates Api V1 Recurring Templates Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["Recurring"],"summary":"Create Template","description":"Declare a recurring template by hand (authoritative — born locked).","operationId":"create_template_api_v1_recurring_templates_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringTemplateCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringTemplateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/recurring/templates/{uid}":{"patch":{"tags":["Recurring"],"summary":"Update Template","description":"Edit a template (transfers ownership to the user).","operationId":"update_template_api_v1_recurring_templates__uid__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringTemplateUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringTemplateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Recurring"],"summary":"Delete Template","description":"Delete a template (its bills cascade).","operationId":"delete_template_api_v1_recurring_templates__uid__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recurring/upcoming":{"get":{"tags":["Recurring"],"summary":"List Upcoming","description":"Forward-looking pending bills due within ``days`` (clamped ≤ 90).","operationId":"list_upcoming_api_v1_recurring_upcoming_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RecurringBillResponse"},"title":"Response List Upcoming Api V1 Recurring Upcoming Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/recurring/history":{"get":{"tags":["Recurring"],"summary":"List History","description":"Backward-looking settled bills within the last ``days`` (clamped ≤ 30).","operationId":"list_history_api_v1_recurring_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RecurringBillResponse"},"title":"Response List History Api V1 Recurring History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/exports/bundle":{"get":{"tags":["Exports"],"summary":"Export Bundle","description":"Export several resources for the current user as a single zip archive —\none clean ``<resource>.<fmt>`` file per member (the multi-select download).\n\nDeclared before ``/{resource}`` so the literal path wins over the enum\npath-param match.","operationId":"export_bundle_api_v1_exports_bundle_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"resources","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated resource ids to bundle into one zip.","title":"Resources"},"description":"Comma-separated resource ids to bundle into one zip."},{"name":"format","in":"query","required":false,"schema":{"$ref":"#/components/schemas/ExportFormat","default":"csv"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/exports/{resource}":{"get":{"tags":["Exports"],"summary":"Export Resource","description":"Export one resource for the current user as CSV (streamed) or JSON.","operationId":"export_resource_api_v1_exports__resource__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"resource","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ExportResource"}},{"name":"format","in":"query","required":false,"schema":{"$ref":"#/components/schemas/ExportFormat","default":"csv"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/ping":{"get":{"tags":["Admin"],"summary":"Admin Ping","description":"Confirm the caller holds the ADMIN role. Returns 403 otherwise.","operationId":"admin_ping_api_v1_admin_ping_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPingResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/admin/users":{"get":{"tags":["Admin"],"summary":"List Users","description":"Paginated, searchable inventory of all non-SYSTEM users (A2).","operationId":"list_users_api_v1_admin_users_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by email or full name (case-insensitive).","title":"Q"},"description":"Search by email or full name (case-insensitive)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":25,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque forward cursor from a prior page.","title":"Cursor"},"description":"Opaque forward cursor from a prior page."},{"name":"include_deleted","in":"query","required":false,"schema":{"type":"boolean","description":"Include soft-deleted accounts (default off).","default":false,"title":"Include Deleted"},"description":"Include soft-deleted accounts (default off)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/{user_id}":{"get":{"tags":["Admin"],"summary":"Get User Detail","description":"Single-user admin detail (A3): identity + locale, recent sessions /\ndevices / activity, the profile-card stats, and soft-delete status.\n\n404 for a non-existent, SYSTEM, or hard-purged ``user_id``; 200 for a\nsoft-deleted user still in the grace window (with ``cemetery_status``).","operationId":"get_user_detail_api_v1_admin_users__user_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/{user_id}/lock":{"patch":{"tags":["Admin"],"summary":"Lock User","description":"Disable an account (B1). Recovery-proof: blocks login, every\nauthenticated request, and password recovery until unlocked. 404 for\nmissing/SYSTEM, 409 if already disabled. Optional ``{reason}`` is logged.","operationId":"lock_user_api_v1_admin_users__user_id__lock_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/AdminLockRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAccountStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/{user_id}/unlock":{"patch":{"tags":["Admin"],"summary":"Unlock User","description":"Re-enable a disabled account (B1). 404 for missing/SYSTEM, 409 if the\naccount is not currently disabled.","operationId":"unlock_user_api_v1_admin_users__user_id__unlock_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAccountStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/{user_id}/role":{"patch":{"tags":["Admin"],"summary":"Set User Role","description":"Promote/demote an account between ``user`` and ``admin`` (RBAC-lite,\nT-bootstrap-cleanup). ``role`` is constrained to ``user``/``admin`` (422 on\n``system`` or garbage). 404 for missing/SYSTEM; 409 if already in that role\nor if it would demote the last operator.","operationId":"set_user_role_api_v1_admin_users__user_id__role_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminRoleUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminRoleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/{user_id}/sessions":{"delete":{"tags":["Admin"],"summary":"Force Logout","description":"Force-logout: terminate every active session for the user (B2).\nIdempotent (0 when none active); does not lock the account (pair with\n/lock for a full eject). 404 for missing/SYSTEM.","operationId":"force_logout_api_v1_admin_users__user_id__sessions_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminForceLogoutResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/cemetery":{"get":{"tags":["Admin"],"summary":"List Cemetery","description":"Paginated post-deletion audit trail (C1): cemetery headstones with\nreplica counts. Email search + ``from``/``to`` date range over the entomb\ntimestamp.","operationId":"list_cemetery_api_v1_admin_cemetery_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search by retained email.","title":"Q"},"description":"Search by retained email."},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Filter deleted_at ≥ this date.","title":"From"},"description":"Filter deleted_at ≥ this date."},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Filter deleted_at ≤ this date.","title":"To"},"description":"Filter deleted_at ≤ this date."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":25,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCemeteryListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/cemetery/{deleted_user_id}":{"get":{"tags":["Admin"],"summary":"Get Cemetery Detail","description":"One cemetery headstone + a truncated peek (≤10 each) at its\ncommittee-bill and expense-total replicas (C1). 404 if not found.","operationId":"get_cemetery_detail_api_v1_admin_cemetery__deleted_user_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"deleted_user_id","in":"path","required":true,"schema":{"type":"integer","title":"Deleted User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCemeteryDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/{user_id}/signal-settings":{"get":{"tags":["Admin"],"summary":"Get User Signal Settings","description":"The target user's disabled activity-feed kinds (E1). 404 missing/SYSTEM.","operationId":"get_user_signal_settings_api_v1_admin_users__user_id__signal_settings_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignalSettingsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Admin"],"summary":"Set User Signal Setting","description":"Operator toggles a feed ``kind`` on/off for the target user (E1). 404\nmissing/SYSTEM or unknown kind.","operationId":"set_user_signal_setting_api_v1_admin_users__user_id__signal_settings_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignalSettingUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignalSettingsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/signal-catalog/{kind}":{"put":{"tags":["Admin"],"summary":"Tune Signal Catalog","description":"System-level signal tuning (E1): adjust a kind's priority / rank /\nplatform-wide enable. 404 unknown kind; 422 invalid priority.","operationId":"tune_signal_catalog_api_v1_admin_signal_catalog__kind__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"kind","in":"path","required":true,"schema":{"type":"string","title":"Kind"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSignalTuneRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/activity":{"get":{"tags":["Activity"],"summary":"Get Feed","operationId":"get_feed_api_v1_activity_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"title":"Limit"}},{"name":"domain","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityFeedOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/activity/seen":{"post":{"tags":["Activity"],"summary":"Post Seen","operationId":"post_seen_api_v1_activity_seen_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeenRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/activity/catalog":{"get":{"tags":["Activity"],"summary":"Get Catalog","operationId":"get_catalog_api_v1_activity_catalog_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogOut"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/activity/signal-settings":{"get":{"tags":["Activity"],"summary":"Get Signal Settings","operationId":"get_signal_settings_api_v1_activity_signal_settings_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignalSettingsOut"}}}}},"security":[{"HTTPBearer":[]}]},"put":{"tags":["Activity"],"summary":"Put Signal Settings","operationId":"put_signal_settings_api_v1_activity_signal_settings_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignalSettingUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignalSettingsOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/internal/run-maintenance":{"post":{"tags":["internal"],"summary":"Trigger Maintenance","description":"Run any due weekly maintenance. Guarded by ``MAINTENANCE_TRIGGER_SECRET``:\nblank secret → 404 (endpoint disabled); wrong secret → 403. Intended for a\nfree external cron on constrained hosts (also doubles as a keepalive ping).","operationId":"trigger_maintenance_internal_run_maintenance_post","parameters":[{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}},{"name":"x-maintenance-secret","in":"header","required":false,"schema":{"type":"string","default":"","title":"X-Maintenance-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"AccountDataResetRequest":{"properties":{"password":{"type":"string","title":"Password"}},"type":"object","required":["password"],"title":"AccountDataResetRequest","description":"Body for ``POST /api/v1/users/me/data-reset`` — password re-entry\n(step-up auth on a destructive 'clean restart'; the account is kept)."},"AccountDeletionCancelRequest":{"properties":{"token":{"type":"string","title":"Token"}},"type":"object","required":["token"],"title":"AccountDeletionCancelRequest","description":"Body for ``POST /api/v1/users/me/delete/cancel`` — the signed token carried\nby the cancel link in the confirmation email (no auth: the user is logged\nout while the account is pending deletion)."},"AccountDeletionCancelResponse":{"properties":{"detail":{"type":"string","title":"Detail"}},"type":"object","required":["detail"],"title":"AccountDeletionCancelResponse","description":"Acknowledgement that a scheduled deletion was cancelled (or was already\ninactive — the endpoint is idempotent)."},"AccountDeletionRequest":{"properties":{"password":{"type":"string","title":"Password"}},"type":"object","required":["password"],"title":"AccountDeletionRequest","description":"Body for ``POST /api/v1/users/me/delete`` — password re-entry (step-up auth\non a destructive action; the caller is already authenticated)."},"AccountDeletionResponse":{"properties":{"detail":{"type":"string","title":"Detail"},"grace_days":{"type":"integer","title":"Grace Days"}},"type":"object","required":["detail","grace_days"],"title":"AccountDeletionResponse","description":"Acknowledgement that an account is now scheduled for deletion."},"AccountIdentifierCreate":{"properties":{"identifier":{"type":"string","maxLength":255,"minLength":1,"title":"Identifier"},"identifier_type":{"$ref":"#/components/schemas/IdentifierType","default":"UPI"}},"type":"object","required":["identifier"],"title":"AccountIdentifierCreate"},"AccountIdentifierResponse":{"properties":{"uid":{"type":"integer","title":"Uid"},"identifier":{"type":"string","title":"Identifier"},"identifier_type":{"$ref":"#/components/schemas/IdentifierType"}},"type":"object","required":["uid","identifier","identifier_type"],"title":"AccountIdentifierResponse"},"AccountPurpose":{"type":"string","enum":["regular_personal","regular_dependent","regular_business","regular_work","savings_investment","savings_emergency","savings_goal"],"title":"AccountPurpose","description":"The account's semantic role — orthogonal to ``account_type`` (see module\ndocstring). Flat ``<bucket>_<role>`` values; the list is expected to grow, and\na new member needs no schema change (plain String column). Lowercase to stay\ndistinct from ``AccountType`` values."},"AccountType":{"type":"string","enum":["REGULAR","SAVINGS","OTHER","INVESTMENT","LOAN","CREDIT_CARD"],"title":"AccountType"},"ActivityFeedOut":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ActivityItemOut"},"type":"array","title":"Items"},"has_more":{"type":"boolean","title":"Has More"}},"type":"object","required":["items","has_more"],"title":"ActivityFeedOut"},"ActivityItemOut":{"properties":{"uid":{"type":"integer","title":"Uid"},"kind":{"type":"string","title":"Kind"},"event_class":{"type":"string","title":"Event Class"},"priority":{"type":"integer","title":"Priority"},"domain":{"type":"string","title":"Domain"},"subject_type":{"type":"string","title":"Subject Type"},"subject_id":{"type":"string","title":"Subject Id"},"state":{"type":"string","title":"State"},"summary":{"type":"string","title":"Summary"},"meta":{"type":"object","title":"Meta"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"refreshed_at":{"type":"string","format":"date-time","title":"Refreshed At"},"aggregate_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Aggregate Count"}},"type":"object","required":["uid","kind","event_class","priority","domain","subject_type","subject_id","state","summary","meta","created_at","refreshed_at"],"title":"ActivityItemOut"},"AdjustmentDiffDTO":{"properties":{"before":{"anyOf":[{"$ref":"#/components/schemas/AdjustmentSideDTO"},{"type":"null"}]},"after":{"anyOf":[{"$ref":"#/components/schemas/AdjustmentSideDTO"},{"type":"null"}]},"txn_alive":{"type":"boolean","title":"Txn Alive","default":false},"added_tags":{"items":{"$ref":"#/components/schemas/TagRefDTO"},"type":"array","title":"Added Tags","default":[]},"removed_tags":{"items":{"$ref":"#/components/schemas/TagRefDTO"},"type":"array","title":"Removed Tags","default":[]}},"type":"object","title":"AdjustmentDiffDTO","description":"The per-txn before→after diff carried by an adjustment item\n(T-tax-adjustment-transparency). ``before`` is the corrected txn's frozen\nstate; ``after`` is its current state, or null when the txn was deleted /\nrecategorized to a non-taxable type (``txn_alive`` distinguishes the two).\n``added_tags`` / ``removed_tags`` are the tag-set drift that drove any\n``txn_type`` change — the \"why\" behind the adjustment."},"AdjustmentSideDTO":{"properties":{"amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amount"},"txn_type":{"anyOf":[{"$ref":"#/components/schemas/TxnType"},{"type":"null"}]},"applied_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Applied Rate"},"tax_amount":{"type":"number","title":"Tax Amount"},"tags":{"items":{"$ref":"#/components/schemas/TagRefDTO"},"type":"array","title":"Tags","default":[]}},"type":"object","required":["tax_amount"],"title":"AdjustmentSideDTO","description":"One side (before / after) of a per-txn adjustment diff."},"AdminAccountStatusResponse":{"properties":{"user_id":{"type":"integer","title":"User Id"},"disabled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disabled At"}},"type":"object","required":["user_id"],"title":"AdminAccountStatusResponse","description":"Result of a lock/unlock — the new disable state. ``disabled_at`` is the\nset timestamp after a lock, ``None`` after an unlock."},"AdminBillGenerateRequest":{"properties":{"period_start":{"type":"string","title":"Period Start"},"period_end":{"type":"string","title":"Period End"},"user_id":{"type":"integer","title":"User Id"}},"type":"object","required":["period_start","period_end","user_id"],"title":"AdminBillGenerateRequest","description":"Admin/ops backfill — generate on behalf of a (possibly auto-mode) user."},"AdminCemeteryBillReplica":{"properties":{"original_bill_id":{"type":"integer","title":"Original Bill Id"},"amount":{"type":"number","title":"Amount"},"bill_status":{"type":"string","title":"Bill Status"},"period_start":{"type":"string","title":"Period Start"},"period_end":{"type":"string","title":"Period End"},"billed_at":{"type":"string","format":"date-time","title":"Billed At"}},"type":"object","required":["original_bill_id","amount","bill_status","period_start","period_end","billed_at"],"title":"AdminCemeteryBillReplica","description":"One committee-bill replica in the cemetery detail peek (truncated)."},"AdminCemeteryDetailResponse":{"properties":{"deleted_user_id":{"type":"integer","title":"Deleted User Id"},"former_user_id":{"type":"integer","title":"Former User Id"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"deleted_at":{"type":"string","format":"date-time","title":"Deleted At"},"account_opened_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Account Opened At"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"committee_bill_replicas_count":{"type":"integer","title":"Committee Bill Replicas Count"},"expense_total_replicas_count":{"type":"integer","title":"Expense Total Replicas Count"},"committee_bill_replicas":{"items":{"$ref":"#/components/schemas/AdminCemeteryBillReplica"},"type":"array","title":"Committee Bill Replicas"},"expense_total_replicas":{"items":{"$ref":"#/components/schemas/AdminCemeteryExpenseReplica"},"type":"array","title":"Expense Total Replicas"}},"type":"object","required":["deleted_user_id","former_user_id","deleted_at","committee_bill_replicas_count","expense_total_replicas_count","committee_bill_replicas","expense_total_replicas"],"title":"AdminCemeteryDetailResponse","description":"Cemetery headstone + a truncated peek (≤10 each) at the replicas. The\nfull replica tables are for SQL, not the UI."},"AdminCemeteryExpenseReplica":{"properties":{"period_type":{"type":"string","title":"Period Type"},"period_start":{"type":"string","title":"Period Start"},"period_end":{"type":"string","title":"Period End"},"total_count":{"type":"integer","title":"Total Count"},"total_debit":{"type":"number","title":"Total Debit"},"total_credit":{"type":"number","title":"Total Credit"}},"type":"object","required":["period_type","period_start","period_end","total_count","total_debit","total_credit"],"title":"AdminCemeteryExpenseReplica","description":"One expense-total replica in the cemetery detail peek (truncated)."},"AdminCemeteryListResponse":{"properties":{"deleted_users":{"items":{"$ref":"#/components/schemas/AdminCemeteryRow"},"type":"array","title":"Deleted Users"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"has_more":{"type":"boolean","title":"Has More"}},"type":"object","required":["deleted_users","has_more"],"title":"AdminCemeteryListResponse","description":"A cursor-paginated page of cemetery headstones."},"AdminCemeteryRow":{"properties":{"deleted_user_id":{"type":"integer","title":"Deleted User Id"},"former_user_id":{"type":"integer","title":"Former User Id"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"deleted_at":{"type":"string","format":"date-time","title":"Deleted At"},"account_opened_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Account Opened At"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"committee_bill_replicas_count":{"type":"integer","title":"Committee Bill Replicas Count"},"expense_total_replicas_count":{"type":"integer","title":"Expense Total Replicas Count"}},"type":"object","required":["deleted_user_id","former_user_id","deleted_at","committee_bill_replicas_count","expense_total_replicas_count"],"title":"AdminCemeteryRow","description":"One headstone in the cemetery audit (``GET /api/v1/admin/cemetery``).\n\nThe cemetery is the *post-purge* tombstone, so ``deleted_at`` is the entomb\ntimestamp; there is no scheduled-purge (it already happened). ``email`` is\nthe one PII field the cemetery retains ([[cemetery-retention-model]])."},"AdminCemeteryStatus":{"properties":{"deleted_at":{"type":"string","format":"date-time","title":"Deleted At"},"scheduled_purge_at":{"type":"string","format":"date-time","title":"Scheduled Purge At"}},"type":"object","required":["deleted_at","scheduled_purge_at"],"title":"AdminCemeteryStatus","description":"Soft-delete status for a user still inside the grace window. ``None`` on\nthe detail response when the account is active; a hard-purged user 404s."},"AdminDeviceRow":{"properties":{"device_uid":{"type":"integer","title":"Device Uid"},"fingerprint":{"type":"string","title":"Fingerprint"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"last_seen_at":{"type":"string","format":"date-time","title":"Last Seen At"}},"type":"object","required":["device_uid","fingerprint","last_seen_at"],"title":"AdminDeviceRow","description":"One of the user's recently-seen known devices (admin user-detail)."},"AdminForceLogoutResponse":{"properties":{"terminated":{"type":"integer","title":"Terminated"}},"type":"object","required":["terminated"],"title":"AdminForceLogoutResponse","description":"Result of force-logout (B2): how many active sessions were terminated."},"AdminLockRequest":{"properties":{"reason":{"anyOf":[{"type":"string","maxLength":280},{"type":"null"}],"title":"Reason"}},"type":"object","title":"AdminLockRequest","description":"Body for ``PATCH /admin/users/{id}/lock`` — an optional free-text reason\n(internal audit only, logged). ``/unlock`` takes no body."},"AdminOnboardingStatus":{"properties":{"profile_complete":{"type":"boolean","title":"Profile Complete"},"tutorial_seen":{"type":"boolean","title":"Tutorial Seen"},"nudge_dismissed":{"type":"boolean","title":"Nudge Dismissed"},"demo_active":{"type":"boolean","title":"Demo Active"},"checklist_state":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Checklist State"}},"type":"object","required":["profile_complete","tutorial_seen","nudge_dismissed","demo_active"],"title":"AdminOnboardingStatus","description":"A user's onboarding journey state for the admin detail (ST7) — lets the\noperator see where the user is in guided setup. Read from ``onboarding_state``."},"AdminPingResponse":{"properties":{"status":{"type":"string","title":"Status"},"user_id":{"type":"integer","title":"User Id"}},"type":"object","required":["status","user_id"],"title":"AdminPingResponse","description":"Liveness/authorization probe for the admin surface."},"AdminRoleResponse":{"properties":{"user_id":{"type":"integer","title":"User Id"},"role":{"type":"string","title":"Role"}},"type":"object","required":["user_id","role"],"title":"AdminRoleResponse","description":"Result of a role change — the account's persisted role after the write."},"AdminRoleUpdateRequest":{"properties":{"role":{"type":"string","title":"Role"}},"type":"object","required":["role"],"title":"AdminRoleUpdateRequest","description":"Body for ``PATCH /admin/users/{id}/role`` (T-bootstrap-cleanup): the new\nrole to assign. Constrained to ``ASSIGNABLE_ROLES`` (``user`` / ``admin``) —\n``system`` is rejected at the schema (422) so the SYSTEM sentinel can never\nbe conferred, mirroring the structural refusal to *target* it (404)."},"AdminSessionRow":{"properties":{"session_id":{"type":"string","title":"Session Id"},"ip_address":{"type":"string","title":"Ip Address"},"device_summary":{"type":"string","title":"Device Summary"},"is_locked":{"type":"boolean","title":"Is Locked"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"type":"object","required":["session_id","ip_address","device_summary","is_locked","created_at","expires_at"],"title":"AdminSessionRow","description":"One of the user's recent sessions (admin user-detail)."},"AdminSignalTuneRequest":{"properties":{"priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Priority"},"rank_order":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rank Order"},"system_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"System Enabled"}},"type":"object","title":"AdminSignalTuneRequest","description":"Body for `PUT /admin/signal-catalog/{kind}` (E1, system-level tuning) —\nall optional; only the provided axes are changed. `priority` 1..3,\n`rank_order` is the within-priority sort, `system_enabled` toggles the kind\nplatform-wide (system default; per-user disable is separate)."},"AdminUserDetailResponse":{"properties":{"user_id":{"type":"integer","title":"User Id"},"email":{"type":"string","title":"Email"},"full_name":{"type":"string","title":"Full Name"},"role":{"type":"string","title":"Role"},"registered_at":{"type":"string","format":"date-time","title":"Registered At"},"last_active_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Active At"},"locked_until":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locked Until"},"deleted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deleted At"},"disabled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disabled At"},"two_factor_enabled":{"type":"boolean","title":"Two Factor Enabled"},"session_count":{"type":"integer","title":"Session Count"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"recent_sessions":{"items":{"$ref":"#/components/schemas/AdminSessionRow"},"type":"array","title":"Recent Sessions"},"recent_known_devices":{"items":{"$ref":"#/components/schemas/AdminDeviceRow"},"type":"array","title":"Recent Known Devices"},"recent_activity":{"anyOf":[{"items":{"$ref":"#/components/schemas/ActivityItemOut"},"type":"array"},{"type":"null"}],"title":"Recent Activity"},"cemetery_status":{"anyOf":[{"$ref":"#/components/schemas/AdminCemeteryStatus"},{"type":"null"}]},"stats":{"$ref":"#/components/schemas/UserStatsResponse"},"onboarding":{"anyOf":[{"$ref":"#/components/schemas/AdminOnboardingStatus"},{"type":"null"}]}},"type":"object","required":["user_id","email","full_name","role","registered_at","two_factor_enabled","session_count","recent_sessions","recent_known_devices","stats"],"title":"AdminUserDetailResponse","description":"Single-user admin detail (``GET /api/v1/admin/users/{id}``) — the\ninventory row plus locale, recent sessions/devices, recent activity, the\nprofile-card stats block, onboarding journey, and soft-delete status.\nComposed by ``admin_services`` from each owning module's read."},"AdminUserListResponse":{"properties":{"users":{"items":{"$ref":"#/components/schemas/AdminUserRow"},"type":"array","title":"Users"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"has_more":{"type":"boolean","title":"Has More"}},"type":"object","required":["users","has_more"],"title":"AdminUserListResponse","description":"A cursor-paginated page of the admin user inventory.\n\n``next_cursor`` is an opaque token to pass back as ``?cursor=``; it is\n``None`` exactly when ``has_more`` is false."},"AdminUserRow":{"properties":{"user_id":{"type":"integer","title":"User Id"},"email":{"type":"string","title":"Email"},"full_name":{"type":"string","title":"Full Name"},"role":{"type":"string","title":"Role"},"registered_at":{"type":"string","format":"date-time","title":"Registered At"},"last_active_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Active At"},"locked_until":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locked Until"},"deleted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deleted At"},"disabled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disabled At"},"two_factor_enabled":{"type":"boolean","title":"Two Factor Enabled"},"session_count":{"type":"integer","title":"Session Count"},"profile_complete":{"type":"boolean","title":"Profile Complete"}},"type":"object","required":["user_id","email","full_name","role","registered_at","two_factor_enabled","session_count","profile_complete"],"title":"AdminUserRow","description":"One row in the admin user inventory (``GET /api/v1/admin/users``).\n\nComposed by ``admin_services`` from the users-owned profile directory\n(``email``/``full_name``) + auth-owned account fields + session stats."},"AliasCheckResponse":{"properties":{"alias":{"type":"string","title":"Alias"},"unique":{"type":"boolean","title":"Unique"}},"type":"object","required":["alias","unique"],"title":"AliasCheckResponse"},"AnswerVerifyRequest":{"properties":{"email_id":{"type":"string","format":"email","title":"Email Id"},"answer":{"type":"string","title":"Answer"}},"type":"object","required":["email_id","answer"],"title":"AnswerVerifyRequest"},"BankAccountArchive":{"properties":{"archived":{"type":"boolean","title":"Archived"}},"type":"object","required":["archived"],"title":"BankAccountArchive","description":"Archive / un-archive. Its own endpoint rather than a field on\n``BankAccountUpdate``: archiving is a lifecycle act, not a property edit, and it must\nnot ride along with a purpose change that triggers a full tag reflow."},"BankAccountCreate":{"properties":{"label":{"type":"string","maxLength":255,"minLength":1,"title":"Label"},"account_type":{"type":"string","maxLength":20,"title":"Account Type"},"purpose":{"type":"string","maxLength":32,"title":"Purpose","default":"regular_personal"},"is_committee_account":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Committee Account"},"identifiers":{"anyOf":[{"items":{"$ref":"#/components/schemas/AccountIdentifierCreate"},"type":"array"},{"type":"null"}],"title":"Identifiers"}},"type":"object","required":["label","account_type"],"title":"BankAccountCreate"},"BankAccountResponse":{"properties":{"uid":{"type":"integer","title":"Uid"},"label":{"type":"string","title":"Label"},"account_type":{"$ref":"#/components/schemas/AccountType"},"purpose":{"$ref":"#/components/schemas/AccountPurpose"},"is_committee_account":{"type":"boolean","title":"Is Committee Account"},"archived_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Archived At"},"identifiers":{"items":{"$ref":"#/components/schemas/AccountIdentifierResponse"},"type":"array","title":"Identifiers","default":[]},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["uid","label","account_type","purpose","is_committee_account","created_at"],"title":"BankAccountResponse"},"BankAccountUpdate":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Label"},"account_type":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Account Type"},"purpose":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Purpose"},"is_committee_account":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Committee Account"}},"type":"object","title":"BankAccountUpdate"},"BeneficiaryAccountCreate":{"properties":{"identifier":{"type":"string","maxLength":255,"minLength":1,"title":"Identifier"},"identifier_type":{"$ref":"#/components/schemas/IdentifierType","default":"UPI"},"label":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Label"}},"type":"object","required":["identifier"],"title":"BeneficiaryAccountCreate","description":"Record one of a beneficiary's own bank accounts (a VPA / last4 the\nrelationship engine keys off). Flat model — a row IS one identifier."},"BeneficiaryAccountResponse":{"properties":{"uid":{"type":"integer","title":"Uid"},"identifier":{"type":"string","title":"Identifier"},"identifier_type":{"$ref":"#/components/schemas/IdentifierType"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["uid","identifier","identifier_type","created_at"],"title":"BeneficiaryAccountResponse"},"BeneficiaryAccountUpdate":{"properties":{"identifier":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Identifier"},"identifier_type":{"anyOf":[{"$ref":"#/components/schemas/IdentifierType"},{"type":"null"}]},"label":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Label"}},"type":"object","title":"BeneficiaryAccountUpdate"},"BeneficiaryCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"aliases":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Aliases"},"beneficiary_type":{"$ref":"#/components/schemas/BeneficiaryType"},"business":{"anyOf":[{"$ref":"#/components/schemas/BusinessDetailInput"},{"type":"null"}]},"person":{"anyOf":[{"$ref":"#/components/schemas/PersonDetailInput"},{"type":"null"}]},"relationships":{"anyOf":[{"items":{"$ref":"#/components/schemas/RelationshipInput"},"type":"array"},{"type":"null"}],"title":"Relationships"}},"type":"object","required":["name","beneficiary_type"],"title":"BeneficiaryCreate"},"BeneficiaryMergeRequest":{"properties":{"source_uid":{"type":"integer","title":"Source Uid"},"target_uid":{"type":"integer","title":"Target Uid"}},"type":"object","required":["source_uid","target_uid"],"title":"BeneficiaryMergeRequest"},"BeneficiaryRelationshipDTO":{"properties":{"uid":{"type":"integer","title":"Uid"},"relationship":{"type":"string","title":"Relationship"},"is_primary":{"type":"boolean","title":"Is Primary","default":false},"bank_account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bank Account Id"},"alias":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Alias"}},"type":"object","required":["uid","relationship"],"title":"BeneficiaryRelationshipDTO","description":"A persisted relationship edge in a beneficiary response (carries ``uid``)."},"BeneficiaryResponse":{"properties":{"uid":{"type":"integer","title":"Uid"},"name":{"type":"string","title":"Name"},"aliases":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Aliases"},"beneficiary_type":{"anyOf":[{"$ref":"#/components/schemas/BeneficiaryType"},{"type":"null"}]},"business":{"anyOf":[{"$ref":"#/components/schemas/BusinessDetailResponse"},{"type":"null"}]},"person":{"anyOf":[{"$ref":"#/components/schemas/PersonDetailResponse"},{"type":"null"}]},"relationships":{"items":{"$ref":"#/components/schemas/BeneficiaryRelationshipDTO"},"type":"array","title":"Relationships","default":[]},"created_at":{"type":"string","format":"date-time","title":"Created At"},"is_system":{"type":"boolean","title":"Is System","default":false},"is_self":{"type":"boolean","title":"Is Self","default":false}},"type":"object","required":["uid","name","created_at"],"title":"BeneficiaryResponse"},"BeneficiaryType":{"type":"string","enum":["business","person","unknown"],"title":"BeneficiaryType"},"BeneficiaryUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"aliases":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Aliases"},"beneficiary_type":{"anyOf":[{"$ref":"#/components/schemas/BeneficiaryType"},{"type":"null"}]},"business":{"anyOf":[{"$ref":"#/components/schemas/BusinessDetailInput"},{"type":"null"}]},"person":{"anyOf":[{"$ref":"#/components/schemas/PersonDetailInput"},{"type":"null"}]},"relationships":{"anyOf":[{"items":{"$ref":"#/components/schemas/RelationshipInput"},"type":"array"},{"type":"null"}],"title":"Relationships"}},"type":"object","title":"BeneficiaryUpdate"},"BillAllocationDTO":{"properties":{"payment_txn_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Payment Txn Id"},"amount":{"type":"number","title":"Amount"},"source":{"type":"string","title":"Source"},"allocated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Allocated At"}},"type":"object","required":["amount","source"],"title":"BillAllocationDTO"},"BillDetailResponse":{"properties":{"bill_id":{"type":"integer","title":"Bill Id"},"bill_no":{"type":"integer","title":"Bill No"},"user_id":{"type":"integer","title":"User Id"},"amount":{"type":"number","title":"Amount"},"amount_paid":{"type":"number","title":"Amount Paid"},"status":{"$ref":"#/components/schemas/BillStatus"},"period_start":{"type":"string","title":"Period Start"},"period_end":{"type":"string","title":"Period End"},"billed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Billed At"},"due_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Due Date"},"paid_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paid At"},"last_modified":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Modified"},"items":{"items":{"$ref":"#/components/schemas/BillItemDTO"},"type":"array","title":"Items"},"allocations":{"items":{"$ref":"#/components/schemas/BillAllocationDTO"},"type":"array","title":"Allocations"},"totals":{"$ref":"#/components/schemas/BillTotalsDTO"}},"type":"object","required":["bill_id","bill_no","user_id","amount","amount_paid","status","period_start","period_end","items","allocations","totals"],"title":"BillDetailResponse"},"BillGenerateResponse":{"properties":{"bill_ids":{"items":{"type":"integer"},"type":"array","title":"Bill Ids"}},"type":"object","required":["bill_ids"],"title":"BillGenerateResponse"},"BillItemDTO":{"properties":{"txn_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Txn Id"},"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date"},"beneficiary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Beneficiary"},"amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amount"},"debit_credit":{"anyOf":[{"$ref":"#/components/schemas/DebitCredit"},{"type":"null"}]},"txn_type":{"anyOf":[{"$ref":"#/components/schemas/TxnType"},{"type":"null"}]},"applied_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Applied Rate"},"tax_amount":{"type":"number","title":"Tax Amount"},"penalty":{"type":"number","title":"Penalty"},"is_adjustment":{"type":"boolean","title":"Is Adjustment","default":false},"adjustment_for_bill_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Adjustment For Bill Id"},"adjustment_for_bill_no":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Adjustment For Bill No"},"penalty_tag_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Penalty Tag Id"},"penalty_tag_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Penalty Tag Name"},"diff":{"anyOf":[{"$ref":"#/components/schemas/AdjustmentDiffDTO"},{"type":"null"}]}},"type":"object","required":["tax_amount","penalty"],"title":"BillItemDTO"},"BillListResponse":{"properties":{"bills":{"items":{"$ref":"#/components/schemas/BillSummaryDTO"},"type":"array","title":"Bills"}},"type":"object","required":["bills"],"title":"BillListResponse"},"BillStatus":{"type":"string","enum":["ACCRUING","BILLED","PAID","OVERDUE","EXPIRED"],"title":"BillStatus"},"BillSummaryDTO":{"properties":{"bill_id":{"type":"integer","title":"Bill Id"},"bill_no":{"type":"integer","title":"Bill No"},"user_id":{"type":"integer","title":"User Id"},"amount":{"type":"number","title":"Amount"},"amount_paid":{"type":"number","title":"Amount Paid"},"status":{"$ref":"#/components/schemas/BillStatus"},"period_start":{"type":"string","title":"Period Start"},"period_end":{"type":"string","title":"Period End"},"billed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Billed At"},"due_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Due Date"},"paid_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paid At"},"last_modified":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Modified"}},"type":"object","required":["bill_id","bill_no","user_id","amount","amount_paid","status","period_start","period_end"],"title":"BillSummaryDTO"},"BillTotalsDTO":{"properties":{"tax_total":{"type":"number","title":"Tax Total"},"penalty_total":{"type":"number","title":"Penalty Total"}},"type":"object","required":["tax_total","penalty_total"],"title":"BillTotalsDTO"},"Body_upload_profile_image_api_v1_users_me_profile_image_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_profile_image_api_v1_users_me_profile_image_post"},"Body_upload_statement_api_v1_statement_uploads_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"},"parser_override":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parser Override"}},"type":"object","required":["file"],"title":"Body_upload_statement_api_v1_statement_uploads_post"},"BrandingResponse":{"properties":{"name":{"type":"string","title":"Name"},"tagline":{"type":"string","title":"Tagline"},"description":{"type":"string","title":"Description"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"}},"type":"object","required":["name","tagline","description"],"title":"BrandingResponse","description":"Product brand identity served to the frontend (name/tagline/copy/logo).\n\nDeployment ``capabilities`` were decoupled to their own endpoint\n(``GET /metadata/capabilities``) by T-capability-gating — no longer carried\nhere."},"BudgetLimitEnvelope":{"properties":{"budget":{"$ref":"#/components/schemas/BudgetLimitResponse"}},"type":"object","required":["budget"],"title":"BudgetLimitEnvelope"},"BudgetLimitListResponse":{"properties":{"budgets":{"items":{"$ref":"#/components/schemas/BudgetLimitResponse"},"type":"array","title":"Budgets"}},"type":"object","required":["budgets"],"title":"BudgetLimitListResponse"},"BudgetLimitResponse":{"properties":{"uid":{"type":"integer","title":"Uid"},"tag_id":{"type":"integer","title":"Tag Id"},"tag_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag Name"},"budget_period":{"$ref":"#/components/schemas/PeriodType"},"limit_amt":{"type":"number","title":"Limit Amt"},"penalty_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Penalty Rate"},"created_by":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Created By"},"is_system":{"type":"boolean","title":"Is System","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["uid","tag_id","budget_period","limit_amt","created_at"],"title":"BudgetLimitResponse"},"BudgetLimitUpsert":{"properties":{"tag_id":{"type":"integer","title":"Tag Id"},"budget_period":{"$ref":"#/components/schemas/PeriodType","default":"monthly"},"limit_amt":{"type":"number","title":"Limit Amt"},"penalty_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Penalty Rate"}},"type":"object","required":["tag_id","limit_amt"],"title":"BudgetLimitUpsert"},"BudgetMode":{"type":"string","enum":["off","manual","auto"],"title":"BudgetMode"},"BudgetStatusResponse":{"properties":{"categories":{"items":{"$ref":"#/components/schemas/BudgetStatusRow"},"type":"array","title":"Categories"},"total_budget":{"anyOf":[{"$ref":"#/components/schemas/BudgetStatusRow"},{"type":"null"}]},"currency":{"type":"string","title":"Currency"},"month":{"type":"string","title":"Month"},"available_months":{"items":{"type":"string"},"type":"array","title":"Available Months"}},"type":"object","required":["categories","currency","month","available_months"],"title":"BudgetStatusResponse"},"BudgetStatusRow":{"properties":{"tag_id":{"type":"integer","title":"Tag Id"},"tag_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag Name"},"tag_type":{"anyOf":[{"$ref":"#/components/schemas/TagType"},{"type":"null"}]},"current_debit":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Current Debit"},"current_credit":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Current Credit"},"current_net_expense":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Current Net Expense"},"avg_net_expense":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Net Expense"},"min_net_expense":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Net Expense"},"max_net_expense":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Net Expense"},"limit_amt":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Limit Amt"},"penalty_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Penalty Rate"},"is_system":{"type":"boolean","title":"Is System","default":false},"budgetable":{"type":"boolean","title":"Budgetable","default":true},"uid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Uid"},"default_penalty_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Default Penalty Rate"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["tag_id"],"title":"BudgetStatusRow","description":"One merged budget-status report row (tag taxonomy ⋈ tracker ⋈ limit).\n\nNULL tracker/limit columns surface as ``None``. ``uid`` is present only on\nthe TOTAL row; ``default_penalty_rate`` only on category rows."},"BulkAnswerResponse":{"properties":{"status":{"type":"string","title":"Status","default":"ok"},"answered":{"type":"integer","title":"Answered"}},"type":"object","required":["answered"],"title":"BulkAnswerResponse","description":"Outcome of answering a whole import's duplicate queue in one act."},"BusinessDetailInput":{"properties":{"category":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Category"},"contact":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact"},"upi_id":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Upi Id"}},"type":"object","title":"BusinessDetailInput"},"BusinessDetailResponse":{"properties":{"category":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Category"},"contact":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contact"},"upi_id":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Upi Id"}},"type":"object","title":"BusinessDetailResponse"},"Cadence":{"type":"string","enum":["WEEKLY","MONTHLY","YEARLY"],"title":"Cadence"},"CapabilitiesResponse":{"properties":{"features":{"$ref":"#/components/schemas/FeatureCapabilities"},"infrastructure":{"$ref":"#/components/schemas/InfrastructureCapabilities"}},"type":"object","required":["features","infrastructure"],"title":"CapabilitiesResponse","description":"Two-tier deployment capability surface (``GET /metadata/capabilities``).\n\nIts own endpoint (decoupled from ``/branding`` by T-capability-gating) — the\nBE source of truth so a lean host (e.g. Render free tier) disables a capability\nand the FE hides it rather than offering an action that 403s. ``features`` are\npopulated; ``infrastructure`` is reserved (empty until a leaf registers)."},"CapabilityFlag":{"properties":{"enabled":{"type":"boolean","title":"Enabled"}},"type":"object","required":["enabled"],"title":"CapabilityFlag","description":"One optional integration's effective enabled state."},"CatalogEntryOut":{"properties":{"kind":{"type":"string","title":"Kind"},"event_class":{"type":"string","title":"Event Class"},"domain":{"type":"string","title":"Domain"},"subject_type":{"type":"string","title":"Subject Type"},"priority":{"type":"integer","title":"Priority"},"rank_order":{"type":"integer","title":"Rank Order"},"system_enabled":{"type":"boolean","title":"System Enabled"},"collapse_threshold":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Collapse Threshold"},"collapse_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Collapse Label"}},"type":"object","required":["kind","event_class","domain","subject_type","priority","rank_order","system_enabled"],"title":"CatalogEntryOut"},"CatalogOut":{"properties":{"entries":{"items":{"$ref":"#/components/schemas/CatalogEntryOut"},"type":"array","title":"Entries"}},"type":"object","required":["entries"],"title":"CatalogOut"},"CategorizationRuleCreate":{"properties":{"name":{"type":"string","title":"Name"},"beneficiary_id":{"type":"integer","title":"Beneficiary Id"},"tag_ids":{"items":{"type":"integer"},"type":"array","title":"Tag Ids"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"relationship":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relationship"},"direction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Direction"}},"type":"object","required":["name","beneficiary_id","tag_ids"],"title":"CategorizationRuleCreate"},"CategorizationRuleCreatedResponse":{"properties":{"rule":{"$ref":"#/components/schemas/CreatedRuleRef"}},"type":"object","required":["rule"],"title":"CategorizationRuleCreatedResponse"},"CategorizationRuleDTO":{"properties":{"uid":{"type":"integer","title":"Uid"},"rule_name":{"type":"string","title":"Rule Name"},"beneficiary_id":{"type":"integer","title":"Beneficiary Id"},"beneficiary_name":{"type":"string","title":"Beneficiary Name"},"beneficiary_aliases":{"items":{"type":"string"},"type":"array","title":"Beneficiary Aliases","default":[]},"beneficiary_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Beneficiary Type"},"tag_ids":{"items":{"type":"integer"},"type":"array","title":"Tag Ids"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"relationship":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relationship"},"relationship_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Relationship Id"},"relationship_is_primary":{"type":"boolean","title":"Relationship Is Primary","default":false},"direction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Direction"},"created_by":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Created By"},"is_system":{"type":"boolean","title":"Is System","default":false},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"last_modified":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Modified"}},"type":"object","required":["uid","rule_name","beneficiary_id","beneficiary_name","tag_ids"],"title":"CategorizationRuleDTO"},"CategorizationRuleListResponse":{"properties":{"rules":{"items":{"$ref":"#/components/schemas/CategorizationRuleDTO"},"type":"array","title":"Rules"}},"type":"object","required":["rules"],"title":"CategorizationRuleListResponse"},"CategorizationRuleUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"beneficiary_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Beneficiary Id"},"tag_ids":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Tag Ids"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"relationship":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relationship"},"direction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Direction"}},"type":"object","title":"CategorizationRuleUpdate"},"ChangePasswordRequest":{"properties":{"current_password":{"type":"string","title":"Current Password"},"new_password":{"type":"string","maxLength":64,"minLength":8,"title":"New Password"}},"type":"object","required":["current_password","new_password"],"title":"ChangePasswordRequest"},"ConsumptionTaxBillGenerateRequest":{"properties":{"period_start":{"type":"string","title":"Period Start"},"period_end":{"type":"string","title":"Period End"}},"type":"object","required":["period_start","period_end"],"title":"ConsumptionTaxBillGenerateRequest"},"CountriesResponse":{"properties":{"countries":{"items":{"$ref":"#/components/schemas/CountryOption"},"type":"array","title":"Countries"}},"type":"object","required":["countries"],"title":"CountriesResponse"},"CountryOption":{"properties":{"name":{"type":"string","title":"Name"},"country_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country Code"},"default_currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Currency"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"timezones":{"items":{"type":"string"},"type":"array","title":"Timezones","default":[]}},"type":"object","required":["name"],"title":"CountryOption"},"CreatedRuleRef":{"properties":{"uid":{"type":"integer","title":"Uid"}},"type":"object","required":["uid"],"title":"CreatedRuleRef","description":"The created rule's identity — `create_rule` returns only its `uid`."},"CurrenciesResponse":{"properties":{"currencies":{"items":{"$ref":"#/components/schemas/CurrencyOption"},"type":"array","title":"Currencies"}},"type":"object","required":["currencies"],"title":"CurrenciesResponse"},"CurrencyOption":{"properties":{"code":{"type":"string","title":"Code"},"label":{"type":"string","title":"Label"},"symbol":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Symbol"},"default_country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default Country"}},"type":"object","required":["code","label"],"title":"CurrencyOption"},"DateFormat":{"type":"string","enum":["system","dmy","mdy","ymd","dmonth"],"title":"DateFormat"},"DebitCredit":{"type":"string","enum":["debit","credit"],"title":"DebitCredit"},"DefaultTxnKind":{"type":"string","enum":["debit","credit"],"title":"DefaultTxnKind"},"DeletedResponse":{"properties":{"message":{"type":"string","title":"Message"}},"type":"object","required":["message"],"title":"DeletedResponse"},"DeviceRevokeRequest":{"properties":{"token":{"type":"string","title":"Token"}},"type":"object","required":["token"],"title":"DeviceRevokeRequest","description":"Body for the one-click revoke link from the new-device intimation email."},"EmailChangeConfirmBody":{"properties":{"otp":{"type":"string","title":"Otp"}},"type":"object","required":["otp"],"title":"EmailChangeConfirmBody"},"EmailChangeRequestBody":{"properties":{"new_email":{"type":"string","format":"email","title":"New Email"},"password":{"type":"string","title":"Password"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"}},"type":"object","required":["new_email","password"],"title":"EmailChangeRequestBody","description":"Start an email change. ``code`` (a current TOTP or backup code) is\nrequired only when the account has 2FA enabled; otherwise omit it."},"EmailResendRequest":{"properties":{"pending_token":{"type":"string","title":"Pending Token"}},"type":"object","required":["pending_token"],"title":"EmailResendRequest","description":"Body for ``POST /auth/verify-email/resend`` — the in-flight pending token."},"EmailResendResponse":{"properties":{"pending_token":{"type":"string","title":"Pending Token"}},"type":"object","required":["pending_token"],"title":"EmailResendResponse","description":"Resend rotates the pending token (extends the window); the FE swaps it in."},"EmailVerifyRequest":{"properties":{"pending_token":{"type":"string","title":"Pending Token"},"otp":{"type":"string","title":"Otp"}},"type":"object","required":["pending_token","otp"],"title":"EmailVerifyRequest","description":"Body for ``POST /auth/verify-email`` — the pending token + emailed OTP.\nOn success the response is a ``TokenResponse`` (the session opens here)."},"ExpenseTrendResponse":{"properties":{"period_type":{"$ref":"#/components/schemas/PeriodType"},"returned_count":{"type":"integer","title":"Returned Count"},"rows":{"items":{"$ref":"#/components/schemas/ExpenseTrendRow"},"type":"array","title":"Rows"}},"type":"object","required":["period_type","returned_count","rows"],"title":"ExpenseTrendResponse"},"ExpenseTrendRow":{"properties":{"tag_id":{"type":"integer","title":"Tag Id"},"tag_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag Name"},"period_type":{"type":"string","title":"Period Type"},"period_start":{"type":"string","title":"Period Start"},"period_end":{"type":"string","title":"Period End"},"total_count":{"type":"integer","title":"Total Count"},"total_debit":{"type":"number","title":"Total Debit"},"total_credit":{"type":"number","title":"Total Credit"},"net_expense":{"type":"number","title":"Net Expense"},"avg_net_expense":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Net Expense"},"min_net_expense":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Net Expense"},"max_net_expense":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Net Expense"}},"type":"object","required":["tag_id","period_type","period_start","period_end","total_count","total_debit","total_credit","net_expense"],"title":"ExpenseTrendRow"},"ExportFormat":{"type":"string","enum":["csv","json"],"title":"ExportFormat"},"ExportResource":{"type":"string","enum":["transactions","beneficiaries","tax-bills","tax-details","spend-by-tag","spend-by-beneficiary","bank-accounts","budgets","tags","categorization-rules","profile"],"title":"ExportResource"},"FeatureCapabilities":{"properties":{"oauth":{"anyOf":[{"$ref":"#/components/schemas/OAuthCapabilities"},{"type":"null"}]},"profile_image_upload":{"$ref":"#/components/schemas/CapabilityFlag"},"statement_import":{"$ref":"#/components/schemas/CapabilityFlag"},"demo":{"$ref":"#/components/schemas/CapabilityFlag"}},"type":"object","required":["profile_image_upload","statement_import","demo"],"title":"FeatureCapabilities","description":"User-facing capabilities the FE gates UI on (off → inert error)."},"ForgotPasswordRequest":{"properties":{"email_id":{"type":"string","format":"email","title":"Email Id"}},"type":"object","required":["email_id"],"title":"ForgotPasswordRequest"},"GroupBy":{"type":"string","enum":["tag","beneficiary"],"title":"GroupBy"},"GroupedTransactionsResponse":{"properties":{"group_by":{"$ref":"#/components/schemas/GroupBy","description":"'tag' or 'beneficiary'"},"period_type":{"type":"string","title":"Period Type","description":"'weekly', 'monthly', or 'all'"},"period_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period Start","description":"Bucket start (YYYY-MM-DD); null for the all-time window"},"groups":{"items":{"$ref":"#/components/schemas/TransactionGroup"},"type":"array","title":"Groups"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"returned_count":{"type":"integer","title":"Returned Count"}},"type":"object","required":["group_by","period_type","groups","limit","offset","returned_count"],"title":"GroupedTransactionsResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"IdentifierType":{"type":"string","enum":["UPI"],"title":"IdentifierType"},"InfrastructureCapabilities":{"properties":{"email":{"anyOf":[{"$ref":"#/components/schemas/CapabilityFlag"},{"type":"null"}]},"cache":{"anyOf":[{"$ref":"#/components/schemas/CapabilityFlag"},{"type":"null"}]},"rate_limit":{"anyOf":[{"$ref":"#/components/schemas/CapabilityFlag"},{"type":"null"}]},"scheduler":{"anyOf":[{"$ref":"#/components/schemas/CapabilityFlag"},{"type":"null"}]},"maintenance":{"anyOf":[{"$ref":"#/components/schemas/CapabilityFlag"},{"type":"null"}]}},"type":"object","title":"InfrastructureCapabilities","description":"Operational capabilities (off → fail-open; ops/health, not a UI gate).\n\nReserved taxonomy — each leaf registers when its seam is next touched, so all\nare optional until then (T-capability-gating keeps the main landing minimal)."},"JobStatusResponse":{"properties":{"job_id":{"type":"integer","title":"Job Id"},"status":{"$ref":"#/components/schemas/StatementJobStatus"},"stage":{"type":"string","title":"Stage"},"file_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"File Name"},"parser_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parser Used"},"source_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Type"},"txns_parsed":{"type":"integer","title":"Txns Parsed"},"txns_inserted":{"type":"integer","title":"Txns Inserted"},"txns_defaulted_misc":{"type":"integer","title":"Txns Defaulted Misc","default":0},"txns_held_duplicate":{"type":"integer","title":"Txns Held Duplicate","default":0},"txns_held_for_review":{"type":"integer","title":"Txns Held For Review","default":0},"error_detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Detail"},"detected_identifier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detected Identifier"},"bank_account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bank Account Id"},"suggest_register_account":{"type":"boolean","title":"Suggest Register Account"},"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":["job_id","status","stage","txns_parsed","txns_inserted","suggest_register_account","created_at"],"title":"JobStatusResponse","description":"Poll payload — the full job state the frontend dock widget renders."},"KnownDeviceInfo":{"properties":{"uid":{"type":"integer","title":"Uid"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"last_seen":{"type":"string","format":"date-time","title":"Last Seen"},"first_seen":{"type":"string","format":"date-time","title":"First Seen"},"is_current":{"type":"boolean","title":"Is Current","default":false}},"type":"object","required":["uid","last_seen","first_seen"],"title":"KnownDeviceInfo","description":"One row in the Settings → Security \"trusted devices\" list.\n\n``is_current`` flags the device this request came from. No fingerprint hash\nis exposed — only the human-readable label + timestamps."},"LandingRoute":{"type":"string","enum":["/dashboard","/transactions","/budgets","/consumption-tax"],"title":"LandingRoute"},"LoginChallengeStatus":{"type":"string","enum":["new_device_verification_required","two_factor_required","email_verification_required"],"title":"LoginChallengeStatus"},"LoginRequest":{"properties":{"email_id":{"type":"string","format":"email","title":"Email Id"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email_id","password"],"title":"LoginRequest"},"ManualTagsRequest":{"properties":{"tag_ids":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Tag Ids"}},"type":"object","title":"ManualTagsRequest"},"ManualTagsResponse":{"properties":{"status":{"type":"string","title":"Status"},"txn_id":{"type":"integer","title":"Txn Id"},"tag_ids":{"items":{"type":"integer"},"type":"array","title":"Tag Ids"}},"type":"object","required":["status","txn_id","tag_ids"],"title":"ManualTagsResponse"},"MarkPaidRequest":{"properties":{"payment_txn_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Payment Txn Id"},"amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amount"}},"type":"object","title":"MarkPaidRequest","description":"Manual settlement. Provide ``payment_txn_id`` to link a real payment txn,\nor leave it null for a pure override. ``amount`` defaults to the txn amount\n(linked) or the remaining balance (override)."},"MarkPaidResponse":{"properties":{"status":{"$ref":"#/components/schemas/BillStatus"},"bill_id":{"type":"integer","title":"Bill Id"},"amount_paid":{"type":"number","title":"Amount Paid"}},"type":"object","required":["status","bill_id","amount_paid"],"title":"MarkPaidResponse"},"MarkUnpaidResponse":{"properties":{"status":{"$ref":"#/components/schemas/BillStatus"},"bill_id":{"type":"integer","title":"Bill Id"}},"type":"object","required":["status","bill_id"],"title":"MarkUnpaidResponse"},"MatchedRowDTO":{"properties":{"txn_uid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Txn Uid"},"staged_uid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Staged Uid"},"amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amount"},"txn_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Txn Date"},"beneficiary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Beneficiary"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"}},"type":"object","title":"MatchedRowDTO","description":"The row an incoming transaction matched — one half of the side-by-side.\n\nExactly one of ``txn_uid`` / ``staged_uid`` is set: the match is either in the\nledger, or is itself a still-held row awaiting its own answer."},"MergeResponse":{"properties":{"status":{"type":"string","title":"Status"},"message":{"type":"string","title":"Message"}},"type":"object","required":["status","message"],"title":"MergeResponse"},"NewDeviceChallengeResponse":{"properties":{"status":{"$ref":"#/components/schemas/LoginChallengeStatus","default":"new_device_verification_required"},"pending_token":{"type":"string","title":"Pending Token"},"masked_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Masked Email"}},"type":"object","required":["pending_token"],"title":"NewDeviceChallengeResponse","description":"Pending-login response when a correct password arrives from an unknown\ndevice (T-new-device-otp). The frontend branches on ``status``: instead of\na session, it gets a short-lived ``pending_token`` and must collect the\nemailed OTP, then call ``POST /api/v1/auth/new-device/verify``."},"NewDeviceResendRequest":{"properties":{"pending_token":{"type":"string","title":"Pending Token"}},"type":"object","required":["pending_token"],"title":"NewDeviceResendRequest"},"NewDeviceVerifyRequest":{"properties":{"pending_token":{"type":"string","title":"Pending Token"},"otp":{"type":"string","title":"Otp"}},"type":"object","required":["pending_token","otp"],"title":"NewDeviceVerifyRequest"},"NumberFormat":{"type":"string","enum":["system","comma-dot","dot-comma","space-comma","indian","plain"],"title":"NumberFormat"},"OAuthCallbackRequest":{"properties":{"code":{"type":"string","title":"Code"},"state":{"type":"string","title":"State"},"oauth_tx":{"type":"string","title":"Oauth Tx"}},"type":"object","required":["code","state","oauth_tx"],"title":"OAuthCallbackRequest","description":"Body for ``POST /auth/oauth/{provider}/callback`` — the provider's\n``code`` + ``state`` plus the ``oauth_tx`` issued at start."},"OAuthCapabilities":{"properties":{"google":{"$ref":"#/components/schemas/CapabilityFlag"},"github":{"$ref":"#/components/schemas/CapabilityFlag"}},"type":"object","required":["google","github"],"title":"OAuthCapabilities","description":"OAuth provider family (registered at the ``feat/accounts`` rebase)."},"OAuthStartResponse":{"properties":{"authorize_url":{"type":"string","title":"Authorize Url"},"oauth_tx":{"type":"string","title":"Oauth Tx"}},"type":"object","required":["authorize_url","oauth_tx"],"title":"OAuthStartResponse","description":"Returned by ``GET /auth/oauth/{provider}/start``.\n\n``authorize_url`` is where the FE sends the browser; ``oauth_tx`` is a\nshort-lived signed transaction (provider + state + PKCE verifier) the FE\nholds and echoes back on the callback so the server stays stateless."},"OTPVerifyRequest":{"properties":{"email_id":{"type":"string","format":"email","title":"Email Id"},"otp":{"type":"string","maxLength":6,"minLength":6,"title":"Otp"}},"type":"object","required":["email_id","otp"],"title":"OTPVerifyRequest"},"OnboardingStateResponse":{"properties":{"user_id":{"type":"integer","title":"User Id"},"profile_complete":{"type":"boolean","title":"Profile Complete"},"tutorial_seen":{"type":"boolean","title":"Tutorial Seen"},"nudge_dismissed":{"type":"boolean","title":"Nudge Dismissed"},"demo_active":{"type":"boolean","title":"Demo Active"},"checklist_state":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Checklist State"}},"type":"object","required":["user_id","profile_complete","tutorial_seen","nudge_dismissed","demo_active"],"title":"OnboardingStateResponse","description":"The current user's onboarding journey state."},"OnboardingStateUpdate":{"properties":{"tutorial_seen":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Tutorial Seen"},"nudge_dismissed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Nudge Dismissed"},"checklist_state":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Checklist State"}},"type":"object","title":"OnboardingStateUpdate","description":"Partial update of the user-controllable journey flags.\n\n``profile_complete`` (derived from the profile step) and ``demo_active``\n(owned by the demo seed / data-reset) are deliberately NOT settable here.\n``checklist_state`` is shallow-merged onto the stored dict."},"ParserInfo":{"properties":{"key":{"type":"string","title":"Key"},"label":{"type":"string","title":"Label"},"source_type":{"type":"string","title":"Source Type"}},"type":"object","required":["key","label","source_type"],"title":"ParserInfo","description":"One entry in the parser catalogue (``GET /parsers`` + the 422 envelope).\n\n``key`` is the registry **class key** (``phonepe`` / ``csv``) the frontend\nsends back as ``parser_override``; ``label`` is the display string;\n``source_type`` is the wire tag persisted on the job."},"PayRequest":{"properties":{"amount":{"type":"number","title":"Amount"},"source_account_id":{"type":"integer","title":"Source Account Id"},"committee_account_id":{"type":"integer","title":"Committee Account Id"},"method":{"type":"string","title":"Method","default":"external"},"allow_duplicate":{"type":"boolean","title":"Allow Duplicate","default":false}},"type":"object","required":["amount","source_account_id","committee_account_id"],"title":"PayRequest","description":"Affordance A — aggregate self-tax payment (T-mark-paid-savings). ``amount``\nis applied FIFO across outstanding bills (excess -> prepaid surplus). The user\npicks the accounts (SoT): ``source_account_id`` is a REGULAR account the money\ncame from, ``committee_account_id`` the tax-pot it lands in. ``method``:\n``external`` mints a synthetic settlement txn; ``upi`` is reserved (rejected)."},"PayResponse":{"properties":{"txn_id":{"type":"integer","title":"Txn Id"},"amount":{"type":"number","title":"Amount"},"applied":{"type":"number","title":"Applied"},"outstanding_remaining":{"type":"number","title":"Outstanding Remaining"}},"type":"object","required":["txn_id","amount","applied","outstanding_remaining"],"title":"PayResponse"},"PeriodType":{"type":"string","enum":["weekly","monthly","quarterly","annual"],"title":"PeriodType"},"PeriodTypeInfo":{"properties":{"key":{"type":"string","title":"Key"},"materialized":{"type":"boolean","title":"Materialized"},"order":{"type":"integer","title":"Order"}},"type":"object","required":["key","materialized","order"],"title":"PeriodTypeInfo","description":"One period grain in the surfaced vocabulary. ``materialized`` = written by\nthe aggregate engine (weekly/monthly) vs derived at read time\n(quarterly/annual); ``order`` is the canonical fine→coarse display order.\nDisplay labels stay FE-owned (UI copy / i18n)."},"PersonDetailInput":{"properties":{"relationship_type":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Relationship Type"},"phone":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Phone"},"upi_id":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Upi Id"}},"type":"object","title":"PersonDetailInput"},"PersonDetailResponse":{"properties":{"relationship_type":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Relationship Type"},"phone":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Phone"},"upi_id":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Upi Id"}},"type":"object","title":"PersonDetailResponse"},"PortraitSelection":{"properties":{"style":{"type":"string","title":"Style"},"seed":{"type":"string","title":"Seed"}},"type":"object","required":["style","seed"],"title":"PortraitSelection","description":"Request body for choosing a client-rendered portrait (style + seed)."},"ProfileStepRequest":{"properties":{"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"dob":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dob"},"contact":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"skip":{"type":"boolean","title":"Skip","default":false}},"type":"object","title":"ProfileStepRequest","description":"Complete *or* skip the onboarding profile section (ST3).\n\n``skip=True`` leaves the profile untouched (``profile_complete`` stays\nFalse; the nudge keeps prompting). Otherwise the name + country are written\nto the profile and ``profile_complete`` flips True — subject to **D7**: at\nleast one of ``first_name`` / ``last_name`` is required when not skipping."},"ProfileUpdate":{"properties":{"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"dob":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dob"},"contact":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"}},"type":"object","title":"ProfileUpdate","description":"Partial profile update — every field optional.\n\n``currency`` is no longer here: it moved to the Preferences tier\n(``PATCH /api/v1/users/preferences``) in T-preferences."},"RecoveryConfigResponse":{"properties":{"questions":{"items":{"$ref":"#/components/schemas/RecoveryQuestionResponse"},"type":"array","title":"Questions"}},"type":"object","required":["questions"],"title":"RecoveryConfigResponse"},"RecoveryQuestionCreate":{"properties":{"question":{"type":"string","title":"Question"},"answer":{"type":"string","title":"Answer"}},"type":"object","required":["question","answer"],"title":"RecoveryQuestionCreate"},"RecoveryQuestionLookupRequest":{"properties":{"email_id":{"type":"string","format":"email","title":"Email Id"}},"type":"object","required":["email_id"],"title":"RecoveryQuestionLookupRequest"},"RecoveryQuestionResponse":{"properties":{"uid":{"type":"integer","title":"Uid"},"question":{"type":"string","title":"Question"}},"type":"object","required":["uid","question"],"title":"RecoveryQuestionResponse","description":"A single configured security question (no answer ever returned)."},"RecurringBillResponse":{"properties":{"uid":{"type":"integer","title":"Uid"},"template_id":{"type":"integer","title":"Template Id"},"beneficiary_id":{"type":"integer","title":"Beneficiary Id"},"expected_amount":{"type":"number","title":"Expected Amount"},"debit_credit":{"$ref":"#/components/schemas/DebitCredit"},"due_date":{"type":"string","format":"date","title":"Due Date"},"status":{"$ref":"#/components/schemas/RecurringBillStatus"},"matched_txn_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Matched Txn Id"}},"type":"object","required":["uid","template_id","beneficiary_id","expected_amount","debit_credit","due_date","status"],"title":"RecurringBillResponse"},"RecurringBillStatus":{"type":"string","enum":["pending","confirmed","expired"],"title":"RecurringBillStatus"},"RecurringPatternType":{"type":"string","enum":["FIXED_AMOUNT","FIXED_CADENCE"],"title":"RecurringPatternType"},"RecurringStatus":{"type":"string","enum":["candidate","locked","review"],"title":"RecurringStatus"},"RecurringTemplateCreate":{"properties":{"beneficiary_id":{"type":"integer","title":"Beneficiary Id"},"debit_credit":{"$ref":"#/components/schemas/DebitCredit"},"cadence":{"$ref":"#/components/schemas/Cadence"},"expected_amount":{"type":"number","exclusiveMinimum":0.0,"title":"Expected Amount"},"next_due_date":{"type":"string","format":"date","title":"Next Due Date"},"pattern_type":{"anyOf":[{"$ref":"#/components/schemas/RecurringPatternType"},{"type":"null"}]},"cadence_interval":{"type":"integer","minimum":1.0,"title":"Cadence Interval","default":1},"amount_tolerance":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Amount Tolerance","default":0.15},"day_of_month":{"anyOf":[{"type":"integer","maximum":31.0,"minimum":1.0},{"type":"null"}],"title":"Day Of Month"},"day_of_week":{"anyOf":[{"type":"integer","maximum":6.0,"minimum":0.0},{"type":"null"}],"title":"Day Of Week"},"week_of_month":{"anyOf":[{"type":"integer","maximum":5.0,"minimum":1.0},{"type":"null"}],"title":"Week Of Month"}},"type":"object","required":["beneficiary_id","debit_credit","cadence","expected_amount","next_due_date"],"title":"RecurringTemplateCreate","description":"User-declared recurring template. The user is source-of-truth, so a\ncreated template is authoritative (born ``locked`` + owned by the user)."},"RecurringTemplateResponse":{"properties":{"uid":{"type":"integer","title":"Uid"},"beneficiary_id":{"type":"integer","title":"Beneficiary Id"},"debit_credit":{"$ref":"#/components/schemas/DebitCredit"},"pattern_type":{"$ref":"#/components/schemas/RecurringPatternType"},"expected_amount":{"type":"number","title":"Expected Amount"},"amount_tolerance":{"type":"number","title":"Amount Tolerance"},"cadence":{"$ref":"#/components/schemas/Cadence"},"cadence_interval":{"type":"integer","title":"Cadence Interval"},"day_of_month":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Day Of Month"},"day_of_week":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Day Of Week"},"week_of_month":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Week Of Month"},"anchor_date":{"type":"string","format":"date","title":"Anchor Date"},"next_due_date":{"type":"string","format":"date","title":"Next Due Date"},"status":{"$ref":"#/components/schemas/RecurringStatus"},"active":{"type":"boolean","title":"Active"},"occurrence_count":{"type":"integer","title":"Occurrence Count"},"last_seen_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Last Seen Date"},"last_confirmed_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Last Confirmed Date"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["uid","beneficiary_id","debit_credit","pattern_type","expected_amount","amount_tolerance","cadence","cadence_interval","anchor_date","next_due_date","status","active","occurrence_count","created_at"],"title":"RecurringTemplateResponse"},"RecurringTemplateUpdate":{"properties":{"expected_amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Expected Amount"},"amount_tolerance":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Amount Tolerance"},"cadence":{"anyOf":[{"$ref":"#/components/schemas/Cadence"},{"type":"null"}]},"cadence_interval":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Cadence Interval"},"pattern_type":{"anyOf":[{"$ref":"#/components/schemas/RecurringPatternType"},{"type":"null"}]},"day_of_month":{"anyOf":[{"type":"integer","maximum":31.0,"minimum":1.0},{"type":"null"}],"title":"Day Of Month"},"day_of_week":{"anyOf":[{"type":"integer","maximum":6.0,"minimum":0.0},{"type":"null"}],"title":"Day Of Week"},"week_of_month":{"anyOf":[{"type":"integer","maximum":5.0,"minimum":1.0},{"type":"null"}],"title":"Week Of Month"},"next_due_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Next Due Date"},"status":{"anyOf":[{"$ref":"#/components/schemas/RecurringStatus"},{"type":"null"}]},"active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Active"}},"type":"object","title":"RecurringTemplateUpdate","description":"Partial update. Any field touched transfers ownership to the user."},"RegisterChallengeResponse":{"properties":{"pending_token":{"type":"string","title":"Pending Token"},"email_verification_required":{"type":"boolean","title":"Email Verification Required","default":true}},"type":"object","required":["pending_token"],"title":"RegisterChallengeResponse","description":"Response to ``POST /auth/register`` (pre-session-challenge rework).\n\nRegistration no longer opens a session — it returns a short-lived\n``pending_token`` (stateless, signed) and the FE routes to the verify page.\nA real session is issued only once ``POST /auth/verify-email`` confirms the\nemailed OTP. Mirrors the new-device challenge contract."},"RegisterRequest":{"properties":{"email_id":{"type":"string","format":"email","title":"Email Id"},"password":{"type":"string","maxLength":64,"minLength":8,"title":"Password"},"security_question":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Security Question"},"security_answer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Security Answer"},"dob":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dob"},"contact":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"}},"type":"object","required":["email_id","password"],"title":"RegisterRequest","description":"Integrated registration: profile + credentials + optional recovery Q&A."},"RegistryMatch":{"properties":{"slug":{"type":"string","title":"Slug"},"canonical_name":{"type":"string","title":"Canonical Name"},"aliases":{"items":{"type":"string"},"type":"array","title":"Aliases","default":[]},"sector":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sector"},"primary_role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Role"},"source":{"type":"string","title":"Source"},"confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence"},"relations":{"items":{"$ref":"#/components/schemas/RegistryRelation"},"type":"array","title":"Relations","default":[]}},"type":"object","required":["slug","canonical_name","source"],"title":"RegistryMatch","description":"A canonical entry, as seen by a module leasing it down.\n\n``confidence`` is the entry's PROVENANCE (how far we trust its canonical\nfacts), never a score for how well the payee text matched — matching is\nboolean. See ``app/constants/registry.py``."},"RegistryRelation":{"properties":{"relationship":{"type":"string","title":"Relationship"},"confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence"},"tags":{"items":{"$ref":"#/components/schemas/RegistryTag"},"type":"array","title":"Tags","default":[]}},"type":"object","required":["relationship"],"title":"RegistryRelation","description":"One relationship the counterparty can stand in to a user, with the tag-sets\nthat apply per direction. The canonical twin of a ``relationship_rules`` edge\nplus its ``categorization_rules``."},"RegistryTag":{"properties":{"direction":{"type":"string","title":"Direction"},"tag_slug":{"type":"string","title":"Tag Slug"},"sort_order":{"type":"integer","title":"Sort Order","default":0}},"type":"object","required":["direction","tag_slug"],"title":"RegistryTag","description":"One ``(direction) -> tag`` of a relation's tag-set. ``tag_slug`` is resolved\nagainst the *leasing user's* own tag tree — the canonical side never holds a\ntag id, because tag ids are per-user."},"Relationship":{"type":"string","enum":["merchant","employer","investment","self","savings","family","friends","colleagues","servants","others"],"title":"Relationship","description":"Granular relationship — user-meaningful, the only value stored."},"RelationshipInput":{"properties":{"relationship":{"$ref":"#/components/schemas/Relationship"},"is_primary":{"type":"boolean","title":"Is Primary","default":false},"bank_account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bank Account Id"},"alias":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Alias"}},"type":"object","required":["relationship"],"title":"RelationshipInput","description":"One relationship edge submitted from the beneficiary form.\n\nThe form manages a beneficiary's relationships directly (they replaced the\nsingle-tag \"Category\"). ``bank_account_id`` pins the edge to one of the\nbeneficiary's OWN accounts (``beneficiary_bank_accounts.uid`` — the\ncore-ledger re-point; the STRONG resolution signal); ``alias`` is the WEAK\nsecondary signal (a narrative header symbolising this relationship)."},"ResetFootprint":{"properties":{"transactions":{"type":"integer","title":"Transactions"},"bank_accounts":{"type":"integer","title":"Bank Accounts"},"budget_limits":{"type":"integer","title":"Budget Limits"},"consumption_tax_bills":{"type":"integer","title":"Consumption Tax Bills"},"beneficiaries":{"type":"integer","title":"Beneficiaries"},"tags":{"type":"integer","title":"Tags"},"categorization_rules":{"type":"integer","title":"Categorization Rules"}},"type":"object","required":["transactions","bank_accounts","budget_limits","consumption_tax_bills","beneficiaries","tags","categorization_rules"],"title":"ResetFootprint","description":"The account's data footprint — the per-entity row counts a data reset\n(``POST /me/data-reset``) would irreversibly delete (T-reset-safety).\n\nThese seven are the *pristine predicate*: an account is empty\n(``is_pristine``) iff every count is 0 (and no taxation rule has been\ntuned). Tier semantics: ``transactions`` / ``bank_accounts`` /\n``budget_limits`` / ``consumption_tax_bills`` are bare ``user_id`` counts;\n``beneficiaries`` / ``tags`` / ``categorization_rules`` are\n``created_by == user_id`` counts (seeded defaults excluded, so a\nfreshly-registered account reads all-zero). Consumed by the ResetZone\nsafeguard copy, the admin usage view, and onboarding demo-routing."},"ResetPasswordFinalRequest":{"properties":{"reset_token":{"type":"string","title":"Reset Token"},"new_password":{"type":"string","maxLength":64,"minLength":8,"title":"New Password"}},"type":"object","required":["reset_token","new_password"],"title":"ResetPasswordFinalRequest"},"ResetTokenResponse":{"properties":{"reset_token":{"type":"string","title":"Reset Token"}},"type":"object","required":["reset_token"],"title":"ResetTokenResponse"},"ReviewQuestionResponse":{"properties":{"staged_uid":{"type":"integer","title":"Staged Uid"},"kind":{"type":"string","title":"Kind"},"amount":{"type":"number","title":"Amount"},"debit_credit":{"type":"string","title":"Debit Credit"},"txn_date":{"type":"string","title":"Txn Date"},"narrative":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Narrative"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"job_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Job Id"},"identifier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Identifier"},"reasons":{"items":{"type":"string"},"type":"array","title":"Reasons"},"matched":{"anyOf":[{"$ref":"#/components/schemas/MatchedRowDTO"},{"type":"null"}]}},"type":"object","required":["staged_uid","kind","amount","debit_credit","txn_date"],"title":"ReviewQuestionResponse","description":"One open review question — a row HELD in staging rather than imported.\n\n**Two kinds, one mechanism** (``kind``): a suspected TRANSFER leg, and a suspected\nDUPLICATE. Neither is in the ledger; neither is lost. Both wait here for exactly\none answer, and pending rows are never purged.\n\n``reasons`` carries the EVIDENCE (``DedupReason`` values) so the surface can\nexplain itself. **The BE ships facts, never a sentence** — the FE owns the label\nmap, so the copy has one home.\n\n⚠️ The surface must render **both rows side by side** (this row + ``matched``).\nWhat makes a duplicate decidable for a human is *seeing both*; an id they cannot\nlook up is no help — and when the match is a staged row there is no id at all."},"SecurityStatusResponse":{"properties":{"has_recovery":{"type":"boolean","title":"Has Recovery"},"two_factor_enabled":{"type":"boolean","title":"Two Factor Enabled"},"backup_codes_remaining":{"type":"integer","title":"Backup Codes Remaining"},"email_verified":{"type":"boolean","title":"Email Verified","default":false}},"type":"object","required":["has_recovery","two_factor_enabled","backup_codes_remaining"],"title":"SecurityStatusResponse","description":"Auth-owned account-protection snapshot for ``GET /api/v1/auth/security``.\n\nKeeps ``UserAuth``-owned security state on an auth-domain route (the rest of\nthe Security tab reads ``/auth/{recovery,sessions,devices}``); it never rides\nthe users-module ``/me`` profile DTO. Status only — the TOTP secret and the\nbackup-code values never leave ``UserAuth``."},"SeenRef":{"properties":{"kind":{"type":"string","title":"Kind"},"subject_type":{"type":"string","title":"Subject Type"},"subject_id":{"type":"string","title":"Subject Id"}},"type":"object","required":["kind","subject_type","subject_id"],"title":"SeenRef"},"SeenRequest":{"properties":{"refs":{"items":{"$ref":"#/components/schemas/SeenRef"},"type":"array","title":"Refs"},"hard":{"type":"boolean","title":"Hard","default":false}},"type":"object","required":["refs"],"title":"SeenRequest"},"SeenResponse":{"properties":{"affected":{"type":"integer","title":"Affected"}},"type":"object","required":["affected"],"title":"SeenResponse"},"SessionInfo":{"properties":{"session_id":{"type":"string","title":"Session Id"},"ip_address":{"type":"string","title":"Ip Address"},"device_data":{"type":"string","title":"Device Data"},"known_device_uid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Known Device Uid"},"is_current":{"type":"boolean","title":"Is Current","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_modified":{"type":"string","format":"date-time","title":"Last Modified"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"type":"object","required":["session_id","ip_address","device_data","created_at","last_modified","expires_at"],"title":"SessionInfo","description":"One active session in the Settings → Security list (T-concurrent-sessions).\n\nDeliberately omits ``refresh_token`` — the list is a read-only inventory of\nwhere the account is signed in, never a credential surface. ``is_current``\nmarks the session the requesting token belongs to so the UI can label \"This\ndevice\" and guard against accidentally revoking it. ``known_device_uid``\ncorrelates the row with the device-trust registry (future trusted-devices UI)."},"SignalSettingUpdate":{"properties":{"kind":{"type":"string","title":"Kind"},"enabled":{"type":"boolean","title":"Enabled"}},"type":"object","required":["kind","enabled"],"title":"SignalSettingUpdate"},"SignalSettingsOut":{"properties":{"disabled":{"items":{"type":"string"},"type":"array","title":"Disabled"}},"type":"object","required":["disabled"],"title":"SignalSettingsOut"},"StatementJobStatus":{"type":"string","enum":["PROCESSING","COMPLETED","FAILED"],"title":"StatementJobStatus"},"StatusResponse":{"properties":{"status":{"type":"string","title":"Status"}},"type":"object","required":["status"],"title":"StatusResponse"},"SystemConstantsResponse":{"properties":{"SYSTEM_USER_ID":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"System User Id"},"TOTAL_TAG_ID":{"type":"integer","title":"Total Tag Id"},"MISC_DEBIT_TAG_ID":{"type":"integer","title":"Misc Debit Tag Id"},"MISC_CREDIT_TAG_ID":{"type":"integer","title":"Misc Credit Tag Id"},"CONSUMPTION_TAX_TAG_ID":{"type":"integer","title":"Consumption Tax Tag Id"},"CONSUMPTION_LOAN_TAG_ID":{"type":"integer","title":"Consumption Loan Tag Id"},"RESERVED_TAG_IDS":{"items":{"type":"integer"},"type":"array","title":"Reserved Tag Ids"},"RULE_FORBIDDEN_TAG_IDS":{"items":{"type":"integer"},"type":"array","title":"Rule Forbidden Tag Ids"},"TAXABLE_TXN_TYPES":{"items":{"type":"string"},"type":"array","title":"Taxable Txn Types"},"VALID_TAG_TYPES":{"items":{"type":"string"},"type":"array","title":"Valid Tag Types"},"VALID_TXN_TYPES":{"items":{"type":"string"},"type":"array","title":"Valid Txn Types"},"RELATIONSHIP_TYPES":{"items":{"type":"string"},"type":"array","title":"Relationship Types"},"PERIOD_TYPES":{"items":{"$ref":"#/components/schemas/PeriodTypeInfo"},"type":"array","title":"Period Types"}},"type":"object","required":["TOTAL_TAG_ID","MISC_DEBIT_TAG_ID","MISC_CREDIT_TAG_ID","CONSUMPTION_TAX_TAG_ID","CONSUMPTION_LOAN_TAG_ID","RESERVED_TAG_IDS","RULE_FORBIDDEN_TAG_IDS","TAXABLE_TXN_TYPES","VALID_TAG_TYPES","VALID_TXN_TYPES","RELATIONSHIP_TYPES","PERIOD_TYPES"],"title":"SystemConstantsResponse","description":"System reference constants the frontend mirrors."},"TagCreate":{"properties":{"tag_name":{"type":"string","title":"Tag Name"},"parent":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Parent"},"aliases":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Aliases"},"tag_type":{"anyOf":[{"$ref":"#/components/schemas/TagType"},{"type":"null"}]}},"type":"object","required":["tag_name"],"title":"TagCreate"},"TagEnvelope":{"properties":{"tag":{"$ref":"#/components/schemas/TagNode"}},"type":"object","required":["tag"],"title":"TagEnvelope"},"TagListResponse":{"properties":{"tags":{"items":{"$ref":"#/components/schemas/TagNode"},"type":"array","title":"Tags"}},"type":"object","required":["tags"],"title":"TagListResponse"},"TagNode":{"properties":{"tag_id":{"type":"integer","title":"Tag Id"},"tag_name":{"type":"string","title":"Tag Name"},"parent":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Parent"},"aliases":{"items":{"type":"string"},"type":"array","title":"Aliases","default":[]},"tag_type":{"anyOf":[{"$ref":"#/components/schemas/TagType"},{"type":"null"}]},"created_by":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Created By"},"is_reserved":{"type":"boolean","title":"Is Reserved","default":false},"created_at":{"type":"string","title":"Created At"},"children":{"anyOf":[{"items":{"$ref":"#/components/schemas/TagNode"},"type":"array"},{"type":"null"}],"title":"Children"}},"type":"object","required":["tag_id","tag_name","created_at"],"title":"TagNode","description":"A single tag in the response tree."},"TagRefDTO":{"properties":{"tag_id":{"type":"integer","title":"Tag Id"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"}},"type":"object","required":["tag_id"],"title":"TagRefDTO","description":"A frozen tag id + its live name (null if the tag was since deleted)."},"TagType":{"type":"string","enum":["total","uncategorized_debit","uncategorized_credit","consumption_tax","consumption_loan","income","transfer","exempted","committed","essential","discretionary"],"title":"TagType"},"TagUpdate":{"properties":{"tag_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag Name"},"aliases":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Aliases"},"tag_type":{"anyOf":[{"$ref":"#/components/schemas/TagType"},{"type":"null"}]}},"type":"object","title":"TagUpdate"},"TaxMode":{"type":"string","enum":["off","manual","auto"],"title":"TaxMode"},"TaxationRuleDTO":{"properties":{"uid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Uid"},"txn_type":{"$ref":"#/components/schemas/TxnType"},"tax_rate":{"type":"number","title":"Tax Rate"},"default_penalty_rate":{"type":"number","title":"Default Penalty Rate"},"created_by":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Created By"},"is_system":{"type":"boolean","title":"Is System","default":false},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"}},"type":"object","required":["txn_type","tax_rate","default_penalty_rate"],"title":"TaxationRuleDTO"},"TaxationRuleListResponse":{"properties":{"rules":{"items":{"$ref":"#/components/schemas/TaxationRuleDTO"},"type":"array","title":"Rules"}},"type":"object","required":["rules"],"title":"TaxationRuleListResponse"},"TaxationRuleRef":{"properties":{"uid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Uid"},"txn_type":{"$ref":"#/components/schemas/TxnType"}},"type":"object","required":["txn_type"],"title":"TaxationRuleRef"},"TaxationRuleUpdate":{"properties":{"tax_rate":{"type":"number","title":"Tax Rate"},"default_penalty_rate":{"type":"number","title":"Default Penalty Rate"}},"type":"object","required":["tax_rate","default_penalty_rate"],"title":"TaxationRuleUpdate","description":"Per-user override of a system taxation rule.\n\nThe router validates the path ``txn_type`` so it is not duplicated here."},"TaxationRuleUpsertResponse":{"properties":{"rule":{"$ref":"#/components/schemas/TaxationRuleRef"}},"type":"object","required":["rule"],"title":"TaxationRuleUpsertResponse"},"TimezoneOption":{"properties":{"name":{"type":"string","title":"Name"},"offset_winter":{"type":"string","title":"Offset Winter"},"offset_summer":{"type":"string","title":"Offset Summer"}},"type":"object","required":["name","offset_winter","offset_summer"],"title":"TimezoneOption"},"TimezonesResponse":{"properties":{"timezones":{"items":{"$ref":"#/components/schemas/TimezoneOption"},"type":"array","title":"Timezones"}},"type":"object","required":["timezones"],"title":"TimezonesResponse"},"TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"}},"type":"object","required":["access_token"],"title":"TokenResponse","description":"Standard authentication response — the **access token only**, no PII.\n\nT-device-binding (BFF): the refresh token is never in the body — it rides an\n``httpOnly`` cookie the browser holds and JS can't read (see ``auth_cookies``).\nThe access token is short-lived + DPoP-bound and returned here for the FE to\nkeep **in memory** and send as a Bearer header with a matching proof. Services\nstill return both tokens internally; the route sets the refresh cookie and this\nresponse_model filters the refresh token out of the serialized body."},"TransactionCreate":{"properties":{"amount":{"type":"number","minimum":0.0,"title":"Amount"},"debit_credit":{"$ref":"#/components/schemas/DebitCredit"},"beneficiary_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Beneficiary Id"},"beneficiary_name":{"type":"string","title":"Beneficiary Name"},"txn_date":{"type":"string","title":"Txn Date","description":"Transaction date in ISO 8601 format (e.g. YYYY-MM-DDTHH:MM:SSZ)"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"tag_ids":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Tag Ids"},"bank_account_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bank Account Id"}},"type":"object","required":["amount","debit_credit","beneficiary_name","txn_date"],"title":"TransactionCreate"},"TransactionEnvelope":{"properties":{"transaction":{"$ref":"#/components/schemas/TransactionResponse"}},"type":"object","required":["transaction"],"title":"TransactionEnvelope"},"TransactionGroup":{"properties":{"tag_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tag Id"},"tag_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag Name"},"tag_type":{"anyOf":[{"$ref":"#/components/schemas/TagType"},{"type":"null"}]},"beneficiary_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Beneficiary Id"},"beneficiary_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Beneficiary Name"},"total_count":{"type":"integer","title":"Total Count"},"net_expense":{"type":"number","title":"Net Expense"}},"type":"object","required":["total_count","net_expense"],"title":"TransactionGroup","description":"One aggregated group in a ``?group_by`` response (T-aggregates-engine).\n\nEntity identity is the entity-specific pair: ``tag_id`` / ``tag_name`` /\n``tag_type`` for ``group_by=tag``; ``beneficiary_id`` / ``beneficiary_name``\nfor ``group_by=beneficiary`` — the other pair is null (beneficiaries have no\n``tag_type``). ``total_count`` is the txn count in the window;\n``net_expense`` = ``total_debit − total_credit`` (expense-positive; refunds\nnet it down, net-inflow tags read negative). ``tag_type`` lets the FE split\nthe budgets surface (expense types) from the general tracker view. Read\njoin-free from the materialized aggregate tables."},"TransactionListResponse":{"properties":{"transactions":{"items":{"$ref":"#/components/schemas/TransactionResponse"},"type":"array","title":"Transactions"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"returned_count":{"type":"integer","title":"Returned Count"}},"type":"object","required":["transactions","limit","offset","returned_count"],"title":"TransactionListResponse"},"TransactionMutationResponse":{"properties":{"status":{"type":"string","title":"Status"},"transaction":{"$ref":"#/components/schemas/TransactionResponse"}},"type":"object","required":["status","transaction"],"title":"TransactionMutationResponse"},"TransactionResponse":{"properties":{"txn_id":{"type":"integer","title":"Txn Id"},"txn_no":{"type":"integer","title":"Txn No"},"user_id":{"type":"integer","title":"User Id"},"amount":{"type":"number","title":"Amount"},"debit_credit":{"$ref":"#/components/schemas/DebitCredit"},"beneficiary_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Beneficiary Id"},"beneficiary_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Beneficiary Name"},"txn_date":{"type":"string","format":"date-time","title":"Txn Date","description":"Transaction date in ISO 8601 format"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"source":{"type":"string","title":"Source"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"tag_ids":{"items":{"type":"integer"},"type":"array","title":"Tag Ids"},"recurring_template_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Recurring Template Id"}},"type":"object","required":["txn_id","txn_no","user_id","amount","debit_credit","txn_date","source","created_at","tag_ids"],"title":"TransactionResponse"},"TransactionUpdate":{"properties":{"amount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Amount"},"debit_credit":{"anyOf":[{"$ref":"#/components/schemas/DebitCredit"},{"type":"null"}]},"beneficiary_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Beneficiary Id"},"beneficiary_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Beneficiary Name"},"txn_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Txn Date","description":"Transaction date in ISO 8601 format"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"tag_ids":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Tag Ids"}},"type":"object","title":"TransactionUpdate"},"TreasurySummaryResponse":{"properties":{"funded_balance":{"type":"number","title":"Funded Balance"},"recognized_revenue":{"type":"number","title":"Recognized Revenue"},"deferred_balance":{"type":"number","title":"Deferred Balance"},"provisioned_total":{"type":"number","title":"Provisioned Total"},"currency":{"type":"string","title":"Currency"},"trend":{"items":{"$ref":"#/components/schemas/TreasuryTrendPointDTO"},"type":"array","title":"Trend"}},"type":"object","required":["funded_balance","recognized_revenue","deferred_balance","provisioned_total","currency","trend"],"title":"TreasurySummaryResponse","description":"The set-aside view consumed by the Savings page + the dashboard hero."},"TreasuryTrendPointDTO":{"properties":{"period_end":{"type":"string","title":"Period End"},"cumulative_balance":{"type":"number","title":"Cumulative Balance"},"delta":{"type":"number","title":"Delta"}},"type":"object","required":["period_end","cumulative_balance","delta"],"title":"TreasuryTrendPointDTO"},"TwoFactorDisableRequest":{"properties":{"password":{"type":"string","title":"Password"}},"type":"object","required":["password"],"title":"TwoFactorDisableRequest","description":"Disabling 2FA re-confirms intent with the account password — the user is\nalready past the login TOTP gate for this session, so password re-entry is\nthe step-up (and rescues a user who has lost their authenticator)."},"TwoFactorEnrollResponse":{"properties":{"secret":{"type":"string","title":"Secret"},"provisioning_uri":{"type":"string","title":"Provisioning Uri"},"enroll_token":{"type":"string","title":"Enroll Token"}},"type":"object","required":["secret","provisioning_uri","enroll_token"],"title":"TwoFactorEnrollResponse","description":"Returned by ``POST /api/v1/auth/2fa/enroll``. The secret is staged but 2FA\nstays *off* until the first code is confirmed at ``verify-enroll``.\n\n``secret`` (base32) and ``provisioning_uri`` (``otpauth://``) are two views\nof the same shared secret: the URI is what the frontend renders as a QR\ncode; ``secret`` is the manual-entry fallback. Both are shown only at\nenrollment and never returned again.\n\n``enroll_token`` is a short-lived signed token carrying the *encrypted* staged\nsecret (stateless staging — nothing is persisted until confirmation). The FE\nholds it across the QR-scan step and passes it back to ``verify-enroll``. On\ncancel the FE simply drops it — no server call needed; it expires on its own."},"TwoFactorEnrollResult":{"properties":{"backup_codes":{"items":{"type":"string"},"type":"array","title":"Backup Codes"}},"type":"object","required":["backup_codes"],"title":"TwoFactorEnrollResult","description":"Returned once when enrollment is confirmed: the single-use backup codes,\nshown exactly this once (only their hashes are stored)."},"TwoFactorLoginVerifyRequest":{"properties":{"pending_token":{"type":"string","title":"Pending Token"},"code":{"type":"string","title":"Code"}},"type":"object","required":["pending_token","code"],"title":"TwoFactorLoginVerifyRequest"},"TwoFactorVerifyEnrollRequest":{"properties":{"enroll_token":{"type":"string","title":"Enroll Token"},"code":{"type":"string","title":"Code"}},"type":"object","required":["enroll_token","code"],"title":"TwoFactorVerifyEnrollRequest"},"TxnType":{"type":"string","enum":["uncategorized_debit","uncategorized_credit","consumption_tax","consumption_loan","income","transfer","exempted","committed","essential","discretionary"],"title":"TxnType"},"UploadAcceptedResponse":{"properties":{"job_id":{"type":"integer","title":"Job Id"},"status":{"$ref":"#/components/schemas/StatementJobStatus"}},"type":"object","required":["job_id","status"],"title":"UploadAcceptedResponse","description":"202 body — the job was queued; poll ``GET /api/v1/statement-uploads/{job_id}``."},"UserEnvelope":{"properties":{"user":{"$ref":"#/components/schemas/UserPrivateResponse"}},"type":"object","required":["user"],"title":"UserEnvelope","description":"Wrapper matching the ``{\"user\": {...}}`` response shape on ``/me``."},"UserPreferencesResponse":{"properties":{"currency":{"type":"string","title":"Currency"},"timezone":{"type":"string","title":"Timezone"},"date_format":{"$ref":"#/components/schemas/DateFormat"},"number_format":{"$ref":"#/components/schemas/NumberFormat"},"landing_route":{"$ref":"#/components/schemas/LandingRoute"},"default_txn_kind":{"$ref":"#/components/schemas/DefaultTxnKind"},"underline_links":{"type":"boolean","title":"Underline Links"},"focus_ring_always":{"type":"boolean","title":"Focus Ring Always"},"tax_mode":{"$ref":"#/components/schemas/TaxMode"},"budget_mode":{"$ref":"#/components/schemas/BudgetMode"}},"type":"object","required":["currency","timezone","date_format","number_format","landing_route","default_txn_kind","underline_links","focus_ring_always","tax_mode","budget_mode"],"title":"UserPreferencesResponse","description":"The user's full preferences row — every Preferences-tier setting plus\nthe two accessibility flags. Served by ``GET /api/v1/users/preferences``; the\nfrontend dissects it across its preference / accessibility stores.\n\n``country`` is *not* here — it is Profile-tier and lives on ``/me``. All\nfields are present (the row is seeded complete at registration)."},"UserPreferencesUpdate":{"properties":{"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"date_format":{"anyOf":[{"$ref":"#/components/schemas/DateFormat"},{"type":"null"}]},"number_format":{"anyOf":[{"$ref":"#/components/schemas/NumberFormat"},{"type":"null"}]},"landing_route":{"anyOf":[{"$ref":"#/components/schemas/LandingRoute"},{"type":"null"}]},"default_txn_kind":{"anyOf":[{"$ref":"#/components/schemas/DefaultTxnKind"},{"type":"null"}]},"underline_links":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Underline Links"},"focus_ring_always":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Focus Ring Always"},"tax_mode":{"anyOf":[{"$ref":"#/components/schemas/TaxMode"},{"type":"null"}]},"budget_mode":{"anyOf":[{"$ref":"#/components/schemas/BudgetMode"},{"type":"null"}]}},"type":"object","title":"UserPreferencesUpdate","description":"Partial preferences update — every field optional; provided fields are\nvalidated against their allowed value sets (unknown values → 422)."},"UserPrivateResponse":{"properties":{"user_id":{"type":"integer","title":"User Id"},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"email_id":{"type":"string","title":"Email Id"},"dob":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dob"},"contact":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"profile_image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile Image Url"},"profile_portrait":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile Portrait"},"role":{"type":"string","title":"Role"}},"type":"object","required":["user_id","email_id","role"],"title":"UserPrivateResponse","description":"The owner's view of their own profile.\n\nReturned only by ``/api/v1/users/me``. Carries every field the owner entered."},"UserStatsResponse":{"properties":{"joined_at":{"type":"string","format":"date-time","title":"Joined At"},"last_active_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Active At"},"total_transactions":{"type":"integer","title":"Total Transactions"},"total_budgets":{"type":"integer","title":"Total Budgets"},"total_beneficiaries":{"type":"integer","title":"Total Beneficiaries"},"total_businesses":{"type":"integer","title":"Total Businesses"},"total_persons":{"type":"integer","title":"Total Persons"},"active_recurring":{"type":"integer","title":"Active Recurring"},"footprint":{"$ref":"#/components/schemas/ResetFootprint"},"is_pristine":{"type":"boolean","title":"Is Pristine"}},"type":"object","required":["joined_at","total_transactions","total_budgets","total_beneficiaries","total_businesses","total_persons","active_recurring","footprint","is_pristine"],"title":"UserStatsResponse","description":"Profile-card stats: account metadata + lifetime counts.\n\nDisplay-only overview of the user's footprint, served by\n``GET /api/v1/users/me/stats``. ``joined_at`` is registration time\n(``UserAuth.created_at``); ``last_active_at`` is the most recent session\nactivity (login / token refresh). ``total_beneficiaries`` counts only\nuser-added beneficiaries — the seeded demo/Self defaults are excluded — and\ndecomposes into ``total_businesses`` + ``total_persons`` for the profile\ncard. ``footprint`` + ``is_pristine`` are the data-reset safeguard /\nonboarding-routing surface (T-reset-safety); ``is_pristine`` additionally\nfactors in whether any taxation rule has been tuned (not a footprint count,\nsince tuned rates re-seed on reset rather than being lost)."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}