{"openapi":"3.1.0","info":{"title":"Elaan API","summary":"One call delivers a notification to an in-app inbox, email and push.","version":"0.1.0"},"paths":{"/v1/auth/register":{"post":{"tags":["auth","auth"],"summary":"Register","operationId":"register_v1_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"409":{"description":"Email already registered, or signup is closed (single-tenant)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Malformed email, too-short password, or blank/over-long tenant name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/login":{"post":{"tags":["auth","auth"],"summary":"Login","operationId":"login_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/AccessTokenResponse"}}}},"401":{"description":"Invalid email or password","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/verify-email":{"post":{"tags":["auth","auth"],"summary":"Verify Email","description":"Confirm an email from the link's token. Unauthenticated (the token is the\nproof). Idempotent enough: a second click on a consumed token 422s cleanly.","operationId":"verify_email_v1_auth_verify_email_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyEmailRequest"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Invalid or expired verification link","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/resend-verification":{"post":{"tags":["auth","auth"],"summary":"Resend Verification","description":"Re-send the verification email. Always 202 (never reveals whether the email\nexists or is already verified); only actually sends for an unverified user.","operationId":"resend_verification_v1_auth_resend_verification_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResendVerificationRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Resend Verification V1 Auth Resend Verification Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/password-reset":{"post":{"tags":["auth","auth"],"summary":"Request Password Reset","description":"Start a password reset. **Always** 202, whether or not the address has an\naccount — the response must not reveal that, and neither must the timing of a\nmail arriving, which is why the send happens post-response like the others.\nRe-requesting inside the cooldown is also a silent no-op.","operationId":"request_password_reset_v1_auth_password_reset_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestPasswordResetRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Request Password Reset V1 Auth Password Reset Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/password-reset/{token}":{"get":{"tags":["auth","auth"],"summary":"Check Password Reset Token","description":"Is this reset link still usable? Lets the console say \"this link expired\"\nbefore the user types a new password, rather than after. Returns only a\nboolean — an unknown token and an expired one are indistinguishable, so this\nreveals nothing an attacker holding the token doesn't already have.","operationId":"check_password_reset_token_v1_auth_password_reset__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetTokenStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/password-reset/confirm":{"post":{"tags":["auth","auth"],"summary":"Confirm Password Reset","description":"Redeem the link and set the new password. The link is consumed on success,\nso a second POST 422s. Note existing access tokens are NOT revoked — they are\nstateless and stay valid until they expire; see CLAUDE.md.","operationId":"confirm_password_reset_v1_auth_password_reset_confirm_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordRequest"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Invalid or expired reset link, or a weak password","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/tenants":{"get":{"tags":["auth","auth"],"summary":"List Tenants","description":"The tenants the signed-in user belongs to — for the tenant picker /\nswitcher. Any valid token identifies the user; the tenants come from their\nmemberships, not from the token's current tenant.","operationId":"list_tenants_v1_auth_tenants_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TenantResponse"},"title":"Response List Tenants V1 Auth Tenants Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["auth","auth"],"summary":"Create Tenant","description":"Create a new tenant owned by the signed-in user. Like signup, the\nroute coordinates single-aggregate use cases in the one request transaction:\nthe Tenant, the owner's Membership (NOT their default — they keep the tenant\nthey're in; switch explicitly via POST /auth/session), and the tenant's\nun-deletable default brand (every tenant has exactly one). Refused on a\nsingle-tenant (on-prem) install, which is pinned to one tenant.","operationId":"create_tenant_v1_auth_tenants_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTenantRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantResponse"}}}},"403":{"description":"Tenant creation is disabled (single-tenant install)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Blank or over-long tenant name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/tenants/{tenant_id}":{"patch":{"tags":["auth","auth"],"summary":"Rename Tenant","description":"Rename a tenant the signed-in user owns. Authorization is by membership\n(not the token's active tenant), so a user can rename any tenant they own —\ne.g. from the switcher — without switching into it first. Owner-only (every\nmembership is OWNER today; the check future-proofs for other roles).","operationId":"rename_tenant_v1_auth_tenants__tenant_id__patch","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameTenantRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantResponse"}}}},"403":{"description":"Not a member, or not the owner, of that tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Blank or over-long tenant name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/tenants/{tenant_id}/archive":{"post":{"tags":["auth","auth"],"summary":"Archive Tenant","description":"Archive (deactivate) a tenant the signed-in user owns: it's hidden from\nthe switcher and can no longer send (the trigger refuses, fan-out skips).\nReversible via unarchive; data is retained.","operationId":"archive_tenant_v1_auth_tenants__tenant_id__archive_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantResponse"}}}},"403":{"description":"Not a member, or not the owner, of that tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/tenants/{tenant_id}/unarchive":{"post":{"tags":["auth","auth"],"summary":"Unarchive Tenant","description":"Reactivate a previously archived tenant the signed-in user owns.","operationId":"unarchive_tenant_v1_auth_tenants__tenant_id__unarchive_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantResponse"}}}},"403":{"description":"Not a member, or not the owner, of that tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/session":{"post":{"tags":["auth","auth"],"summary":"Start Session","description":"Select or switch to a tenant: verify the user's membership and mint an\naccess token scoped to that tenant. `remember` sets it as their default.","operationId":"start_session_v1_auth_session_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessTokenResponse"}}}},"403":{"description":"Not a member of that tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/tenants/{tenant_id}/members":{"get":{"tags":["auth","auth"],"summary":"List Members","description":"The tenant's current members (email + role). Admin+ only.","operationId":"list_members_v1_auth_tenants__tenant_id__members_get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/MemberResponse"},"title":"Response List Members V1 Auth Tenants  Tenant Id  Members Get"}}}},"403":{"description":"Not an admin (or higher) of that tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/tenants/{tenant_id}/members/{user_id}":{"patch":{"tags":["auth","auth"],"summary":"Change Member Role","description":"Change a member's role (admin+ only; owners only touchable by owners; never\nyourself; never *to* owner).","operationId":"change_member_role_v1_auth_tenants__tenant_id__members__user_id__patch","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeMemberRoleRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberResponse"}}}},"403":{"description":"Not an admin of that tenant, or an admin targeting an owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"That person is not a member of this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"You can't change your own membership from here","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"A role of owner (not assignable via a role change)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["auth","auth"],"summary":"Remove Member","description":"Remove a member from the tenant (admin+ only; owners only removable by\nowners; never yourself). If the removed membership was that user's default\ntenant and they still belong to others, one of those is promoted to default\nso their next login still resolves a tenant.","operationId":"remove_member_v1_auth_tenants__tenant_id__members__user_id__delete","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"403":{"description":"Not an admin of that tenant, or an admin targeting an owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"That person is not a member of this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"You can't remove yourself from here","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/tenants/{tenant_id}/invitations":{"post":{"tags":["auth","auth"],"summary":"Invite Member","description":"Invite someone to the tenant by email with a role. Admin+ only. Refuses if\nthe email already belongs to a member (nothing to invite) or has a live pending\ninvite. Emits `UserInvited` (post-commit) → the dogfooded invite email with the\naccept link.","operationId":"invite_member_v1_auth_tenants__tenant_id__invitations_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteMemberRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationResponse"}}}},"403":{"description":"Not an admin (or higher) of that tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"A live invite for this email exists, or they're already a member","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Malformed email, or a role of owner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["auth","auth"],"summary":"List Invitations","description":"The tenant's outstanding (pending) invitations. Admin+ only.","operationId":"list_invitations_v1_auth_tenants__tenant_id__invitations_get","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InvitationResponse"},"title":"Response List Invitations V1 Auth Tenants  Tenant Id  Invitations Get"}}}},"403":{"description":"Not an admin (or higher) of that tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/tenants/{tenant_id}/invitations/{invitation_id}":{"delete":{"tags":["auth","auth"],"summary":"Revoke Invitation","description":"Cancel a pending invitation. Admin+ only.","operationId":"revoke_invitation_v1_auth_tenants__tenant_id__invitations__invitation_id__delete","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"invitation_id","in":"path","required":true,"schema":{"type":"string","title":"Invitation Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"403":{"description":"Not an admin (or higher) of that tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such invitation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"This invitation is no longer pending","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/invitations/{token}":{"get":{"tags":["auth","auth"],"summary":"Preview Invitation","description":"Public: what the accept page shows before acceptance. Only resolves a\n*pending* token (a used/revoked one is a 404, so a spent link leaks nothing).\n`user_exists` tells the console whether to ask for a password (new user) or\nprompt sign-in (existing user).","operationId":"preview_invitation_v1_auth_invitations__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationPreviewResponse"}}}},"404":{"description":"No valid pending invitation for this token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/invitations/{token}/accept":{"post":{"tags":["auth","auth"],"summary":"Accept Invitation As New User","description":"Accept an invite as a brand-new user (unauthenticated — the emailed token is\nthe proof of address control). Creates an already-verified user + their\nmembership + marks the invite accepted, all in the one request transaction,\nthen auto-logs-in (token scoped to the joined tenant, set as their default).\nIf a user already exists for the email, it's a 409 → use the sign-in flow.","operationId":"accept_invitation_as_new_user_v1_auth_invitations__token__accept_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptInvitationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessTokenResponse"}}}},"404":{"description":"No such invitation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Already accepted/revoked, or a tenant exists for this email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Expired invitation, or a too-short password","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/invitations/{token}/accept-existing":{"post":{"tags":["auth","auth"],"summary":"Accept Invitation As Existing User","description":"Accept an invite as an already-registered user who is signed in (the\n'log-in-first' path). The signed-in user must BE the invited address. Adds the\nmembership (not their default — they keep the tenant they were in) + marks the\ninvite accepted, then mints a token switched into the joined tenant.","operationId":"accept_invitation_as_existing_user_v1_auth_invitations__token__accept_existing_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessTokenResponse"}}}},"403":{"description":"This invite was sent to a different email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such invitation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Already accepted/revoked, or already a member","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Expired invitation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/api-keys":{"post":{"tags":["api-keys"],"summary":"Issue Api Key","operationId":"issue_api_key_v1_api_keys_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueApiKeyRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssuedApiKeyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["api-keys"],"summary":"List Api Keys","operationId":"list_api_keys_v1_api_keys_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyResponse"},"title":"Response List Api Keys V1 Api Keys Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/api-keys/{api_key_id}":{"delete":{"tags":["api-keys"],"summary":"Revoke Api Key","operationId":"revoke_api_key_v1_api_keys__api_key_id__delete","parameters":[{"name":"api_key_id","in":"path","required":true,"schema":{"type":"string","title":"Api Key Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"API key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/notification-types":{"post":{"tags":["notification-types"],"summary":"Create Notification Type","operationId":"create_notification_type_v1_notification_types_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateNotificationTypeRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationTypeResponse"}}}},"409":{"description":"A live notification type with this key already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["notification-types"],"summary":"List Notification Types","operationId":"list_notification_types_v1_notification_types_get","parameters":[{"name":"include_deleted","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Deleted"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/NotificationTypeResponse"},"title":"Response List Notification Types V1 Notification Types Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/notification-types/{key}":{"get":{"tags":["notification-types"],"summary":"Get Notification Type","operationId":"get_notification_type_v1_notification_types__key__get","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationTypeResponse"}}}},"404":{"description":"No notification type with this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["notification-types"],"summary":"Delete Notification Type","operationId":"delete_notification_type_v1_notification_types__key__delete","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"No notification type with this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/notification-types/{key}/channel-defaults":{"patch":{"tags":["notification-types"],"summary":"Update Channel Defaults","operationId":"update_channel_defaults_v1_notification_types__key__channel_defaults_patch","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateChannelDefaultsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationTypeResponse"}}}},"404":{"description":"No notification type with this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/notification-types/{key}/opt-out-policy":{"patch":{"tags":["notification-types"],"summary":"Set Opt Out Policy","description":"Turn opting out on or off for this type. Turning it OFF leaves existing\ncontact preferences in place but dormant, so turning it back ON restores what\neach contact had chosen.","operationId":"set_opt_out_policy_v1_notification_types__key__opt_out_policy_patch","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetOptOutPolicyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationTypeResponse"}}}},"404":{"description":"No notification type with this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/notification-types/{key}/variables":{"put":{"tags":["notification-types"],"summary":"Set Variables","operationId":"set_variables_v1_notification_types__key__variables_put","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetVariablesRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationTypeResponse"}}}},"404":{"description":"No notification type with this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/notification-types/{key}/restore":{"post":{"tags":["notification-types"],"summary":"Restore Notification Type","operationId":"restore_notification_type_v1_notification_types__key__restore_post","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationTypeResponse"}}}},"404":{"description":"No notification type with this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/brandings":{"post":{"tags":["brandings"],"summary":"Create Branding","operationId":"create_branding_v1_brandings_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBrandingRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandingResponse"}}}},"409":{"description":"A branding with this key already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"The plan's brand cap is reached — upgrade to add more","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["brandings"],"summary":"List Brandings","operationId":"list_brandings_v1_brandings_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/BrandingResponse"},"title":"Response List Brandings V1 Brandings Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/brandings/{key}":{"get":{"tags":["brandings"],"summary":"Get Branding","operationId":"get_branding_v1_brandings__key__get","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandingResponse"}}}},"404":{"description":"No branding with this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["brandings"],"summary":"Delete Branding","operationId":"delete_branding_v1_brandings__key__delete","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"No branding with this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/brandings/{key}/values":{"put":{"tags":["brandings"],"summary":"Set Branding Values","operationId":"set_branding_values_v1_brandings__key__values_put","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetBrandingValuesRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandingResponse"}}}},"404":{"description":"No branding with this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/brandings/{key}/sender":{"put":{"tags":["brandings"],"summary":"Set Branding Sender","operationId":"set_branding_sender_v1_brandings__key__sender_put","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetBrandingSenderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandingResponse"}}}},"404":{"description":"No branding with this key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/tenants/{tenant_id}/data-requests":{"post":{"tags":["auth"],"summary":"Submit Data Request","description":"Ask us to export or delete this tenant's data.\n\n**Owner only.** Deleting a tenant is not an administrative chore, it ends the\nrelationship — so this uses the same gate as rename/archive rather than the\nadmin+ gate that covers member management. An admin can invite people; only\nan owner can ask for the tenant to be erased.","operationId":"submit_data_request_v1_auth_tenants__tenant_id__data_requests_post","parameters":[{"name":"tenant_id","in":"path","required":true,"schema":{"type":"string","title":"Tenant Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataRequestBody"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataRequestResponse"}}}},"403":{"description":"Not a member, or not the owner, of that tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Could not be filed; the caller should email support instead","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sender-domains":{"post":{"tags":["sender-domains"],"summary":"Register Sender Domain","operationId":"register_sender_domain_v1_sender_domains_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterSenderDomainRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SenderDomainResponse"}}}},"409":{"description":"This domain is already registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["sender-domains"],"summary":"List Sender Domains","operationId":"list_sender_domains_v1_sender_domains_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SenderDomainResponse"},"title":"Response List Sender Domains V1 Sender Domains Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sender-domains/{domain}":{"get":{"tags":["sender-domains"],"summary":"Get Sender Domain","operationId":"get_sender_domain_v1_sender_domains__domain__get","parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string","title":"Domain"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SenderDomainResponse"}}}},"404":{"description":"No sender domain with this name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["sender-domains"],"summary":"Delete Sender Domain","operationId":"delete_sender_domain_v1_sender_domains__domain__delete","parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string","title":"Domain"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"No sender domain with this name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sender-domains/{domain}/refresh":{"post":{"tags":["sender-domains"],"summary":"Refresh Sender Domain","operationId":"refresh_sender_domain_v1_sender_domains__domain__refresh_post","parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string","title":"Domain"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SenderDomainResponse"}}}},"404":{"description":"No sender domain with this name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/email-transport":{"get":{"tags":["email-transport"],"summary":"Get Email Transport","operationId":"get_email_transport_v1_email_transport_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTransportResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["email-transport"],"summary":"Put Email Transport","operationId":"put_email_transport_v1_email_transport_put","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTransportRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTransportResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["email-transport"],"summary":"Delete Email Transport","operationId":"delete_email_transport_v1_email_transport_delete","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/email-transport/test":{"post":{"tags":["email-transport"],"summary":"Send Test Email","operationId":"send_test_email_v1_email_transport_test_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendTestEmailRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendTestEmailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/push-transport":{"get":{"tags":["push-transport"],"summary":"List Push Transports","description":"Every transport this tenant has configured. An empty list means they can't\nsend push at all — there is no platform default, so that is a real answer\nrather than a 404.\n\nAn entry with `credentials_unreadable` is configured but unusable (the\nencryption key was rotated). It is listed rather than hidden so the state is\ndistinguishable from \"never set up\", which is what it silently looked like\nbefore — while every send through it kept failing.","operationId":"list_push_transports_v1_push_transport_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PushTransportResponse"},"title":"Response List Push Transports V1 Push Transport Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/push-transport/{provider}":{"put":{"tags":["push-transport"],"summary":"Put Push Transport","description":"Replace one provider's transport. Only that provider's entry is touched.","operationId":"put_push_transport_v1_push_transport__provider__put","parameters":[{"name":"provider","in":"path","required":true,"schema":{"$ref":"#/components/schemas/PushProvider"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushTransportRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushTransportResponse"}}}},"422":{"description":"The provider has no adapter yet, or its settings are malformed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["push-transport"],"summary":"Delete Push Transport","operationId":"delete_push_transport_v1_push_transport__provider__delete","parameters":[{"name":"provider","in":"path","required":true,"schema":{"$ref":"#/components/schemas/PushProvider"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/web-push/public-key":{"get":{"tags":["web-push"],"summary":"Get Web Push Public Key","description":"The VAPID public key a browser needs as `applicationServerKey`.\n\nNo path parameter and no role gate: the tenant comes from the verified token\nlike everywhere else, and a contact token (the subscribing browser) resolves\none just as a tenant token (a backend, or the console) does.\n\n404 when the tenant has no Web Push transport — deliberately not an empty\nstring, which a caller would hand to `subscribe()` and get an opaque browser\nerror instead of \"this account hasn't enabled browser push\".","operationId":"get_web_push_public_key_v1_web_push_public_key_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebPushPublicKeyResponse"}}}},"404":{"description":"This tenant has no Web Push transport configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/push-transport/{provider}/test":{"post":{"tags":["push-transport"],"summary":"Send Test Push","operationId":"send_test_push_v1_push_transport__provider__test_post","parameters":[{"name":"provider","in":"path","required":true,"schema":{"$ref":"#/components/schemas/PushProvider"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendTestPushRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendTestPushResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/email-templates":{"post":{"tags":["email-templates"],"summary":"Create Email Template","operationId":"create_email_template_v1_email_templates_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEmailTemplateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateResponse"}}}},"409":{"description":"A template for this notification type + brand already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The keys are malformed, the notification type isn't live, the branding doesn't exist, or the content is empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["email-templates"],"summary":"List Email Templates","operationId":"list_email_templates_v1_email_templates_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/EmailTemplateResponse"},"title":"Response List Email Templates V1 Email Templates Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/email-templates/{template_id}":{"get":{"tags":["email-templates"],"summary":"Get Email Template","operationId":"get_email_template_v1_email_templates__template_id__get","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateResponse"}}}},"404":{"description":"No email template with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["email-templates"],"summary":"Update Email Template","operationId":"update_email_template_v1_email_templates__template_id__put","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEmailTemplateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateResponse"}}}},"404":{"description":"No email template with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Another template already occupies this notification type + brand + language slot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The new content is empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["email-templates"],"summary":"Delete Email Template","operationId":"delete_email_template_v1_email_templates__template_id__delete","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"No email template with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/email-templates/render":{"post":{"tags":["email-templates"],"summary":"Render Email","description":"Resolve the brand override (else tenant default) and render it with the\nevent variables merged over the brand's values — the 'what would we send'\npreview.","operationId":"render_email_v1_email_templates_render_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderedEmailResponse"}}}},"422":{"description":"The branding doesn't exist, no template resolves, or the event variables + branding values don't cover every slot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/email-templates/affected-by/{notification_type_key}":{"get":{"tags":["email-templates"],"summary":"List Affected Templates","description":"Templates for this notification type that reference variables it no longer\nexposes — the fallout to fix after narrowing/renaming its variables.","operationId":"list_affected_templates_v1_email_templates_affected_by__notification_type_key__get","parameters":[{"name":"notification_type_key","in":"path","required":true,"schema":{"type":"string","title":"Notification Type Key"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AffectedTemplateResponse"},"title":"Response List Affected Templates V1 Email Templates Affected By  Notification Type Key  Get"}}}},"422":{"description":"The notification_type_key is malformed or doesn't refer to a notification type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/inbox-templates":{"post":{"tags":["inbox-templates"],"summary":"Create Inbox Template","operationId":"create_inbox_template_v1_inbox_templates_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInboxTemplateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxTemplateResponse"}}}},"409":{"description":"A template for this notification type + brand already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The keys are malformed, the notification type isn't live, the branding doesn't exist, or the content is empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["inbox-templates"],"summary":"List Inbox Templates","operationId":"list_inbox_templates_v1_inbox_templates_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InboxTemplateResponse"},"title":"Response List Inbox Templates V1 Inbox Templates Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/inbox-templates/{template_id}":{"get":{"tags":["inbox-templates"],"summary":"Get Inbox Template","operationId":"get_inbox_template_v1_inbox_templates__template_id__get","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxTemplateResponse"}}}},"404":{"description":"No inbox template with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["inbox-templates"],"summary":"Update Inbox Template","operationId":"update_inbox_template_v1_inbox_templates__template_id__put","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateInboxTemplateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxTemplateResponse"}}}},"404":{"description":"No inbox template with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Another template already occupies this notification type + brand + language slot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The new content is empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["inbox-templates"],"summary":"Delete Inbox Template","operationId":"delete_inbox_template_v1_inbox_templates__template_id__delete","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"No inbox template with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/push-templates":{"post":{"tags":["push-templates"],"summary":"Create Push Template","operationId":"create_push_template_v1_push_templates_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePushTemplateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushTemplateResponse"}}}},"409":{"description":"A template for this notification type + brand already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The keys are malformed, the notification type isn't live, the branding doesn't exist, or the content is empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["push-templates"],"summary":"List Push Templates","operationId":"list_push_templates_v1_push_templates_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PushTemplateResponse"},"title":"Response List Push Templates V1 Push Templates Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/push-templates/{template_id}":{"get":{"tags":["push-templates"],"summary":"Get Push Template","operationId":"get_push_template_v1_push_templates__template_id__get","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushTemplateResponse"}}}},"404":{"description":"No push template with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["push-templates"],"summary":"Update Push Template","operationId":"update_push_template_v1_push_templates__template_id__put","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePushTemplateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushTemplateResponse"}}}},"404":{"description":"No push template with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Another template already occupies this notification type + brand + language slot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The new content is empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["push-templates"],"summary":"Delete Push Template","operationId":"delete_push_template_v1_push_templates__template_id__delete","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"No push template with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/web-push-templates":{"post":{"tags":["web-push-templates"],"summary":"Create Web Push Template","operationId":"create_web_push_template_v1_web_push_templates_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebPushTemplateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebPushTemplateResponse"}}}},"409":{"description":"A template for this notification type + brand already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The keys are malformed, the notification type isn't live, the branding doesn't exist, or the content is empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["web-push-templates"],"summary":"List Web Push Templates","operationId":"list_web_push_templates_v1_web_push_templates_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebPushTemplateResponse"},"title":"Response List Web Push Templates V1 Web Push Templates Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/web-push-templates/{template_id}":{"get":{"tags":["web-push-templates"],"summary":"Get Web Push Template","operationId":"get_web_push_template_v1_web_push_templates__template_id__get","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebPushTemplateResponse"}}}},"404":{"description":"No web-push template with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["web-push-templates"],"summary":"Update Web Push Template","operationId":"update_web_push_template_v1_web_push_templates__template_id__put","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebPushTemplateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebPushTemplateResponse"}}}},"404":{"description":"No web-push template with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Another template already occupies this notification type + brand + language slot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The new content is empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["web-push-templates"],"summary":"Delete Web Push Template","operationId":"delete_web_push_template_v1_web_push_templates__template_id__delete","parameters":[{"name":"template_id","in":"path","required":true,"schema":{"type":"string","title":"Template Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"No web-push template with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/tokens":{"post":{"tags":["contacts"],"summary":"Issue Contact Token","description":"Mint a short-lived, contact-scoped token for the tenant's backend to hand\nto that contact's browser. Tenant-level only (router gate). The browser then\ncalls the contact-facing endpoints directly, scoped to this contact.","operationId":"issue_contact_token_v1_contacts_tokens_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactTokenRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactTokenResponse"}}}},"404":{"description":"No contact with this external_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts":{"post":{"tags":["contacts"],"summary":"Create Contact","operationId":"create_contact_v1_contacts_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContactRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"409":{"description":"A contact with this external_id already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["contacts"],"summary":"List Contacts","operationId":"list_contacts_v1_contacts_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":25,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Search"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/bulk":{"post":{"tags":["contacts"],"summary":"Import Contacts","description":"Create or update up to 500 contacts in one call, matched by `external_id`.\n\nReturns **200 with a per-row report**, not 201: a batch is rarely all one\nthing, and a status code cannot say \"4,997 created, 3 rejected\". Each row\nreports created, updated or failed with a reason, so a client can show the\nuser exactly which lines of their file need fixing.\n\nA field omitted from a row is left as it was; a field sent empty is cleared.\nThat is what makes an `external_id,email` file safe to import over contacts\nthat already carry phones and attributes.","operationId":"import_contacts_v1_contacts_bulk_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportContactsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportContactsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}":{"get":{"tags":["contacts"],"summary":"Get Contact","operationId":"get_contact_v1_contacts__contact_id__get","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"404":{"description":"No contact with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["contacts"],"summary":"Update Contact","operationId":"update_contact_v1_contacts__contact_id__put","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateContactRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"404":{"description":"No contact with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The contact was modified concurrently; reload and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["contacts"],"summary":"Delete Contact","description":"Delete a contact and erase their personal data everywhere it was copied.\n\nDeleting the `contacts` row is not enough on its own. Every channel freezes a\n*rendered copy* of the message at enqueue time, so that sending needs no\ntemplate or contact access, and the intake outbox keeps the raw trigger\npayload. None of that is reachable by the foreign key cascade, so a plain\ndelete used to leave the recipient's address, device token and message\ncontent behind indefinitely.\n\nDelivery records are kept but blanked: what was sent, when and with what\noutcome survives, while the personal data in it does not. Anything still\nqueued is cancelled so it can never be delivered.","operationId":"delete_contact_v1_contacts__contact_id__delete","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"No contact with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}/emails":{"post":{"tags":["contacts"],"summary":"Add Email","operationId":"add_email_v1_contacts__contact_id__emails_post","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailValueRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"404":{"description":"No contact with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The contact was modified concurrently; reload and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["contacts"],"summary":"Remove Email","operationId":"remove_email_v1_contacts__contact_id__emails_delete","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"value","in":"query","required":true,"schema":{"type":"string","title":"Value"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"404":{"description":"No contact with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The contact was modified concurrently; reload and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}/phones":{"post":{"tags":["contacts"],"summary":"Add Phone","operationId":"add_phone_v1_contacts__contact_id__phones_post","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneValueRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"404":{"description":"No contact with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The contact was modified concurrently; reload and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["contacts"],"summary":"Remove Phone","operationId":"remove_phone_v1_contacts__contact_id__phones_delete","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"value","in":"query","required":true,"schema":{"type":"string","title":"Value"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"404":{"description":"No contact with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The contact was modified concurrently; reload and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}/attributes/{key}":{"put":{"tags":["contacts"],"summary":"Set Attribute","operationId":"set_attribute_v1_contacts__contact_id__attributes__key__put","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttributeValueRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"404":{"description":"No contact with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The contact was modified concurrently; reload and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["contacts"],"summary":"Remove Attribute","operationId":"remove_attribute_v1_contacts__contact_id__attributes__key__delete","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"404":{"description":"No contact with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The contact was modified concurrently; reload and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}/push-subscriptions":{"post":{"tags":["contacts"],"summary":"Add Push Subscription","operationId":"add_push_subscription_v1_contacts__contact_id__push_subscriptions_post","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushSubscriptionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"404":{"description":"No contact with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The contact was modified concurrently; reload and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["contacts"],"summary":"Remove Push Subscription","operationId":"remove_push_subscription_v1_contacts__contact_id__push_subscriptions_delete","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"value","in":"query","required":true,"schema":{"type":"string","title":"Value"}},{"name":"provider","in":"query","required":true,"schema":{"$ref":"#/components/schemas/PushProvider"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"404":{"description":"No contact with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The contact was modified concurrently; reload and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}/preferences":{"put":{"tags":["contacts"],"summary":"Set Preference","operationId":"set_preference_v1_contacts__contact_id__preferences_put","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreferenceDTO"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"404":{"description":"No contact with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The contact was modified concurrently; reload and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["notifications"],"summary":"Get Contact Preferences","description":"The contact's whole preference centre in one read: their saved preferred\n`language` plus the notification-type × channel matrix (`types`) — every type\nthey can act on × the channels it can reach, with the effective on/off and\nwhether it's an explicit override. Powers a client preferences UI without a\nsecond call. Contact token reads its own; a tenant token reads any contact's.\n\nServed here rather than by `contact` because it composes three aggregates\n(contact, notification_type, channel_template) and `contact` is contractually\nindependent of the template module.\n\nNB: the payload is an object `{language, types}`; `types` is the former\ntop-level array (the `@elaanio/*` SDK's `getPreferences` moved in lockstep).","operationId":"get_contact_preferences_v1_contacts__contact_id__preferences_get","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactPreferencesResponse"}}}},"404":{"description":"No contact with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}/language":{"put":{"tags":["contacts"],"summary":"Set Language","description":"Set (or clear, with null) the contact's preferred language — the contact's\nown choice, so it lives on the self-service router (own-contact-or-403).","operationId":"set_language_v1_contacts__contact_id__language_put","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactLanguageRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"404":{"description":"No contact with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The contact was modified concurrently; reload and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}/preferences/{notification_type_key}/{channel}":{"delete":{"tags":["contacts"],"summary":"Clear Preference","operationId":"clear_preference_v1_contacts__contact_id__preferences__notification_type_key___channel__delete","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"notification_type_key","in":"path","required":true,"schema":{"type":"string","title":"Notification Type Key"}},{"name":"channel","in":"path","required":true,"schema":{"type":"string","title":"Channel"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"404":{"description":"No contact with this id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The contact was modified concurrently; reload and retry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/notifications":{"post":{"tags":["notifications"],"summary":"Trigger Notification","description":"The tenant front door: record one triggered event per recipient and accept\n(202). The request does no per-channel work — channel resolution, rendering\nand delivery happen asynchronously in the fan-out worker, so one channel's\n(or one recipient's) failure can't roll back another. Each recipient's\n`event_id` correlates its accepted event for status.\n\nAn archived account can't send: reject up front (403) rather than record\nevents that fan-out would only skip. (A None tenant — a tenant_id that\nauthenticated but has no row — is a data-integrity impossibility, not an\narchived account, so it's deliberately allowed through rather than 403'd.)\n\nThe archived check runs *before* the idempotency replay deliberately: an\narchived account gets the same 403 whether or not it happens to be resending\na token, rather than one answer for a fresh trigger and another for a retry.","operationId":"trigger_notification_v1_notifications_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional dedupe token for this request. Send the same token again and the original event ids come back without sending anything a second time — for callers driven by an at-least-once source (a queue, a webhook, a retried job), where a redelivery is routine. Reusing a token for a *different* payload is a 409. Scoped to your account, at most 255 characters.","title":"Idempotency-Key"},"description":"Optional dedupe token for this request. Send the same token again and the original event ids come back without sending anything a second time — for callers driven by an at-least-once source (a queue, a webhook, a retried job), where a redelivery is routine. Reusing a token for a *different* payload is a 409. Scoped to your account, at most 255 characters."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerNotificationRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationAcceptedResponse"}}}},"409":{"description":"The Idempotency-Key was already used for a request with a different payload (permanent — use a new key), or a concurrent copy of this same request is holding it (transient — retry for the replay)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/notifications/{event_id}":{"get":{"tags":["notifications"],"summary":"Notification Event Status","description":"Delivery status of a previously-triggered event: pending / processed /\nfailed, plus any per-channel error summary and the processing timestamp.","operationId":"notification_event_status_v1_notifications__event_id__get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationEventStatusResponse"}}}},"404":{"description":"No triggered event with this id for this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/stats":{"get":{"tags":["notifications"],"summary":"Delivery Stats","description":"Tenant-wide delivery counts by status across the three outboxes (event\nintake, email, push) — powers the console dashboard's delivery panel.","operationId":"delivery_stats_v1_stats_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryStatsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/deliveries":{"get":{"tags":["notifications"],"summary":"List Deliveries","operationId":"list_deliveries_v1_deliveries_get","parameters":[{"name":"channel","in":"query","required":true,"schema":{"$ref":"#/components/schemas/DeliveryChannel","description":"Which store to read."},"description":"Which store to read."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Repeatable. Values are channel-specific; an unknown one is a 422.","title":"Status"},"description":"Repeatable. Values are channel-specific; an unknown one is a 422."},{"name":"contact_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Id"}},{"name":"external_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Event channel only — the tenant's own recipient reference.","title":"External Id"},"description":"Event channel only — the tenant's own recipient reference."},{"name":"notification_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notification Type"}},{"name":"event_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"All rows produced by one trigger.","title":"Event Id"},"description":"All rows produced by one trigger."},{"name":"created_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created From"}},{"name":"created_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created To"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":25,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/deliveries/{channel}/{delivery_id}":{"get":{"tags":["notifications"],"summary":"Get Delivery","description":"One delivery with the message exactly as it went out — the frozen render,\nnot a re-render from the current template. The body is served only here, never\nin the list, because it is unbounded text.","operationId":"get_delivery_v1_deliveries__channel___delivery_id__get","parameters":[{"name":"channel","in":"path","required":true,"schema":{"$ref":"#/components/schemas/DeliveryChannel"}},{"name":"delivery_id","in":"path","required":true,"schema":{"type":"string","title":"Delivery Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryDetail"}}}},"404":{"description":"No delivery with this id for this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/notifications/{event_id}/deliveries":{"get":{"tags":["notifications"],"summary":"Event Deliveries","description":"Every channel's outcome for one trigger. Unpaged by design — one event\nproduces a handful of rows, so this is a bounded fan-in, not a scan.","operationId":"event_deliveries_v1_notifications__event_id__deliveries_get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventDeliveriesResponse"}}}},"404":{"description":"No event with this id for this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}/notifications":{"get":{"tags":["notifications"],"summary":"List Notifications","operationId":"list_notifications_v1_contacts__contact_id__notifications_get","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"unread","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Unread"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/NotificationResponse"},"title":"Response List Notifications V1 Contacts  Contact Id  Notifications Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}/notifications/unread-count":{"get":{"tags":["notifications"],"summary":"Unread Count","operationId":"unread_count_v1_contacts__contact_id__notifications_unread_count_get","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnreadCountResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}/notifications/read-all":{"post":{"tags":["notifications"],"summary":"Mark All Read","operationId":"mark_all_read_v1_contacts__contact_id__notifications_read_all_post","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}/notifications/{notification_id}/read":{"post":{"tags":["notifications"],"summary":"Mark Read","operationId":"mark_read_v1_contacts__contact_id__notifications__notification_id__read_post","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"notification_id","in":"path","required":true,"schema":{"type":"string","title":"Notification Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationResponse"}}}},"404":{"description":"No notification with this id for this contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}/notifications/{notification_id}/unread":{"post":{"tags":["notifications"],"summary":"Mark Unread","operationId":"mark_unread_v1_contacts__contact_id__notifications__notification_id__unread_post","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"notification_id","in":"path","required":true,"schema":{"type":"string","title":"Notification Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationResponse"}}}},"404":{"description":"No notification with this id for this contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}/notifications/{notification_id}":{"delete":{"tags":["notifications"],"summary":"Delete Notification","operationId":"delete_notification_v1_contacts__contact_id__notifications__notification_id__delete","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"notification_id","in":"path","required":true,"schema":{"type":"string","title":"Notification Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"204":{"description":"Successful Response"},"404":{"description":"No notification with this id for this contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/erasures":{"post":{"tags":["contacts"],"summary":"Erase Recipient","description":"Erase everything held about one recipient, addressed by **your** id for\nthem, whether or not a contact currently exists.\n\n`DELETE /v1/contacts/{id}` covers the ordinary case, but it can only reach\nwhat a live contact points at. A trigger records the recipient by the\n`external_id` you sent, and resolution to a contact happens later, in the\nbackground — so a typo, an integration that addresses people by email while\nyour contacts are keyed by CRM id, a trigger fired before the contact was\ncreated, or a contact deleted before this endpoint existed all leave trigger\npayloads that no contact delete can reach. Those payloads are the rawest data\nwe hold: your `variables`, plus the external id itself, which is frequently\nan email address.\n\nUse this to answer an erasure request when you are not certain a contact\nexists. It is idempotent, safe to call repeatedly, and returns what it\ntouched so you can evidence the request. If a contact does exist it is\ndeleted too, making this a strict superset of the contact delete.","operationId":"erase_recipient_v1_erasures_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErasureRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErasureResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/contacts/{contact_id}/stream":{"get":{"tags":["notifications"],"summary":"Stream Notifications","operationId":"stream_notifications_v1_contacts__contact_id__stream_get","parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/billing":{"get":{"tags":["billing"],"summary":"Get Billing Overview","operationId":"get_billing_overview_v1_billing_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingOverviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/billing/portal":{"post":{"tags":["billing"],"summary":"Create Portal Session","operationId":"create_portal_session_v1_billing_portal_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalResponse"}}}},"409":{"description":"The tenant has no billing account yet (Free / never paid)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/billing/change-plan/preview":{"post":{"tags":["billing"],"summary":"Preview Plan Change","description":"Quote what /change-plan with the same body would do — Paddle's dry-run\nproration preview, run through the exact admissibility rules of the execute\npath so a shown quote can always be confirmed. Commits nothing.","operationId":"preview_plan_change_v1_billing_change_plan_preview_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePlanRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanChangeQuoteResponse"}}}},"409":{"description":"No paid subscription to change, or already on that tier","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unknown plan/period, or no price configured for it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/billing/change-plan":{"post":{"tags":["billing"],"summary":"Change Plan","description":"The paid→paid path (Pro↔Business, monthly↔annual): Paddle swaps the\nexisting subscription's price with immediate proration — never a second\ncheckout. 202 because the local plan flips only when the resulting\n`subscription.updated` webhook lands; the console polls, like checkout.","operationId":"change_plan_v1_billing_change_plan_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePlanRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Change Plan V1 Billing Change Plan Post"}}}},"409":{"description":"No paid subscription to change, or already on that tier","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unknown plan/period, or no price configured for it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/meta":{"get":{"summary":"Meta","description":"Deployment capabilities + build version. Public (like /health): the\nconsole reads it before rendering nav to hide features this profile\ndoesn't offer.\n\n`registration_open` is the one dynamic capability — a single-tenant\ninstall starts open and closes permanently once its tenant is claimed.\nMulti-tenant installs answer it as a constant, without a DB read.\n\n`account_creation_open` mirrors the backend rule that a single-tenant\n(on-prem) install is pinned to one account (POST /auth/tenants is 403\nthere): the console reads it to hide \"Create new account\".\n\n`data_requests` reports whether we operate this deployment on the\ntenant's behalf, and so whether asking us to export or erase their data\nmeans anything. Off on-prem, where they hold the database themselves.","operationId":"meta_v1_meta_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Meta V1 Meta Get"}}}}}}},"/webhooks/paddle":{"post":{"summary":"Paddle Webhook","description":"Verify the signature, normalize the event, and reconcile it onto the\ntenant's subscription. Returns 200 for anything verified (even events we\nignore) so Paddle doesn't redeliver; an unverifiable signature raises → 400.\nThe transactional route commits before responding.\n\nThe raw body goes to the verifier undecoded — the HMAC covers exactly these\nbytes, and a body that isn't UTF-8 is the verifier's 400 to raise, not an\nunhandled decode error on an anonymous endpoint.","operationId":"paddle_webhook_webhooks_paddle_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Paddle Webhook Webhooks Paddle Post"}}}}}}},"/health":{"get":{"summary":"Root","operationId":"root_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"AcceptInvitationRequest":{"properties":{"password":{"type":"string","title":"Password"}},"type":"object","required":["password"],"title":"AcceptInvitationRequest","description":"Accept an invitation as a brand-new user: set the password for the account\nbeing created. (Existing users accept via the authenticated endpoint and send\nno body.)"},"AcceptedEvent":{"properties":{"external_id":{"type":"string","title":"External Id"},"event_id":{"type":"string","title":"Event Id"}},"type":"object","required":["external_id","event_id"],"title":"AcceptedEvent","description":"One recorded event: which recipient it's for and its id (for status)."},"AccessTokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"}},"type":"object","required":["access_token"],"title":"AccessTokenResponse"},"AffectedTemplateResponse":{"properties":{"id":{"type":"string","title":"Id"},"notification_type_key":{"type":"string","title":"Notification Type Key"},"unknown_variables":{"items":{"type":"string"},"type":"array","title":"Unknown Variables"}},"type":"object","required":["id","notification_type_key","unknown_variables"],"title":"AffectedTemplateResponse","description":"A template that uses variables its notification type no longer exposes."},"ApiKeyResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"prefix":{"type":"string","title":"Prefix"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At"}},"type":"object","required":["id","name","prefix","created_at","revoked_at"],"title":"ApiKeyResponse","description":"Listing shape — never includes the secret or its digest."},"AttributeValueRequest":{"properties":{"value":{"type":"string","title":"Value","examples":["Ada"]}},"additionalProperties":false,"type":"object","required":["value"],"title":"AttributeValueRequest"},"BillingOverviewResponse":{"properties":{"plan":{"type":"string","title":"Plan"},"status":{"type":"string","title":"Status"},"billing_period":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Billing Period"},"current_period_end":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Current Period End"},"limits":{"$ref":"#/components/schemas/PlanLimitsResponse"},"has_billing_account":{"type":"boolean","title":"Has Billing Account"},"checkout":{"$ref":"#/components/schemas/CheckoutConfigResponse"}},"type":"object","required":["plan","status","billing_period","current_period_end","limits","has_billing_account","checkout"],"title":"BillingOverviewResponse"},"BillingPeriod":{"type":"string","enum":["monthly","annual"],"title":"BillingPeriod","description":"How often a paid tier renews. Mirrors the pricing page's monthly/annual\ntoggle; each (plan, period) pair maps to one Paddle price id."},"BrandingResponse":{"properties":{"key":{"type":"string","title":"Key"},"values":{"additionalProperties":{"type":"string"},"type":"object","title":"Values"},"is_default":{"type":"boolean","title":"Is Default","description":"The tenant's fallback brand, used when a trigger names no brand or an unknown one. Exactly one per tenant, provisioned at signup, and it cannot be deleted."},"from_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Address"},"from_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Name"}},"type":"object","required":["key","values","is_default","from_address","from_name"],"title":"BrandingResponse"},"ChangeMemberRoleRequest":{"properties":{"role":{"type":"string","title":"Role"}},"type":"object","required":["role"],"title":"ChangeMemberRoleRequest","description":"Change an existing member's role. `role` must be admin/member/viewer —\nowner is rejected (422); ownership isn't assigned via a role change."},"ChangePlanRequest":{"properties":{"plan":{"$ref":"#/components/schemas/Plan"},"period":{"$ref":"#/components/schemas/BillingPeriod"}},"additionalProperties":false,"type":"object","required":["plan","period"],"title":"ChangePlanRequest","description":"Move an existing paid subscription to this tier/period. The fields *are*\nthe domain enums, so an unknown value is a clean 422 listing the permitted\nones — the same way the other modules type their enum fields."},"Channel":{"type":"string","enum":["email","inbox","push","web_push"],"title":"Channel","description":"A delivery channel a notification can be sent over.\n\nThis is an enum (not a bare string) so adding SMS / WHATSAPP later is a\none-line change and every per-channel mapping (notification-type defaults,\ncontact preferences) extends automatically. Crucially, a newly-added channel\nis **opted out by default** everywhere: `NotificationType.default_for` returns\nFalse for a channel absent from `channel_defaults`, and a contact has no\npreference until they set one — so an existing install gains a channel that\ndelivers to nobody until a tenant explicitly enables it. That's why adding a\nmember needs no data migration.\n\nSubclassing `str` makes members compare/serialize as their value\n(\"email\"), which keeps JSON and dict keys clean."},"ChannelPreferenceResponse":{"properties":{"channel":{"$ref":"#/components/schemas/Channel"},"enabled":{"type":"boolean","title":"Enabled"},"overridden":{"type":"boolean","title":"Overridden"}},"type":"object","required":["channel","enabled","overridden"],"title":"ChannelPreferenceResponse"},"CheckoutConfigResponse":{"properties":{"client_token":{"type":"string","title":"Client Token"},"environment":{"type":"string","title":"Environment"},"tenant_ref":{"type":"string","title":"Tenant Ref"},"prices":{"additionalProperties":{"type":"string"},"type":"object","title":"Prices"}},"type":"object","required":["client_token","environment","tenant_ref","prices"],"title":"CheckoutConfigResponse","description":"Everything the console needs to open the Paddle.js overlay checkout:\nthe client-side token (public by design — it can only open checkouts), the\nenvironment flag, the tenant reference to stamp into `customData`, and the\nprice id per self-serve tier (`\"pro:monthly\"` → `pri_…`).\n\n`tenant_ref` is *signed* rather than a raw tenant id: `customData` is set in\nthe browser, so an unsigned id would be a claim anyone could make about any\ntenant. Minted here because this endpoint is already authenticated and\ntenant-scoped — a caller can only ever get a reference to their own tenant."},"ContactImportRowRequest":{"properties":{"external_id":{"type":"string","title":"External Id","examples":["crm-12345"]},"emails":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Emails","examples":[["a@example.com"]]},"phones":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Phones","examples":[["+15551234567"]]},"attributes":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Attributes","examples":[{"first_name":"Ada","tier":"gold"}]},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","examples":["es"]}},"additionalProperties":false,"type":"object","required":["external_id"],"title":"ContactImportRowRequest","description":"One row of an import.\n\nEvery value stays a **raw string** here on purpose. Validating addresses in\nthe DTO would make one malformed row a 422 for the whole file, which is\nexactly the behaviour bulk import exists to avoid; the row is converted to\nvalue objects one at a time instead, and a failure is reported against that\nrow while the rest import.\n\nA field that is absent is left untouched on an existing contact; a field\npresent but empty clears it. `external_id` is required because it is the key\nthe upsert matches on."},"ContactLanguageRequest":{"properties":{"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","examples":["es"]}},"additionalProperties":false,"type":"object","title":"ContactLanguageRequest","description":"Set (or clear) a contact's preferred language. `null` clears it, reverting\nto the default template."},"ContactPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ContactResponse"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["items","total","limit","offset"],"title":"ContactPage","description":"A page of contacts plus the total matching the filter, for pagination."},"ContactPreferencesResponse":{"properties":{"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"types":{"items":{"$ref":"#/components/schemas/TypePreferenceResponse"},"type":"array","title":"Types"}},"type":"object","required":["language","types"],"title":"ContactPreferencesResponse","description":"The whole contact preference centre in one payload: the saved preferred\n`language` (null = default) plus the notification `types` matrix. A single\ncontact-token read renders everything the contact previously chose."},"ContactResponse":{"properties":{"id":{"type":"string","title":"Id"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"The contact's preferred language tag (e.g. \"es\"), or null for the language-less default. Set at create or via PUT /v1/contacts/{id}/language — deliberately NOT part of the whole-resource PUT, so a partial-shaped update can't silently clear it."},"emails":{"items":{"type":"string"},"type":"array","title":"Emails"},"phones":{"items":{"type":"string"},"type":"array","title":"Phones"},"push_subscriptions":{"items":{"$ref":"#/components/schemas/PushSubscriptionDTO"},"type":"array","title":"Push Subscriptions"},"attributes":{"additionalProperties":{"type":"string"},"type":"object","title":"Attributes"},"preferences":{"items":{"$ref":"#/components/schemas/PreferenceDTO"},"type":"array","title":"Preferences"}},"type":"object","required":["id","external_id","language","emails","phones","push_subscriptions","attributes","preferences"],"title":"ContactResponse"},"ContactTokenRequest":{"properties":{"external_id":{"type":"string","title":"External Id","examples":["crm-12345"]}},"additionalProperties":false,"type":"object","required":["external_id"],"title":"ContactTokenRequest","description":"A tenant backend asking for a short-lived token scoped to one of its\ncontacts (by the tenant's own `external_id`), to hand to that contact's\nbrowser."},"ContactTokenResponse":{"properties":{"token":{"type":"string","title":"Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"},"expires_in":{"type":"integer","title":"Expires In"},"contact_id":{"type":"string","title":"Contact Id"}},"type":"object","required":["token","expires_in","contact_id"],"title":"ContactTokenResponse","description":"A contact-scoped bearer token. The frontend uses it to call the\ncontact-facing endpoints (inbox, push tokens, preferences, the SSE stream) for\n*this* contact only; re-mint from the tenant's session when it expires.\n\n`contact_id` is the contact's *internal* id — the one the contact-facing URLs\ntake (e.g. `/v1/contacts/{contact_id}/stream`). The backend passes the tenant's\nown `external_id` in and gets both the token and the internal id back, so it\ncan hand the browser everything it needs without tracking internal ids itself."},"CreateBrandingRequest":{"properties":{"key":{"type":"string","title":"Key","examples":["acme"]},"values":{"additionalProperties":{"type":"string"},"type":"object","title":"Values","examples":[{"color":"#0a0","logo":"https://cdn.acme.test/logo.png"}]},"from_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Address","examples":["hello@acme.com"]},"from_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Name","examples":["Acme"]}},"additionalProperties":false,"type":"object","required":["key"],"title":"CreateBrandingRequest"},"CreateContactRequest":{"properties":{"emails":{"items":{"type":"string"},"type":"array","title":"Emails","examples":[["a@example.com"]]},"phones":{"items":{"type":"string"},"type":"array","title":"Phones","examples":[["+15551234567"]]},"push_subscriptions":{"items":{"$ref":"#/components/schemas/PushSubscriptionDTO"},"type":"array","title":"Push Subscriptions"},"attributes":{"additionalProperties":{"type":"string"},"type":"object","title":"Attributes","examples":[{"first_name":"Ada"}]},"preferences":{"items":{"$ref":"#/components/schemas/PreferenceDTO"},"type":"array","title":"Preferences"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id","examples":["crm-12345"]},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","examples":["es"]}},"additionalProperties":false,"type":"object","title":"CreateContactRequest","description":"Initial state of a new contact: an optional `external_id` plus the same\nmutable collections as a full update (all optional, defaulting to empty), so a\ncontact can be created fully populated in one call or bare and filled in via\nthe sub-resource endpoints. `language` is settable at create; afterwards it's\nchanged only via its own endpoint (it's deliberately *not* in the whole-resource\nupdate, so a partial-shaped PUT can't silently clear it)."},"CreateEmailTemplateRequest":{"properties":{"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","examples":["es"]},"notification_type_key":{"type":"string","title":"Notification Type Key","examples":["order_shipped"]},"branding_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branding Key","examples":["acme"]},"subject":{"type":"string","title":"Subject","examples":["Your order has shipped"]},"body":{"type":"string","title":"Body","examples":["Hi {{ contact.first_name }}, your order is on its way."]}},"additionalProperties":false,"type":"object","required":["notification_type_key","subject","body"],"title":"CreateEmailTemplateRequest"},"CreateInboxTemplateRequest":{"properties":{"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","examples":["es"]},"notification_type_key":{"type":"string","title":"Notification Type Key","examples":["order_shipped"]},"branding_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branding Key","examples":["acme"]},"title":{"type":"string","title":"Title","examples":["Your order shipped"]},"body":{"type":"string","title":"Body","examples":["Hi {{ contact.first_name }}, your order is on its way."]}},"additionalProperties":false,"type":"object","required":["notification_type_key","title","body"],"title":"CreateInboxTemplateRequest"},"CreateNotificationTypeRequest":{"properties":{"key":{"type":"string","title":"Key","examples":["order_shipped"]},"channel_defaults":{"additionalProperties":{"type":"boolean"},"propertyNames":{"$ref":"#/components/schemas/Channel"},"type":"object","title":"Channel Defaults","examples":[{"email":true}]},"variables":{"items":{"type":"string"},"type":"array","uniqueItems":true,"title":"Variables","examples":[["first_name","order_id"]]},"allows_opt_out":{"type":"boolean","title":"Allows Opt Out","description":"Whether contacts may override this type's channel defaults. Set false for transactional notifications a recipient must not be able to switch off (email verification, password reset). This does NOT force delivery on every channel — channel_defaults still decides which channels fire; only the contact's veto is removed.","default":true,"examples":[true]}},"additionalProperties":false,"type":"object","required":["key"],"title":"CreateNotificationTypeRequest"},"CreatePushTemplateRequest":{"properties":{"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","examples":["es"]},"notification_type_key":{"type":"string","title":"Notification Type Key","examples":["order_shipped"]},"branding_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branding Key","examples":["acme"]},"title":{"type":"string","title":"Title","default":"","examples":["Your order shipped"]},"body":{"type":"string","title":"Body","default":"","examples":["It's on its way."]},"data":{"additionalProperties":{"type":"string"},"type":"object","title":"Data","examples":[{"deep_link":"/orders/{{ order_id }}"}]}},"additionalProperties":false,"type":"object","required":["notification_type_key"],"title":"CreatePushTemplateRequest"},"CreateTenantRequest":{"properties":{"name":{"type":"string","title":"Name"}},"type":"object","required":["name"],"title":"CreateTenantRequest","description":"Create a new account (tenant) owned by the signed-in user. `name` is the\ndisplay label; blank/whitespace-only is a clean 422 (mirrors the Tenant rule)."},"CreateWebPushTemplateRequest":{"properties":{"title":{"type":"string","title":"Title","examples":["Your order shipped"]},"body":{"type":"string","title":"Body","default":"","examples":["It's on its way."]},"icon":{"type":"string","title":"Icon","default":"","examples":["{{ brand.icon_url }}"]},"badge":{"type":"string","title":"Badge","default":"","examples":["https://example.com/badge.png"]},"url":{"type":"string","title":"Url","default":"","examples":["https://example.com/orders/{{ order_id }}"]},"data":{"additionalProperties":{"type":"string"},"type":"object","title":"Data","examples":[{"order_id":"{{ order_id }}"}]},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","examples":["es"]},"notification_type_key":{"type":"string","title":"Notification Type Key","examples":["order_shipped"]},"branding_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branding Key","examples":["acme"]}},"additionalProperties":false,"type":"object","required":["title","notification_type_key"],"title":"CreateWebPushTemplateRequest"},"DataRequestBody":{"properties":{"kind":{"type":"string","enum":["export","deletion"],"title":"Kind","description":"What is being asked for. `export` requests a copy of everything in the account; `deletion` requests that the account and its data be erased. Both are actioned by a human, so neither takes effect immediately and submitting one changes nothing about the account."}},"type":"object","required":["kind"],"title":"DataRequestBody"},"DataRequestResponse":{"properties":{"kind":{"type":"string","enum":["export","deletion"],"title":"Kind"},"contact_email":{"type":"string","title":"Contact Email","description":"Where to follow up, and where to write if this ever fails."}},"type":"object","required":["kind","contact_email"],"title":"DataRequestResponse","description":"No id, because nothing is stored to have one. The console shows the\nacknowledgement and the address to chase, which is all a caller can act on."},"DeliveryChannel":{"type":"string","enum":["event","email","push","inbox","web_push"],"title":"DeliveryChannel","description":"Which store to read. `EVENT` is the trigger intake itself — not a delivery\nchannel, but the same question (\"what did we send?\") one level up, and the\nparent of the other three."},"DeliveryDetail":{"properties":{"item":{"$ref":"#/components/schemas/DeliveryItem"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body"},"body_retained":{"type":"boolean","title":"Body Retained","default":true},"data":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Data"},"variables":{"anyOf":[{"additionalProperties":{"$ref":"#/components/schemas/JsonValue"},"type":"object"},{"type":"null"}],"title":"Variables"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["item"],"title":"DeliveryDetail","description":"A single delivery with its frozen content. `body` is served only here, never\nin the list — it is unbounded text and 50 of them per page would be brutal.\n\n`body_retained` distinguishes \"we still hold the rendered message\" from \"we\nno longer do\". It was added ahead of need, so that offloading bodies (to S3\non the SaaS) or dropping them after a retention window (on-prem) would be an\nadditive change rather than a breaking one. **Contact erasure is the first\nthing that actually makes it false**: erasing a contact blanks the rendered\ncontent of everything already sent to them, and a `body` of `null` has to be\ndistinguishable from a message that was genuinely empty."},"DeliveryItem":{"properties":{"id":{"type":"string","title":"Id"},"channel":{"$ref":"#/components/schemas/DeliveryChannel"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"notification_type_key":{"type":"string","title":"Notification Type Key"},"notification_event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notification Event Id"},"contact_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Id"},"recipient":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"attempts":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Attempts"},"next_attempt_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Attempt At"},"sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sent At"},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"redacted":{"type":"boolean","title":"Redacted","default":false}},"type":"object","required":["id","channel","status","created_at","notification_type_key"],"title":"DeliveryItem"},"DeliveryPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/DeliveryItem"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["items","total","limit","offset"],"title":"DeliveryPage"},"DeliveryStatsResponse":{"properties":{"events":{"additionalProperties":{"type":"integer"},"type":"object","title":"Events","examples":[{"pending":1,"processed":12}]},"emails":{"additionalProperties":{"type":"integer"},"type":"object","title":"Emails","examples":[{"failed":1,"sent":9}]},"pushes":{"additionalProperties":{"type":"integer"},"type":"object","title":"Pushes","description":"Mobile push only (Expo/FCM/APNs/OneSignal). Browser sends are counted under `web_pushes` — they share a store but are separate channels, and one number over both hides which of the two is failing.","examples":[{"delivered":5}]},"web_pushes":{"additionalProperties":{"type":"integer"},"type":"object","title":"Web Pushes","description":"Browser push (Web Push/VAPID), the `web_push` channel.","examples":[{"delivered":2}]},"emails_retrying":{"type":"integer","title":"Emails Retrying","default":0,"examples":[3]},"pushes_retrying":{"type":"integer","title":"Pushes Retrying","default":0,"examples":[0]},"web_pushes_retrying":{"type":"integer","title":"Web Pushes Retrying","default":0,"examples":[0]}},"type":"object","required":["events","emails","pushes","web_pushes"],"title":"DeliveryStatsResponse","description":"Tenant delivery counts by status across the outboxes, for the console\ndashboard. Each map is status-string → count; statuses with no rows are\nomitted."},"DnsRecord":{"properties":{"type":{"type":"string","title":"Type"},"name":{"type":"string","title":"Name"},"value":{"type":"string","title":"Value"},"priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Priority"}},"type":"object","required":["type","name","value"],"title":"DnsRecord"},"EmailTemplateResponse":{"properties":{"id":{"type":"string","title":"Id"},"notification_type_key":{"type":"string","title":"Notification Type Key"},"branding_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branding Key"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"variables":{"items":{"type":"string"},"type":"array","title":"Variables"},"subject":{"type":"string","title":"Subject"},"body":{"type":"string","title":"Body"}},"type":"object","required":["id","notification_type_key","branding_key","language","variables","subject","body"],"title":"EmailTemplateResponse"},"EmailTransportRequest":{"properties":{"provider":{"type":"string","enum":["smtp","ses","sendgrid","mailgun"],"title":"Provider"},"from_address":{"type":"string","title":"From Address","examples":["orders@acme.com"]},"from_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Name","examples":["Acme"]},"smtp":{"anyOf":[{"$ref":"#/components/schemas/SmtpTransportInput"},{"type":"null"}]},"ses":{"anyOf":[{"$ref":"#/components/schemas/SesTransportInput"},{"type":"null"}]},"sendgrid":{"anyOf":[{"$ref":"#/components/schemas/SendGridTransportInput"},{"type":"null"}]},"mailgun":{"anyOf":[{"$ref":"#/components/schemas/MailgunTransportInput"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["provider","from_address"],"title":"EmailTransportRequest"},"EmailTransportResponse":{"properties":{"provider":{"anyOf":[{"type":"string","const":"platform"},{"$ref":"#/components/schemas/TransportProvider"}],"title":"Provider"},"from_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Address"},"from_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Name"},"smtp":{"anyOf":[{"$ref":"#/components/schemas/SmtpTransportView"},{"type":"null"}]},"ses":{"anyOf":[{"$ref":"#/components/schemas/SesTransportView"},{"type":"null"}]},"sendgrid":{"anyOf":[{"$ref":"#/components/schemas/SendGridTransportView"},{"type":"null"}]},"mailgun":{"anyOf":[{"$ref":"#/components/schemas/MailgunTransportView"},{"type":"null"}]},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["provider","from_address","from_name","updated_at"],"title":"EmailTransportResponse","description":"`provider: \"platform\"` (all sections null) is the no-config state — a\nreal answer, so GET always returns 200 rather than 404ing the default."},"EmailValueRequest":{"properties":{"value":{"type":"string","title":"Value","examples":["a@example.com"]}},"additionalProperties":false,"type":"object","required":["value"],"title":"EmailValueRequest"},"ErasureRequest":{"properties":{"external_id":{"type":"string","minLength":1,"title":"External Id","description":"Your identifier for the person, the same value you pass to `POST /v1/notifications`. Used rather than our contact id because the data this reaches may exist when no contact does."}},"type":"object","required":["external_id"],"title":"ErasureRequest","description":"Which recipient to erase, named the tenant's own way."},"ErasureResponse":{"properties":{"external_id":{"type":"string","title":"External Id"},"contact_deleted":{"type":"boolean","title":"Contact Deleted","description":"Whether a contact existed for this external id and was deleted."},"rows_redacted":{"type":"integer","title":"Rows Redacted","description":"Delivery records whose personal data was blanked, across all channels."},"rows_cancelled":{"type":"integer","title":"Rows Cancelled","description":"Of those, how many were still queued and have been stopped, so they can never be delivered. Always less than or equal to rows_redacted."}},"type":"object","required":["external_id","contact_deleted","rows_redacted","rows_cancelled"],"title":"ErasureResponse","description":"What an erasure touched — returned so the caller can evidence the request."},"ErrorResponse":{"properties":{"detail":{"type":"string","title":"Detail"}},"type":"object","required":["detail"],"title":"ErrorResponse","description":"The body shape every error handler returns."},"EventDeliveriesResponse":{"properties":{"event":{"$ref":"#/components/schemas/DeliveryItem"},"inbox":{"items":{"$ref":"#/components/schemas/DeliveryItem"},"type":"array","title":"Inbox"},"emails":{"items":{"$ref":"#/components/schemas/DeliveryItem"},"type":"array","title":"Emails"},"pushes":{"items":{"$ref":"#/components/schemas/DeliveryItem"},"type":"array","title":"Pushes"}},"type":"object","required":["event","inbox","emails","pushes"],"title":"EventDeliveriesResponse","description":"Every channel's outcome for one trigger — the cross-channel drill-down."},"EventStatus":{"type":"string","enum":["pending","processed","failed"],"title":"EventStatus","description":"Lifecycle of a recorded event. `PENDING` until the fan-out worker picks it\nup; `PROCESSED` once fan-out finished (possibly with per-channel errors noted\nin `last_error`); `FAILED` when the event couldn't be processed at all (the\nrecipient / type didn't resolve, so no channel could be attempted). An unknown\nbranding is *not* fatal — fan-out falls back to the tenant default."},"ExpoTransportInput":{"properties":{"access_token":{"type":"string","title":"Access Token","examples":["ExpoAccessToken-xxxx"]}},"additionalProperties":false,"type":"object","required":["access_token"],"title":"ExpoTransportInput"},"ExpoTransportView":{"properties":{"has_access_token":{"type":"boolean","title":"Has Access Token"}},"type":"object","required":["has_access_token"],"title":"ExpoTransportView"},"FcmTransportInput":{"properties":{"service_account_json":{"type":"string","title":"Service Account Json","examples":["{\"type\":\"service_account\", ...}"]}},"additionalProperties":false,"type":"object","required":["service_account_json"],"title":"FcmTransportInput"},"FcmTransportView":{"properties":{"project_id":{"type":"string","title":"Project Id"},"client_email":{"type":"string","title":"Client Email"},"has_private_key":{"type":"boolean","title":"Has Private Key"}},"type":"object","required":["project_id","client_email","has_private_key"],"title":"FcmTransportView"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ImportContactsRequest":{"properties":{"contacts":{"items":{"$ref":"#/components/schemas/ContactImportRowRequest"},"type":"array","maxItems":500,"minItems":1,"title":"Contacts","description":"Up to 500 rows. Each is matched to an existing contact by `external_id` and updated, or created if there is none. A field you omit is left as it was; a field you send empty is cleared."}},"additionalProperties":false,"type":"object","required":["contacts"],"title":"ImportContactsRequest","description":"A batch of rows to create or update, keyed by `external_id`."},"ImportContactsResponse":{"properties":{"created":{"type":"integer","title":"Created"},"updated":{"type":"integer","title":"Updated"},"failed":{"type":"integer","title":"Failed"},"rows":{"items":{"$ref":"#/components/schemas/ImportRowResultResponse"},"type":"array","title":"Rows"}},"additionalProperties":false,"type":"object","required":["created","updated","failed","rows"],"title":"ImportContactsResponse","description":"Per-row outcomes for the batch.\n\nA row-level problem (a malformed address, a blank id, the same\n`external_id` twice) is reported here while every other row still imports.\n\nA malformed request *shape* is not: a value of the wrong JSON type, or an\nunexpected key, is rejected before any row is read and returns 422 for the\nwhole batch. What is checked per row is the content of the strings."},"ImportRowResultResponse":{"properties":{"index":{"type":"integer","title":"Index","description":"The row's position in the submitted `contacts` array, so a client can point at the line of the file the user uploaded."},"external_id":{"type":"string","title":"External Id"},"outcome":{"type":"string","enum":["created","updated","failed"],"title":"Outcome"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why the row failed. Null unless outcome is failed."}},"additionalProperties":false,"type":"object","required":["index","external_id","outcome"],"title":"ImportRowResultResponse","description":"What happened to one row, and where it was."},"InboxTemplateResponse":{"properties":{"id":{"type":"string","title":"Id"},"notification_type_key":{"type":"string","title":"Notification Type Key"},"branding_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branding Key"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"variables":{"items":{"type":"string"},"type":"array","title":"Variables"},"title":{"type":"string","title":"Title"},"body":{"type":"string","title":"Body"}},"type":"object","required":["id","notification_type_key","branding_key","language","variables","title","body"],"title":"InboxTemplateResponse"},"InvitationPreviewResponse":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id"},"email":{"type":"string","title":"Email"},"tenant_name":{"type":"string","title":"Tenant Name"},"role":{"type":"string","title":"Role"},"expired":{"type":"boolean","title":"Expired"},"user_exists":{"type":"boolean","title":"User Exists"}},"type":"object","required":["tenant_id","email","tenant_name","role","expired","user_exists"],"title":"InvitationPreviewResponse","description":"What the accept page shows before acceptance: which account + role, and\nwhether the invited email already has a user (so the console knows to ask for\na password vs. prompt sign-in). Served for a valid pending token only.\n\n`tenant_id` is included so the console can label the account it auto-logs the\ninvitee into after acceptance (the invitee is joining it — not sensitive)."},"InvitationResponse":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"role":{"type":"string","title":"Role"},"status":{"type":"string","title":"Status"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","email","role","status","expires_at","created_at"],"title":"InvitationResponse","description":"A pending invitation, for the account's members/invites screen."},"InviteMemberRequest":{"properties":{"email":{"type":"string","title":"Email"},"role":{"type":"string","title":"Role"}},"type":"object","required":["email","role"],"title":"InviteMemberRequest","description":"Invite someone to the account with a role. `role` must be\nadmin/member/viewer — owner is rejected (422)."},"IssueApiKeyRequest":{"properties":{"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"}},"additionalProperties":false,"type":"object","required":["name"],"title":"IssueApiKeyRequest"},"IssuedApiKeyResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"prefix":{"type":"string","title":"Prefix"},"api_key":{"type":"string","title":"Api Key"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","name","prefix","api_key","created_at"],"title":"IssuedApiKeyResponse","description":"Returned ONCE, at creation. `api_key` is the plaintext secret — it is never\nretrievable again, so the tenant must store it now."},"JsonValue":{},"LoginRequest":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","maxLength":1024,"title":"Password"}},"type":"object","required":["email","password"],"title":"LoginRequest"},"MailgunRegion":{"type":"string","enum":["us","eu"],"title":"MailgunRegion","description":"Mailgun hosts accounts in one of two regions with different API base URLs.\nStored as the region name; the URL mapping is the sending adapter's concern."},"MailgunTransportInput":{"properties":{"api_key":{"type":"string","title":"Api Key"},"domain":{"type":"string","title":"Domain","examples":["mg.acme.com"]},"region":{"type":"string","enum":["us","eu"],"title":"Region","default":"us"}},"additionalProperties":false,"type":"object","required":["api_key","domain"],"title":"MailgunTransportInput"},"MailgunTransportView":{"properties":{"domain":{"type":"string","title":"Domain"},"region":{"$ref":"#/components/schemas/MailgunRegion"},"has_api_key":{"type":"boolean","title":"Has Api Key"}},"type":"object","required":["domain","region","has_api_key"],"title":"MailgunTransportView"},"MemberResponse":{"properties":{"user_id":{"type":"string","title":"User Id"},"email":{"type":"string","title":"Email"},"role":{"type":"string","title":"Role"},"is_default":{"type":"boolean","title":"Is Default"}},"type":"object","required":["user_id","email","role","is_default"],"title":"MemberResponse","description":"One current member of an account (email + role)."},"NotificationAcceptedResponse":{"properties":{"events":{"items":{"$ref":"#/components/schemas/AcceptedEvent"},"type":"array","title":"Events"}},"type":"object","required":["events"],"title":"NotificationAcceptedResponse","description":"202 response: one event was recorded per recipient and will be delivered\nasynchronously. `events` maps each recipient to its event id so the tenant\ncan correlate and query per-recipient delivery status."},"NotificationEventStatusResponse":{"properties":{"event_id":{"type":"string","title":"Event Id"},"status":{"$ref":"#/components/schemas/EventStatus"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"processed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Processed At"},"redacted":{"type":"boolean","title":"Redacted","description":"True once the recipient was erased and this event's personal data was removed. `error` and `notes` are then always null, because both quote the recipient — without this flag an integration polling a failed event would see the reason vanish and read it as a bug. A `failed` status on a redacted event may mean the trigger was cancelled by the erasure rather than that delivery failed.","default":false}},"type":"object","required":["event_id","status","error","notes","created_at","processed_at"],"title":"NotificationEventStatusResponse","description":"Delivery status of a triggered event. `status` is pending/processed/failed;\n`error` is the failure reason (whole-event) or a per-channel failure summary on\nan otherwise-processed event (None when every enabled channel succeeded).\n`notes` is non-failure diagnostics (e.g. the named brand didn't resolve, so the\ndefault was used) — present even on a fully-successful delivery."},"NotificationResponse":{"properties":{"id":{"type":"string","title":"Id"},"notification_type_key":{"type":"string","title":"Notification Type Key"},"title":{"type":"string","title":"Title"},"body":{"type":"string","title":"Body"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"read_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Read At"},"is_read":{"type":"boolean","title":"Is Read"}},"type":"object","required":["id","notification_type_key","title","body","created_at","read_at","is_read"],"title":"NotificationResponse"},"NotificationTypeResponse":{"properties":{"key":{"type":"string","title":"Key"},"channel_defaults":{"additionalProperties":{"type":"boolean"},"propertyNames":{"$ref":"#/components/schemas/Channel"},"type":"object","title":"Channel Defaults"},"variables":{"items":{"type":"string"},"type":"array","title":"Variables"},"allows_opt_out":{"type":"boolean","title":"Allows Opt Out"},"is_deleted":{"type":"boolean","title":"Is Deleted"}},"type":"object","required":["key","channel_defaults","variables","allows_opt_out","is_deleted"],"title":"NotificationTypeResponse"},"PasswordResetTokenStatusResponse":{"properties":{"valid":{"type":"boolean","title":"Valid"}},"type":"object","required":["valid"],"title":"PasswordResetTokenStatusResponse","description":"Whether a reset link is still usable, so the console can say so before the\nuser types a new password. Deliberately carries nothing about the account: an\nunknown token and an expired one look identical."},"PhoneValueRequest":{"properties":{"value":{"type":"string","title":"Value","examples":["+15551234567"]}},"additionalProperties":false,"type":"object","required":["value"],"title":"PhoneValueRequest"},"Plan":{"type":"string","enum":["free","pro","business","scale"],"title":"Plan","description":"A subscription tier. The natural identity of a plan is its slug; the price\nlives in Paddle (per price id), and the *limits* live in this module's\n`PLAN_LIMITS` so a pricing change is a one-line edit, not a data migration."},"PlanChangeDueNow":{"properties":{"action":{"type":"string","title":"Action"},"amount":{"type":"string","title":"Amount"},"currency_code":{"type":"string","title":"Currency Code"}},"type":"object","required":["action","amount","currency_code"],"title":"PlanChangeDueNow","description":"What confirming would charge (or credit) immediately. `action` is\nPaddle's vocabulary (`charge`/`credit`); `amount` is minor units."},"PlanChangeNextBill":{"properties":{"amount":{"type":"string","title":"Amount"},"currency_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency Code"},"billed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Billed At"}},"type":"object","required":["amount","currency_code","billed_at"],"title":"PlanChangeNextBill"},"PlanChangeQuoteResponse":{"properties":{"current":{"$ref":"#/components/schemas/TierResponse"},"current_period_end":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Current Period End"},"target":{"$ref":"#/components/schemas/TierResponse"},"due_now":{"$ref":"#/components/schemas/PlanChangeDueNow"},"next_bill":{"anyOf":[{"$ref":"#/components/schemas/PlanChangeNextBill"},{"type":"null"}]}},"type":"object","required":["current","current_period_end","target","due_now","next_bill"],"title":"PlanChangeQuoteResponse","description":"The confirmation dialog's content: current tier, target tier, the\nimmediate proration, and (when Paddle supplied it) the next bill."},"PlanLimitsResponse":{"properties":{"included_active_contacts":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Included Active Contacts"},"max_brands":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Brands"}},"type":"object","required":["included_active_contacts","max_brands"],"title":"PlanLimitsResponse"},"Platform":{"type":"string","enum":["ios","android","web"],"title":"Platform","description":"The device platform a subscription targets. Optional metadata — used for\npayload shaping once the push channel lands (APNs, Android and Web bodies\ndiffer), never for routing (that is `PushProvider`'s job)."},"PortalResponse":{"properties":{"portal_url":{"type":"string","title":"Portal Url"}},"type":"object","required":["portal_url"],"title":"PortalResponse"},"PreferenceDTO":{"properties":{"notification_type_key":{"type":"string","title":"Notification Type Key","examples":["order_shipped"]},"channel":{"$ref":"#/components/schemas/Channel","examples":["email"]},"enabled":{"type":"boolean","title":"Enabled"}},"additionalProperties":false,"type":"object","required":["notification_type_key","channel","enabled"],"title":"PreferenceDTO","description":"One preference slot: a notification type on a channel, opted in or out."},"PushProvider":{"type":"string","enum":["fcm","apns","expo","onesignal","webpush"],"title":"PushProvider","description":"The service a push subscription is delivered *through* — the routing key\nthat ties a contact's `PushSubscription` to the tenant's configured\n`PushTransportConfig` and selects which sender adapter delivers it.\n\nCross-cutting vocabulary (like `Channel`): it lives in `shared` because both\n`contact` (the destination side) and `push_transport` / `outbound_push` (the\nsending side) reference the same members, and they must not drift apart.\n\nCovers direct transports (FCM / APNs / Web Push) and aggregators that fan out\nto the native platforms themselves (Expo, OneSignal). Subclassing `str` keeps\nJSON and dict keys clean."},"PushSubscriptionDTO":{"properties":{"value":{"type":"string","title":"Value","examples":["fcm-token-abc"]},"provider":{"$ref":"#/components/schemas/PushProvider","examples":["fcm"]},"platform":{"anyOf":[{"$ref":"#/components/schemas/Platform"},{"type":"null"}],"examples":["android"]},"auth":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Auth","examples":["kQm...=="]},"p256dh":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"P256Dh","examples":["BN1...=="]}},"additionalProperties":false,"type":"object","required":["value","provider"],"title":"PushSubscriptionDTO","description":"One push destination: the token/subscription id, the provider it routes\nthrough (required — see PushProvider), and an optional device platform. The\n`provider`/`platform` enums validate membership automatically (bad value ->\n422).\n\nFor `provider: \"webpush\"`, `value` is the browser subscription's `endpoint`\nand `auth`/`p256dh` are its two keys — i.e. the browser's own\n`PushSubscription.toJSON()` flattened: `{endpoint, keys: {auth, p256dh}}`\nbecomes `{value, auth, p256dh}`. Flattened rather than nested because this\nDTO serves every provider and a `keys` object would be dead weight on the\nfour that address a plain token."},"PushSubscriptionRequest":{"properties":{"value":{"type":"string","title":"Value","examples":["fcm-token-abc"]},"provider":{"$ref":"#/components/schemas/PushProvider","examples":["fcm"]},"platform":{"anyOf":[{"$ref":"#/components/schemas/Platform"},{"type":"null"}],"examples":["android"]},"auth":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Auth","examples":["kQm...=="]},"p256dh":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"P256Dh","examples":["BN1...=="]}},"additionalProperties":false,"type":"object","required":["value","provider"],"title":"PushSubscriptionRequest","description":"Add-a-single-subscription body: same shape as one item of the collection\n(value + provider + optional platform, plus the Web Push keys).\n\nInput-only, so this is where the registration rule belongs — the base class\nit extends is also the response shape and must stay permissive enough to\nrender whatever is stored."},"PushTemplateResponse":{"properties":{"id":{"type":"string","title":"Id"},"notification_type_key":{"type":"string","title":"Notification Type Key"},"branding_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branding Key"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"variables":{"items":{"type":"string"},"type":"array","title":"Variables"},"title":{"type":"string","title":"Title"},"body":{"type":"string","title":"Body"},"data":{"additionalProperties":{"type":"string"},"type":"object","title":"Data"}},"type":"object","required":["id","notification_type_key","branding_key","language","variables","title","body","data"],"title":"PushTemplateResponse"},"PushTransportRequest":{"properties":{"expo":{"anyOf":[{"$ref":"#/components/schemas/ExpoTransportInput"},{"type":"null"}]},"fcm":{"anyOf":[{"$ref":"#/components/schemas/FcmTransportInput"},{"type":"null"}]},"webpush":{"anyOf":[{"$ref":"#/components/schemas/WebPushTransportInput"},{"type":"null"}]}},"additionalProperties":false,"type":"object","title":"PushTransportRequest","description":"The settings for one provider. Which provider is in the **path**\n(`PUT /v1/push-transport/{provider}`), so it is no longer a body field — the\nresource identifies itself, and a body/path disagreement can't arise."},"PushTransportResponse":{"properties":{"provider":{"$ref":"#/components/schemas/PushProvider"},"expo":{"anyOf":[{"$ref":"#/components/schemas/ExpoTransportView"},{"type":"null"}]},"fcm":{"anyOf":[{"$ref":"#/components/schemas/FcmTransportView"},{"type":"null"}]},"webpush":{"anyOf":[{"$ref":"#/components/schemas/WebPushTransportView"},{"type":"null"}]},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"credentials_unreadable":{"type":"boolean","title":"Credentials Unreadable","description":"This provider has a stored transport whose credentials can no longer be decrypted (the encryption key was rotated), so its settings are absent from this entry. It is NOT the same as being unconfigured: sends through it fail until it is replaced. PUT the provider again with fresh credentials to repair it.","default":false,"examples":[false]}},"type":"object","required":["provider"],"title":"PushTransportResponse","description":"One configured transport. The no-config state is now an **empty list**\nfrom GET rather than a `provider: \"none\"` sentinel — with several transports\npossible per tenant, \"none\" was a statement the singleton shape needed and a\ncollection expresses directly."},"RegisterRequest":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"},"tenant_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tenant Name"}},"type":"object","required":["email","password"],"title":"RegisterRequest"},"RegisterSenderDomainRequest":{"properties":{"domain":{"type":"string","title":"Domain","examples":["acme.com"]},"from_local_part":{"type":"string","title":"From Local Part","default":"notifications","examples":["notifications"]},"from_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Name","examples":["Acme"]}},"additionalProperties":false,"type":"object","required":["domain"],"title":"RegisterSenderDomainRequest"},"RenameTenantRequest":{"properties":{"name":{"type":"string","title":"Name"}},"type":"object","required":["name"],"title":"RenameTenantRequest","description":"Rename an account the signed-in user owns. Blank/whitespace-only is a clean\n422 (mirrors the Tenant domain rule)."},"RenderRequest":{"properties":{"notification_type_key":{"type":"string","title":"Notification Type Key","examples":["order_shipped"]},"branding_key":{"type":"string","title":"Branding Key","examples":["acme"]},"variables":{"additionalProperties":{"$ref":"#/components/schemas/JsonValue"},"type":"object","title":"Variables","description":"Values for the template's `{{ slot }}` markers — the same shape `POST /v1/notifications` takes, so a preview can be built from the payload you would really send. Any JSON: strings, numbers, nested objects, and lists a template repeats over with `{% for %}`.","examples":[{"lines":[{"amount":"$40.00","description":"Widget"}],"order_id":"1234"}]},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","examples":["es"]}},"additionalProperties":false,"type":"object","required":["notification_type_key","branding_key"],"title":"RenderRequest","description":"Resolve + render one channel's template for a notification type under a\nbrand.\n\nThe brand key is always required. `variables` is the event payload, rendered\nat the top level with the brand under `brand.*` — the same shape a send uses.\nA preview has no recipient, so `{{ contact.* }}` renders empty here."},"RenderedEmailResponse":{"properties":{"template_id":{"type":"string","title":"Template Id"},"branding_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branding Key"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"subject":{"type":"string","title":"Subject"},"body":{"type":"string","title":"Body"}},"type":"object","required":["template_id","branding_key","language","subject","body"],"title":"RenderedEmailResponse"},"RequestPasswordResetRequest":{"properties":{"email":{"type":"string","title":"Email"}},"type":"object","required":["email"],"title":"RequestPasswordResetRequest"},"ResendVerificationRequest":{"properties":{"email":{"type":"string","title":"Email"}},"type":"object","required":["email"],"title":"ResendVerificationRequest"},"ResetPasswordRequest":{"properties":{"token":{"type":"string","minLength":1,"title":"Token"},"password":{"type":"string","maxLength":1024,"title":"Password"}},"type":"object","required":["token","password"],"title":"ResetPasswordRequest"},"SendGridTransportInput":{"properties":{"api_key":{"type":"string","title":"Api Key"}},"additionalProperties":false,"type":"object","required":["api_key"],"title":"SendGridTransportInput"},"SendGridTransportView":{"properties":{"has_api_key":{"type":"boolean","title":"Has Api Key"}},"type":"object","required":["has_api_key"],"title":"SendGridTransportView"},"SendTestEmailRequest":{"properties":{"to_address":{"type":"string","title":"To Address","examples":["me@acme.com"]}},"additionalProperties":false,"type":"object","required":["to_address"],"title":"SendTestEmailRequest"},"SendTestEmailResponse":{"properties":{"provider":{"type":"string","title":"Provider"},"message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message Id"}},"type":"object","required":["provider","message_id"],"title":"SendTestEmailResponse"},"SendTestPushRequest":{"properties":{"token":{"type":"string","title":"Token","examples":["ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]"]},"auth":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Auth","examples":["kQm...=="]},"p256dh":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"P256Dh","examples":["BN1...=="]}},"additionalProperties":false,"type":"object","required":["token"],"title":"SendTestPushRequest","description":"A destination to probe the tenant's configured transport with.\n\n`token` is the device token for the token-based providers and the browser's\nsubscription `endpoint` for Web Push, which also needs `auth`/`p256dh` — the\nsame flattening the contact API uses for `PushSubscriptionDTO`, so an operator\ncan paste one subscription into either."},"SendTestPushResponse":{"properties":{"provider":{"type":"string","title":"Provider"},"ticket_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ticket Id"}},"type":"object","required":["provider","ticket_id"],"title":"SendTestPushResponse"},"SenderDomainResponse":{"properties":{"domain":{"type":"string","title":"Domain"},"status":{"$ref":"#/components/schemas/VerificationStatus"},"from_address":{"type":"string","title":"From Address"},"from_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Name"},"mail_from_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mail From Domain"},"dns_records":{"items":{"$ref":"#/components/schemas/DnsRecord"},"type":"array","title":"Dns Records"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Verified At"}},"type":"object","required":["domain","status","from_address","from_name","mail_from_domain","dns_records","created_at","verified_at"],"title":"SenderDomainResponse"},"SesTransportInput":{"properties":{"access_key_id":{"type":"string","title":"Access Key Id","examples":["AKIAIOSFODNN7EXAMPLE"]},"secret_access_key":{"type":"string","title":"Secret Access Key"},"region":{"type":"string","title":"Region","examples":["eu-central-1"]}},"additionalProperties":false,"type":"object","required":["access_key_id","secret_access_key","region"],"title":"SesTransportInput"},"SesTransportView":{"properties":{"access_key_id":{"type":"string","title":"Access Key Id"},"region":{"type":"string","title":"Region"},"has_secret_access_key":{"type":"boolean","title":"Has Secret Access Key"}},"type":"object","required":["access_key_id","region","has_secret_access_key"],"title":"SesTransportView"},"SessionRequest":{"properties":{"tenant_id":{"type":"string","minLength":1,"title":"Tenant Id"},"remember":{"type":"boolean","title":"Remember","default":false}},"type":"object","required":["tenant_id"],"title":"SessionRequest","description":"Select/switch to an account: mint an access token scoped to this tenant.\n`remember` makes it the user's default account for next login."},"SetBrandingSenderRequest":{"properties":{"from_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Address","examples":["hello@acme.com"]},"from_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Name","examples":["Acme"]}},"additionalProperties":false,"type":"object","title":"SetBrandingSenderRequest"},"SetBrandingValuesRequest":{"properties":{"values":{"additionalProperties":{"type":"string"},"type":"object","title":"Values","examples":[{"color":"#00f"}]}},"additionalProperties":false,"type":"object","required":["values"],"title":"SetBrandingValuesRequest"},"SetOptOutPolicyRequest":{"properties":{"allows_opt_out":{"type":"boolean","title":"Allows Opt Out","examples":[false]}},"additionalProperties":false,"type":"object","required":["allows_opt_out"],"title":"SetOptOutPolicyRequest"},"SetVariablesRequest":{"properties":{"variables":{"items":{"type":"string"},"type":"array","uniqueItems":true,"title":"Variables","examples":[["first_name","order_id"]]}},"additionalProperties":false,"type":"object","required":["variables"],"title":"SetVariablesRequest"},"SmtpTransportInput":{"properties":{"host":{"type":"string","title":"Host","examples":["smtp.acme.com"]},"port":{"type":"integer","title":"Port","examples":[587]},"username":{"type":"string","title":"Username","default":""},"password":{"type":"string","title":"Password","default":""},"use_tls":{"type":"boolean","title":"Use Tls","default":false}},"additionalProperties":false,"type":"object","required":["host","port"],"title":"SmtpTransportInput"},"SmtpTransportView":{"properties":{"host":{"type":"string","title":"Host"},"port":{"type":"integer","title":"Port"},"username":{"type":"string","title":"Username"},"use_tls":{"type":"boolean","title":"Use Tls"},"has_password":{"type":"boolean","title":"Has Password"}},"type":"object","required":["host","port","username","use_tls","has_password"],"title":"SmtpTransportView"},"TenantResponse":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id"},"name":{"type":"string","title":"Name"},"role":{"type":"string","title":"Role"},"is_default":{"type":"boolean","title":"Is Default"},"status":{"type":"string","title":"Status","description":"\"active\" or \"archived\". An archived account is hidden from the switcher and can no longer send — the trigger refuses it and fan-out skips it. Reversible via unarchive; data is retained.","default":"active"}},"type":"object","required":["tenant_id","name","role","is_default"],"title":"TenantResponse","description":"One account the signed-in user belongs to (UI: 'account'; code: tenant)."},"TierResponse":{"properties":{"plan":{"type":"string","title":"Plan"},"period":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period"}},"type":"object","required":["plan","period"],"title":"TierResponse"},"TransportProvider":{"type":"string","enum":["smtp","ses","sendgrid","mailgun"],"title":"TransportProvider","description":"Which transport a tenant configured. The platform default is deliberately\n*not* a member: \"use the platform\" is the absence of a config, never a stored\nrow, so there's exactly one representation of the default state."},"TriggerNotificationRequest":{"properties":{"notification_type_key":{"type":"string","title":"Notification Type Key","examples":["order_shipped"]},"external_ids":{"items":{"type":"string"},"type":"array","maxItems":100,"minItems":1,"title":"External Ids","examples":[["crm-12345","crm-67890"]]},"branding_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branding Key","examples":["acme"]},"strict":{"type":"boolean","title":"Strict","description":"Refuse the trigger rather than substituting a different brand for the one it named. By default a `branding_key` that does not exist falls back to the account's default brand and the send goes out, so a typo cannot stop your mail. With `strict` that same trigger is a **422** and nothing is queued. Covers the branding key only; it is not a general validation mode, and an unknown `notification_type_key` is still accepted here and reported on the event. Leave it off unless you would rather send nothing than send under the wrong brand.","default":false},"variables":{"additionalProperties":{"$ref":"#/components/schemas/JsonValue"},"type":"object","title":"Variables","description":"Values for the template's `{{ slot }}` markers. Any JSON is accepted: strings, numbers, booleans, nested objects (read with `{{ order.customer.name }}`) and lists a template repeats over with `{% for line in lines %} … {% endfor %}` — invoice lines, order items, digest entries. A name you don't send renders empty rather than failing, and a list you don't send repeats zero times.","examples":[{"invoice_number":"INV-1042","lines":[{"amount":"$40.00","description":"Widget","qty":2},{"amount":"$80.00","description":"Gadget","qty":1}],"total":"$120.00"}]},"secret_variables":{"additionalProperties":{"type":"string"},"type":"object","title":"Secret Variables","description":"Values that must reach the recipient but must not be readable back afterwards — a one-time code, a password-reset link, a signed URL. They fill `{{ slot }}` markers exactly like `variables` and are sent in full, but the activity log shows them masked: the raw value is hidden, and the message body shown there is a second render in which the secret is replaced. Flat strings only (no repeat-block lists), and a name used here must not also appear in `variables`.","examples":[{"code":"550913"}]}},"additionalProperties":false,"type":"object","required":["notification_type_key","external_ids"],"title":"TriggerNotificationRequest","description":"A tenant triggering an event for one or more recipients (their own\n`external_id`s — e.g. every subscriber of a resource). One event is recorded\nper recipient, so each fans out, retries, and reports status independently.\nThe branding key is optional — omit it (or send an unknown one) and fan-out\nfalls back to the tenant's default branding, unless `strict` is set, which\nrejects an unknown one instead. Recorded + accepted (202); channel resolution\n+ delivery happen asynchronously in the fan-out worker."},"TypePreferenceResponse":{"properties":{"notification_type_key":{"type":"string","title":"Notification Type Key"},"channels":{"items":{"$ref":"#/components/schemas/ChannelPreferenceResponse"},"type":"array","title":"Channels"}},"type":"object","required":["notification_type_key","channels"],"title":"TypePreferenceResponse","description":"One notification type's per-channel preference state for a contact — the\nunit of the preferences matrix a client renders.\n\n`channels` carries only the channels this type actually has a template for,\nso a client never renders a switch for a channel that could not be delivered."},"UnreadCountResponse":{"properties":{"unread":{"type":"integer","title":"Unread"}},"type":"object","required":["unread"],"title":"UnreadCountResponse"},"UpdateChannelDefaultsRequest":{"properties":{"channel_defaults":{"additionalProperties":{"type":"boolean"},"propertyNames":{"$ref":"#/components/schemas/Channel"},"type":"object","title":"Channel Defaults","examples":[{"email":false}]}},"additionalProperties":false,"type":"object","required":["channel_defaults"],"title":"UpdateChannelDefaultsRequest"},"UpdateContactRequest":{"properties":{"emails":{"items":{"type":"string"},"type":"array","title":"Emails","examples":[["a@example.com"]]},"phones":{"items":{"type":"string"},"type":"array","title":"Phones","examples":[["+15551234567"]]},"push_subscriptions":{"items":{"$ref":"#/components/schemas/PushSubscriptionDTO"},"type":"array","title":"Push Subscriptions"},"attributes":{"additionalProperties":{"type":"string"},"type":"object","title":"Attributes","examples":[{"first_name":"Ada"}]},"preferences":{"items":{"$ref":"#/components/schemas/PreferenceDTO"},"type":"array","title":"Preferences"}},"additionalProperties":false,"type":"object","title":"UpdateContactRequest","description":"Full new desired state of a contact's mutable collections (whole-resource\nPUT) — each list/map replaces what's stored, it is not a delta."},"UpdateEmailTemplateRequest":{"properties":{"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","examples":["es"]},"subject":{"type":"string","title":"Subject"},"body":{"type":"string","title":"Body"}},"additionalProperties":false,"type":"object","required":["subject","body"],"title":"UpdateEmailTemplateRequest"},"UpdateInboxTemplateRequest":{"properties":{"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","examples":["es"]},"title":{"type":"string","title":"Title"},"body":{"type":"string","title":"Body"}},"additionalProperties":false,"type":"object","required":["title","body"],"title":"UpdateInboxTemplateRequest"},"UpdatePushTemplateRequest":{"properties":{"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","examples":["es"]},"title":{"type":"string","title":"Title","default":""},"body":{"type":"string","title":"Body","default":""},"data":{"additionalProperties":{"type":"string"},"type":"object","title":"Data"}},"additionalProperties":false,"type":"object","title":"UpdatePushTemplateRequest"},"UpdateWebPushTemplateRequest":{"properties":{"title":{"type":"string","title":"Title","examples":["Your order shipped"]},"body":{"type":"string","title":"Body","default":"","examples":["It's on its way."]},"icon":{"type":"string","title":"Icon","default":"","examples":["{{ brand.icon_url }}"]},"badge":{"type":"string","title":"Badge","default":"","examples":["https://example.com/badge.png"]},"url":{"type":"string","title":"Url","default":"","examples":["https://example.com/orders/{{ order_id }}"]},"data":{"additionalProperties":{"type":"string"},"type":"object","title":"Data","examples":[{"order_id":"{{ order_id }}"}]},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","examples":["es"]}},"additionalProperties":false,"type":"object","required":["title"],"title":"UpdateWebPushTemplateRequest"},"UserResponse":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"tenant_id":{"type":"string","title":"Tenant Id"}},"type":"object","required":["id","email","tenant_id"],"title":"UserResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VerificationStatus":{"type":"string","enum":["pending","verified","failed"],"title":"VerificationStatus","description":"Where a sender domain is in the SES verification lifecycle. `PENDING` until\nthe tenant adds the DKIM records and SES confirms them; `VERIFIED` once SES will\nsign+send for the domain; `FAILED` if SES gave up (records never appeared)."},"VerifyEmailRequest":{"properties":{"token":{"type":"string","minLength":1,"title":"Token"}},"type":"object","required":["token"],"title":"VerifyEmailRequest"},"WebPushPublicKeyResponse":{"properties":{"public_key":{"type":"string","title":"Public Key","description":"The tenant's VAPID application server public key, base64url-encoded — pass it to `pushManager.subscribe({ applicationServerKey })`. Absent as a 404 when the tenant has no Web Push transport, i.e. nobody can subscribe to them yet.","examples":["BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkR..."]}},"type":"object","required":["public_key"],"title":"WebPushPublicKeyResponse","description":"The tenant's VAPID public key, for a browser about to subscribe.\n\nA whole response for one field, because that is the whole question: a browser\ncannot call `pushManager.subscribe()` without this value as its\n`applicationServerKey`. Returned to *contact* tokens, which no other read on\nthis aggregate is — every other field here is a credential, and this one is\ngenerated in order to be published."},"WebPushTemplateResponse":{"properties":{"id":{"type":"string","title":"Id"},"notification_type_key":{"type":"string","title":"Notification Type Key"},"branding_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Branding Key"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"variables":{"items":{"type":"string"},"type":"array","title":"Variables"},"title":{"type":"string","title":"Title"},"body":{"type":"string","title":"Body"},"icon":{"type":"string","title":"Icon"},"badge":{"type":"string","title":"Badge"},"url":{"type":"string","title":"Url"},"data":{"additionalProperties":{"type":"string"},"type":"object","title":"Data"}},"type":"object","required":["id","notification_type_key","branding_key","language","variables","title","body","icon","badge","url","data"],"title":"WebPushTemplateResponse"},"WebPushTransportInput":{"properties":{"public_key":{"type":"string","title":"Public Key","examples":["BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkR..."]},"private_key":{"type":"string","title":"Private Key","examples":["UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls"]},"subject":{"type":"string","title":"Subject","examples":["mailto:ops@example.com"]}},"additionalProperties":false,"type":"object","required":["public_key","private_key","subject"],"title":"WebPushTransportInput","description":"The tenant's VAPID application server keys.\n\nGenerated by the tenant (`npx web-push generate-vapid-keys`, `vapid --gen`),\nnot by a provider console — Web Push has no account to sign up for. The pair\nis long-lived and **must not be rotated casually**: existing browser\nsubscriptions were created against the public key and stop being deliverable\nif it changes, so a rotation means re-subscribing every browser."},"WebPushTransportView":{"properties":{"public_key":{"type":"string","title":"Public Key"},"subject":{"type":"string","title":"Subject"},"has_private_key":{"type":"boolean","title":"Has Private Key"}},"type":"object","required":["public_key","subject","has_private_key"],"title":"WebPushTransportView","description":"The public half is returned in full, deliberately: the console has to hand\nit to the browser as `applicationServerKey` for `pushManager.subscribe()`, and\nit is a public key. Only the private half is reduced to a `has_` boolean."}}}}