{"openapi":"3.0.3","info":{"title":"Vender API","description":"Comprehensive business management API for Vender platform","contact":{"name":"Vender Team","email":"support@vender.cloud"},"license":{"name":"Proprietary"},"version":"1.0.0"},"paths":{"/api/access-tokens":{"get":{"tags":["access_tokens"],"operationId":"list_access_tokens_handler","responses":{"200":{"description":"Access tokens listed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAccessTokensResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]},"post":{"tags":["access_tokens"],"operationId":"create_access_token_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAccessTokenRequest"}}},"required":true},"responses":{"201":{"description":"Access token created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAccessTokenResponse"}}}},"400":{"description":"Bad request"},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]}},"/api/access-tokens/current/revoke":{"post":{"tags":["access_tokens"],"operationId":"revoke_current_access_token_handler","responses":{"200":{"description":"Current access token revoked successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAccessTokenResponse"}}}},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"}},"security":[{"jwt":[]}]}},"/api/access-tokens/{id}":{"delete":{"tags":["access_tokens"],"operationId":"delete_access_token_handler","parameters":[{"name":"id","in":"path","description":"Access token UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Access token deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAccessTokenResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/access-tokens/{id}/revoke":{"post":{"tags":["access_tokens"],"operationId":"revoke_access_token_handler","parameters":[{"name":"id","in":"path","description":"Access token UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Access token revoked successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeAccessTokenResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/access-tokens/{id}/roll":{"post":{"tags":["access_tokens"],"operationId":"rotate_access_token_handler","parameters":[{"name":"id","in":"path","description":"Access token UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotateAccessTokenRequest"}}},"required":true},"responses":{"200":{"description":"Access token rotated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAccessTokenResponse"}}}},"400":{"description":"Bad request"},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/accounts":{"get":{"tags":["accounts"],"operationId":"get_accounts","parameters":[{"name":"limit","in":"query","description":"Number of items to return","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Number of items to skip","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"List of accounts","content":{"application/json":{"schema":{}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["accounts"],"operationId":"create_account","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountInput"}}},"required":true},"responses":{"201":{"description":"Account created successfully","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/accounts/bulk-import":{"post":{"tags":["accounts"],"summary":"Bulk create/update accounts from an external source.","operationId":"bulk_import_accounts","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportAccountsRequest"}}},"required":true},"responses":{"200":{"description":"Per-row import outcome","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportResponse"}}}},"400":{"description":"Invalid request"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/accounts/merge":{"post":{"tags":["accounts"],"operationId":"merge_accounts","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeAccountsPayload"}}},"required":true},"responses":{"200":{"description":"Accounts merged successfully","content":{"application/json":{"schema":{}}}},"404":{"description":"Target account not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/accounts/quick-search":{"get":{"tags":["accounts"],"operationId":"quickSearchAccounts","parameters":[{"name":"search","in":"query","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"fields","in":"query","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Account search results","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AccountSearchResult"}}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/accounts/recent":{"get":{"tags":["accounts"],"operationId":"get_recent_accounts","parameters":[{"name":"limit","in":"query","description":"Maximum number of accounts to return (default 10)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Recently updated accounts","content":{"application/json":{"schema":{}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/accounts/walk-in":{"get":{"tags":["accounts"],"operationId":"get_walk_in_account","responses":{"200":{"description":"Static walk-in customer account","content":{"application/json":{"schema":{}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/accounts/{id}":{"get":{"tags":["accounts"],"operationId":"get_account","parameters":[{"name":"id","in":"path","description":"Account ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Account details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebappAccount"}}}},"404":{"description":"Account not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["accounts"],"operationId":"update_account","parameters":[{"name":"id","in":"path","description":"Account ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountInput"}}},"required":true},"responses":{"200":{"description":"Account updated successfully","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"404":{"description":"Account not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["accounts"],"operationId":"delete_account","parameters":[{"name":"id","in":"path","description":"Account ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Account deleted successfully"},"400":{"description":"Account has sales orders and cannot be deleted"},"404":{"description":"Account not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["accounts"],"operationId":"patch_account","parameters":[{"name":"id","in":"path","description":"Account ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchAccountInput"}}},"required":true},"responses":{"200":{"description":"Account patched","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"404":{"description":"Account not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/accounts/{id}/frequent-products":{"get":{"tags":["accounts"],"operationId":"get_account_frequent_products","parameters":[{"name":"id","in":"path","description":"Account ID","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Most frequently ordered products for account","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FrequentProductResponse"}}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Account not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/admin/jobs":{"get":{"tags":["jobs_admin"],"operationId":"listAdminJobs","parameters":[{"name":"status","in":"query","description":"Filter by status: queued | running | completed | `failed_permanent`.","required":false,"schema":{"type":"string","nullable":true}},{"name":"kind","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"company_id","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Jobs matching the filters, plus the dead-letter count","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobListResponse"}}}},"403":{"description":"Platform admin access required"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/admin/jobs/dead-letter-count":{"get":{"tags":["jobs_admin"],"operationId":"getAdminJobsDeadLetterCount","responses":{"200":{"description":"Count of jobs in failed_permanent status"},"403":{"description":"Platform admin access required"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/admin/jobs/{id}":{"get":{"tags":["jobs_admin"],"operationId":"getAdminJob","responses":{"200":{"description":"Job detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobView"}}}},"403":{"description":"Platform admin access required"},"404":{"description":"Job not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/admin/jobs/{id}/requeue":{"post":{"tags":["jobs_admin"],"operationId":"requeueAdminJob","responses":{"200":{"description":"Requeue result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequeueResponse"}}}},"403":{"description":"Platform admin access required"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/admin/qbpos/tenants/{company_id}/agent-token":{"post":{"tags":["internal_admin"],"operationId":"qbpos_admin_mint_agent_token","parameters":[{"name":"company_id","in":"path","description":"Tenant company id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposMintAgentTokenRequest"}}},"required":true},"responses":{"201":{"description":"Agent token minted (plaintext shown ONCE)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAccessTokenResponse"}}}},"400":{"description":"Invalid request"},"403":{"description":"Platform admin access required"},"404":{"description":"Tenant not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/admin/qbpos/tenants/{company_id}/status":{"get":{"tags":["internal_admin"],"operationId":"qbpos_admin_tenant_status","parameters":[{"name":"company_id","in":"path","description":"Tenant company id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"QBPOS integration status for the tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposPlatformTenantStatusResponse"}}}},"403":{"description":"Platform admin access required"},"404":{"description":"Tenant not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/admin/qbpos/tenants/{company_id}/write-back-suspension":{"get":{"tags":["internal_admin"],"operationId":"qbpos_admin_get_write_back_suspension","parameters":[{"name":"company_id","in":"path","description":"Tenant company id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Current platform write-back suspension","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposWriteBackSuspensionResponse"}}}},"403":{"description":"Platform admin access required"},"404":{"description":"Tenant not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["internal_admin"],"operationId":"qbpos_admin_put_write_back_suspension","parameters":[{"name":"company_id","in":"path","description":"Tenant company id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposWriteBackSuspensionRequest"}}},"required":true},"responses":{"200":{"description":"Suspension updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposWriteBackSuspensionResponse"}}}},"403":{"description":"Platform admin access required"},"404":{"description":"Tenant not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/auth":{"get":{"tags":["auth"],"summary":"Get current user information from JWT claims","operationId":"authentication","responses":{"200":{"description":"User information retrieved successfully"},"401":{"description":"Invalid or expired token"},"500":{"description":"Internal server error"}},"security":[{"jwt":[]}]}},"/api/auth/google":{"post":{"tags":["auth"],"summary":"POST /api/auth/google — Sign In with Google","description":"Verifies the Google ID token, finds the user by email, and returns a JWT.","operationId":"google_login","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAuthRequest"}}},"required":true},"responses":{"200":{"description":"Login successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAuthResponse"}}}},"401":{"description":"Invalid Google token or user not found"},"500":{"description":"Internal server error"}}}},"/api/auth/google-signup":{"post":{"tags":["auth"],"summary":"POST /api/auth/google-signup — Sign Up with Google","description":"Verifies the Google ID token, creates a new user + company + profile,\nand returns a JWT. Requires `company_name` in the request body.","operationId":"google_signup","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleSignupRequest"}}},"required":true},"responses":{"201":{"description":"User created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAuthResponse"}}}},"400":{"description":"Invalid Google token or missing fields"},"409":{"description":"Email already registered"},"429":{"description":"Too many signup attempts"},"500":{"description":"Internal server error"}}}},"/api/auth/mfa/admin/reset":{"post":{"tags":["mfa"],"operationId":"admin_reset_user_mfa","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminMfaResetRequest"}}},"required":true},"responses":{"200":{"description":"The target user's MFA was cleared","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminMfaResetResponse"}}}},"403":{"description":"Missing permission, or step-up required"},"404":{"description":"No such user in this company"}},"security":[{"jwt":[]}]}},"/api/auth/mfa/admin/status/{user_id}":{"get":{"tags":["mfa"],"operationId":"admin_get_user_mfa_status","parameters":[{"name":"user_id","in":"path","description":"Target user id","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"MFA state for the target user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserMfaStatus"}}}},"403":{"description":"Missing permission"},"404":{"description":"No such user in this company"}},"security":[{"jwt":[]}]}},"/api/auth/mfa/challenge":{"post":{"tags":["mfa"],"operationId":"submit_mfa_challenge","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaChallengeRequest"}}},"required":true},"responses":{"200":{"description":"Second factor accepted; session issued","content":{"application/json":{"schema":{}}}},"401":{"description":"Invalid code or challenge"},"429":{"description":"Too many attempts"}}}},"/api/auth/mfa/disable":{"post":{"tags":["mfa"],"operationId":"disable_mfa","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaDisableRequest"}}},"required":true},"responses":{"200":{"description":"MFA removed"},"401":{"description":"Invalid code"},"403":{"description":"Company policy forbids disabling MFA"},"404":{"description":"MFA is not enabled"}},"security":[{"jwt":[]}]}},"/api/auth/mfa/enroll/start":{"post":{"tags":["mfa"],"operationId":"start_forced_mfa_enrolment","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForcedEnrolStartRequest"}}},"required":true},"responses":{"200":{"description":"Enrolment started; secret returned once","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TotpSetupResponse"}}}},"401":{"description":"Invalid or expired setup token"},"409":{"description":"MFA already enabled"},"429":{"description":"Too many attempts"}}}},"/api/auth/mfa/enroll/verify":{"post":{"tags":["mfa"],"operationId":"verify_forced_mfa_enrolment","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForcedEnrolVerifyRequest"}}},"required":true},"responses":{"200":{"description":"Enrolment confirmed and session issued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TotpSetupVerifyResponse"}}}},"401":{"description":"Invalid code or setup token"},"429":{"description":"Too many attempts"}}}},"/api/auth/mfa/recovery-codes":{"post":{"tags":["mfa"],"operationId":"regenerate_recovery_codes","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegenerateRecoveryCodesRequest"}}},"required":true},"responses":{"200":{"description":"A new recovery-code set, shown once","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoveryCodesResponse"}}}},"401":{"description":"Invalid code"},"404":{"description":"MFA is not enabled"}},"security":[{"jwt":[]}]}},"/api/auth/mfa/status":{"get":{"tags":["mfa"],"operationId":"get_mfa_status","responses":{"200":{"description":"Current MFA state for the signed-in user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaStatusResponse"}}}},"401":{"description":"Not authenticated"}},"security":[{"jwt":[]}]}},"/api/auth/mfa/totp/setup":{"post":{"tags":["mfa"],"operationId":"start_totp_setup","responses":{"200":{"description":"Enrolment started; secret returned once","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TotpSetupResponse"}}}},"401":{"description":"Not authenticated"},"409":{"description":"MFA already enabled"},"500":{"description":"MFA is not configured on this deployment"}},"security":[{"jwt":[]}]}},"/api/auth/mfa/totp/verify":{"post":{"tags":["mfa"],"operationId":"verify_totp_setup","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TotpSetupVerifyRequest"}}},"required":true},"responses":{"200":{"description":"Enrolment confirmed; recovery codes returned once","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TotpSetupVerifyResponse"}}}},"401":{"description":"Invalid code"},"404":{"description":"No enrolment in progress"}},"security":[{"jwt":[]}]}},"/api/auth/password-reset-confirm":{"post":{"tags":["auth"],"summary":"Confirm password reset with token","operationId":"password_reset_confirm","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetConfirmRequest"}}},"required":true},"responses":{"200":{"description":"Password reset successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetConfirmResponse"}}}},"400":{"description":"Bad request - invalid token or password"},"404":{"description":"Reset token not found or expired"},"500":{"description":"Internal server error"}}}},"/api/auth/password-reset-request":{"post":{"tags":["auth"],"summary":"Request password reset - sends reset link to email","operationId":"password_reset_request","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetRequest"}}},"required":true},"responses":{"200":{"description":"Password reset email sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetResponse"}}}},"400":{"description":"Bad request - email required"},"404":{"description":"User not found"},"500":{"description":"Internal server error"}}}},"/api/auth/resend-verification":{"post":{"tags":["auth"],"summary":"Resend verification email","operationId":"resend_verification","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResendVerificationRequest"}}},"required":true},"responses":{"200":{"description":"Verification email sent"},"400":{"description":"Invalid request"},"404":{"description":"Email not found"},"409":{"description":"Email already verified"},"429":{"description":"Too many requests - per-IP quota or the 60s per-address cooldown"},"500":{"description":"Internal server error"}}}},"/api/auth/signup":{"post":{"tags":["auth"],"operationId":"signup","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupRequest"}}},"required":true},"responses":{"201":{"description":"User created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupResponse"}}}},"400":{"description":"Bad request - validation errors"},"409":{"description":"Username or email already exists"},"429":{"description":"Too many signup attempts"},"500":{"description":"Internal server error"},"503":{"description":"CAPTCHA verification temporarily unavailable"}}}},"/api/auth/step-up":{"post":{"tags":["mfa"],"operationId":"step_up_auth","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StepUpRequest"}}},"required":true},"responses":{"200":{"description":"Step-up granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StepUpResponse"}}}},"401":{"description":"Wrong password or code"},"429":{"description":"Too many attempts"}},"security":[{"jwt":[]}]}},"/api/auth/verify-email":{"get":{"tags":["auth"],"summary":"Verify email address using the token from the verification link","operationId":"verify_email","parameters":[{"name":"token","in":"query","description":"Email verification token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Email verified successfully"},"400":{"description":"Invalid or missing token"},"404":{"description":"Token not found or already used"},"410":{"description":"Token has expired"},"500":{"description":"Internal server error"}}}},"/api/backups":{"get":{"tags":["backups"],"operationId":"list_backups","parameters":[{"name":"backup_type","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"status","in":"query","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"List of backups for the company","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/BackupRecordResponse"}}}}},"500":{"description":"Internal server error"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["backups"],"operationId":"create_backup","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBackupRequest"}}},"required":true},"responses":{"201":{"description":"Backup created and export completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupRecordResponse"}}}},"409":{"description":"A backup is already running for this company"},"413":{"description":"Tenant data exceeds the export size limit"},"500":{"description":"Internal server error"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/backups/{id}":{"get":{"tags":["backups"],"operationId":"get_backup","parameters":[{"name":"id","in":"path","description":"Backup ID","required":true,"schema":{"$ref":"#/components/schemas/uuid.Uuid"}}],"responses":{"200":{"description":"Backup details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupRecordResponse"}}}},"404":{"description":"Backup not found"},"500":{"description":"Internal server error"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["backups"],"operationId":"delete_backup","parameters":[{"name":"id","in":"path","description":"Backup ID","required":true,"schema":{"$ref":"#/components/schemas/uuid.Uuid"}}],"responses":{"204":{"description":"Backup deleted"},"404":{"description":"Backup not found"},"500":{"description":"Internal server error"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/backups/{id}/download":{"get":{"tags":["backups"],"operationId":"download_backup","parameters":[{"name":"id","in":"path","description":"Backup ID","required":true,"schema":{"$ref":"#/components/schemas/uuid.Uuid"}}],"responses":{"200":{"description":"Backup ZIP archive bytes"},"404":{"description":"Backup not found, not owned by this company, or not completed"},"500":{"description":"Internal server error"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/billing/agreement":{"get":{"tags":["billing"],"operationId":"getMyCommercialAgreement","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommercialAgreement"}}}},"404":{"description":"No accepted modular agreement"}},"security":[{"bearer_auth":[]}]}},"/api/billing/checkout":{"post":{"tags":["Billing"],"summary":"POST /api/billing/checkout","description":"Creates a Stripe Checkout Session URL for the user to complete payment.","operationId":"createBillingCheckout","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCheckoutRequest"}}},"required":true},"responses":{"200":{"description":"Stripe Checkout Session URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResponse"}}}},"400":{"description":"Checkout session could not be created"},"403":{"description":"Forbidden - insufficient permissions"}},"security":[{"bearer_auth":[]}]}},"/api/billing/invoices":{"get":{"tags":["Billing"],"summary":"GET /api/billing/invoices","description":"Lists the company's Stripe invoices for the tenant-facing billing page.","operationId":"listBillingInvoices","responses":{"200":{"description":"The company's Stripe invoices, newest first. A tenant with no Stripe customer gets an empty list, not an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceListResponse"}}}},"400":{"description":"Stripe request failed or Stripe is not configured"},"403":{"description":"Forbidden - insufficient permissions"}},"security":[{"bearer_auth":[]}]}},"/api/billing/licences":{"get":{"tags":["billing"],"summary":"Return the authorized company's allocation, agreement capacity and eligible subjects.","operationId":"getCommercialLicences","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommercialLicences"}}}}},"security":[{"bearer_auth":[]}]},"put":{"tags":["billing"],"summary":"Replace an authorized company's allocation with revision, capacity and subject checks.","operationId":"replaceCommercialLicences","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplaceLicenceAllocation"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LicenceAllocation"}}}}},"security":[{"bearer_auth":[]}]}},"/api/billing/modular/checkout":{"post":{"tags":["billing"],"operationId":"createModularBillingCheckout","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateModularCheckout"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModularCheckoutView"}}}},"400":{"description":""},"403":{"description":""},"409":{"description":""},"503":{"description":""}},"security":[{"bearer_auth":[]}]}},"/api/billing/modular/checkout-availability":{"get":{"tags":["billing"],"operationId":"getModularCheckoutAvailability","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModularCheckoutAvailability"}}}},"403":{"description":""},"500":{"description":""}},"security":[{"bearer_auth":[]}]}},"/api/billing/modular/checkouts/{id}":{"get":{"tags":["billing"],"operationId":"getModularBillingCheckout","parameters":[{"name":"id","in":"path","description":"Checkout UUID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModularCheckoutView"}}}},"403":{"description":""},"404":{"description":""}},"security":[{"bearer_auth":[]}]}},"/api/billing/modular/checkouts/{id}/cancel":{"post":{"tags":["billing"],"operationId":"cancelModularBillingCheckout","parameters":[{"name":"id","in":"path","description":"Checkout UUID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModularCheckoutView"}}}},"403":{"description":""},"404":{"description":""},"409":{"description":""},"503":{"description":""}},"security":[{"bearer_auth":[]}]}},"/api/billing/modular/checkouts/{id}/reconcile":{"post":{"tags":["billing"],"operationId":"reconcileModularBillingCheckout","parameters":[{"name":"id","in":"path","description":"Checkout UUID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModularCheckoutView"}}}},"403":{"description":""},"404":{"description":""},"409":{"description":""},"503":{"description":""}},"security":[{"bearer_auth":[]}]}},"/api/billing/modular/checkouts/{id}/resume":{"post":{"tags":["billing"],"summary":"Resume the immutable stored request after a browser reload or lost response.","description":"`create_checkout` reuses the company reservation and provider idempotency key,\nrechecks collection policy/mode and refuses an elapsed provider retry window.","operationId":"resumeModularBillingCheckout","parameters":[{"name":"id","in":"path","description":"Checkout UUID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModularCheckoutView"}}}},"403":{"description":""},"404":{"description":""},"409":{"description":""},"503":{"description":""}},"security":[{"bearer_auth":[]}]}},"/api/billing/portal":{"post":{"tags":["Billing"],"summary":"POST /api/billing/portal","description":"Creates a Stripe Billing Portal session so the customer can manage their subscription.","operationId":"createBillingPortal","responses":{"200":{"description":"Stripe Billing Portal session URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalResponse"}}}},"400":{"description":"Portal session could not be created"},"403":{"description":"Forbidden - insufficient permissions"}},"security":[{"bearer_auth":[]}]}},"/api/billing/status":{"get":{"tags":["Billing"],"summary":"GET /api/billing/status","description":"Returns the current subscription status for the authenticated user's company.","operationId":"getBillingStatus","responses":{"200":{"description":"Current subscription status for the caller's company","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingStatusResponse"}}}},"500":{"description":"Database connection error"}},"security":[{"bearer_auth":[]}]}},"/api/billing/webhook":{"post":{"tags":["Billing"],"summary":"POST /api/billing/webhook  (PUBLIC — no JWT required)","description":"Receives and processes Stripe webhook events.","operationId":"stripeBillingWebhook","requestBody":{"description":"","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}},"required":true},"responses":{"200":{"description":"Event received (processed, duplicate, or non-fatal processing warning)"},"400":{"description":"Missing/invalid signature, malformed JSON, or missing event id"},"401":{"description":"Invalid webhook signature"},"500":{"description":"Webhook secret not configured or database unavailable"}}}},"/api/billing/workspace":{"get":{"tags":["billing"],"operationId":"getCommercialWorkspace","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommercialWorkspace"}}}}},"security":[{"bearer_auth":[]}]}},"/api/billing/workspace/dashboard":{"get":{"tags":["billing"],"operationId":"getCommercialDashboard","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommercialDashboard"}}}}},"security":[{"bearer_auth":[]}]}},"/api/catalog-rules":{"get":{"tags":["Catalog Rules"],"operationId":"admin_list_catalog_rules","responses":{"200":{"description":"Catalog rules for the company","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CatalogRuleResponse"}}}}},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["Catalog Rules"],"operationId":"admin_create_catalog_rule","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCreateCatalogRuleRequest"}}},"required":true},"responses":{"201":{"description":"Catalog rule created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogRuleResponse"}}}},"400":{"description":"Bad request"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Account not found for company"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/catalog-rules/{id}":{"get":{"tags":["Catalog Rules"],"operationId":"admin_get_catalog_rule","parameters":[{"name":"id","in":"path","description":"Catalog rule ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Catalog rule","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogRuleResponse"}}}},"400":{"description":"Invalid ID"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["Catalog Rules"],"operationId":"admin_update_catalog_rule","parameters":[{"name":"id","in":"path","description":"Catalog rule ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUpdateCatalogRuleRequest"}}},"required":true},"responses":{"200":{"description":"Catalog rule updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogRuleResponse"}}}},"400":{"description":"Bad request"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["Catalog Rules"],"operationId":"admin_delete_catalog_rule","parameters":[{"name":"id","in":"path","description":"Catalog rule ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Catalog rule deleted","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid ID"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/categories":{"get":{"tags":["categories"],"operationId":"getCategories","parameters":[{"name":"limit","in":"query","description":"Number of items to return","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"offset","in":"query","description":"Number of items to skip","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"parent_id","in":"query","description":"Filter by parent category ID","required":false,"schema":{"type":"string","nullable":true}},{"name":"search","in":"query","description":"Search by name or code","required":false,"schema":{"type":"string","nullable":true}},{"name":"show_excluded","in":"query","description":"Include excluded categories (true = show all, false = hide excluded)","required":false,"schema":{"type":"boolean","nullable":true}},{"name":"order_by","in":"query","description":"Sort field: name or created_at","required":false,"schema":{"type":"string","nullable":true}},{"name":"order_dir","in":"query","description":"Sort direction: asc or desc","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"List of categories","content":{"application/json":{"schema":{}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["categories"],"operationId":"create_category","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryInput"}}},"required":true},"responses":{"201":{"description":"Category created","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/categories/bulk-import":{"post":{"tags":["Categories"],"summary":"Bulk create/update categories from an external source.","operationId":"bulk_import_categories","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportCategoriesRequest"}}},"required":true},"responses":{"200":{"description":"Per-row import outcome","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportResponse"}}}},"400":{"description":"Invalid request"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/categories/merge":{"post":{"tags":["categories"],"operationId":"merge_categories","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeCategoriesPayload"}}},"required":true},"responses":{"200":{"description":"Categories merged","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/categories/{id}":{"get":{"tags":["categories"],"operationId":"get_category","parameters":[{"name":"id","in":"path","description":"Category ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Category details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebappCategory"}}}},"404":{"description":"Category not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["categories"],"operationId":"update_category","parameters":[{"name":"id","in":"path","description":"Category ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryInput"}}},"required":true},"responses":{"200":{"description":"Category updated","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"404":{"description":"Category not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["categories"],"operationId":"delete_category","parameters":[{"name":"id","in":"path","description":"Category ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Category deleted"},"404":{"description":"Category not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["categories"],"operationId":"patch_category","parameters":[{"name":"id","in":"path","description":"Category ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchCategoryInput"}}},"required":true},"responses":{"200":{"description":"Category patched","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebappCategory"}}}},"400":{"description":"Bad request"},"404":{"description":"Category not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/categories/{id}/join":{"post":{"tags":["categories"],"operationId":"join_category","parameters":[{"name":"id","in":"path","description":"Source category ID to alias","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinCategoryPayload"}}},"required":true},"responses":{"200":{"description":"Category joined successfully","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request (self-join, cycle, or already deleted)"},"404":{"description":"Source or target category not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["categories"],"operationId":"unjoin_category","parameters":[{"name":"id","in":"path","description":"Category ID to unjoin","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Category unjoined (merged_into_id cleared)","content":{"application/json":{"schema":{}}}},"404":{"description":"Category not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/client-downloads":{"get":{"tags":["client-downloads"],"summary":"Return the current public client release catalogue.","operationId":"getClientDownloads","responses":{"200":{"description":"Current client releases","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientDownloadsResponse"}}}},"503":{"description":"Release catalogue unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientDownloadsError"}}}}}}},"/api/client-downloads/{release_id}":{"get":{"tags":["client-downloads"],"summary":"Redirect a stable release id to its current versioned CDN object.","operationId":"downloadClientRelease","parameters":[{"name":"release_id","in":"path","description":"Release id from the catalogue","required":true,"schema":{"type":"string"}}],"responses":{"307":{"description":"Redirect to public CDN object"},"404":{"description":"Release id not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientDownloadsError"}}}},"503":{"description":"Release catalogue unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientDownloadsError"}}}}}}},"/api/company/offline-images":{"get":{"tags":["crate::features::settings::offline_images"],"summary":"Read the authenticated till's narrow offline image policy.","operationId":"get_offline_images_policy","responses":{"200":{"description":"Company image policy for this authenticated till","content":{"application/json":{"schema":{"$ref":"#/components/schemas/crate.features.settings.dto.OfflineImagesSettings"}}}},"403":{"description":"POS selling or company settings permission required"},"500":{"description":"Policy unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/company/receipt-settings":{"get":{"tags":["crate::features::settings::receipt_sync"],"summary":"Return only the authenticated company's receipt policy to authorized tills.","operationId":"get_receipt_settings_policy","responses":{"200":{"description":"Company receipt policy for this authenticated till","content":{"application/json":{"schema":{"$ref":"#/components/schemas/crate.features.settings.dto.ReceiptSettings"}}}},"403":{"description":"POS selling or company settings permission required"},"500":{"description":"Policy unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/company/till-settings":{"get":{"tags":["crate::features::settings::till_settings"],"summary":"Return the authenticated company’s operational settings after checking till access.","operationId":"get_till_settings","responses":{"200":{"description":"Operational configuration for the authenticated till","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TillSettings"}}}},"403":{"description":"POS selling or company settings permission required"},"500":{"description":"Configuration unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/company/users/roster":{"get":{"tags":["Users"],"summary":"The staff roster a POS terminal needs, and nothing else.","description":"# Why this exists rather than loosening `GET /api/company/users`\n\nA terminal has to know who its staff are: `verify_employee_pin` matches a\ntyped PIN against the local `employees` table, and that table is populated\nby syncing this list. But `GET /api/company/users` is gated on\n`user.create|user.edit|user.delete` — Admin — and the desktop sync swallowed\nthe resulting 403 silently. On any till not signed in as a company Admin the\n`employees` table therefore stayed EMPTY: `verify_employee_pin` returned\n`NoStaff`, every manager-approval gate became permanently un-passable, and\n(before this workstream) every ungated action stayed wide open. That is the\nmechanism behind \"the till only works when it is signed in as an admin\",\nwhich is how a terminal ends up holding an admin credential.\n\nWidening the existing endpoint was the wrong fix: it returns `username`,\n`email`, `date_joined`, `last_login`, `is_superuser` and the user's full\ncustom permission array. A cashier-run terminal has no business with any of\nthat. This returns the four fields the sync actually maps\n(`src-tauri/src/sync/service.rs`'s `map_employee_users`) plus the resolved\nrole and permissions it needs to derive a till role — and no account\nmetadata.\n\nAdditive: the original endpoint is untouched, so no existing consumer moves.","operationId":"get_staff_roster","responses":{"200":{"description":"Staff roster","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffRosterEnvelope"}}}},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/company/users/{id}/pin-access":{"get":{"tags":["crate::features::user_management::pin_access"],"summary":"Authorize a PIN mutation without receiving or storing the PIN itself.","operationId":"get_pin_access","parameters":[{"name":"id","in":"path","description":"Employee account ID","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PinAccess"}}}},"403":{"description":"User editing permission required"},"404":{"description":"Employee not in this company"},"500":{"description":"Authorization unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/crm/activities":{"get":{"tags":["CRM"],"operationId":"list_crm_activities","parameters":[{"name":"entity_type","in":"query","description":"contact | organization | deal | case.","required":true,"schema":{"type":"string"}},{"name":"entity_id","in":"query","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"name":"page","in":"query","description":"1-based page number (default 1).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"page_size","in":"query","description":"Rows per page (default 20, max 100).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Timeline for one record, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityListResponse"}}}},"400":{"description":"Invalid entity_type"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Record not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["CRM"],"operationId":"log_crm_activity","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogActivityRequest"}}},"required":true},"responses":{"201":{"description":"Activity logged (append-only — no PATCH/DELETE exists)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Record not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/activities/feed":{"get":{"tags":["CRM"],"operationId":"crm_activity_feed","parameters":[{"name":"entity_type","in":"query","description":"Optional filter: contact | organization | deal | case.","required":false,"schema":{"type":"string","nullable":true}},{"name":"activity_type","in":"query","description":"Optional filter on activity type (any DB-known value).","required":false,"schema":{"type":"string","nullable":true}},{"name":"page","in":"query","description":"1-based page number (default 1).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"page_size","in":"query","description":"Rows per page (default 20, max 100).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Cross-entity activity feed for the company, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityListResponse"}}}},"400":{"description":"Invalid filter"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/bulk-reassign-owner":{"post":{"tags":["CRM"],"operationId":"crm_bulk_reassign_owner","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkReassignOwnerRequest"}}},"required":true},"responses":{"200":{"description":"Ownership handed over; per-entity counts returned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkReassignOwnerResponse"}}}},"400":{"description":"Validation error (same user, or target not in company)"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/campaigns":{"get":{"tags":["CRM"],"operationId":"list_crm_campaigns","responses":{"200":{"description":"Campaigns for the company","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CampaignDetailResponse"}}}}},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["CRM"],"operationId":"create_crm_campaign","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignRequest"}}},"required":true},"responses":{"201":{"description":"Campaign created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignDetailResponse"}}}},"400":{"description":"Validation error"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/campaigns/{id}":{"get":{"tags":["CRM"],"operationId":"get_crm_campaign","parameters":[{"name":"id","in":"path","description":"Campaign id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Campaign detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignDetailResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Campaign not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["CRM"],"operationId":"update_crm_campaign","parameters":[{"name":"id","in":"path","description":"Campaign id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCampaignRequest"}}},"required":true},"responses":{"200":{"description":"Campaign updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignDetailResponse"}}}},"400":{"description":"Validation error"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Campaign not found"},"409":{"description":"Campaign is not editable in its current status"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/campaigns/{id}/pause":{"post":{"tags":["CRM"],"operationId":"pause_crm_campaign","parameters":[{"name":"id","in":"path","description":"Campaign id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Campaign paused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignDetailResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Campaign not found"},"409":{"description":"Only scheduled or sending campaigns can be paused"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/campaigns/{id}/send":{"post":{"tags":["CRM"],"operationId":"send_crm_campaign","parameters":[{"name":"id","in":"path","description":"Campaign id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendCampaignRequest"}}},"required":true},"responses":{"200":{"description":"Campaign sent; per-recipient rows recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignSendResponse"}}}},"400":{"description":"Validation error (e.g. over the v1 recipient cap)"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Campaign not found"},"409":{"description":"Not sendable in current status, or the suppression check is stale"},"500":{"description":"Internal server error"},"502":{"description":"Email transport failed; campaign returned to draft"}},"security":[{"bearer_auth":[]}]}},"/api/crm/campaigns/{id}/suppression-check":{"get":{"tags":["CRM"],"operationId":"crm_campaign_suppression_check","parameters":[{"name":"id","in":"path","description":"Campaign id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Fresh server-computed suppression counts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressionCheckResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Campaign not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/cases":{"get":{"tags":["CRM"],"operationId":"list_crm_cases","parameters":[{"name":"page","in":"query","description":"1-based page number (default 1).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"page_size","in":"query","description":"Rows per page (default 20, max 100).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"search","in":"query","description":"Case-insensitive prefix match on subject.","required":false,"schema":{"type":"string","nullable":true}},{"name":"status","in":"query","description":"new | open | pending | resolved | closed.","required":false,"schema":{"type":"string","nullable":true}},{"name":"priority","in":"query","description":"low | normal | high | urgent.","required":false,"schema":{"type":"string","nullable":true}},{"name":"contact_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"organization_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"assigned_to_user_id","in":"query","required":false,"schema":{"type":"integer","format":"int32","nullable":true}},{"name":"sort_by","in":"query","description":"One of: `created_at` | `updated_at` | priority | `first_response_due_at` (default `created_at`).","required":false,"schema":{"type":"string","nullable":true}},{"name":"sort_dir","in":"query","description":"asc | desc (default desc).","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/CrmSortDir"}],"nullable":true}}],"responses":{"200":{"description":"Paged cases","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseListResponse"}}}},"400":{"description":"Invalid query parameters"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["CRM"],"operationId":"create_crm_case","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCaseRequest"}}},"required":true},"responses":{"201":{"description":"Case created; SLA due-dates snapshotted from the default policy for its priority","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseDetailResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/cases/{id}":{"get":{"tags":["CRM"],"operationId":"get_crm_case","parameters":[{"name":"id","in":"path","description":"Case id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Case detail with comments + custom fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseDetailResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Case not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["CRM"],"operationId":"update_crm_case","parameters":[{"name":"id","in":"path","description":"Case id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCaseRequest"}}},"required":true},"responses":{"200":{"description":"Case updated (resolved stamps resolved_at; SLA deadlines never re-snapshot)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseDetailResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Case not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/cases/{id}/comments":{"post":{"tags":["CRM"],"operationId":"add_crm_case_comment","parameters":[{"name":"id","in":"path","description":"Case id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCaseCommentRequest"}}},"required":true},"responses":{"201":{"description":"Comment added; the first non-internal comment stamps first_responded_at","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseCommentResponse"}}}},"400":{"description":"Bad request - empty body"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Case not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/cases/{id}/escalate":{"post":{"tags":["CRM"],"operationId":"escalate_crm_case","parameters":[{"name":"id","in":"path","description":"Case id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EscalateCaseRequest"}}},"required":true},"responses":{"200":{"description":"Priority raised one level; system activity logged","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseDetailResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Case not found"},"409":{"description":"Already urgent, or case is resolved/closed"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/contacts":{"get":{"tags":["CRM"],"operationId":"list_crm_contacts","parameters":[{"name":"page","in":"query","description":"1-based page number (default 1).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"page_size","in":"query","description":"Rows per page (default 20, max 100).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"search","in":"query","description":"Case-insensitive prefix match on first/last name and primary email.","required":false,"schema":{"type":"string","nullable":true}},{"name":"organization_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"lifecycle_stage_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"owner_user_id","in":"query","required":false,"schema":{"type":"integer","format":"int32","nullable":true}},{"name":"sort_by","in":"query","description":"One of: `created_at` | `updated_at` | `first_name` | `last_name` (default `created_at`).","required":false,"schema":{"type":"string","nullable":true}},{"name":"sort_dir","in":"query","description":"asc | desc (default desc).","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/CrmSortDir"}],"nullable":true}}],"responses":{"200":{"description":"Paged contacts for the company","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListResponse"}}}},"400":{"description":"Invalid query parameters"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["CRM"],"operationId":"create_crm_contact","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContactRequest"}}},"required":true},"responses":{"201":{"description":"Contact created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactDetailResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/contacts/import":{"post":{"tags":["CRM"],"operationId":"import_crm_contacts","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportContactsRequest"}}},"required":true},"responses":{"202":{"description":"Import accepted; poll the report endpoint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportContactsResponse"}}}},"400":{"description":"Validation error (empty/oversized CSV, missing name columns)"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/contacts/import/{id}":{"get":{"tags":["CRM"],"operationId":"get_crm_contact_import","parameters":[{"name":"id","in":"path","description":"Import id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Import status + report","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactImportReportResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Import not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/contacts/{id}":{"get":{"tags":["CRM"],"operationId":"get_crm_contact","parameters":[{"name":"id","in":"path","description":"Contact id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Contact detail (merged ids resolve to the surviving record)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactDetailResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Contact not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["CRM"],"operationId":"delete_crm_contact","parameters":[{"name":"id","in":"path","description":"Contact id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"Contact soft-deleted"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Contact not found"},"409":{"description":"Contact still referenced by deals or cases"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["CRM"],"operationId":"update_crm_contact","parameters":[{"name":"id","in":"path","description":"Contact id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateContactRequest"}}},"required":true},"responses":{"200":{"description":"Contact updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactDetailResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Contact not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/contacts/{id}/consents":{"get":{"tags":["CRM"],"operationId":"get_crm_contact_consents","parameters":[{"name":"id","in":"path","description":"Contact id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Current consent state per channel+purpose","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactConsentsResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Contact not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["CRM"],"operationId":"put_crm_contact_consents","parameters":[{"name":"id","in":"path","description":"Contact id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutContactConsentsRequest"}}},"required":true},"responses":{"200":{"description":"Consents upserted; each change also logs a consent_changed timeline activity (D-014 audit trail)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactConsentsResponse"}}}},"400":{"description":"Bad request - invalid channel/purpose/status"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Contact not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/contacts/{id}/merge":{"post":{"tags":["CRM"],"operationId":"merge_crm_contacts","parameters":[{"name":"id","in":"path","description":"Target contact id (survives the merge)","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeContactsRequest"}}},"required":true},"responses":{"200":{"description":"Merged; returns the surviving contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactDetailResponse"}}}},"400":{"description":"Bad request - source invalid or same as target"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Target contact not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/deal-pipelines":{"get":{"tags":["CRM"],"operationId":"list_crm_deal_pipelines","responses":{"200":{"description":"All pipelines with nested stages","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DealPipelineResponse"}}}}},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["CRM"],"operationId":"create_crm_deal_pipeline","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DealPipelineRequest"}}},"required":true},"responses":{"201":{"description":"Pipeline created with its stages","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DealPipelineResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/deal-pipelines/{id}":{"delete":{"tags":["CRM"],"operationId":"delete_crm_deal_pipeline","parameters":[{"name":"id","in":"path","description":"Pipeline id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"Pipeline deactivated (stages and history kept)"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Pipeline not found"},"409":{"description":"Pipeline still holds deals"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["CRM"],"operationId":"update_crm_deal_pipeline","parameters":[{"name":"id","in":"path","description":"Pipeline id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDealPipelineRequest"}}},"required":true},"responses":{"200":{"description":"Pipeline updated; stages upserted by code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DealPipelineResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Pipeline not found"},"409":{"description":"A removed stage still holds deals"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/deals":{"get":{"tags":["CRM"],"operationId":"list_crm_deals","parameters":[{"name":"page","in":"query","description":"1-based page number (default 1).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"page_size","in":"query","description":"Rows per page (default 20, max 100).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"search","in":"query","description":"Case-insensitive prefix match on name.","required":false,"schema":{"type":"string","nullable":true}},{"name":"pipeline_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"stage_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"organization_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"owner_user_id","in":"query","required":false,"schema":{"type":"integer","format":"int32","nullable":true}},{"name":"status","in":"query","description":"open | won | lost — derived from the deal's stage flags.","required":false,"schema":{"type":"string","nullable":true}},{"name":"sort_by","in":"query","description":"One of: `created_at` | `updated_at` | name | amount | `expected_close_date` (default `created_at`).","required":false,"schema":{"type":"string","nullable":true}},{"name":"sort_dir","in":"query","description":"asc | desc (default desc).","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/CrmSortDir"}],"nullable":true}}],"responses":{"200":{"description":"Paged deals","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DealListResponse"}}}},"400":{"description":"Invalid query parameters"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["CRM"],"operationId":"create_crm_deal","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDealRequest"}}},"required":true},"responses":{"201":{"description":"Deal created (initial stage recorded in history)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DealDetailResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"409":{"description":"stage_id does not belong to pipeline_id"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/deals/{id}":{"get":{"tags":["CRM"],"operationId":"get_crm_deal","parameters":[{"name":"id","in":"path","description":"Deal id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Deal detail with stage history + custom fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DealDetailResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Deal not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["CRM"],"operationId":"delete_crm_deal","parameters":[{"name":"id","in":"path","description":"Deal id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"Deal soft-deleted"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Deal not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["CRM"],"operationId":"update_crm_deal","parameters":[{"name":"id","in":"path","description":"Deal id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDealRequest"}}},"required":true},"responses":{"200":{"description":"Deal updated (stage changes go through move-stage)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DealDetailResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Deal not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/deals/{id}/move-stage":{"post":{"tags":["CRM"],"operationId":"move_crm_deal_stage","parameters":[{"name":"id","in":"path","description":"Deal id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoveDealStageRequest"}}},"required":true},"responses":{"200":{"description":"Deal moved; history + stage_changed activity written; closed_at follows won/lost flags","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DealDetailResponse"}}}},"400":{"description":"Bad request - unknown stage or invalid field values"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Deal not found"},"409":{"description":"Target stage belongs to a different pipeline"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/email-threads":{"get":{"tags":["CRM"],"operationId":"list_crm_email_threads","parameters":[{"name":"mailbox_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"status","in":"query","description":"open | pending | closed","required":false,"schema":{"type":"string","nullable":true}},{"name":"page","in":"query","description":"1-based page number (default 1).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"page_size","in":"query","description":"Rows per page (default 20, max 100).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Paged inbox threads","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadListResponse"}}}},"400":{"description":"Invalid query parameters"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/email-threads/{id}":{"get":{"tags":["CRM"],"operationId":"get_crm_email_thread","parameters":[{"name":"id","in":"path","description":"Thread id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Thread with messages and recipients","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadDetailResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Thread not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/email-threads/{id}/assign":{"post":{"tags":["CRM"],"operationId":"assign_crm_email_thread","parameters":[{"name":"id","in":"path","description":"Thread id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignThreadRequest"}}},"required":true},"responses":{"200":{"description":"Thread assignment updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadDetailResponse"}}}},"400":{"description":"User is not in this company"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Thread not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/email-threads/{id}/reply":{"post":{"tags":["CRM"],"operationId":"reply_crm_email_thread","parameters":[{"name":"id","in":"path","description":"Thread id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyThreadRequest"}}},"required":true},"responses":{"200":{"description":"Reply sent and recorded on the thread","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailMessageResponse"}}}},"400":{"description":"Validation error (empty body, no counterparty)"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Thread not found"},"409":{"description":"Counterparty is on the suppression list"},"500":{"description":"Internal server error"},"502":{"description":"Email transport failed; nothing recorded"}},"security":[{"bearer_auth":[]}]}},"/api/crm/email-threads/{id}/status":{"patch":{"tags":["CRM"],"operationId":"update_crm_email_thread_status","parameters":[{"name":"id","in":"path","description":"Thread id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateThreadStatusRequest"}}},"required":true},"responses":{"200":{"description":"Thread status updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadDetailResponse"}}}},"400":{"description":"Invalid status"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Thread not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/field-definitions":{"get":{"tags":["CRM"],"operationId":"list_crm_field_definitions","parameters":[{"name":"entity_type","in":"query","description":"contact | organization | deal | case — omit for all entity types.","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Field definitions, optionally filtered by entity type","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FieldDefinitionResponse"}}}}},"400":{"description":"Invalid entity_type"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["CRM"],"operationId":"create_crm_field_definition","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldDefinitionRequest"}}},"required":true},"responses":{"201":{"description":"Field definition created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldDefinitionResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"409":{"description":"A field with this code already exists for the entity type"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/field-definitions/{id}":{"patch":{"tags":["CRM"],"operationId":"update_crm_field_definition","parameters":[{"name":"id","in":"path","description":"Field definition id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFieldDefinitionRequest"}}},"required":true},"responses":{"200":{"description":"Field definition updated (entity_type/code/field_type are immutable)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldDefinitionResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Field definition not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/field-layouts/{entity_type}":{"get":{"tags":["CRM"],"operationId":"get_crm_field_layout","parameters":[{"name":"entity_type","in":"path","description":"contact | organization | deal | case","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The default layout for the entity type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldLayoutResponse"}}}},"400":{"description":"Invalid entity_type"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"No layout defined yet for this entity type"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["CRM"],"operationId":"put_crm_field_layout","parameters":[{"name":"entity_type","in":"path","description":"contact | organization | deal | case","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldLayoutRequest"}}},"required":true},"responses":{"200":{"description":"Default layout replaced (created if missing)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldLayoutResponse"}}}},"400":{"description":"Bad request - unknown or mismatched field definition"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/field-values/{entity_type}/{entity_id}":{"get":{"tags":["CRM"],"operationId":"get_crm_field_values","parameters":[{"name":"entity_type","in":"path","description":"contact | organization | deal | case","required":true,"schema":{"type":"string"}},{"name":"entity_id","in":"path","description":"Record id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Custom-field values for the record, keyed by field code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldValuesResponse"}}}},"400":{"description":"Invalid entity_type"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Record not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["CRM"],"operationId":"put_crm_field_values","parameters":[{"name":"entity_type","in":"path","description":"contact | organization | deal | case","required":true,"schema":{"type":"string"}},{"name":"entity_id","in":"path","description":"Record id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldValuesRequest"}}},"required":true},"responses":{"200":{"description":"Values upserted (null clears, absent keys untouched); returns the full value map","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldValuesResponse"}}}},"400":{"description":"Bad request - unknown definition, type mismatch, or foreign option value"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Record not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/lifecycle-stages":{"get":{"tags":["CRM"],"operationId":"list_crm_lifecycle_stages","responses":{"200":{"description":"All lifecycle stages (including inactive), sorted by sort_order","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/LifecycleStageResponse"}}}}},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["CRM"],"operationId":"create_crm_lifecycle_stage","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LifecycleStageRequest"}}},"required":true},"responses":{"201":{"description":"Lifecycle stage created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LifecycleStageResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"409":{"description":"A stage with this code already exists"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/lifecycle-stages/seed-defaults":{"post":{"tags":["CRM"],"operationId":"seed_crm_lifecycle_stage_defaults","responses":{"200":{"description":"Default stages inserted where missing; returns the full stage list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/LifecycleStageResponse"}}}}},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/lifecycle-stages/{id}":{"delete":{"tags":["CRM"],"operationId":"delete_crm_lifecycle_stage","parameters":[{"name":"id","in":"path","description":"Lifecycle stage id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"Lifecycle stage deactivated (rows referencing it keep resolving)"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Lifecycle stage not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["CRM"],"operationId":"update_crm_lifecycle_stage","parameters":[{"name":"id","in":"path","description":"Lifecycle stage id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLifecycleStageRequest"}}},"required":true},"responses":{"200":{"description":"Lifecycle stage updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LifecycleStageResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Lifecycle stage not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/mailboxes":{"get":{"tags":["CRM"],"operationId":"list_crm_mailboxes","responses":{"200":{"description":"Connected mailboxes (credentials never included)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailboxListResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["CRM"],"operationId":"connect_crm_mailbox","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectMailboxRequest"}}},"required":true},"responses":{"201":{"description":"Mailbox connected (credentials sealed at rest)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailboxResponse"}}}},"400":{"description":"Validation error (OAuth providers not yet available; IMAP config required)"},"403":{"description":"Forbidden - insufficient permissions"},"409":{"description":"A mailbox with this address is already connected"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/mailboxes/{id}":{"delete":{"tags":["CRM"],"operationId":"delete_crm_mailbox","parameters":[{"name":"id","in":"path","description":"Mailbox id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"Mailbox disconnected; threads and messages removed"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Mailbox not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["CRM"],"operationId":"update_crm_mailbox","parameters":[{"name":"id","in":"path","description":"Mailbox id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMailboxRequest"}}},"required":true},"responses":{"200":{"description":"Mailbox updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailboxResponse"}}}},"400":{"description":"Validation error"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Mailbox not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/organizations":{"get":{"tags":["CRM"],"operationId":"list_crm_organizations","parameters":[{"name":"page","in":"query","description":"1-based page number (default 1).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"page_size","in":"query","description":"Rows per page (default 20, max 100).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"search","in":"query","description":"Case-insensitive prefix match on name and domain.","required":false,"schema":{"type":"string","nullable":true}},{"name":"industry_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"lifecycle_stage_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"owner_user_id","in":"query","required":false,"schema":{"type":"integer","format":"int32","nullable":true}},{"name":"sort_by","in":"query","description":"One of: `created_at` | `updated_at` | name (default `created_at`).","required":false,"schema":{"type":"string","nullable":true}},{"name":"sort_dir","in":"query","description":"asc | desc (default desc).","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/CrmSortDir"}],"nullable":true}}],"responses":{"200":{"description":"Paged organizations for the company","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationListResponse"}}}},"400":{"description":"Invalid query parameters"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["CRM"],"operationId":"create_crm_organization","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrganizationRequest"}}},"required":true},"responses":{"201":{"description":"Organization created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationDetailResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/organizations/{id}":{"get":{"tags":["CRM"],"operationId":"get_crm_organization","parameters":[{"name":"id","in":"path","description":"Organization id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Organization detail (merged ids resolve to the surviving record)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationDetailResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Organization not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["CRM"],"operationId":"delete_crm_organization","parameters":[{"name":"id","in":"path","description":"Organization id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"Organization soft-deleted"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Organization not found"},"409":{"description":"Organization still referenced by contacts, deals or cases"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["CRM"],"operationId":"update_crm_organization","parameters":[{"name":"id","in":"path","description":"Organization id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOrganizationRequest"}}},"required":true},"responses":{"200":{"description":"Organization updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationDetailResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Organization not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/organizations/{id}/link-account":{"post":{"tags":["CRM"],"operationId":"link_crm_organization_account","parameters":[{"name":"id","in":"path","description":"Organization id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkAccountRequest"}}},"required":true},"responses":{"200":{"description":"Account linked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationDetailResponse"}}}},"400":{"description":"Account not found or already linked to another organization"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Organization not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/organizations/{id}/merge":{"post":{"tags":["CRM"],"operationId":"merge_crm_organizations","parameters":[{"name":"id","in":"path","description":"Target organization id (survives the merge)","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeOrganizationsRequest"}}},"required":true},"responses":{"200":{"description":"Merged; returns the surviving organization","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationDetailResponse"}}}},"400":{"description":"Bad request - source invalid or same as target"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Target organization not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/segments":{"get":{"tags":["CRM"],"operationId":"list_crm_segments","responses":{"200":{"description":"All segments with member counts","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SegmentResponse"}}}}},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["CRM"],"operationId":"create_crm_segment","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentRequest"}}},"required":true},"responses":{"201":{"description":"Segment created (dynamic segments store filter_json; evaluation ships with A7c)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/segments/{id}":{"delete":{"tags":["CRM"],"operationId":"delete_crm_segment","parameters":[{"name":"id","in":"path","description":"Segment id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"Segment deleted (membership cascades)"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Segment not found"},"409":{"description":"Campaigns still use this segment"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["CRM"],"operationId":"update_crm_segment","parameters":[{"name":"id","in":"path","description":"Segment id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSegmentRequest"}}},"required":true},"responses":{"200":{"description":"Segment updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Segment not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/segments/{id}/members":{"post":{"tags":["CRM"],"operationId":"add_crm_segment_member","parameters":[{"name":"id","in":"path","description":"Segment id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSegmentMemberRequest"}}},"required":true},"responses":{"204":{"description":"Contact added (idempotent; static segments only)"},"400":{"description":"Dynamic segment or unknown contact"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Segment not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/segments/{id}/members/{contact_id}":{"delete":{"tags":["CRM"],"operationId":"remove_crm_segment_member","parameters":[{"name":"id","in":"path","description":"Segment id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"name":"contact_id","in":"path","description":"Contact id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"Contact removed"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Segment not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/segments/{id}/preview":{"get":{"tags":["CRM"],"operationId":"preview_crm_segment","parameters":[{"name":"id","in":"path","description":"Segment id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Member count + first members (static segments)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentPreviewResponse"}}}},"400":{"description":"Dynamic segments are not evaluatable yet"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Segment not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/sla-policies":{"get":{"tags":["CRM"],"operationId":"list_crm_sla_policies","responses":{"200":{"description":"All SLA policies","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SlaPolicyResponse"}}}}},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["CRM"],"operationId":"create_crm_sla_policy","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlaPolicyRequest"}}},"required":true},"responses":{"201":{"description":"SLA policy created (one default per priority)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlaPolicyResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/sla-policies/{id}":{"patch":{"tags":["CRM"],"operationId":"update_crm_sla_policy","parameters":[{"name":"id","in":"path","description":"SLA policy id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSlaPolicyRequest"}}},"required":true},"responses":{"200":{"description":"SLA policy updated (in-flight cases keep their snapshotted deadlines)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlaPolicyResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"SLA policy not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/suppressions":{"get":{"tags":["CRM"],"operationId":"list_crm_suppressions","parameters":[{"name":"page","in":"query","description":"1-based page number (default 1).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"page_size","in":"query","description":"Rows per page (default 20, max 100).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"search","in":"query","description":"Prefix match on email.","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Company do-not-send list (D-014 platform floor)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressionListResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["CRM"],"operationId":"add_crm_suppression","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSuppressionRequest"}}},"required":true},"responses":{"201":{"description":"Address suppressed (idempotent upsert)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressionResponse"}}}},"400":{"description":"Bad request - invalid email or reason"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/crm/suppressions/{id}":{"delete":{"tags":["CRM"],"operationId":"remove_crm_suppression","parameters":[{"name":"id","in":"path","description":"Suppression id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"Suppression removed"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Suppression not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/customer-returns":{"get":{"tags":["customer-returns"],"operationId":"listCustomerReturns","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"original_sales_receipt_id","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"account_id","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"location_id","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Customer return list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerReturnListResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]},"post":{"tags":["customer-returns"],"operationId":"createCustomerReturn","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCustomerReturnRequest"}}},"required":true},"responses":{"201":{"description":"Customer return created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerReturnResponse"}}}},"400":{"description":"Invalid return"},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]}},"/api/customer-returns/{id}":{"get":{"tags":["customer-returns"],"operationId":"getCustomerReturn","parameters":[{"name":"id","in":"path","description":"Customer return ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Customer return","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerReturnResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/customer-returns/{id}/post":{"post":{"tags":["customer-returns"],"operationId":"postCustomerReturn","parameters":[{"name":"id","in":"path","description":"Customer return ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostCustomerReturnRequest"}}},"required":true},"responses":{"200":{"description":"Customer return posted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerReturnResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Return exceeds remaining quantity"},"422":{"description":"Invalid state"}},"security":[{"jwt":[]}]}},"/api/dashboard-values":{"get":{"tags":["Dashboard"],"operationId":"get_dashboard_values","responses":{"200":{"description":"Dashboard statistics and metrics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardValuesResponse"}}}},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/datasets":{"get":{"tags":["crate::features::datasets::handlers"],"operationId":"getDatasets","parameters":[{"name":"limit","in":"query","description":"Number of items to return","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Number of items to skip","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"List of datasets","content":{"application/json":{"schema":{}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["crate::features::datasets::handlers"],"operationId":"createDataset","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatasetInput"}}},"required":true},"responses":{"201":{"description":"Dataset created","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/datasets/{id}":{"get":{"tags":["crate::features::datasets::handlers"],"operationId":"getDataset","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Dataset details","content":{"application/json":{"schema":{}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Dataset not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["crate::features::datasets::handlers"],"operationId":"updateDataset","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDataset"}}},"required":true},"responses":{"200":{"description":"Dataset updated","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"403":{"description":"Insufficient permissions"},"404":{"description":"Dataset not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["crate::features::datasets::handlers"],"operationId":"deleteDataset","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Dataset deleted"},"403":{"description":"Insufficient permissions"},"404":{"description":"Dataset not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/datasets/{id}/enrich":{"post":{"tags":["crate::features::datasets::handlers"],"operationId":"enrichDataset","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"description":"Dataset enriched","content":{"application/json":{"schema":{}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Dataset not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/datasets/{id}/export":{"post":{"tags":["crate::features::datasets::handlers"],"operationId":"exportEnrichedDataset","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"description":"Dataset exported","content":{"application/json":{"schema":{}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Dataset not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/datasets/{id}/versions":{"get":{"tags":["crate::features::datasets::handlers"],"operationId":"getDatasetVersions","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Dataset versions","content":{"application/json":{"schema":{}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Dataset not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/device-diagnostics":{"post":{"tags":["Device Diagnostics"],"operationId":"upload_device_diagnostics","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceDiagnosticsUploadRequest"}}},"required":true},"responses":{"201":{"description":"Diagnostic bundle stored","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceDiagnosticsUploadResponse"}}}},"400":{"description":"Malformed bundle or unknown trigger"},"403":{"description":"Auto-upload disabled for this company"},"413":{"description":"Bundle exceeds the size cap"},"429":{"description":"Daily upload limit reached"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/displays/claim":{"post":{"tags":["digital_displays"],"summary":"Admin claims a pairing code, creating and linking a new display.","operationId":"claim_pairing_code","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimPairingCodeRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisplayResponse"}}}},"404":{"description":"Code not found or expired"}},"security":[{"jwt":[]}]}},"/api/displays/media/upload":{"post":{"tags":["digital_displays"],"summary":"Upload an image or video for display content.","description":"Multipart form with a single `file` field. Returns the public URL plus a\n`kind` hint (`image` | `video`) so the frontend can prefill the right\ncontent field.","operationId":"upload_display_media","requestBody":{"content":{"application/json":{"schema":{"default":null,"nullable":true}}},"required":true},"responses":{"200":{"description":"Media uploaded","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid file"},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"jwt":[]}]}},"/api/events":{"get":{"tags":["events"],"summary":"GET /api/events","description":"Returns the domain-event outbox for the **authenticated user's company\nonly**, newest first. Supports filtering by `event_type`, `entity_type`,\n`entity_id`, dispatched status, and an `occurred_at` date range.","operationId":"listDomainEvents","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"event_type","in":"query","description":"Filter by exact `event_type` (e.g. \"product.created\"). See\n`models::domain_event::domain_event_type` for the allowed catalogue.","required":false,"schema":{"type":"string","nullable":true}},{"name":"entity_type","in":"query","description":"Filter by `entity_type` (e.g. \"product\").","required":false,"schema":{"type":"string","nullable":true}},{"name":"entity_id","in":"query","description":"Filter by `entity_id`.","required":false,"schema":{"type":"string","nullable":true}},{"name":"dispatched","in":"query","description":"Filter by dispatch status: `true` = already fanned out\n(`dispatched_at IS NOT NULL`), `false` = still pending.","required":false,"schema":{"type":"boolean","nullable":true}},{"name":"from","in":"query","description":"ISO-8601 lower bound on `occurred_at` (e.g. \"2025-01-01T00:00:00Z\").","required":false,"schema":{"type":"string","nullable":true}},{"name":"to","in":"query","description":"ISO-8601 upper bound on `occurred_at`.","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Paginated domain event list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainEventListResponse"}}}},"403":{"description":"Insufficient permissions"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/events/{id}":{"get":{"tags":["events"],"summary":"GET /api/events/{id}","description":"Returns a single domain event, scoped to the caller's company. Returns 404\nfor both an unknown id and an id owned by another tenant (no existence\nleak across companies).","operationId":"getDomainEvent","parameters":[{"name":"id","in":"path","description":"Domain event UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The domain event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainEventResponse"}}}},"400":{"description":"Invalid id"},"403":{"description":"Insufficient permissions"},"404":{"description":"Not found"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/external-references":{"post":{"tags":["ExternalReferences"],"summary":"Bulk create/update source-system references for existing local entities.","operationId":"upsert_external_references","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUpsertExternalReferencesRequest"}}},"required":true},"responses":{"200":{"description":"Per-row upsert outcome","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUpsertExternalReferencesResponse"}}}},"400":{"description":"Invalid request"},"403":{"description":"Insufficient permissions"}},"security":[{"bearer_auth":[]}]}},"/api/external-references/entity/{entity_type}/{entity_id}":{"get":{"tags":["ExternalReferences"],"summary":"List every source-system reference attached to a local entity.","operationId":"list_entity_external_references","parameters":[{"name":"entity_type","in":"path","description":"Local entity kind: product, account or category","required":true,"schema":{"type":"string"}},{"name":"entity_id","in":"path","description":"Local entity id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"References for the entity","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ExternalReferenceResponse"}}}}},"400":{"description":"Invalid entity type"},"403":{"description":"Insufficient permissions"}},"security":[{"bearer_auth":[]}]}},"/api/external-references/resolve":{"get":{"tags":["ExternalReferences"],"summary":"Resolve a local entity id from a source-system identity.","operationId":"resolve_external_reference","parameters":[{"name":"entity_type","in":"query","description":"Local entity kind: product, account or category","required":true,"schema":{"type":"string"}},{"name":"source_system","in":"query","description":"Source system label, e.g. 'exact_online' or 'legacy_pos'","required":true,"schema":{"type":"string"}},{"name":"external_id","in":"query","description":"Record id in the source system","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Resolution result (entity_id is null when unmapped)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveExternalReferenceResponse"}}}},"400":{"description":"Invalid parameters"},"403":{"description":"Insufficient permissions"}},"security":[{"bearer_auth":[]}]}},"/api/external-references/{id}":{"delete":{"tags":["ExternalReferences"],"summary":"Delete one external reference by id.","operationId":"delete_external_reference","parameters":[{"name":"id","in":"path","description":"External reference id (UUID)","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"400":{"description":"Invalid id"},"403":{"description":"Insufficient permissions"},"404":{"description":"Not found"}},"security":[{"bearer_auth":[]}]}},"/api/fulfillment/delivery/options":{"get":{"tags":["fulfillment"],"operationId":"getDeliveryOptions","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryOptionsResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"bearer_auth":[]}]}},"/api/fulfillment/delivery/{id}":{"get":{"tags":["fulfillment"],"operationId":"getDeliveryDetails","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryDetailsResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"bearer_auth":[]}]}},"/api/fulfillment/delivery/{id}/assign":{"post":{"tags":["fulfillment"],"summary":"`POST /fulfillment/delivery/{id}/assign` — `{id}` is the ORDER id (locked","description":"contract: `frontend/apps/admin/app/pages/Sales/SalesOrderDetailPage.tsx`\n`assignDelivery()`). Creates-or-updates the order's active shipment\n(`shipments.status` stays `packing` — the shipment hasn't left the\nwarehouse yet) and writes `webapp_salesorder.delivery_status = \"assigned\"`\nso the UI's button-visibility logic (which branches on that exact string)\nadvances to \"Out For Delivery\".","operationId":"assignDelivery","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignDeliveryRequest"}}},"required":true},"responses":{"200":{"description":"Delivery assigned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShipmentActionResponse"}}}},"400":{"description":"Bad request"},"403":{"description":"Insufficient permissions"},"404":{"description":"Order not found"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/fulfillment/delivery/{id}/delivered":{"post":{"tags":["fulfillment"],"summary":"`POST /fulfillment/delivery/{id}/delivered` — `{id}` is the ORDER id.","description":"Body: `{ proof_of_delivery_url, delivery_notes }` (both optional, matching\n`SalesOrderDetailPage.tsx`'s `markDelivered()`). Transitions the active\nshipment to `delivered` (stamping `delivered_at`), writes\n`webapp_salesorder.delivery_status = \"delivered\"`, advances the order\nstatus `shipped -> delivered` when applicable (ADR-002), and — inside the\nSAME transaction (ADR-004 same-tx durability) — enqueues a\n`delivery_completed` confirmation email job IF the tenant has opted in via\n`CompanySettings.fulfillment_notifications.delivered_email_enabled`\n(default OFF, additive).","operationId":"markDelivered","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkDeliveredRequest"}}},"required":true},"responses":{"200":{"description":"Marked delivered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShipmentActionResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Order not found, or has no active shipment"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/fulfillment/delivery/{id}/out-for-delivery":{"post":{"tags":["fulfillment"],"summary":"`POST /fulfillment/delivery/{id}/out-for-delivery` — `{id}` is the ORDER","description":"id. No request body (the UI sends a bare POST). Transitions the active\nshipment to `out_for_delivery` and writes\n`webapp_salesorder.delivery_status = \"out_for_delivery\"`; when the order's\nown status is `ready_to_ship`, also advances it to `shipped` per the\nADR-002 state machine (see [`compute_shipment_order_transition`]).","operationId":"markOutForDelivery","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MarkOutForDeliveryRequest"}],"nullable":true}}},"required":false},"responses":{"200":{"description":"Marked out for delivery","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShipmentActionResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Order not found, or has no active shipment"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/fulfillment/delivery/{id}/pod":{"get":{"tags":["fulfillment"],"summary":"`GET /fulfillment/delivery/{id}/pod` — `{id}` is the ORDER id.","description":"Company-scoped download of the POD image bytes uploaded via the handler\nabove (mirrors `backups::handlers::download_backup_handler`). 404s (never\n403) for a foreign company's order/shipment or one with no POD on file.","operationId":"downloadProofOfDelivery","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"POD image bytes"},"403":{"description":"Insufficient permissions"},"404":{"description":"Shipment or POD not found"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["fulfillment"],"summary":"`POST /fulfillment/delivery/{id}/pod` — `{id}` is the ORDER id. Multipart","description":"image upload (same convention as\n`products::image_handlers::upload_product_image`): stores the\nproof-of-delivery photo via the SAME storage-service pattern `backups`\nuses (`BackupStorageService` — R2 in prod, `BACKUP_STORAGE=mock` local\nfilesystem in tests), under `pod/{company_id}/{shipment_id}.{ext}` in the\nPRIVATE bucket (never the public product-image bucket — a delivery photo\ncan show a customer's address/doorway). Sets `shipments.pod_url` to the\nopaque object key; retrieve the bytes via the matching GET below.","operationId":"uploadProofOfDelivery","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Proof-of-delivery image file","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/PodUploadForm"}}},"required":true},"responses":{"201":{"description":"POD uploaded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PodUploadResponse"}}}},"400":{"description":"Bad request / no image file provided"},"403":{"description":"Insufficient permissions"},"404":{"description":"Order not found, or has no shipment"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/fulfillment/documents/{id}/payments":{"get":{"tags":["fulfillment"],"summary":"`GET /fulfillment/documents/{id}/payments` — the payment ledger plus the","description":"invoice's freshly-computed derived state (E-5.3, ADR-006).","operationId":"listInvoicePayments","parameters":[{"name":"id","in":"path","description":"Invoice document ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Payment ledger","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListPaymentsResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Document not found"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["fulfillment"],"summary":"`POST /fulfillment/documents/{id}/payments` — record a payment against an","description":"ISSUED invoice (E-5.3, ADR-006). `amount` must be strictly positive, the\ndocument must resolve to an issued `invoice` (never a draft/void invoice,\ndelivery note, or credit note), and an overpaying amount is rejected\nunless `invoice_settings.allow_overpayment` is on. Idempotent on\n`idempotency_key`: a replay returns the original payment (still 201 —\nthe client asked to record a payment and got exactly that payment back).","operationId":"recordInvoicePayment","parameters":[{"name":"id","in":"path","description":"Invoice document ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordPaymentRequest"}}},"required":true},"responses":{"201":{"description":"Payment recorded (or idempotent replay)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentActionResponse"}}}},"400":{"description":"payment_method is blank"},"403":{"description":"Insufficient permissions"},"404":{"description":"Invoice not found"},"409":{"description":"Document is not an issued invoice"},"422":{"description":"amount is not positive, or would overpay the invoice"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/fulfillment/documents/{id}/pdf":{"get":{"tags":["fulfillment"],"summary":"`GET /fulfillment/documents/{id}/pdf` — generate-on-demand, then cache","description":"into the pre-existing `pdf_data` bytea column. A document rendered once\nserves the cached bytes on every subsequent request.","operationId":"getFulfillmentDocumentPdf","parameters":[{"name":"id","in":"path","description":"Document ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Document PDF bytes"},"403":{"description":"Insufficient permissions"},"404":{"description":"Document not found"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/fulfillment/orders/{order_id}/documents":{"post":{"tags":["fulfillment"],"summary":"`POST /fulfillment/orders/{order_id}/documents` — the DERIVED create flow","description":"(E-5.2, ADR-005/ADR-006). Totals are always computed server-side from the\norder's line-item price snapshots; any `subtotal`/`tax_amount`/\n`total_amount` present in the request body is logged with `warn!` and\nthen ignored — see `document_service::create_document_from_order`.","operationId":"createFulfillmentDocumentFromOrder","parameters":[{"name":"order_id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDocumentFromOrderRequest"}}},"required":true},"responses":{"201":{"description":"Document created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FulfillmentDocumentResponse"}}}},"400":{"description":"Invalid document_type, or credit_note missing/with an invalid parent_invoice_id"},"403":{"description":"Insufficient permissions"},"404":{"description":"Order not found, or (for credit_note) parent invoice not found"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/fulfillment/payments/{id}/void":{"post":{"tags":["fulfillment"],"summary":"`POST /fulfillment/payments/{id}/void` — void a payment (never deletes","description":"it) and recompute the invoice's derived `payment_status` in the same\ntransaction (E-5.3, ADR-006).","operationId":"voidInvoicePayment","parameters":[{"name":"id","in":"path","description":"Payment ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoidPaymentRequest"}}},"required":true},"responses":{"200":{"description":"Payment voided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentActionResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Payment not found"},"409":{"description":"Payment is already voided"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/goods-receipts":{"get":{"tags":["procurement"],"operationId":"listGoodsReceipts","parameters":[{"name":"search","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"status","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"supplier_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"purchase_order_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"location_id","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Goods-receipt list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoodsReceiptListResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]},"post":{"tags":["procurement"],"operationId":"createGoodsReceipt","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGoodsReceiptRequest"}}},"required":true},"responses":{"201":{"description":"Goods receipt created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoodsReceiptResponse"}}}},"400":{"description":"Invalid receipt"},"403":{"description":"Forbidden"},"409":{"description":"Document-number conflict"}},"security":[{"jwt":[]}]}},"/api/goods-receipts/{id}":{"get":{"tags":["procurement"],"operationId":"getGoodsReceipt","parameters":[{"name":"id","in":"path","description":"Goods receipt ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Goods receipt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoodsReceiptResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/goods-receipts/{id}/approve-discrepancy":{"post":{"tags":["procurement"],"operationId":"approveGoodsReceiptDiscrepancy","parameters":[{"name":"id","in":"path","description":"Goods receipt ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostGoodsReceiptRequest"}}},"required":true},"responses":{"200":{"description":"Discrepancy approved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoodsReceiptResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"}},"security":[{"jwt":[]}]}},"/api/goods-receipts/{id}/post":{"post":{"tags":["procurement"],"operationId":"postGoodsReceipt","parameters":[{"name":"id","in":"path","description":"Goods receipt ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostGoodsReceiptRequest"}}},"required":true},"responses":{"200":{"description":"Receipt posted atomically","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoodsReceiptResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Unresolved discrepancy or invalid state"}},"security":[{"jwt":[]}]}},"/api/horeca-public/orders/{public_token}":{"get":{"tags":["crate::features::horeca::public_handlers"],"summary":"`GET /api/horeca-public/orders/{public_token}` — anonymous live status.","description":"\"Nothing sensitive\" (design §3.4): no line items, prices, or customer\ninfo, just enough for a status page to render. Also reused directly as\nthe kiosk-authenticated `GET /api/horeca/kiosk/orders/{public_token}`\nhandler (`routes.rs`) — same \"nothing sensitive\" contract applies there\ntoo, so no separate implementation is needed.","operationId":"get_public_order_status","parameters":[{"name":"public_token","in":"path","description":"Anonymous order status-lookup token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Order status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicOrderStatusResponse"}}}},"404":{"description":"Order not found"},"500":{"description":"Internal server error"}}}},"/api/horeca-public/t/{table_token}":{"get":{"tags":["crate::features::horeca::public_handlers"],"summary":"`GET /api/horeca-public/t/{table_token}` — menu + table context for the","description":"table the QR code on the table points at.","operationId":"get_table_menu","parameters":[{"name":"table_token","in":"path","description":"Table QR token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Table menu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableMenuResponse"}}}},"404":{"description":"Table not found"},"500":{"description":"Internal server error"}}}},"/api/horeca-public/t/{table_token}/orders":{"post":{"tags":["crate::features::horeca::public_handlers"],"summary":"`POST /api/horeca-public/t/{table_token}/orders` — place a QR order.","description":"Always `source = qr`, `order_type = dine_in` (design §3.4: the whole\npoint of scanning a table's code is that table).","operationId":"create_qr_order","parameters":[{"name":"table_token","in":"path","description":"Table QR token","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateChannelOrderInput"}}},"required":true},"responses":{"201":{"description":"Order placed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateChannelOrderResponse"}}}},"404":{"description":"Table not found"},"422":{"description":"Validation failed (no items, too many lines, qty too high, modifier validation)"},"500":{"description":"Internal server error"}}}},"/api/horeca/floor-plans":{"get":{"tags":["crate::features::horeca::handlers"],"operationId":"list_floor_plans","responses":{"200":{"description":"List of floor plans","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FloorPlan"}}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["crate::features::horeca::handlers"],"operationId":"create_floor_plan","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFloorPlanInput"}}},"required":true},"responses":{"201":{"description":"Floor plan created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FloorPlan"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/floor-plans/{id}":{"get":{"tags":["crate::features::horeca::handlers"],"operationId":"get_floor_plan","parameters":[{"name":"id","in":"path","description":"Floor plan ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Floor plan with objects","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FloorPlan"}}}},"404":{"description":"Not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["crate::features::horeca::handlers"],"operationId":"update_floor_plan","parameters":[{"name":"id","in":"path","description":"Floor plan ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFloorPlanInput"}}},"required":true},"responses":{"200":{"description":"Floor plan updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FloorPlan"}}}},"404":{"description":"Not found"},"409":{"description":"Conflict - removing objects with active table sessions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["crate::features::horeca::handlers"],"operationId":"delete_floor_plan","parameters":[{"name":"id","in":"path","description":"Floor plan ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Floor plan deleted"},"409":{"description":"Conflict - active sessions exist"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/floor-plans/{id}/state":{"get":{"tags":["crate::features::horeca::handlers"],"operationId":"get_floor_plan_state","parameters":[{"name":"id","in":"path","description":"Floor plan ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Floor plan state with table statuses","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FloorPlanStateResponse"}}}},"404":{"description":"Not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/kds/bump":{"post":{"tags":["crate::features::horeca::handlers"],"summary":"`POST /api/horeca/kds/bump` — advance one item, a whole course, or every","description":"item on an order to `preparing`/`ready`/`served` (design §3.2's item\nstatus machine, `status::OpenOrderItemStatus`).\n\nTransition rule: an item only advances if its CURRENT status is the\nunique legal predecessor of `to_status` (`OpenOrderItemStatus::\nrequired_predecessor`, e.g. `ready`'s predecessor is `preparing`). For\n`scope = \"item\"` a failing transition is a 422\n(`IllegalItemTransition`) — the caller named exactly one item and it\ncouldn't advance. For `scope = \"course\"`/`\"order\"` a non-eligible item\n(e.g. already `served`, or still `sent` when the batch targets `ready`)\nis silently left alone instead of failing the whole batch — a\nmixed-status course/order is the normal case, not an error.\n\nOrder-status side effects (new in this wave — previously nothing ever\nadvanced `open_orders.status` past `accepted`, see\n`models::domain_event::domain_event_type::HORECA_ORDER_READY`'s old\n\"NOT YET EMITTED\" doc): if any item actually bumped and the order is\nstill `accepted`, the order opportunistically advances to `in_prep`\nfirst (bridging the state machine's `Accepted -> InPrep` step, which no\nother handler drives). Then, if `to_status` is `ready` or `served` and\nEVERY non-voided item on the order is now `ready` or `served`, the order\nadvances `in_prep -> ready`, stamps `open_orders.ready_at`, and emits\n`horeca.order.ready` + its webhook. This never skips a step in\n`OpenOrderStatus`'s state machine (e.g. `placed -> ready` directly) —\nan order still sitting in `placed` (never staff-accepted) is left alone.\n\nEvery item that lands on `ready` in this call — regardless of scope —\nemits `horeca.order.item_ready` + its webhook (item 9: consistent\nper-item events regardless of whether the bump came in as\n`item`/`course`/`order` scope).","operationId":"kds_bump","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KdsBumpInput"}}},"required":true},"responses":{"200":{"description":"Items bumped","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KdsBumpResponse"}}}},"404":{"description":"Open order or item not found"},"422":{"description":"Invalid scope/to_status, order already terminal, or an illegal single-item transition"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/kds/queue":{"get":{"tags":["crate::features::horeca::handlers"],"summary":"`GET /api/horeca/kds/queue?since={cursor}` — poll-cheap KDS delta feed","description":"(design §3.7). Returns every non-terminal order updated after `since`\n(RFC3339; omit for a full snapshot) plus its items, and a `cursor` to\npass as `since` on the next poll.","operationId":"get_kds_queue","parameters":[{"name":"since","in":"query","description":"RFC3339 cursor from a previous response. Applied with a small server-side overlap margin (see KDS_CURSOR_OVERLAP_MARGIN_SECONDS) to avoid missing rows from out-of-order transaction commits, so an order the client already has may occasionally be redelivered — harmless, dedup by id","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"KDS queue delta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KdsQueueResponse"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/kiosk-devices":{"get":{"tags":["crate::features::horeca::kiosk"],"summary":"`GET /api/horeca/kiosk-devices` — staff list, company-scoped.","operationId":"list_kiosk_devices","responses":{"200":{"description":"Kiosk devices","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/KioskDeviceResponse"}}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/kiosk-devices/claim":{"post":{"tags":["crate::features::horeca::kiosk"],"summary":"`POST /api/horeca/kiosk-devices/claim` — staff claims a pairing code,","description":"minting the kiosk's bearer token in the same transaction the\n`kiosk_devices` row is created in (so a device row never exists without\na token, and vice versa).","operationId":"claim_kiosk_device","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimKioskDeviceInput"}}},"required":true},"responses":{"201":{"description":"Kiosk device claimed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KioskDeviceResponse"}}}},"404":{"description":"Pairing code not found or expired"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/kiosk-devices/{id}":{"delete":{"tags":["crate::features::horeca::kiosk"],"summary":"`DELETE /api/horeca/kiosk-devices/{id}` — revoke: marks the device row","description":"revoked AND revokes its `access_tokens` row (via\n`access_tokens::services::revoke_token`) so a stolen/decommissioned\nkiosk's credential stops working immediately, not just its DB row.","operationId":"revoke_kiosk_device","parameters":[{"name":"id","in":"path","description":"Kiosk device ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Kiosk device revoked"},"404":{"description":"Kiosk device not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["crate::features::horeca::kiosk"],"summary":"`PATCH /api/horeca/kiosk-devices/{id}` — rename only (Horeca Ordering","description":"Suite wave B4, item 7). Staff-gated the same way [`revoke_kiosk_device`]\nis; company-scoped.","operationId":"rename_kiosk_device","parameters":[{"name":"id","in":"path","description":"Kiosk device ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameKioskDeviceInput"}}},"required":true},"responses":{"200":{"description":"Kiosk device renamed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KioskDeviceResponse"}}}},"404":{"description":"Kiosk device not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/kiosk/menu":{"get":{"tags":["crate::features::horeca::kiosk"],"operationId":"kiosk_menu","responses":{"200":{"description":"Menu","content":{"application/json":{"schema":{"$ref":"#/components/schemas/crate.features.horeca.dto.MenuResponse"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/kiosk/orders":{"post":{"tags":["crate::features::horeca::kiosk"],"operationId":"create_kiosk_order","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateChannelOrderInput"}}},"required":true},"responses":{"201":{"description":"Order placed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/crate.features.horeca.dto.CreateChannelOrderResponse"}}}},"422":{"description":"Validation failed"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders":{"get":{"tags":["crate::features::horeca::handlers"],"summary":"`GET /api/horeca/open-orders?status=placed&needs_pos=1` — desktop POS","description":"incoming-orders panel (design §4.4). See [`ListOpenOrdersQuery::needs_pos`]\ndoc for what's not wired yet.","operationId":"list_open_orders","parameters":[{"name":"status","in":"query","description":"Filter by exact status string","required":false,"schema":{"type":"string","nullable":true}},{"name":"needs_pos","in":"query","description":"1 = only orders needing POS attention, i.e. NOT created by a POS terminal","required":false,"schema":{"type":"integer","format":"int32","nullable":true}},{"name":"table_session_id","in":"query","description":"Only orders bound to this table session — the server-authoritative table→order lookup","required":false,"schema":{"type":"string","nullable":true}},{"name":"open_only","in":"query","description":"1 = exclude terminal (completed/cancelled) orders","required":false,"schema":{"type":"integer","format":"int32","nullable":true}}],"responses":{"200":{"description":"List of open orders","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OpenOrder"}}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["crate::features::horeca::handlers"],"operationId":"create_open_order","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOpenOrderInput"}}},"required":true},"responses":{"201":{"description":"Open order created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenOrder"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders/{id}":{"get":{"tags":["crate::features::horeca::handlers"],"operationId":"get_open_order","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Open order with items","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenOrderWithItems"}}}},"404":{"description":"Not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders/{id}/accept":{"post":{"tags":["crate::features::horeca::handlers"],"operationId":"accept_open_order","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Order accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenOrder"}}}},"404":{"description":"Open order not found"},"422":{"description":"Illegal transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IllegalTransitionResponse"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders/{id}/assign":{"post":{"tags":["crate::features::horeca::handlers"],"summary":"`POST /api/horeca/open-orders/{id}/assign` — hand a tab to another server","description":"(POS-ship workstream E, audit 05 gap (a)).","operationId":"assign_open_order","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignOpenOrderInput"}}},"required":true},"responses":{"200":{"description":"Order reassigned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenOrder"}}}},"404":{"description":"Order not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders/{id}/close":{"post":{"tags":["crate::features::horeca::handlers"],"operationId":"close_open_order","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CloseOrderInput"}}},"required":true},"responses":{"200":{"description":"Order closed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CloseOrderResponse"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders/{id}/items":{"post":{"tags":["crate::features::horeca::handlers"],"operationId":"add_order_item","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddOrderItemInput"}}},"required":true},"responses":{"201":{"description":"Item added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenOrderItem"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders/{id}/items/{item_id}":{"put":{"tags":["crate::features::horeca::handlers"],"operationId":"update_order_item","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}},{"name":"item_id","in":"path","description":"Order item ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOrderItemInput"}}},"required":true},"responses":{"200":{"description":"Item updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenOrderItem"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["crate::features::horeca::handlers"],"operationId":"delete_order_item","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}},{"name":"item_id","in":"path","description":"Order item ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Item removed"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders/{id}/items/{item_id}/void":{"post":{"tags":["crate::features::horeca::handlers"],"summary":"`POST /api/horeca/open-orders/{id}/items/{item_id}/void` — void a line","description":"with a reason, keeping the row (POS-ship workstream E; audit 05 finding\nB3).\n\nVoided lines are excluded from the running total, from the kitchen router\nand from settle, but stay in the table so the void is auditable.","operationId":"void_order_item","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}},{"name":"item_id","in":"path","description":"Order item ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoidOrderItemInput"}}},"required":true},"responses":{"200":{"description":"Item voided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenOrderItem"}}}},"404":{"description":"Order item not found"},"409":{"description":"Item already voided"},"422":{"description":"Missing reason, or already served and policy forbids it"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders/{id}/move":{"post":{"tags":["crate::features::horeca::handlers"],"summary":"`POST /api/horeca/open-orders/{id}/move` — move a tab to another table, or","description":"off the floor entirely (POS-ship workstream E, audit 05 gap (a)).","operationId":"move_open_order","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoveOpenOrderInput"}}},"required":true},"responses":{"200":{"description":"Order moved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenOrder"}}}},"404":{"description":"Order or target table session not found"},"409":{"description":"Order is terminal and cannot be moved"},"422":{"description":"Unknown order_type"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders/{id}/print-proforma":{"post":{"tags":["crate::features::horeca::handlers"],"summary":"`POST /api/horeca/open-orders/{id}/print-proforma` — enqueue a pro-forma","description":"bill for the guest (POS-ship workstream E, audit 05 gap (d)).\n\nA pro-forma is NOT a receipt: no money moves, no `webapp_salesreceipt` row\nis created, and the order stays open. It is the \"what do we owe\" slip a\nguest asks for before paying.","operationId":"print_proforma","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Pro-forma enqueued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrintProformaResponse"}}}},"404":{"description":"Order not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders/{id}/quote":{"get":{"tags":["crate::features::horeca::handlers"],"summary":"`GET /api/horeca/open-orders/{id}/quote` — what this tab costs, priced by","description":"the server, without settling it.\n\nThe till must display THIS as the amount due, not a figure it derived from\nits own tax configuration — see `OpenOrderQuoteResponse`.","operationId":"quote_open_order","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Server-priced total for this order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenOrderQuoteResponse"}}}},"404":{"description":"Order not found"},"422":{"description":"Order has no sellable items, or a product is unavailable"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["crate::features::horeca::handlers"],"summary":"`POST /api/horeca/open-orders/{id}/quote` — the same quote, for a chosen","description":"PART of the bill.\n\nAdditive sibling of the GET, not a replacement: the GET keeps answering the\nwhole-tab question every deployed till and the service app already ask, and\nthis one answers \"what does THIS guest owe\" for a split. It exists because\nthe till had no way to ask: `settle` has taken `item_ids` / `split` since\nH8 but the quote could not, so the desktop bill dialog fell back to the\nwhole-tab total for every split and the payment keypad demanded the entire\nbill from the first guest (verified on the Windows till, 2026-09-04).\n\nA POST rather than a GET body: a GET with a request body is not reliably\ncarried by fetch, by the Tauri HTTP client, or by intermediaries, so the\nbody would have been silently dropped — which on this endpoint means\nsilently quoting the whole tab again.\n\nStill read-only: it creates no receipt, claims no line, and does not\nadvance the order.","operationId":"quote_open_order_part","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteOpenOrderInput"}}},"required":true},"responses":{"200":{"description":"Server-priced total for this order and, when a bill decision was sent, for that part of it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenOrderQuoteResponse"}}}},"404":{"description":"Order not found"},"422":{"description":"Invalid split, unclaimable item, unconfigured service charge, tip over the ceiling, or nothing left to settle"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders/{id}/reject":{"post":{"tags":["crate::features::horeca::handlers"],"operationId":"reject_open_order","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RejectOrderInput"}}},"required":true},"responses":{"200":{"description":"Order rejected (cancelled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenOrder"}}}},"404":{"description":"Open order not found"},"422":{"description":"Illegal transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IllegalTransitionResponse"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders/{id}/request-bill":{"post":{"tags":["crate::features::horeca::handlers"],"operationId":"request_bill","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Bill requested","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenOrder"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders/{id}/send-course":{"post":{"tags":["crate::features::horeca::handlers"],"operationId":"send_course","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendCourseInput"}}},"required":true},"responses":{"200":{"description":"Course sent to kitchen","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendCourseResponse"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders/{id}/settle":{"post":{"tags":["crate::features::horeca::handlers"],"operationId":"settle_open_order","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettleOpenOrderInput"}}},"required":true},"responses":{"200":{"description":"Order settled (or already-settled receipt returned)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettleOpenOrderResponse"}}}},"404":{"description":"Open order not found"},"409":{"description":"Order changed since the client's copy was read (expected_updated_at mismatch) — reload and retry"},"422":{"description":"Order cannot be settled (cancelled, no items, modifier/tender validation failed)"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/open-orders/{id}/settlements/{key}":{"get":{"tags":["crate::features::horeca::receipt_recovery"],"operationId":"get_settlement_receipt","parameters":[{"name":"id","in":"path","description":"Open order ID","required":true,"schema":{"type":"string"}},{"name":"key","in":"path","description":"Original settlement retry key","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Existing receipt; no payment is submitted","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SettleOpenOrderResponse"}],"nullable":true}}}},"403":{"description":"Receipt view permission required"},"500":{"description":"Receipt lookup unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/printer-profiles":{"get":{"tags":["crate::features::horeca::handlers"],"operationId":"list_printer_profiles","responses":{"200":{"description":"List of printer profiles","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PrinterProfileWithRules"}}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["crate::features::horeca::handlers"],"operationId":"upsert_printer_profiles","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertPrinterProfilesInput"}}},"required":true},"responses":{"200":{"description":"Printer profiles upserted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertPrinterProfilesResponse"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/tables/{object_id}/close":{"post":{"tags":["crate::features::horeca::handlers"],"operationId":"close_table_session","parameters":[{"name":"object_id","in":"path","description":"Floor plan object ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CloseTableSessionInput"}}},"required":true},"responses":{"200":{"description":"Table session closed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableSession"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/tables/{object_id}/open":{"post":{"tags":["crate::features::horeca::handlers"],"operationId":"open_table_session","parameters":[{"name":"object_id","in":"path","description":"Floor plan object ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenTableSessionInput"}}},"required":true},"responses":{"201":{"description":"Table session opened","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableSession"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/tables/{object_id}/qr-tokens":{"get":{"tags":["crate::features::horeca::handlers"],"operationId":"list_table_qr_tokens","parameters":[{"name":"object_id","in":"path","description":"Floor plan object ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"QR tokens for this table","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TableQrTokenResponse"}}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["crate::features::horeca::handlers"],"summary":"`POST /api/horeca/tables/{object_id}/qr-tokens` — rotate: revokes every","description":"currently-active token for the table, then issues a fresh one. A table\nonly ever has at most one active token at a time (the QR code printed on\nthe table physically points at exactly one), so \"generate\" and \"rotate\"\nare the same operation here — matches the coordinator's stated contract.","operationId":"create_table_qr_token","parameters":[{"name":"object_id","in":"path","description":"Floor plan object ID","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"New QR token issued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableQrTokenResponse"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/horeca/tables/{object_id}/qr-tokens/{token_id}":{"delete":{"tags":["crate::features::horeca::handlers"],"operationId":"revoke_table_qr_token","parameters":[{"name":"object_id","in":"path","description":"Floor plan object ID","required":true,"schema":{"type":"string"}},{"name":"token_id","in":"path","description":"QR token ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"QR token revoked"},"404":{"description":"Token not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/image-documents":{"get":{"tags":["image_documents"],"summary":"List image documents for the authenticated company.","operationId":"list_image_documents","parameters":[{"name":"product_id","in":"query","description":"Filter to documents belonging to this product","required":false,"schema":{"type":"string","nullable":true}},{"name":"page","in":"query","description":"1-based page number (default 1)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"page_size","in":"query","description":"Items per page (default 20, max 100)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Image document list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageDocumentListResponse"}}}},"403":{"description":"Insufficient permissions"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["image_documents"],"summary":"Create a new image document.","operationId":"create_image_document","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateImageDocumentRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageDocumentResponse"}}}},"403":{"description":"Insufficient permissions"}},"security":[{"bearer_auth":[]}]}},"/api/image-documents/{id}":{"get":{"tags":["image_documents"],"summary":"Get a single image document by id.","operationId":"get_image_document","parameters":[{"name":"id","in":"path","description":"Image document id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Image document detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageDocumentResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Not found"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["image_documents"],"summary":"Update an existing image document. `version` is incremented server-side","description":"on every call; the request body cannot influence it.","operationId":"update_image_document","parameters":[{"name":"id","in":"path","description":"Image document id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateImageDocumentRequest"}}},"required":true},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageDocumentResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Not found"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["image_documents"],"summary":"Soft-delete an image document (sets `is_deleted` + `deleted_at`; never a","description":"hard delete).","operationId":"delete_image_document","parameters":[{"name":"id","in":"path","description":"Image document id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"Deleted"},"403":{"description":"Insufficient permissions"},"404":{"description":"Not found"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/bootstrap-account-links":{"post":{"tags":["integrations"],"summary":"Atomically attach reviewed QBPOS customer `ListIDs` to existing live Vender","description":"accounts. This endpoint intentionally refuses to move an existing mapping\nor replace a different QuickBooks identity already attached to an account.","operationId":"qbpos_bootstrap_account_links","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposAccountBootstrapRequest"}}},"required":true},"responses":{"200":{"description":"Reviewed account links recorded atomically","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposAccountBootstrapResponse"}}}},"400":{"description":"Invalid, duplicate, stale, or conflicting link set"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/commands":{"get":{"tags":["integrations"],"operationId":"qbpos_list_commands","responses":{"200":{"description":"Recent company-scoped agent commands","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposCommandListResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["integrations"],"operationId":"qbpos_create_command","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposCreateCommandRequest"}}},"required":true},"responses":{"201":{"description":"Command queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposCommandResponse"}}}},"403":{"description":"Insufficient permissions"},"409":{"description":"An equivalent run command is already active"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/commands/claim":{"post":{"tags":["integrations"],"operationId":"qbpos_claim_command","responses":{"200":{"description":"Oldest pending command, if any","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposClaimCommandResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/commands/{id}/complete":{"post":{"tags":["integrations"],"operationId":"qbpos_complete_command","parameters":[{"name":"id","in":"path","description":"Command UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposCompleteCommandRequest"}}},"required":true},"responses":{"200":{"description":"Command completed or failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposCommandResponse"}}}},"400":{"description":"Invalid error detail"},"403":{"description":"Insufficient permissions"},"404":{"description":"Claimed command not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/config":{"get":{"tags":["integrations"],"operationId":"qbpos_get_agent_config","responses":{"200":{"description":"Effective company-scoped agent configuration","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposAgentConfigResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/field-conflicts":{"get":{"tags":["integrations"],"operationId":"qbpos_list_field_conflicts","responses":{"200":{"description":"Current unresolved account/product field conflicts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposFieldConflictListResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/field-conflicts/{id}/resolve":{"post":{"tags":["integrations"],"operationId":"qbpos_resolve_field_conflict","parameters":[{"name":"id","in":"path","description":"Conflict state UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposResolveFieldConflictRequest"}}},"required":true},"responses":{"200":{"description":"Conflict resolution recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposResolveFieldConflictResponse"}}}},"400":{"description":"Conflict state is incomplete"},"403":{"description":"Insufficient permissions"},"404":{"description":"Conflict not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/heartbeat":{"post":{"tags":["integrations"],"summary":"Agent heartbeat: upsert this company's QBPOS agent status","operationId":"qbpos_heartbeat","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposHeartbeatRequest"}}},"required":true},"responses":{"200":{"description":"Heartbeat recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposAgentStatusResponse"}}}},"400":{"description":"Invalid heartbeat payload"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/operations":{"get":{"tags":["integrations"],"operationId":"qbpos_list_operations","parameters":[{"name":"status","in":"query","description":"Filter by status","required":false,"schema":{"type":"string","nullable":true}},{"name":"entity_type","in":"query","description":"Filter by entity type","required":false,"schema":{"type":"string","nullable":true}},{"name":"origin","in":"query","description":"Filter by origin","required":false,"schema":{"type":"string","nullable":true}},{"name":"batch_id","in":"query","description":"Filter by batch","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"limit","in":"query","description":"Page size (1-200, default 50)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Page offset","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Company-scoped write operations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposOperationListResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["integrations"],"operationId":"qbpos_create_operations","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposCreateOperationsRequest"}}},"required":true},"responses":{"201":{"description":"Operations enqueued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposCreateOperationsResponse"}}}},"400":{"description":"Invalid operation input"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/operations/batches/{id}/approve":{"post":{"tags":["integrations"],"operationId":"qbpos_approve_operation_batch","parameters":[{"name":"id","in":"path","description":"Batch UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"All pending operations in the batch queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposOperationListResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/operations/claim":{"post":{"tags":["integrations"],"operationId":"qbpos_claim_operations","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposClaimOperationsRequest"}}},"required":true},"responses":{"200":{"description":"Claimed operations (empty when write-back is disabled or queue is drained)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposClaimOperationsResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/operations/{id}":{"get":{"tags":["integrations"],"operationId":"qbpos_get_operation","parameters":[{"name":"id","in":"path","description":"Operation UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Operation detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposOperationResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Operation not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/operations/{id}/approve":{"post":{"tags":["integrations"],"operationId":"qbpos_approve_operation","parameters":[{"name":"id","in":"path","description":"Operation UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Operation transitioned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposOperationResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Operation not awaiting approval"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/operations/{id}/cancel":{"post":{"tags":["integrations"],"operationId":"qbpos_cancel_operation","parameters":[{"name":"id","in":"path","description":"Operation UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Operation transitioned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposOperationResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Operation not cancellable (already claimed or finished)"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/operations/{id}/complete":{"post":{"tags":["integrations"],"operationId":"qbpos_complete_operation","parameters":[{"name":"id","in":"path","description":"Operation UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposCompleteOperationRequest"}}},"required":true},"responses":{"200":{"description":"Operation applied or failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposOperationResponse"}}}},"400":{"description":"Invalid completion detail"},"403":{"description":"Insufficient permissions"},"404":{"description":"Claimed operation not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/operations/{id}/reject":{"post":{"tags":["integrations"],"operationId":"qbpos_reject_operation","parameters":[{"name":"id","in":"path","description":"Operation UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Operation transitioned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposOperationResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Operation not awaiting approval"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/reconcile-accounts":{"post":{"tags":["integrations"],"summary":"Read-only account preflight. It uses the same identity resolver as the","description":"account importer and performs no account, external-reference, or audit-log\nwrites.","operationId":"qbpos_reconcile_accounts","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposAccountReconcileRequest"}}},"required":true},"responses":{"200":{"description":"Per-row account create/update/conflict projection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposAccountReconcileResponse"}}}},"400":{"description":"Invalid or oversized reconciliation batch"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/reconcile-products":{"post":{"tags":["integrations"],"summary":"Read-only product preflight. It performs the exact company-scoped identity","description":"resolution used by the importer without inserting, updating, linking, or\nauditing any product.","operationId":"qbpos_reconcile_products","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposProductReconcileRequest"}}},"required":true},"responses":{"200":{"description":"Per-row create/update/conflict projection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposProductReconcileResponse"}}}},"400":{"description":"Invalid or oversized reconciliation batch"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/runs":{"get":{"tags":["integrations"],"operationId":"qbpos_list_runs","parameters":[{"name":"limit","in":"query","description":"Page size, 1..100","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Zero-based offset","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"status","in":"query","description":"Optional run status","required":false,"schema":{"type":"string","nullable":true}},{"name":"mode","in":"query","description":"Optional run mode","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Company-scoped QBPOS run history","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposRunListResponse"}}}},"400":{"description":"Invalid filter"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/runs/{id}":{"get":{"tags":["integrations"],"operationId":"qbpos_get_run","parameters":[{"name":"id","in":"path","description":"Run UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"QBPOS run detail and structured logs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QbposRunDetailResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Run not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/qbpos/status":{"get":{"tags":["integrations"],"summary":"Admin UI: read this company's QBPOS agent status (null when no agent has","description":"ever reported in)","operationId":"qbpos_status","responses":{"200":{"description":"Current agent status, or null when no agent has reported","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/QbposAgentStatusResponse"}],"nullable":true}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/integrations/{id}/credentials":{"put":{"tags":["Integrations"],"summary":"Store (write-only) credentials for an integration.","operationId":"set_integration_credentials","parameters":[{"name":"id","in":"path","description":"Integration id (UUID)","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetIntegrationCredentialsRequest"}}},"required":true},"responses":{"200":{"description":"Credentials stored (never returned)"},"400":{"description":"Invalid request"},"403":{"description":"Insufficient permissions"},"404":{"description":"Integration not found"},"503":{"description":"Credential encryption unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/internal/analytics":{"get":{"tags":["internal_admin"],"operationId":"getInternalAnalytics","responses":{"200":{"description":"Platform analytics summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/articles":{"get":{"tags":["internal_admin"],"operationId":"internal_list_onboarding_articles","parameters":[{"name":"category","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"app_target","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"is_published","in":"query","required":false,"schema":{"type":"boolean","nullable":true}}],"responses":{"200":{"description":"List of help articles (drafts included)","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/HelpArticleResponse"}}}}},"403":{"description":"Platform admin access required"},"500":{"description":"Internal server error"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["internal_admin"],"operationId":"internal_create_onboarding_article","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateHelpArticleRequest"}}},"required":true},"responses":{"201":{"description":"Created help article","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HelpArticleResponse"}}}},"403":{"description":"Platform admin access required"},"500":{"description":"Internal server error"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/internal/articles/{id}":{"put":{"tags":["internal_admin"],"operationId":"internal_update_onboarding_article","parameters":[{"name":"id","in":"path","description":"Help article ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateHelpArticleRequest"}}},"required":true},"responses":{"200":{"description":"Updated help article","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HelpArticleResponse"}}}},"403":{"description":"Platform admin access required"},"404":{"description":"Article not found"},"500":{"description":"Internal server error"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["internal_admin"],"operationId":"internal_delete_onboarding_article","parameters":[{"name":"id","in":"path","description":"Help article ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Article deleted"},"403":{"description":"Platform admin access required"},"404":{"description":"Article not found"},"500":{"description":"Internal server error"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/internal/audit-log":{"get":{"tags":["internal_admin"],"operationId":"listInternalAuditLog","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"action","in":"query","description":"Filter by exact action string (e.g. \"request\").","required":false,"schema":{"type":"string","nullable":true}},{"name":"entity","in":"query","description":"Filter by entity type (e.g. \"account\").","required":false,"schema":{"type":"string","nullable":true}},{"name":"entity_id","in":"query","description":"Filter by entity ID.","required":false,"schema":{"type":"string","nullable":true}},{"name":"actor_id","in":"query","description":"Filter by actor UUID string.","required":false,"schema":{"type":"string","nullable":true}},{"name":"company_id","in":"query","description":"Filter by company UUID string.","required":false,"schema":{"type":"string","nullable":true}},{"name":"success","in":"query","description":"Filter by success flag.","required":false,"schema":{"type":"boolean","nullable":true}},{"name":"from","in":"query","description":"ISO-8601 lower bound on `created_at`.","required":false,"schema":{"type":"string","nullable":true}},{"name":"to","in":"query","description":"ISO-8601 upper bound on `created_at`.","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"List of audit log entries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditLogListResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/agreements":{"post":{"tags":["internal_admin"],"operationId":"createCommercialAgreement","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgreementRevision"}}},"required":true},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommercialAgreement"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/agreements/{company_id}":{"get":{"tags":["internal_admin"],"operationId":"listCommercialAgreements","parameters":[{"name":"company_id","in":"path","description":"Company ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CommercialAgreement"}}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/agreements/{id}/status":{"put":{"tags":["internal_admin"],"operationId":"setCommercialAgreementStatus","parameters":[{"name":"id","in":"path","description":"Revision UUID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetAgreementStatus"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommercialAgreement"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/checkout-policy":{"get":{"tags":["internal_admin"],"operationId":"getCommercialCheckoutPolicy","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommercialCheckoutPolicyView"}}}}},"security":[{"bearer_auth":[]}]},"put":{"tags":["internal_admin"],"operationId":"updateCommercialCheckoutPolicy","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCommercialCheckoutPolicy"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommercialCheckoutPolicyView"}}}},"400":{"description":""},"409":{"description":""}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/companies/{company_id}/ai-credits":{"get":{"tags":["internal_admin"],"operationId":"getCompanyAiCredits","parameters":[{"name":"company_id","in":"path","description":"Company id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Managed-AI allowance, usage and top-ups","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyAiCreditsResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Company not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/companies/{company_id}/ai-credits/topups":{"post":{"tags":["internal_admin"],"operationId":"createCompanyAiCreditTopup","parameters":[{"name":"company_id","in":"path","description":"Company id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAiCreditTopupRequest"}}},"required":true},"responses":{"200":{"description":"The granted top-up","content":{"application/json":{"schema":{"$ref":"#/components/schemas/crate.features.billing.ai_credits.AiCreditTopup"}}}},"400":{"description":"credits out of range or expires_at in the past","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Company not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/companies/{company_id}/resolved-features":{"get":{"tags":["internal_admin"],"operationId":"resolveCompanyFeatures","parameters":[{"name":"company_id","in":"path","description":"Company id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Effective feature set with provenance","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ResolvedFeatureRow"}}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No active contract for this company","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/contracts":{"get":{"tags":["internal_admin"],"operationId":"listContracts","parameters":[{"name":"company_id","in":"query","description":"Filter to one company","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Contracts","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ContractRow"}}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]},"post":{"tags":["internal_admin"],"operationId":"createContract","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContractRequest"}}},"required":true},"responses":{"201":{"description":"Created contract","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContractRow"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/contracts/{id}":{"put":{"tags":["internal_admin"],"operationId":"updateContract","parameters":[{"name":"id","in":"path","description":"Contract UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateContractRequest"}}},"required":true},"responses":{"200":{"description":"Updated contract","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContractRow"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Contract not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/contracts/{id}/overrides/{code}":{"put":{"tags":["internal_admin"],"operationId":"setContractOverride","parameters":[{"name":"id","in":"path","description":"Contract UUID","required":true,"schema":{"type":"string"}},{"name":"code","in":"path","description":"Feature code","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetContractOverrideRequest"}}},"required":true},"responses":{"204":{"description":"Override set"},"400":{"description":"Core feature or invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown contract or feature","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/features":{"get":{"tags":["internal_admin"],"operationId":"listBillingFeatures","responses":{"200":{"description":"Feature catalog","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/BillingFeatureRow"}}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/features/{code}":{"put":{"tags":["internal_admin"],"operationId":"updateBillingFeature","parameters":[{"name":"code","in":"path","description":"Feature code, e.g. pos.checkout","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBillingFeatureRequest"}}},"required":true},"responses":{"200":{"description":"Updated feature","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingFeatureRow"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown feature code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/offer-features":{"get":{"tags":["internal_admin"],"operationId":"listOfferFeatureChoices","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OfferFeatureChoice"}}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/offers":{"get":{"tags":["internal_admin"],"operationId":"listInternalOffers","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InternalOfferRevision"}}}}}},"security":[{"bearer_auth":[]}]},"post":{"tags":["internal_admin"],"operationId":"createInternalOfferRevision","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOfferRevision"}}},"required":true},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalOfferRevision"}}}},"409":{"description":"Revision already exists"}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/offers/{id}/publication":{"put":{"tags":["internal_admin"],"operationId":"publishInternalOfferRevision","parameters":[{"name":"id","in":"path","description":"Revision UUID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishOfferRevision"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalOfferRevision"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/plans/{id}/features":{"get":{"tags":["internal_admin"],"operationId":"listPlanFeatures","parameters":[{"name":"id","in":"path","description":"Plan UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Features composing this plan","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PlanFeatureRow"}}}}},"400":{"description":"Invalid plan id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/plans/{id}/features/{code}":{"put":{"tags":["internal_admin"],"operationId":"setPlanFeature","parameters":[{"name":"id","in":"path","description":"Plan UUID","required":true,"schema":{"type":"string"}},{"name":"code","in":"path","description":"Feature code, e.g. pos.checkout","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPlanFeatureRequest"}}},"required":true},"responses":{"200":{"description":"Feature set on plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanFeatureRow"}}}},"400":{"description":"Core feature, or invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown feature or plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["internal_admin"],"operationId":"removePlanFeature","parameters":[{"name":"id","in":"path","description":"Plan UUID","required":true,"schema":{"type":"string"}},{"name":"code","in":"path","description":"Feature code","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Feature removed from plan"},"400":{"description":"Core feature cannot be removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown feature code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/revenue":{"get":{"tags":["internal_admin"],"operationId":"getInternalCommercialRevenue","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommercialRevenueReport"}}}},"403":{"description":""},"500":{"description":""}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/services":{"get":{"tags":["internal_admin"],"operationId":"listInternalCommercialServices","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CommercialServiceRevision"}}}}}},"security":[{"bearer_auth":[]}]},"post":{"tags":["internal_admin"],"operationId":"createCommercialServiceRevision","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCommercialService"}}},"required":true},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommercialServiceRevision"}}}},"400":{"description":""},"409":{"description":""}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/services/{id}/publication":{"put":{"tags":["internal_admin"],"operationId":"publishCommercialServiceRevision","parameters":[{"name":"id","in":"path","description":"Service revision UUID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishOfferRevision"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommercialServiceRevision"}}}},"400":{"description":""},"404":{"description":""}},"security":[{"bearer_auth":[]}]}},"/api/internal/billing/subscriptions":{"get":{"tags":["internal_admin"],"operationId":"listInternalSubscriptions","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"status","in":"query","description":"Filter by exact subscription status (e.g. \"active\", \"trialing\", \"canceled\").","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Cross-tenant subscription list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionListResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/dashboard/stats":{"get":{"tags":["internal_admin"],"operationId":"getInternalDashboardStats","responses":{"200":{"description":"Dashboard statistics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardStats"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/device-diagnostics":{"get":{"tags":["Internal Admin"],"operationId":"list_device_diagnostics","parameters":[{"name":"company_id","in":"query","description":"Filter by tenant company id.","required":false,"schema":{"type":"string","nullable":true}},{"name":"user_id","in":"query","description":"Filter by uploader user id.","required":false,"schema":{"type":"integer","format":"int32","nullable":true}},{"name":"trigger","in":"query","description":"Filter by trigger value.","required":false,"schema":{"type":"string","nullable":true}},{"name":"date_from","in":"query","description":"ISO-8601 lower bound on `created_at`.","required":false,"schema":{"type":"string","nullable":true}},{"name":"date_to","in":"query","description":"ISO-8601 upper bound on `created_at`.","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Diagnostic uploads (bundle omitted)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceDiagnosticsListResponse"}}}},"403":{"description":"Platform admin access required"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/internal/device-diagnostics/{id}":{"get":{"tags":["Internal Admin"],"operationId":"get_device_diagnostics","parameters":[{"name":"id","in":"path","description":"Diagnostic upload UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Full diagnostic row including the bundle","content":{"application/json":{"schema":{}}}},"403":{"description":"Platform admin access required"},"404":{"description":"Not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/internal/emails/log":{"get":{"tags":["internal_admin"],"operationId":"listInternalEmailLog","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"status","in":"query","description":"Filter by exact status (e.g. \"sent\", \"failed\").","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Paginated email delivery log","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailLogListResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/emails/send-test":{"post":{"tags":["internal_admin"],"operationId":"sendInternalTestEmail","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendTestEmailRequest"}}},"required":true},"responses":{"200":{"description":"Test email sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendTestEmailResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/feature-flags":{"get":{"tags":["internal_admin"],"operationId":"listInternalFeatureFlags","responses":{"200":{"description":"Feature flags","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FeatureFlag"}}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]},"post":{"tags":["internal_admin"],"operationId":"createInternalFeatureFlag","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFlagRequest"}}},"required":true},"responses":{"200":{"description":"Created flag","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureFlag"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Flag key already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/feature-flags/{id}":{"put":{"tags":["internal_admin"],"operationId":"updateInternalFeatureFlag","parameters":[{"name":"id","in":"path","description":"Flag UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFlagRequest"}}},"required":true},"responses":{"200":{"description":"Updated flag","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureFlag"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Flag not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["internal_admin"],"operationId":"deleteInternalFeatureFlag","parameters":[{"name":"id","in":"path","description":"Flag UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MutationResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Flag not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/home-speaker":{"get":{"tags":["internal_admin"],"operationId":"getHomeSpeakerSettings","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HomeSpeakerSettings"}}}},"403":{"description":"Platform access required"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["internal_admin"],"operationId":"updateHomeSpeakerSettings","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HomeSpeakerSettings"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HomeSpeakerSettings"}}}},"400":{"description":"Invalid settings"},"403":{"description":"Platform access required"}},"security":[{"bearer_auth":[]}]}},"/api/internal/leads":{"get":{"tags":["internal_admin"],"operationId":"listInternalLeads","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"search","in":"query","description":"Free-text match on name / email / company.","required":false,"schema":{"type":"string","nullable":true}},{"name":"status","in":"query","description":"Filter by exact status (e.g. \"new\", \"contacted\").","required":false,"schema":{"type":"string","nullable":true}},{"name":"lead_type","in":"query","description":"Filter by exact `lead_type` (e.g. \"contact\", \"signup\").","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Paginated lead / signup funnel","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadListResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/login":{"post":{"tags":["internal_admin"],"operationId":"internalAdminLogin","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Internal admin login successful"},"401":{"description":"Invalid credentials"},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}}}},"/api/internal/plans":{"get":{"tags":["internal_admin"],"operationId":"listInternalPlans","responses":{"200":{"description":"Plan catalog","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/BillingPlan"}}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]},"post":{"tags":["internal_admin"],"operationId":"createInternalPlan","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePlanRequest"}}},"required":true},"responses":{"200":{"description":"Created plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingPlan"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Plan code already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/plans/{id}":{"put":{"tags":["internal_admin"],"operationId":"updateInternalPlan","parameters":[{"name":"id","in":"path","description":"Plan UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePlanRequest"}}},"required":true},"responses":{"200":{"description":"Updated plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingPlan"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Plan not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["internal_admin"],"operationId":"deleteInternalPlan","parameters":[{"name":"id","in":"path","description":"Plan UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MutationResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Plan not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/system/health":{"get":{"tags":["internal_admin"],"operationId":"getInternalSystemHealth","responses":{"200":{"description":"System health snapshot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SystemHealthResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/tenants":{"get":{"tags":["internal_admin"],"operationId":"listInternalTenants","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"search","in":"query","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"List of tenants","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantListResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]},"post":{"tags":["internal_admin"],"operationId":"createInternalTenant","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTenantRequest"}}},"required":true},"responses":{"201":{"description":"Created tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tenant"}}}},"400":{"description":"Invalid tenant data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/tenants/{id}":{"get":{"tags":["internal_admin"],"operationId":"getInternalTenant","parameters":[{"name":"id","in":"path","description":"Tenant ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Tenant details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tenant"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]},"put":{"tags":["internal_admin"],"operationId":"updateInternalTenant","parameters":[{"name":"id","in":"path","description":"Tenant ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTenantRequest"}}},"required":true},"responses":{"200":{"description":"Updated tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tenant"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["internal_admin"],"operationId":"deleteInternalTenant","parameters":[{"name":"id","in":"path","description":"Tenant ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Tenant archived","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tenant"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/tenants/{id}/activate":{"post":{"tags":["internal_admin"],"operationId":"activateInternalTenant","parameters":[{"name":"id","in":"path","description":"Tenant ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Activated tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tenant"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/tenants/{id}/deactivate":{"post":{"tags":["internal_admin"],"operationId":"deactivateInternalTenant","parameters":[{"name":"id","in":"path","description":"Tenant ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deactivated tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tenant"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/token/refresh":{"post":{"tags":["internal_admin"],"operationId":"internalAdminRefreshToken","responses":{"200":{"description":"Token refreshed successfully"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/internal/users":{"get":{"tags":["internal_admin"],"operationId":"listInternalUsers","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"search","in":"query","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"List of users","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserListResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]},"post":{"tags":["internal_admin"],"operationId":"createInternalUser","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInternalUserRequest"}}},"required":true},"responses":{"201":{"description":"Created user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInternalUserResponse"}}}},"400":{"description":"Invalid user data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/users/{id}":{"get":{"tags":["internal_admin"],"operationId":"getInternalUser","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"User details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUser"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]},"put":{"tags":["internal_admin"],"operationId":"updateInternalUser","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"integer","format":"int32"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateInternalUserRequest"}}},"required":true},"responses":{"200":{"description":"Updated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUser"}}}},"400":{"description":"Invalid user data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"User or tenant not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["internal_admin"],"operationId":"deleteInternalUser","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Deleted user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MutationResponse"}}}},"400":{"description":"Invalid delete request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/users/{id}/impersonate":{"post":{"tags":["internal_admin"],"operationId":"impersonateInternalUser","parameters":[{"name":"id","in":"path","description":"Target user id","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Impersonation token minted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImpersonateResponse"}}}},"400":{"description":"User has no tenant profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/users/{id}/reset-password":{"post":{"tags":["internal_admin"],"operationId":"resetInternalUserPassword","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Password reset","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordResponse"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/internal/users/{id}/toggle-active":{"post":{"tags":["internal_admin"],"operationId":"toggleInternalUserActive","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"Updated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUser"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/inventory-costs/exceptions":{"get":{"tags":["procurement"],"summary":"`GET /api/inventory-costs/exceptions` — the cost ledger's queued exceptions.","operationId":"listInventoryCostExceptions","parameters":[{"name":"reason","in":"query","description":"Only this reason (e.g. `missing_inventory_cost`).","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","description":"Page size (1–200, default 50).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Rows to skip.","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Sales, returns and adjustments the cost ledger could not cost normally, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCostExceptionsResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]}},"/api/inventory-costs/opening-balances":{"post":{"tags":["procurement"],"operationId":"initializeOpeningInventoryCost","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitializeOpeningCostRequest"}}},"required":true},"responses":{"201":{"description":"Opening inventory cost initialized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitializeOpeningCostResponse"}}}},"400":{"description":"Invalid quantity or cost"},"403":{"description":"Forbidden or inaccessible location"},"404":{"description":"Stock row not found"},"409":{"description":"Cost already initialized or stock changed"}},"security":[{"jwt":[]}]}},"/api/inventory-costs/reconciliations":{"post":{"tags":["procurement"],"summary":"`POST /api/inventory-costs/reconciliations` — see the module docs.","operationId":"reconcileInventoryCost","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReconcileInventoryCostRequest"}}},"required":true},"responses":{"201":{"description":"Cost ledger quantity aligned with stock at the current average cost","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReconcileInventoryCostResponse"}}}},"400":{"description":"Invalid quantity"},"403":{"description":"Forbidden or inaccessible location"},"404":{"description":"No stock row or no cost row (enter an opening cost instead)"},"409":{"description":"Costing is off, nothing to reconcile, or stock changed since it was read"}},"security":[{"jwt":[]}]}},"/api/inventory-counts":{"get":{"tags":["inventory-counts"],"operationId":"listInventoryCounts","parameters":[{"name":"location_id","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"status","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","default":100,"nullable":true,"maximum":200}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64","default":0,"nullable":true}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCountListResponse"}}}}},"security":[{"jwt":[]}]},"post":{"tags":["inventory-counts"],"operationId":"createInventoryCount","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInventoryCountRequest"}}},"required":true},"responses":{"200":{"description":"Idempotent replay returned the original count","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCountResponse"}}}},"201":{"description":"Inventory count created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCountResponse"}}}}},"security":[{"jwt":[]}]}},"/api/inventory-counts/{id}":{"get":{"tags":["inventory-counts"],"operationId":"getInventoryCount","parameters":[{"name":"id","in":"path","description":"Inventory count session ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCountResponse"}}}}},"security":[{"jwt":[]}]}},"/api/inventory-counts/{id}/approve":{"post":{"tags":["inventory-counts"],"operationId":"approveInventoryCount","parameters":[{"name":"id","in":"path","description":"Inventory count session ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCountTransitionRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCountResponse"}}}}},"security":[{"jwt":[]}]}},"/api/inventory-counts/{id}/cancel":{"post":{"tags":["inventory-counts"],"operationId":"cancelInventoryCount","parameters":[{"name":"id","in":"path","description":"Inventory count session ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCountTransitionRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCountResponse"}}}}},"security":[{"jwt":[]}]}},"/api/inventory-counts/{id}/lines/{line_id}":{"put":{"tags":["inventory-counts"],"operationId":"updateInventoryCountLine","parameters":[{"name":"id","in":"path","description":"Inventory count session ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"name":"line_id","in":"path","description":"Inventory count line ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateInventoryCountLineRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCountResponse"}}}}},"security":[{"jwt":[]}]}},"/api/inventory-counts/{id}/post":{"post":{"tags":["inventory-counts"],"operationId":"postInventoryCount","parameters":[{"name":"id","in":"path","description":"Inventory count session ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCountTransitionRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCountResponse"}}}}},"security":[{"jwt":[]}]}},"/api/inventory-counts/{id}/recount":{"post":{"tags":["inventory-counts"],"operationId":"requestInventoryCountRecount","parameters":[{"name":"id","in":"path","description":"Inventory count session ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCountTransitionRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCountResponse"}}}}},"security":[{"jwt":[]}]}},"/api/inventory-counts/{id}/submit":{"post":{"tags":["inventory-counts"],"operationId":"submitInventoryCount","parameters":[{"name":"id","in":"path","description":"Inventory count session ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCountTransitionRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryCountResponse"}}}}},"security":[{"jwt":[]}]}},"/api/inventory/imports":{"get":{"tags":["Inventory"],"operationId":"list_import_runs","parameters":[{"name":"limit","in":"query","description":"Page size (1-500, default 25)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Rows to skip (default 0)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"status","in":"query","description":"Filter by status; 'all' for every status","required":false,"schema":{"type":"string","nullable":true}},{"name":"search","in":"query","description":"Case-insensitive file-name substring","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Import runs, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportRunListResponse"}}}},"403":{"description":"Insufficient permissions"}}}},"/api/inventory/imports/{id}":{"get":{"tags":["Inventory"],"operationId":"get_import_run_report","parameters":[{"name":"id","in":"path","description":"Import run id (history UUID or upload_id)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Import run report","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportRunReportResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Run not found"}}}},"/api/inventory/imports/{id}/changes":{"get":{"tags":["Inventory"],"operationId":"list_import_run_changes","parameters":[{"name":"id","in":"path","description":"Import run id (history UUID or upload_id)","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Page size (1-500, default 50)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Rows to skip (default 0)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"attribute","in":"query","description":"Filter to one changed attribute","required":false,"schema":{"type":"string","nullable":true}},{"name":"search","in":"query","description":"Item number or description substring","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Changed fields for the run","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportChangeListResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Run not found"}}}},"/api/inventory/imports/{id}/changes.csv":{"get":{"tags":["Inventory"],"operationId":"export_import_run_changes_csv","parameters":[{"name":"id","in":"path","description":"Import run id (history UUID or upload_id)","required":true,"schema":{"type":"string"}},{"name":"attribute","in":"query","description":"Filter to one changed attribute","required":false,"schema":{"type":"string","nullable":true}},{"name":"search","in":"query","description":"Item number or description substring","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"CSV of changed fields"},"403":{"description":"Insufficient permissions"},"404":{"description":"Run not found"}}}},"/api/inventory/upload":{"post":{"tags":["Inventory"],"operationId":"start_inventory_upload","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryUploadRequest"}}},"required":true},"responses":{"200":{"description":"Upload session created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryUploadResponse"}}}},"400":{"description":"Invalid request"},"403":{"description":"Insufficient permissions"},"500":{"description":"Server error"}}}},"/api/inventory/upload/{upload_id}":{"post":{"tags":["Inventory"],"operationId":"upload_inventory_file","parameters":[{"name":"upload_id","in":"path","description":"Upload session ID","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"CSV file to import","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/InventoryUploadFileForm"}}},"required":true},"responses":{"200":{"description":"File uploaded and processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryImportResult"}}}},"400":{"description":"Invalid file or request"},"403":{"description":"Insufficient permissions"},"404":{"description":"Upload session not found"},"500":{"description":"Server error"}}}},"/api/inventory/upload/{upload_id}/progress":{"get":{"tags":["Inventory"],"operationId":"get_inventory_progress","parameters":[{"name":"upload_id","in":"path","description":"Upload session ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Upload progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryProgressResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Upload not found"}}}},"/api/inventory/uploads/history":{"get":{"tags":["Inventory"],"operationId":"inventory_upload_history","parameters":[{"name":"limit","in":"query","description":"Maximum number of results to return","required":false,"schema":{"type":"integer","format":"int32","nullable":true}}],"responses":{"200":{"description":"Import history","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryHistoryResponse"}}}},"403":{"description":"Insufficient permissions"}}}},"/api/login":{"post":{"tags":["auth"],"operationId":"login","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Login successful","content":{"application/json":{"schema":{}}}},"401":{"description":"Invalid credentials"},"500":{"description":"Internal server error"}}}},"/api/logout":{"post":{"tags":["auth"],"summary":"Logout user and clear JWT cookie","operationId":"logout","responses":{"200":{"description":"Logout successful"},"401":{"description":"Invalid or expired token"},"500":{"description":"Internal server error"}},"security":[{"jwt":[]}]}},"/api/lookbooks":{"get":{"tags":["Lookbooks"],"operationId":"list_lookbooks_handler","responses":{"200":{"description":"Lookbooks","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/LookbookResponse"}}}}},"403":{"description":"Insufficient permissions"},"503":{"description":"Database unavailable"}}},"post":{"tags":["Lookbooks"],"operationId":"create_lookbook_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLookbookRequest"}}},"required":true},"responses":{"201":{"description":"Created lookbook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LookbookResponse"}}}},"403":{"description":"Insufficient permissions"},"503":{"description":"Database unavailable"}}}},"/api/lookbooks/{id}":{"get":{"tags":["Lookbooks"],"operationId":"get_lookbook_handler","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Lookbook detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LookbookDetailResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Lookbook not found"},"503":{"description":"Database unavailable"}}},"put":{"tags":["Lookbooks"],"operationId":"update_lookbook_handler","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLookbookRequest"}}},"required":true},"responses":{"200":{"description":"Updated lookbook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LookbookResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Lookbook not found"},"503":{"description":"Database unavailable"}}},"delete":{"tags":["Lookbooks"],"operationId":"delete_lookbook_handler","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Lookbook deleted"},"403":{"description":"Insufficient permissions"},"404":{"description":"Lookbook not found"},"503":{"description":"Database unavailable"}}}},"/api/lookbooks/{id}/items":{"put":{"tags":["Lookbooks"],"operationId":"replace_lookbook_items_handler","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplaceLookbookItemsRequest"}}},"required":true},"responses":{"200":{"description":"Items replaced"},"403":{"description":"Insufficient permissions"},"404":{"description":"Lookbook not found"},"503":{"description":"Database unavailable"}}}},"/api/lookbooks/{id}/publish":{"post":{"tags":["Lookbooks"],"operationId":"publish_lookbook_handler","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishLookbookRequest"}}},"required":true},"responses":{"200":{"description":"Publish state changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LookbookResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Lookbook not found"},"503":{"description":"Database unavailable"}}}},"/api/marketing-images":{"get":{"tags":["marketing_images"],"operationId":"list_marketing_images","responses":{"200":{"description":"Marketing image list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketingImageListResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]},"post":{"tags":["marketing_images"],"operationId":"create_marketing_image","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMarketingImageRequest"}}},"required":true},"responses":{"201":{"description":"Created"},"400":{"description":"Validation error"},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]}},"/api/marketing-images/reorder":{"post":{"tags":["marketing_images"],"operationId":"reorder_marketing_images","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReorderMarketingImagesRequest"}}},"required":true},"responses":{"204":{"description":"Reordered"},"400":{"description":"Validation error"},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]}},"/api/marketing-images/{id}":{"put":{"tags":["marketing_images"],"operationId":"update_marketing_image","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMarketingImageRequest"}}},"required":true},"responses":{"200":{"description":"Updated"},"400":{"description":"Validation error"},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]},"delete":{"tags":["marketing_images"],"operationId":"delete_marketing_image","responses":{"204":{"description":"Deleted"},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/mobile-order-inbox":{"get":{"tags":["Mobile Order Inbox"],"operationId":"list_mobile_order_inbox","parameters":[{"name":"status","in":"query","description":"Filter by inbox status (received | processed | quarantined | duplicate\n| resolved | discarded).","required":false,"schema":{"type":"string","nullable":true}},{"name":"date_from","in":"query","description":"ISO-8601 (or YYYY-MM-DD) lower bound on `received_at`.","required":false,"schema":{"type":"string","nullable":true}},{"name":"date_to","in":"query","description":"ISO-8601 (or YYYY-MM-DD) upper bound on `received_at`.","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Inbox rows (raw payload omitted)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileOrderInboxListResponse"}}}},"400":{"description":"Invalid status filter"},"403":{"description":"Forbidden - requires sales_orders.manage"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/mobile-order-inbox/{id}":{"get":{"tags":["Mobile Order Inbox"],"operationId":"get_mobile_order_inbox","parameters":[{"name":"id","in":"path","description":"Inbox row UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Full inbox row including raw_payload and a resolved, human-readable view","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileOrderInboxDetail"}}}},"403":{"description":"Forbidden - requires sales_orders.manage"},"404":{"description":"Not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/mobile-order-inbox/{id}/discard":{"post":{"tags":["Mobile Order Inbox"],"operationId":"discard_mobile_order_inbox","parameters":[{"name":"id","in":"path","description":"Inbox row UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Row discarded (audited)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxActionResponse"}}}},"403":{"description":"Forbidden - requires sales_orders.manage"},"404":{"description":"Not found"},"409":{"description":"Row is not in an actionable status"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/mobile-order-inbox/{id}/force-create":{"post":{"tags":["Mobile Order Inbox"],"summary":"Force the creation of a real sales order from an inbox row, bypassing stock","description":"validation entirely. This is the \"create sales order anyway\" action for rows\nthat quarantined on insufficient stock — it lets an admin push the order\nthrough even when it drives stock negative. Any OTHER quarantine cause\n(missing account, malformed payload, …) still fails; use resolve to edit\nthose. Reuses the standard transactional processing engine so idempotency and\naudit still hold.","operationId":"force_create_mobile_order_inbox","parameters":[{"name":"id","in":"path","description":"Inbox row UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Sales order created from the inbox row, bypassing the stock check (may drive stock negative)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxActionResponse"}}}},"403":{"description":"Forbidden - requires sales_orders.manage"},"404":{"description":"Not found"},"409":{"description":"Row is not in an actionable status"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/mobile-order-inbox/{id}/reprocess":{"post":{"tags":["Mobile Order Inbox"],"operationId":"reprocess_mobile_order_inbox","parameters":[{"name":"id","in":"path","description":"Inbox row UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Row reprocessed — status is `processed` (order created) or still `quarantined` with an updated error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxActionResponse"}}}},"403":{"description":"Forbidden - requires sales_orders.manage"},"404":{"description":"Not found"},"409":{"description":"Row is not in an actionable status"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/mobile-order-inbox/{id}/resolve":{"post":{"tags":["Mobile Order Inbox"],"operationId":"resolve_mobile_order_inbox","parameters":[{"name":"id","in":"path","description":"Inbox row UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveInboxRequest"}}},"required":true},"responses":{"200":{"description":"Row resolved with the edited payload — status is `resolved` (order created) or still `quarantined` with an updated error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxActionResponse"}}}},"403":{"description":"Forbidden - requires sales_orders.manage"},"404":{"description":"Not found"},"409":{"description":"Row is not in an actionable status"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/mobile/login":{"post":{"tags":["access_tokens"],"operationId":"mobile_login_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileLoginRequest"}}},"required":true},"responses":{"200":{"description":"Mobile login successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileLoginResponse"}}}},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"}}}},"/api/modifier-groups":{"get":{"tags":["crate::features::modifier_groups::handlers"],"operationId":"list_modifier_groups","responses":{"200":{"description":"List of modifier groups with options","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ModifierGroupWithOptions"}}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["crate::features::modifier_groups::handlers"],"operationId":"create_modifier_group","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateModifierGroupInput"}}},"required":true},"responses":{"201":{"description":"Modifier group created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModifierGroupWithOptions"}}}},"400":{"description":"Bad request"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/modifier-groups/{id}":{"get":{"tags":["crate::features::modifier_groups::handlers"],"operationId":"get_modifier_group","parameters":[{"name":"id","in":"path","description":"Modifier group ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Modifier group with options","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModifierGroupWithOptions"}}}},"404":{"description":"Not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["crate::features::modifier_groups::handlers"],"operationId":"update_modifier_group","parameters":[{"name":"id","in":"path","description":"Modifier group ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateModifierGroupInput"}}},"required":true},"responses":{"200":{"description":"Modifier group updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModifierGroup"}}}},"404":{"description":"Not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["crate::features::modifier_groups::handlers"],"operationId":"delete_modifier_group","parameters":[{"name":"id","in":"path","description":"Modifier group ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Modifier group deleted"},"404":{"description":"Not found"},"409":{"description":"Conflict - group is attached to products"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/modifier-groups/{id}/options":{"put":{"tags":["crate::features::modifier_groups::handlers"],"operationId":"upsert_modifier_options","parameters":[{"name":"id","in":"path","description":"Modifier group ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertModifierOptionsInput"}}},"required":true},"responses":{"200":{"description":"Options updated","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ModifierOption"}}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/onboarding/articles":{"get":{"tags":["onboarding"],"operationId":"list_onboarding_articles","parameters":[{"name":"category","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"app_target","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"is_published","in":"query","required":false,"schema":{"type":"boolean","nullable":true}}],"responses":{"200":{"description":"List of help articles","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/HelpArticleResponse"}}}}},"500":{"description":"Internal server error"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/onboarding/profile":{"post":{"tags":["onboarding"],"operationId":"save_onboarding_profile","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveOnboardingProfileRequest"}}},"required":true},"responses":{"200":{"description":"Wizard answers saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingStateResponse"}}}},"400":{"description":"Unknown sales channel"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/onboarding/sample-data":{"post":{"tags":["onboarding"],"operationId":"load_onboarding_sample_data","requestBody":{"description":"","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/LoadSampleDataRequest"}],"nullable":true}}},"required":false},"responses":{"200":{"description":"Sample catalogue loaded into the caller's company","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SampleDataResponse"}}}},"403":{"description":"Insufficient permissions"},"409":{"description":"Company already has products — nothing was changed"},"500":{"description":"Internal server error"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["onboarding"],"operationId":"remove_onboarding_sample_data","responses":{"200":{"description":"Sample catalogue removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SampleDataRemovalResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/onboarding/state":{"get":{"tags":["onboarding"],"operationId":"get_onboarding_state","responses":{"200":{"description":"Onboarding state for the caller's company","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingStateResponse"}}}},"500":{"description":"Internal server error"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/onboarding/state/step":{"post":{"tags":["onboarding"],"operationId":"update_onboarding_step","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOnboardingStepRequest"}}},"required":true},"responses":{"200":{"description":"Updated onboarding state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingStateResponse"}}}},"500":{"description":"Internal server error"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/online-store/ai/generate":{"post":{"tags":["Online Store"],"summary":"POST /api/online-store/ai/generate — ask the assistant for a homepage,","description":"section copy, a theme, a page or SEO copy. Returns a validated proposal;\nnothing is saved until the merchant applies it in the builder.","operationId":"online_store_ai_generate_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnlineStoreAiRequest"}}},"required":true},"responses":{"200":{"description":"A proposal to apply or discard","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnlineStoreAiResponse"}}}},"400":{"description":"Invalid request"},"402":{"description":"Managed mode with no credits left"},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"jwt":[]}]}},"/api/online-store/ai/status":{"get":{"tags":["Online Store"],"summary":"GET /api/online-store/ai/status — mode (byok / managed / offline), the","description":"provider and model that would answer, and the managed-credit position.","operationId":"online_store_ai_status_handler","responses":{"200":{"description":"How the assistant will answer for this company","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnlineStoreAiStatusResponse"}}}},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"jwt":[]}]}},"/api/online-store/media/upload":{"post":{"tags":["Online Store"],"summary":"POST /api/online-store/media/upload — multipart `file` field. Images and","description":"short videos for sections, stored on the public bucket under\n`storefront-media/{company}/`. Size caps reuse the company's digital\ndisplay upload settings (`digital_display.max_image_upload_mb` /\n`max_video_upload_mb`) so there is one place to tune them.","operationId":"upload_online_store_media","requestBody":{"content":{"application/json":{"schema":{"default":null,"nullable":true}}},"required":true},"responses":{"200":{"description":"Media uploaded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnlineStoreMediaResponse"}}}},"400":{"description":"Invalid file"},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"jwt":[]}]}},"/api/online-store/preview":{"post":{"tags":["Online Store"],"summary":"POST /api/online-store/preview — render a draft exactly as the live store","description":"would (same denormalisation, same sanitiser), without saving it. Hidden\nsections and unpublished pages are included so the builder can show them.","operationId":"online_store_preview_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnlineStorePreviewRequest"}}},"required":true},"responses":{"200":{"description":"Denormalised draft, ready to render","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnlineStorePreviewResponse"}}}},"400":{"description":"Draft failed validation — message names the field"},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"jwt":[]}]}},"/api/online-store/status":{"get":{"tags":["Online Store"],"summary":"GET /api/online-store/status — what the Online Store overview shows:","description":"enabled/slug/URL, catalogue counts, the launch checklist and order count.","operationId":"online_store_status_handler","responses":{"200":{"description":"Launch status of the online store","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnlineStoreStatusResponse"}}}},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"jwt":[]}]}},"/api/orders/fulfillment-policy":{"get":{"tags":["orders"],"operationId":"getFulfillmentPolicy","responses":{"200":{"description":"Fulfillment policy for the caller's company","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FulfillmentPolicyResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["orders"],"operationId":"updateFulfillmentPolicy","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFulfillmentPolicy"}}},"required":true},"responses":{"200":{"description":"Fulfillment policy updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FulfillmentPolicyResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/orders/ready-to-pick":{"get":{"tags":["orders"],"operationId":"getReadyOrders","parameters":[{"name":"priority","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"zone","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"status","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"List of orders ready for picking","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadyOrdersListResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/orders/{id}/complete-picking":{"post":{"tags":["orders"],"operationId":"completePickingSession","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompletePickingRequest"}}},"required":true},"responses":{"200":{"description":"Picking session completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PickingActionResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Order not found"},"409":{"description":"Order is not in a state that can transition to ready_to_ship"},"422":{"description":"A stock change failed for one of the picked products; the whole completion was rolled back"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/orders/{id}/line-resolutions":{"get":{"tags":["orders"],"operationId":"getLineResolutions","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Line resolutions for the order","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OrderLineResolutionResponse"}}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["orders"],"operationId":"upsertLineResolutions","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/LineResolutionInput"}}}},"required":true},"responses":{"200":{"description":"Line resolutions upserted","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OrderLineResolutionResponse"}}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Order not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/orders/{id}/line-resolutions/{resolution_id}/approval":{"put":{"tags":["orders"],"operationId":"update_line_resolution_approval","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}},{"name":"resolution_id","in":"path","description":"Line resolution ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLineResolutionApprovalRequest"}}},"required":true},"responses":{"200":{"description":"Updated line resolution","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderLineResolutionResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Resolution not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/orders/{id}/picking-adjustments":{"get":{"tags":["orders"],"operationId":"getPickingAdjustments","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Picking adjustments for the order","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PickingAdjustmentResponse"}}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Order not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["orders"],"operationId":"upsertPickingAdjustment","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PickingAdjustmentInput"}}},"required":true},"responses":{"200":{"description":"Adjustment recorded (created or updated)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PickingAdjustmentResponse"}}}},"400":{"description":"Invalid adjustment payload"},"403":{"description":"Insufficient permissions or disabled by policy"},"404":{"description":"Order, line, or product not found"},"422":{"description":"A note is required by policy"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/orders/{id}/picking-adjustments/{adjustment_id}":{"delete":{"tags":["orders"],"operationId":"deletePickingAdjustment","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}},{"name":"adjustment_id","in":"path","description":"Adjustment ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Adjustment removed"},"403":{"description":"Insufficient permissions"},"404":{"description":"Adjustment not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/orders/{id}/picking-details":{"get":{"tags":["orders"],"operationId":"getPickingDetails","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Picking details for the order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PickingDetailsResponseDto"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Order not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/orders/{id}/picking-notes":{"put":{"tags":["orders"],"operationId":"updatePickingNotes","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePickingNotesRequest"}}},"required":true},"responses":{"200":{"description":"Session notes updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PickingNotesResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Order not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/orders/{id}/picking-status":{"get":{"tags":["orders"],"operationId":"get_picking_status","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Picking status for the order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PickingStatusResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Order not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/orders/{id}/record-pick":{"post":{"tags":["orders"],"operationId":"recordPick","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordPickRequest"}}},"required":true},"responses":{"200":{"description":"Pick recorded (created or updated)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordPickResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Order or item not found"},"409":{"description":"Picked quantity exceeds ordered (over-picking disabled)"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/orders/{id}/start-picking":{"post":{"tags":["orders"],"operationId":"startPickingSession","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartPickingRequest"}}},"required":true},"responses":{"200":{"description":"Picking session started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PickingActionResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Order not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/pipeline-hooks/{token}":{"post":{"tags":["pipelines"],"summary":"POST /api/pipeline-hooks/{token} — PUBLIC (no JWT): an external system","description":"starts a pipeline run. Authorization is possession of the 256-bit token,\nwhich only `pipelines.manage` can mint and which maps to exactly one\npipeline (unique index). The pipeline must be enabled, not deleted, and\nhave an active version — otherwise a uniform 404, indistinguishable from\na wrong token (no oracle). The JSON body (optional, ≤64KB) becomes the\nrun's `{{ trigger.* }}` payload. Webhook runs share the tenant's hourly\nevent-run budget → 429 once exhausted.","operationId":"triggerPipelineWebhook","parameters":[{"name":"token","in":"path","description":"Pipeline webhook token","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}},"required":true},"responses":{"202":{"description":"Run queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedRunResponse"}}}},"400":{"description":"Body is not valid JSON"},"404":{"description":"Unknown token, or the pipeline cannot run (disabled/deleted/no active version)"},"413":{"description":"Body exceeds 64KB"},"429":{"description":"Tenant's hourly run budget exhausted"},"503":{"description":"Database unavailable"}}}},"/api/pipelines":{"get":{"tags":["pipelines"],"operationId":"listPipelines","responses":{"200":{"description":"This company's pipelines, not deleted","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PipelineResponse"}}}}},"403":{"description":"Insufficient permissions"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["pipelines"],"operationId":"createPipeline","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePipelineRequest"}}},"required":true},"responses":{"201":{"description":"Pipeline created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PipelineResponse"}}}},"403":{"description":"Insufficient permissions"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/pipelines/events":{"get":{"tags":["pipelines"],"summary":"GET /api/pipelines/events","description":"The trigger event catalogue: every domain event a pipeline may subscribe\nto, with its payload JSON Schema. JWT-only like `GET /nodes` — the\ncatalogue is static metadata, not tenant data.","operationId":"listPipelineTriggerEvents","responses":{"200":{"description":"Every event type a pipeline can trigger on, with payload schemas","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TriggerEventResponse"}}}}}},"security":[{"bearer_auth":[]}]}},"/api/pipelines/nodes":{"get":{"tags":["pipelines"],"summary":"GET /api/pipelines/nodes","description":"The node palette. JWT required (any authenticated user may see the\ntrimmed palette), but each node is only listed if the caller holds its\n`required_permission` (`None` => always listed) — the palette is itself\nan RBAC surface: a user who could never save a graph containing a node\nshould not be offered it.","operationId":"listPipelineNodes","responses":{"200":{"description":"Node registry filtered to the caller's permissions","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/NodeSpecResponse"}}}}}},"security":[{"bearer_auth":[]}]}},"/api/pipelines/runs/{runId}":{"get":{"tags":["pipelines"],"operationId":"getPipelineRunDetail","parameters":[{"name":"runId","in":"path","description":"Pipeline run id (UUID)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The run inspector: run row + its step runs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PipelineRunDetailResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Not found (including a non-UUID id)"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/pipelines/runs/{runId}/cancel":{"post":{"tags":["pipelines"],"operationId":"cancelPipelineRun","parameters":[{"name":"runId","in":"path","description":"Pipeline run id (UUID)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Run cancelled (a queued run never starts; a running run stops at the next node boundary)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PipelineRunResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Not found (including a non-UUID id)"},"409":{"description":"Run is already in a terminal state"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/pipelines/runs/{runId}/replay":{"post":{"tags":["pipelines"],"summary":"Replay a finished run: enqueue a NEW run pinned to the SAME immutable","description":"pipeline version, carrying the SAME trigger context (`trigger_event_id`\nfor event runs, stored `trigger_payload` for manual/webhook/schedule\nruns) — so `{{ trigger.* }}` resolves identically. The source run must be\nterminal; the pipeline must still exist (the replay ignores `enabled` —\nreplaying against a paused pipeline is a deliberate act by a manager).\nThe new run is `manual`-typed with no causation and is not counted\nagainst the hourly event/webhook budget.","operationId":"replayPipelineRun","parameters":[{"name":"runId","in":"path","description":"Source pipeline run id (UUID)","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplayRunRequest"}}},"required":true},"responses":{"201":{"description":"Replay queued against the source run's pinned version","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedRunResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Run not found, or its pipeline was deleted"},"409":{"description":"Source run is not terminal yet"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/pipelines/usage":{"get":{"tags":["pipelines"],"summary":"GET /api/pipelines/usage?month=YYYY-MM","description":"The tenant's pipeline metering rollup for one calendar month — runs by\ntrigger type, executed steps and credits by node kind. This is the\nbillable-usage source of truth; skipped runs are excluded everywhere.","operationId":"getPipelineUsage","parameters":[{"name":"month","in":"query","description":"Calendar month YYYY-MM (UTC); defaults to the current month","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"The month's pipeline usage rollup","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PipelineUsageResponse"}}}},"400":{"description":"month is not YYYY-MM"},"403":{"description":"Insufficient permissions"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/pipelines/{id}":{"get":{"tags":["pipelines"],"operationId":"getPipeline","parameters":[{"name":"id","in":"path","description":"Pipeline id (UUID)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The pipeline","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PipelineResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Not found (including a non-UUID id)"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["pipelines"],"operationId":"updatePipeline","parameters":[{"name":"id","in":"path","description":"Pipeline id (UUID)","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePipelineRequest"}}},"required":true},"responses":{"200":{"description":"Updated pipeline metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PipelineResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Not found (including a non-UUID id)"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["pipelines"],"operationId":"deletePipeline","parameters":[{"name":"id","in":"path","description":"Pipeline id (UUID)","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Pipeline soft-deleted (also disabled); its versions and run history remain readable via the run inspector"},"403":{"description":"Insufficient permissions"},"404":{"description":"Not found (including a non-UUID id)"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/pipelines/{id}/activate":{"post":{"tags":["pipelines"],"operationId":"activatePipeline","parameters":[{"name":"id","in":"path","description":"Pipeline id (UUID)","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivatePipelineRequest"}}},"required":true},"responses":{"200":{"description":"Pipeline activated on the given version","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PipelineResponse"}}}},"400":{"description":"version_id is not a UUID"},"403":{"description":"Insufficient permissions"},"404":{"description":"Pipeline or version not found"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/pipelines/{id}/run":{"post":{"tags":["pipelines"],"operationId":"runPipeline","parameters":[{"name":"id","in":"path","description":"Pipeline id (UUID)","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunPipelineRequest"}}},"required":true},"responses":{"201":{"description":"Run queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedRunResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Pipeline not found, or has no published (active) version to run"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/pipelines/{id}/runs":{"get":{"tags":["pipelines"],"operationId":"listPipelineRuns","parameters":[{"name":"id","in":"path","description":"Pipeline id (UUID)","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Page size, clamped to 1..=100 (default 25)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Rows to skip, newest first (default 0)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Runs for this pipeline, newest first. A page shorter than `limit` is the last page.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PipelineRunResponse"}}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Pipeline not found (including a non-UUID id)"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/pipelines/{id}/versions":{"get":{"tags":["pipelines"],"operationId":"listPipelineVersions","parameters":[{"name":"id","in":"path","description":"Pipeline id (UUID)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"This pipeline's versions, newest first","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PipelineVersionResponse"}}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Pipeline not found (including a non-UUID id)"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["pipelines"],"operationId":"createPipelineVersion","parameters":[{"name":"id","in":"path","description":"Pipeline id (UUID)","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePipelineVersionRequest"}}},"required":true},"responses":{"201":{"description":"Immutable version created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PipelineVersionResponse"}}}},"400":{"description":"Graph failed validation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphValidationErrorResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Pipeline not found (including a non-UUID id)"},"429":{"description":"Too many concurrent code.script compiles in flight for this company"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/pipelines/{id}/versions/{versionId}":{"get":{"tags":["pipelines"],"operationId":"getPipelineVersion","parameters":[{"name":"id","in":"path","description":"Pipeline id (UUID)","required":true,"schema":{"type":"string"}},{"name":"versionId","in":"path","description":"Version id (UUID)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The version, including its graph","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PipelineVersionResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Pipeline or version not found (including a non-UUID id)"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/pipelines/{id}/webhook-token":{"post":{"tags":["pipelines"],"operationId":"generatePipelineWebhookToken","parameters":[{"name":"id","in":"path","description":"Pipeline id (UUID)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"New (or rotated) inbound webhook token — the only response that ever carries it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTokenResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Not found (including a non-UUID id)"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["pipelines"],"operationId":"deletePipelineWebhookToken","parameters":[{"name":"id","in":"path","description":"Pipeline id (UUID)","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Inbound webhook trigger disabled; the old token stops working immediately"},"403":{"description":"Insufficient permissions"},"404":{"description":"Not found (including a non-UUID id)"},"503":{"description":"Database unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/portal-settings":{"get":{"tags":["Portal Settings"],"summary":"GET /api/portal-settings — fetch portal settings for the authenticated admin's company","operationId":"get_portal_settings_handler","responses":{"200":{"description":"Portal settings for the company","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalSettingsResponse"}}}},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["Portal Settings"],"summary":"PUT /api/portal-settings — create or update portal settings for the company","operationId":"upsert_portal_settings_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertPortalSettingsRequest"}}},"required":true},"responses":{"200":{"description":"Portal settings created or updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalSettingsResponse"}}}},"400":{"description":"Invalid input"},"403":{"description":"Forbidden"},"422":{"description":"Portal name already taken"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal-settings/logo":{"post":{"tags":["Portal Settings"],"summary":"POST /api/portal-settings/logo — upload a portal logo to R2","operationId":"uploadPortalLogo","requestBody":{"content":{"application/json":{"schema":{"default":null,"nullable":true}}},"required":true},"responses":{"200":{"description":"Logo uploaded successfully","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request - invalid file"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal-users":{"get":{"tags":["Portal Users"],"summary":"GET /api/portal-users?account_id=...","operationId":"list_portal_users_handler","parameters":[{"name":"account_id","in":"query","description":"Filter by account ID","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"List of portal users for the company","content":{"application/json":{"schema":{}}}},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["Portal Users"],"summary":"POST /api/portal-users","operationId":"create_portal_user_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePortalUserRequest"}}},"required":true},"responses":{"201":{"description":"Portal user created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalUserResponse"}}}},"400":{"description":"Missing required fields"},"403":{"description":"Forbidden"},"422":{"description":"Unprocessable entity - e.g. email already exists"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal-users/{id}":{"delete":{"tags":["Portal Users"],"summary":"DELETE /api/portal-users/{id}","operationId":"delete_portal_user_handler","parameters":[{"name":"id","in":"path","description":"Portal user UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Portal user deleted"},"400":{"description":"Invalid user ID"},"403":{"description":"Forbidden"},"404":{"description":"Portal user not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["Portal Users"],"summary":"PATCH /api/portal-users/{id}","operationId":"update_portal_user_handler","parameters":[{"name":"id","in":"path","description":"Portal user UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePortalUserRequest"}}},"required":true},"responses":{"200":{"description":"Portal user updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalUserResponse"}}}},"400":{"description":"Invalid user ID"},"403":{"description":"Forbidden"},"404":{"description":"Portal user not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal-users/{id}/reset-password":{"post":{"tags":["Portal Users"],"summary":"POST /api/portal-users/{id}/reset-password","operationId":"reset_portal_user_password_handler","parameters":[{"name":"id","in":"path","description":"Portal user UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPortalPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Password reset successfully"},"400":{"description":"Invalid user ID or missing new_password"},"403":{"description":"Forbidden"},"404":{"description":"Portal user not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal-users/{id}/send-reset-link":{"post":{"tags":["Portal Users"],"summary":"POST /api/portal-users/{id}/send-reset-link — generate a reset link and optionally email it","operationId":"send_portal_reset_link_handler","parameters":[{"name":"id","in":"path","description":"Portal user UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Reset link generated (and emailed if email is enabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResetLinkResponse"}}}},"400":{"description":"Invalid user ID"},"403":{"description":"Forbidden"},"404":{"description":"Portal user not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal/auth/logout":{"post":{"tags":["Customer Portal"],"summary":"POST /api/portal/auth/logout — clear portal JWT cookie","operationId":"portal_logout_handler","responses":{"200":{"description":"Logout successful"},"401":{"description":"Unauthorized"}},"security":[{"bearer_auth":[]}]}},"/api/portal/auth/me":{"get":{"tags":["Customer Portal"],"summary":"GET /api/portal/auth/me — return current portal user info from JWT","operationId":"portal_me_handler","responses":{"200":{"description":"Current portal user info","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalUserInfo"}}}},"401":{"description":"Unauthorized - invalid or expired token"}},"security":[{"bearer_auth":[]}]}},"/api/portal/cart":{"get":{"tags":["Customer Portal"],"summary":"GET /api/portal/cart — the caller's persistent cart, priced with the","description":"account's effective prices (never raw wholesale).","operationId":"portal_get_cart_handler","responses":{"200":{"description":"The portal user's cart with price-list-resolved prices","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalCartResponseDto"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["Customer Portal"],"summary":"PUT /api/portal/cart — full-replace upsert of the caller's cart. An empty","description":"`items` array clears the cart. Lines are validated against the company's\ncatalogue exactly like order lines (a foreign/deleted product is a 400).","operationId":"portal_put_cart_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePortalCartRequest"}}},"required":true},"responses":{"200":{"description":"Cart replaced; returns the updated cart","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalCartResponseDto"}}}},"400":{"description":"Invalid lines (bad qty, duplicate or foreign products)"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal/cart/checkout":{"post":{"tags":["Customer Portal"],"summary":"POST /api/portal/cart/checkout — convert the caller's server-side cart into","description":"an order through the one-and-only portal order path, then clear the cart.\nPass `client_order_id` to make the conversion idempotent across retries.","operationId":"portal_cart_checkout_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalCartCheckoutRequest"}}},"required":true},"responses":{"201":{"description":"Order created from the cart; cart cleared","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderResponseDto"}}}},"400":{"description":"Empty cart or order validation failure"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal/catalog":{"get":{"tags":["Customer Portal"],"summary":"GET /api/portal/catalog — list products for the customer's company","description":"Product visibility filters come from admin-configured portal settings.","operationId":"portal_catalog_handler","parameters":[{"name":"category_id","in":"query","description":"Filter by category ID","required":false,"schema":{"type":"string","nullable":true}},{"name":"search","in":"query","description":"Search term for product name or item number","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","description":"Maximum number of results (default 50, max 200)","required":false,"schema":{"type":"integer","format":"int32","nullable":true,"minimum":0}},{"name":"offset","in":"query","description":"Offset for pagination","required":false,"schema":{"type":"integer","format":"int32","nullable":true,"minimum":0}}],"responses":{"200":{"description":"List of products in catalog","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogResponseDto"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal/catalog/{id}":{"get":{"tags":["Customer Portal"],"summary":"GET /api/portal/catalog/{id} — get single product with its first image","operationId":"portal_product_detail_handler","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Product details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalProductDto"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Product not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal/favorites":{"get":{"tags":["Customer Portal"],"operationId":"list_portal_favorites","responses":{"200":{"description":"The customer's favorite products","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FavoriteResponse"}}}}},"401":{"description":"Not authenticated"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["Customer Portal"],"operationId":"add_portal_favorite","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddToListRequest"}}},"required":true},"responses":{"201":{"description":"Favorite added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FavoriteResponse"}}}},"401":{"description":"Not authenticated"},"409":{"description":"Already favorited"}},"security":[{"bearer_auth":[]}]}},"/api/portal/favorites/{product_id}":{"delete":{"tags":["Customer Portal"],"operationId":"remove_portal_favorite","parameters":[{"name":"product_id","in":"path","description":"Product id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Favorite removed"},"401":{"description":"Not authenticated"},"404":{"description":"Not found"}},"security":[{"bearer_auth":[]}]}},"/api/portal/frequent-products":{"get":{"tags":["Customer Portal"],"summary":"GET /api/portal/frequent-products — frequently ordered products for this account","operationId":"portal_frequent_products_handler","responses":{"200":{"description":"Frequently ordered products for the authenticated account","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PortalFrequentProductDto"}}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal/home":{"get":{"tags":["Customer Portal"],"summary":"GET /api/portal/home — denormalised homepage sections for the signed-in","description":"customer's company, or `enabled: false` when the merchant has not\nswitched the custom homepage on.","operationId":"portal_home_handler","responses":{"200":{"description":"Portal homepage sections (empty when the custom homepage is off)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalHomeResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal/login":{"post":{"tags":["Customer Portal"],"summary":"POST /api/portal/login — authenticate portal user and issue JWT","operationId":"portal_login_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalLoginRequest"}}},"required":true},"responses":{"200":{"description":"Login successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalLoginResponse"}}}},"400":{"description":"Missing required fields"},"401":{"description":"Invalid credentials or account disabled"},"500":{"description":"Internal server error"}}}},"/api/portal/orders":{"get":{"tags":["Customer Portal"],"summary":"GET /api/portal/orders — list orders for the portal user's account","operationId":"portal_orders_handler","parameters":[{"name":"limit","in":"query","description":"Maximum number of results (default 50, max 200)","required":false,"schema":{"type":"integer","format":"int32","nullable":true,"minimum":0}},{"name":"offset","in":"query","description":"Offset for pagination","required":false,"schema":{"type":"integer","format":"int32","nullable":true,"minimum":0}}],"responses":{"200":{"description":"List of orders for the authenticated portal user's account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrdersResponseDto"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["Customer Portal"],"summary":"POST /api/portal/orders — create a new order from the portal","operationId":"portal_create_order_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePortalOrderRequest"}}},"required":true},"responses":{"201":{"description":"Order created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderResponseDto"}}}},"400":{"description":"Bad request - order must have at least one item"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal/orders/{id}":{"get":{"tags":["Customer Portal"],"summary":"GET /api/portal/orders/{id} — get a single order (must belong to this account)","operationId":"portal_order_detail_handler","parameters":[{"name":"id","in":"path","description":"Order ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Order details with line items","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalOrderDetailDto"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Order not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal/reset-password/confirm":{"post":{"tags":["Customer Portal"],"summary":"POST /api/portal/reset-password/confirm — validate token and set new password","operationId":"portal_reset_password_confirm_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalResetPasswordConfirmRequest"}}},"required":true},"responses":{"200":{"description":"Password reset successful"},"400":{"description":"Missing or invalid fields"},"404":{"description":"Invalid or expired token"},"500":{"description":"Internal server error"}}}},"/api/portal/reset-password/request":{"post":{"tags":["Customer Portal"],"summary":"POST /api/portal/reset-password/request — self-service forgot password (public, no auth)","description":"Always returns a generic success response regardless of whether the email exists,\nto prevent account enumeration.","operationId":"portal_request_password_reset_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalRequestPasswordResetRequest"}}},"required":true},"responses":{"200":{"description":"If an account exists with this email, a reset link has been sent"},"400":{"description":"Missing required fields"},"500":{"description":"Internal server error"}}}},"/api/portal/returns":{"get":{"tags":["Customer Portal"],"summary":"GET /api/portal/returns — list the account's return requests.","operationId":"portal_returns_handler","parameters":[{"name":"limit","in":"query","description":"Maximum number of results (default 50, max 200)","required":false,"schema":{"type":"integer","format":"int32","nullable":true,"minimum":0}},{"name":"offset","in":"query","description":"Offset for pagination","required":false,"schema":{"type":"integer","format":"int32","nullable":true,"minimum":0}}],"responses":{"200":{"description":"The account's return requests, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalReturnsResponseDto"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["Customer Portal"],"summary":"POST /api/portal/returns — create a return request against one of the","description":"caller's own orders. Lines must be a subset of the order's lines.","operationId":"portal_create_return_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePortalReturnRequest"}}},"required":true},"responses":{"201":{"description":"Return request created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalReturnDetailDto"}}}},"400":{"description":"Invalid lines (not in order, qty above ordered, bad reason)"},"401":{"description":"Unauthorized"},"404":{"description":"Order not found for this account"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal/returns/{id}":{"get":{"tags":["Customer Portal"],"summary":"GET /api/portal/returns/{id} — one return request (must belong to this","description":"account; a foreign id is a 404).","operationId":"portal_return_detail_handler","parameters":[{"name":"id","in":"path","description":"Return request ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Return request with line items","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalReturnDetailDto"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Return not found for this account"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/portal/saved-lists":{"get":{"tags":["Customer Portal"],"operationId":"list_portal_saved_lists","responses":{"200":{"description":"The customer's saved lists","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SavedListResponse"}}}}},"401":{"description":"Not authenticated"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["Customer Portal"],"operationId":"create_portal_saved_list","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSavedListRequest"}}},"required":true},"responses":{"201":{"description":"List created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedListResponse"}}}},"401":{"description":"Not authenticated"}},"security":[{"bearer_auth":[]}]}},"/api/portal/saved-lists/items/{item_id}":{"put":{"tags":["Customer Portal"],"operationId":"update_portal_saved_list_item","parameters":[{"name":"item_id","in":"path","description":"Item id (UUID)","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateListItemRequest"}}},"required":true},"responses":{"200":{"description":"Item updated"},"401":{"description":"Not authenticated"},"404":{"description":"Not found"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["Customer Portal"],"operationId":"remove_portal_saved_list_item","parameters":[{"name":"item_id","in":"path","description":"Item id (UUID)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Item removed"},"401":{"description":"Not authenticated"},"404":{"description":"Not found"}},"security":[{"bearer_auth":[]}]}},"/api/portal/saved-lists/{id}":{"get":{"tags":["Customer Portal"],"operationId":"get_portal_saved_list","parameters":[{"name":"id","in":"path","description":"List id (UUID)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"List with items","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedListDetailResponse"}}}},"401":{"description":"Not authenticated"},"404":{"description":"Not found"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["Customer Portal"],"operationId":"update_portal_saved_list","parameters":[{"name":"id","in":"path","description":"List id (UUID)","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSavedListRequest"}}},"required":true},"responses":{"200":{"description":"List renamed"},"401":{"description":"Not authenticated"},"404":{"description":"Not found"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["Customer Portal"],"operationId":"delete_portal_saved_list","parameters":[{"name":"id","in":"path","description":"List id (UUID)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"List deleted"},"401":{"description":"Not authenticated"},"404":{"description":"Not found"}},"security":[{"bearer_auth":[]}]}},"/api/portal/saved-lists/{id}/items":{"post":{"tags":["Customer Portal"],"operationId":"add_portal_saved_list_item","parameters":[{"name":"id","in":"path","description":"List id (UUID)","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddToListRequest"}}},"required":true},"responses":{"201":{"description":"Item added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedListItemResponse"}}}},"401":{"description":"Not authenticated"},"404":{"description":"List not found"}},"security":[{"bearer_auth":[]}]}},"/api/portal/settings":{"get":{"tags":["Portal Settings"],"summary":"GET /api/portal/settings — fetch currency, language, and branding for the authenticated portal company","operationId":"getPortalExperienceSettingsHandler","responses":{"200":{"description":"Portal-safe company presentation settings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalExperienceSettingsResponse"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/pos-terminals":{"get":{"tags":["pos_terminals"],"operationId":"list_pos_terminals","responses":{"200":{"description":"List of POS terminals","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListPosTerminalsResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]},"post":{"tags":["pos_terminals"],"operationId":"register_terminal","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterPosTerminalRequest"}}},"required":true},"responses":{"200":{"description":"Terminal already registered; refreshed and returned with its config","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosTerminalResponse"}}}},"201":{"description":"Terminal registered successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosTerminalResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Terminal id belongs to another company"}},"security":[{"jwt":[]}]}},"/api/pos-terminals/{id}":{"get":{"tags":["pos_terminals"],"operationId":"get_terminal","parameters":[{"name":"id","in":"path","description":"Terminal UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Terminal details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosTerminalResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]},"put":{"tags":["pos_terminals"],"operationId":"update_terminal","parameters":[{"name":"id","in":"path","description":"Terminal UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePosTerminalRequest"}}},"required":true},"responses":{"200":{"description":"Terminal updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosTerminalResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/pos-terminals/{id}/commands":{"post":{"tags":["pos_terminals"],"operationId":"send_command","parameters":[{"name":"id","in":"path","description":"Terminal UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendCommandRequest"}}},"required":true},"responses":{"200":{"description":"Command sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommandResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/pos-terminals/{id}/config":{"get":{"tags":["pos_terminals"],"operationId":"get_terminal_config","parameters":[{"name":"id","in":"path","description":"Terminal UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Terminal config"},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]},"put":{"tags":["pos_terminals"],"operationId":"update_terminal_config","parameters":[{"name":"id","in":"path","description":"Terminal UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTerminalConfigRequest"}}},"required":true},"responses":{"200":{"description":"Terminal config updated"},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/pos-terminals/{id}/sales-history":{"get":{"tags":["pos_terminals"],"operationId":"get_sales_history","parameters":[{"name":"id","in":"path","description":"Terminal UUID","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Limit results (default: 50)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Offset for pagination","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Sales history","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TerminalSalesHistory"}}}},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/pos-terminals/{terminal_id}/heartbeat":{"post":{"tags":["pos_terminals"],"operationId":"heartbeat","parameters":[{"name":"terminal_id","in":"path","description":"Terminal ID (not UUID)","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HeartbeatRequest"}}},"required":true},"responses":{"200":{"description":"Heartbeat received","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosTerminalResponse"}}}},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/pos/account-ledger":{"post":{"tags":["pos_ledgers"],"operationId":"push_account_ledger","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushAccountLedgerRequest"}}},"required":true},"responses":{"200":{"description":"Per-entry results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushLedgerResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]}},"/api/pos/accounts":{"post":{"tags":["pos_ledgers"],"summary":"Create a customer from the till (idempotent on the till's id).","operationId":"create_pos_account","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosAccountCreateRequest"}}},"required":true},"responses":{"200":{"description":"Customer already existed (retried push)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosAccountCreateResponse"}}}},"201":{"description":"Customer created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosAccountCreateResponse"}}}},"400":{"description":"Invalid customer"},"403":{"description":"Selling permission missing, or the company does not allow customer creation at the till"},"500":{"description":"Database error"}},"security":[{"jwt":[]}]}},"/api/pos/cash-movements":{"post":{"tags":["pos_ledgers"],"operationId":"push_cash_movements","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushCashMovementsRequest"}}},"required":true},"responses":{"200":{"description":"Per-entry results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushLedgerResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]}},"/api/pos/gift-card-redemptions":{"post":{"tags":["pos_ledgers"],"operationId":"push_gift_card_redemptions","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushGiftCardRedemptionsRequest"}}},"required":true},"responses":{"200":{"description":"Per-entry results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushLedgerResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]}},"/api/pos/orders":{"post":{"tags":["pos_checkout"],"operationId":"create_pos_order","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosOrderRequest"}}},"required":true},"responses":{"201":{"description":"Order created with receipt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosReceiptResponse"}}}},"400":{"description":"Validation error (empty cart, bad amounts, underpayment)"},"404":{"description":"Session not found"},"409":{"description":"Session is closed"}},"security":[{"jwt":[]}]}},"/api/pos/receipts/{id}/email":{"post":{"tags":["pos_ledgers"],"summary":"Queue a receipt e-mail (the till's \"E-mail receipt\" button).","operationId":"email_receipt","parameters":[{"name":"id","in":"path","description":"Cloud receipt id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiptEmailRequest"}}},"required":true},"responses":{"202":{"description":"E-mail queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiptEmailResponse"}}}},"400":{"description":"Invalid address"},"403":{"description":"Selling permission missing, or receipt e-mails switched off"},"404":{"description":"Receipt not found for this company"},"500":{"description":"Database error"}},"security":[{"jwt":[]}]}},"/api/pos/sessions":{"get":{"tags":["pos_checkout"],"operationId":"list_pos_sessions","parameters":[{"name":"status","in":"query","description":"Filter by status (open/closed)","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","description":"Limit (default: 20)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Offset","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"List of sessions","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PosSessionResponse"}}}}}},"security":[{"jwt":[]}]},"post":{"tags":["pos_checkout"],"operationId":"open_pos_session","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenSessionRequest"}}},"required":true},"responses":{"201":{"description":"Session opened","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosSessionResponse"}}}},"400":{"description":"Invalid starting cash amount"},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]}},"/api/pos/sessions/{id}":{"get":{"tags":["pos_checkout"],"operationId":"get_pos_session","parameters":[{"name":"id","in":"path","description":"Session UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Session details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosSessionResponse"}}}},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/pos/sessions/{id}/close":{"post":{"tags":["pos_checkout"],"operationId":"close_pos_session","parameters":[{"name":"id","in":"path","description":"Session UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CloseSessionRequest"}}},"required":true},"responses":{"200":{"description":"Session closed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosSessionResponse"}}}},"400":{"description":"Invalid ending cash amount"},"404":{"description":"Not found"},"409":{"description":"Session already closed"}},"security":[{"jwt":[]}]}},"/api/pricing/collections":{"get":{"tags":["Pricing"],"operationId":"list_collections_handler","responses":{"200":{"description":"Product collections","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ProductCollectionResponse"}}}}},"403":{"description":"Insufficient permissions"},"503":{"description":"Database unavailable"}}},"post":{"tags":["Pricing"],"operationId":"create_collection_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductCollectionRequest"}}},"required":true},"responses":{"201":{"description":"Created collection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductCollectionResponse"}}}},"403":{"description":"Insufficient permissions"},"503":{"description":"Database unavailable"}}}},"/api/pricing/collections/{id}":{"get":{"tags":["Pricing"],"operationId":"get_collection_handler","parameters":[{"name":"id","in":"path","description":"Collection ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Collection detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductCollectionDetailResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Collection not found"},"503":{"description":"Database unavailable"}}},"put":{"tags":["Pricing"],"operationId":"update_collection_handler","parameters":[{"name":"id","in":"path","description":"Collection ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProductCollectionRequest"}}},"required":true},"responses":{"200":{"description":"Updated collection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductCollectionResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Collection not found"},"503":{"description":"Database unavailable"}}},"delete":{"tags":["Pricing"],"operationId":"delete_collection_handler","parameters":[{"name":"id","in":"path","description":"Collection ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted collection"},"403":{"description":"Insufficient permissions"},"404":{"description":"Collection not found"},"503":{"description":"Database unavailable"}}}},"/api/pricing/collections/{id}/assignments":{"post":{"tags":["Pricing"],"operationId":"create_collection_assignment_handler","parameters":[{"name":"id","in":"path","description":"Collection ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCollectionAssignmentRequest"}}},"required":true},"responses":{"201":{"description":"Created assignment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductCollectionAssignmentResponse"}}}},"400":{"description":"Invalid request"},"403":{"description":"Insufficient permissions"},"404":{"description":"Collection not found"},"503":{"description":"Database unavailable"}}}},"/api/pricing/collections/{id}/assignments/{assignment_id}":{"delete":{"tags":["Pricing"],"operationId":"delete_collection_assignment_handler","parameters":[{"name":"id","in":"path","description":"Collection ID","required":true,"schema":{"type":"string"}},{"name":"assignment_id","in":"path","description":"Assignment ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Assignment deleted"},"403":{"description":"Insufficient permissions"},"404":{"description":"Assignment not found"},"503":{"description":"Database unavailable"}}}},"/api/pricing/collections/{id}/items":{"post":{"tags":["Pricing"],"operationId":"add_collection_items_handler","parameters":[{"name":"id","in":"path","description":"Collection ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManageCollectionItemsRequest"}}},"required":true},"responses":{"200":{"description":"Products added"},"403":{"description":"Insufficient permissions"},"404":{"description":"Collection not found"},"503":{"description":"Database unavailable"}}},"delete":{"tags":["Pricing"],"operationId":"remove_collection_items_handler","parameters":[{"name":"id","in":"path","description":"Collection ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManageCollectionItemsRequest"}}},"required":true},"responses":{"200":{"description":"Products removed"},"403":{"description":"Insufficient permissions"},"404":{"description":"Collection not found"},"503":{"description":"Database unavailable"}}}},"/api/pricing/customer-groups/{id}":{"get":{"tags":["Pricing"],"operationId":"get_customer_group_handler","parameters":[{"name":"id","in":"path","description":"Customer group ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Customer group","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerGroupResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Customer group not found"},"503":{"description":"Database unavailable"}}},"put":{"tags":["Pricing"],"operationId":"update_customer_group_handler","parameters":[{"name":"id","in":"path","description":"Customer group ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCustomerGroupRequest"}}},"required":true},"responses":{"200":{"description":"Updated customer group","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerGroupResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Customer group not found"},"503":{"description":"Database unavailable"}}}},"/api/pricing/customer-groups/{id}/members":{"get":{"tags":["Pricing"],"operationId":"list_group_members_handler","parameters":[{"name":"id","in":"path","description":"Customer group ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Group members","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CustomerGroupMemberResponse"}}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Customer group not found"},"503":{"description":"Database unavailable"}}}},"/api/pricing/discount-rules/{id}":{"get":{"tags":["Pricing"],"operationId":"get_discount_rule_handler","parameters":[{"name":"id","in":"path","description":"Discount rule ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Discount rule","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscountRuleResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Discount rule not found"},"503":{"description":"Database unavailable"}}},"put":{"tags":["Pricing"],"operationId":"update_discount_rule_handler","parameters":[{"name":"id","in":"path","description":"Discount rule ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDiscountRuleRequest"}}},"required":true},"responses":{"200":{"description":"Updated discount rule","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscountRuleResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Discount rule not found"},"503":{"description":"Database unavailable"}}}},"/api/pricing/price-lists/{id}":{"put":{"tags":["Pricing"],"operationId":"update_price_list_handler","parameters":[{"name":"id","in":"path","description":"Price list ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePriceListRequest"}}},"required":true},"responses":{"200":{"description":"Updated price list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceListResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Price list not found"},"503":{"description":"Database unavailable"}}}},"/api/pricing/promotions/active":{"get":{"tags":["Pricing"],"summary":"Every live promotion rule for the company, shaped for an offline terminal.","description":"Distinct from `list_discount_rules_handler`, which is the admin CRUD view.\nThis one:\n* returns only rules that are active and not expired, since a till has no\nreason to carry a promotion that ended last month;\n* pre-resolves collection membership into a flat product-id list, so the\nterminal never has to join `product_collection_items` locally — it has\nno copy of that table and no reason to grow one;\n* is a single call returning the whole (small) rule set, because the\nterminal caches it and evaluates in memory on every basket.","operationId":"active_promotions_handler","responses":{"200":{"description":"Active promotion rules for offline evaluation"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/pricing/resolve":{"post":{"tags":["Pricing"],"operationId":"resolve_price_handler","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolvePriceRequest"}}},"required":true},"responses":{"200":{"description":"Resolved account-specific product prices","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolvePriceResponse"}}}},"403":{"description":"Insufficient permissions"},"503":{"description":"Database unavailable"}}}},"/api/print-jobs":{"get":{"tags":["crate::features::horeca::print_jobs"],"summary":"`GET /api/print-jobs?claimable=1&terminal_id=...` — the print bridge's","description":"poll. Claim is atomic (`WHERE status='queued'` guard, design §5/§8):\neach matched row is individually claimed with a status-guarded UPDATE,\nso two bridges racing the same poll window never both print the same\njob.","operationId":"list_or_claim_print_jobs","parameters":[{"name":"claimable","in":"query","description":"Pass 1 to atomically claim queued jobs instead of just listing","required":false,"schema":{"type":"integer","format":"int32","nullable":true}},{"name":"terminal_id","in":"query","description":"Terminal identifier recorded on claimed jobs","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","description":"Max jobs to return (default 10)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"printer_profile_ids","in":"query","description":"Comma-separated printer_profile_id values. With claimable=1, restricts claiming to queued jobs targeting one of these printers (a bridge should not claim jobs for printers it has no local mapping for). Ignored on plain listing requests; absent/empty preserves the prior claim-any-queued-job behavior","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Print jobs","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PrintJobResponse"}}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/print-jobs/{id}/ack":{"post":{"tags":["crate::features::horeca::print_jobs"],"summary":"`POST /api/print-jobs/{id}/ack` — printed or failed. Failure increments","description":"`attempts`; the bridge/operator decides whether to re-queue (not\nautomatic in this wave — design §5's \"retry with backoff\" needs a\nbackground sweep this wave does not add, see report gap).","operationId":"ack_print_job","parameters":[{"name":"id","in":"path","description":"Print job ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AckPrintJobInput"}}},"required":true},"responses":{"200":{"description":"Print job updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrintJobResponse"}}}},"404":{"description":"Print job not found"},"422":{"description":"Invalid status value"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products":{"get":{"tags":["products"],"operationId":"get_products","parameters":[{"name":"limit","in":"query","description":"Number of items to return","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"offset","in":"query","description":"Number of items to skip","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"search","in":"query","description":"Search by name, item number, code, or description","required":false,"schema":{"type":"string","nullable":true}},{"name":"product_id","in":"query","description":"Filter by specific product ID (UUID)","required":false,"schema":{"type":"string","nullable":true}},{"name":"category","in":"query","description":"Filter by category ID","required":false,"schema":{"type":"string","nullable":true}},{"name":"min_price","in":"query","description":"Minimum price filter","required":false,"schema":{"type":"string","nullable":true}},{"name":"max_price","in":"query","description":"Maximum price filter","required":false,"schema":{"type":"string","nullable":true}},{"name":"min_qty","in":"query","description":"Minimum quantity filter","required":false,"schema":{"type":"string","nullable":true}},{"name":"max_qty","in":"query","description":"Maximum quantity filter","required":false,"schema":{"type":"string","nullable":true}},{"name":"show_excluded","in":"query","description":"Show excluded products (true/false)","required":false,"schema":{"type":"string","nullable":true}},{"name":"has_images","in":"query","description":"Filter by image presence: 'true' (with images), 'false' (without images)","required":false,"schema":{"type":"string","nullable":true}},{"name":"stock_level","in":"query","description":"Filter by stock level: 'low' (<10), 'out' (=0), 'negative' (<0)","required":false,"schema":{"type":"string","nullable":true}},{"name":"enrichment_status","in":"query","description":"Filter by enrichment status","required":false,"schema":{"type":"string","nullable":true}},{"name":"created_after","in":"query","description":"Filter by created date (YYYY-MM-DD), products created on or after this date","required":false,"schema":{"type":"string","nullable":true}},{"name":"created_before","in":"query","description":"Filter by created date (YYYY-MM-DD), products created on or before this date","required":false,"schema":{"type":"string","nullable":true}},{"name":"order_by","in":"query","description":"Sort field: created_at, name, price, qty, product_type","required":false,"schema":{"type":"string","nullable":true}},{"name":"order_dir","in":"query","description":"Sort direction: asc, desc","required":false,"schema":{"type":"string","nullable":true}},{"name":"product_exists","in":"query","description":"Check if product exists by item number","required":false,"schema":{"type":"string","nullable":true}},{"name":"zip_image_output","in":"query","description":"Get zip image output URL","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"List of products","content":{"application/json":{"schema":{}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["products"],"operationId":"create_product","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductInput"}}},"required":true},"responses":{"201":{"description":"Product created successfully","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/barcode":{"get":{"tags":["crate::features::products::handlers"],"operationId":"search_products_by_barcode","parameters":[{"name":"barcode","in":"query","description":"Barcode to search","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Barcode search results","content":{"application/json":{"schema":{}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/by-item-numbers":{"post":{"tags":["products"],"summary":"Get products by item numbers (batch lookup)","operationId":"get_products_by_item_numbers","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetProductsByItemNumbersRequest"}}},"required":true},"responses":{"200":{"description":"Products found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetProductsByItemNumbersResponse"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/batches":{"get":{"tags":["products"],"operationId":"list_enrichment_batches","parameters":[{"name":"limit","in":"query","description":"Max batches to return (default 20, max 100)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Batches to skip for paging (default 0)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Batches, most recent first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichmentBatchListResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["products"],"operationId":"create_enrichment_batch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichmentBatchCreateRequest"}}},"required":true},"responses":{"200":{"description":"Batch created and jobs queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichmentBatchCreateResponse"}}}},"400":{"description":"Neither product_ids nor collection_id provided"},"403":{"description":"Insufficient permissions"},"404":{"description":"Collection not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/batches/{id}":{"get":{"tags":["products"],"operationId":"get_enrichment_batch","parameters":[{"name":"id","in":"path","description":"Batch ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Batch with live job counts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichmentBatchSummary"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Batch not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/batches/{id}/cancel":{"post":{"tags":["products"],"operationId":"cancel_enrichment_batch","parameters":[{"name":"id","in":"path","description":"Batch ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Queued/claimed jobs cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichmentBatchCancelResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Batch not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/batches/{id}/progress/stream":{"get":{"tags":["products"],"operationId":"stream_enrichment_batch_progress","parameters":[{"name":"id","in":"path","description":"Batch ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"SSE stream of batch progress; closes on terminal status"},"403":{"description":"Insufficient permissions"},"404":{"description":"Batch not found"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/bulk-run":{"post":{"tags":["products"],"operationId":"bulk_run_product_enrichment","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnrichmentBulkRunRequest"}}},"required":true},"responses":{"200":{"description":"Bulk enrichment results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnrichmentBulkRunResponse"}}}},"400":{"description":"Real AI model is not configured"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/completeness":{"get":{"tags":["products"],"operationId":"get_enrichment_completeness","responses":{"200":{"description":"Catalog-health score and gap facet counts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichmentCompletenessResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/embeddings/backfill":{"post":{"tags":["products"],"operationId":"backfill_product_embeddings","responses":{"200":{"description":"Embedding backfill enqueued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbeddingsBackfillResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/groups":{"get":{"tags":["products"],"operationId":"list_product_groups","parameters":[{"name":"status","in":"query","description":"proposed | approved | dismissed. Omit for all.","required":false,"schema":{"type":"string","nullable":true}},{"name":"page","in":"query","description":"1-based page (default 1)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"page_size","in":"query","description":"Items per page (default 20, max 100)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Product groups with members","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductGroupsResponse"}}}},"400":{"description":"Invalid status filter"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/groups/merge":{"post":{"tags":["products"],"operationId":"merge_product_groups","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductGroupMergeRequest"}}},"required":true},"responses":{"200":{"description":"Groups merged into target","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductGroupActionResponse"}}}},"400":{"description":"Invalid merge (same group)"},"403":{"description":"Insufficient permissions"},"404":{"description":"Group not found"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/groups/propose":{"post":{"tags":["products"],"operationId":"propose_product_groups","responses":{"200":{"description":"Group-proposal job enqueued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductGroupProposeResponse"}}}},"400":{"description":"Real embeddings are not configured"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/groups/{id}/approve":{"post":{"tags":["products"],"operationId":"approve_product_group","parameters":[{"name":"id","in":"path","description":"Group ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Group approved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductGroupActionResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Group not found"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/groups/{id}/dismiss":{"post":{"tags":["products"],"operationId":"dismiss_product_group","parameters":[{"name":"id","in":"path","description":"Group ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Group dismissed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductGroupActionResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Group not found"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/groups/{id}/members":{"post":{"tags":["products"],"operationId":"add_product_group_member","parameters":[{"name":"id","in":"path","description":"Group ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductGroupAddMemberRequest"}}},"required":true},"responses":{"200":{"description":"Member added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductGroupActionResponse"}}}},"400":{"description":"Invalid product"},"403":{"description":"Insufficient permissions"},"404":{"description":"Group or product not found"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/groups/{id}/members/{product_id}":{"delete":{"tags":["products"],"operationId":"remove_product_group_member","parameters":[{"name":"id","in":"path","description":"Group ID","required":true,"schema":{"type":"string"}},{"name":"product_id","in":"path","description":"Product ID to remove","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Member removed; never-group overrides recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductGroupActionResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Group or member not found"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/jobs":{"get":{"tags":["products"],"operationId":"list_enrichment_jobs","parameters":[{"name":"status","in":"query","description":"Job status filter: dead, queued, claimed, processing, completed, failed, cancelled. Omit for all.","required":false,"schema":{"type":"string","nullable":true}},{"name":"page","in":"query","description":"1-based page number (default 1)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"page_size","in":"query","description":"Items per page (default 50, max 200)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Durable enrichment jobs, most recent first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichmentJobsResponse"}}}},"400":{"description":"Invalid status filter"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/jobs/requeue-dead":{"post":{"tags":["products"],"operationId":"requeue_dead_enrichment_jobs","responses":{"200":{"description":"All dead jobs requeued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichmentJobRequeueResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/jobs/{id}/requeue":{"post":{"tags":["products"],"operationId":"requeue_enrichment_job","parameters":[{"name":"id","in":"path","description":"Enrichment job ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Job requeued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichmentJobRequeueResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Job not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/queue":{"get":{"tags":["products"],"operationId":"get_product_enrichment_queue","parameters":[{"name":"status","in":"query","description":"Queue status filter: pending, claimed, processing, completed, failed, dead","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","description":"Max items to return (default 50, max 200)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Items to skip for paging (default 0)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Enrichment queue entries, most recent first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnrichmentQueueResponse"}}}},"400":{"description":"Invalid status filter"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/queue-health":{"get":{"tags":["products"],"operationId":"get_enrichment_queue_health","responses":{"200":{"description":"Queue health snapshot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichmentQueueHealthResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/usage":{"get":{"tags":["products"],"operationId":"get_enrichment_usage","parameters":[{"name":"months","in":"query","description":"Months back to aggregate (default 3, max 24)","required":false,"schema":{"type":"integer","format":"int32","nullable":true}}],"responses":{"200":{"description":"AI usage/credits per month and provider","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichmentUsageResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/enrichment/worklist":{"get":{"tags":["products"],"operationId":"get_enrichment_worklist","parameters":[{"name":"gap","in":"query","description":"Gap filter (see EnrichmentWorklistQuery)","required":false,"schema":{"type":"string","nullable":true}},{"name":"search","in":"query","description":"Match name or item number","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","description":"Page size (default 50, max 200)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Page offset","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Products with completeness indicators","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichmentWorklistResponse"}}}},"400":{"description":"Unknown gap filter"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/featured":{"get":{"tags":["crate::features::products::handlers"],"operationId":"get_featured_products","responses":{"200":{"description":"Featured products","content":{"application/json":{"schema":{}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/images/by-external-id":{"post":{"tags":["products"],"summary":"Upload an image for the product mapped to (`source_system`, `external_id`)","description":"Resolves the product through the `external_references` identity map\n(`entity_type` \"product\") and then performs the same upload as\nPOST /api/products/{id}/images. 404 when no mapping exists — sync agents\nshould import the product before pushing its pictures.","operationId":"upload_product_image_by_external_id","parameters":[{"name":"source_system","in":"query","description":"Source-system label in the `external_references` identity map\n(e.g. \"`quickbooks_pos`\").","required":true,"schema":{"type":"string"}},{"name":"external_id","in":"query","description":"The record's id in that source system (e.g. a QBPOS `ListID`).","required":true,"schema":{"type":"string"}},{"name":"sync_key","in":"query","description":"Stable source-side image slot. `latest` is one replaceable slot; all\nmode sends one stable key per QBPOS picture.","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Image file","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/ProductImageUploadForm"}}},"required":true},"responses":{"201":{"description":"Image uploaded"},"400":{"description":"Bad request"},"404":{"description":"No product is mapped to this external id"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/images/check-existence":{"post":{"tags":["products"],"summary":"Check if images exist for a list of item numbers","description":"Returns enriched results including product name and existing image count,\nso the frontend can build a full preview without a second API call.","operationId":"check_images_existence","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkCheckImagesRequest"}}},"required":true},"responses":{"200":{"description":"Enriched image existence results per item number","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/BulkCheckImageResult"}}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/images/manifest":{"post":{"tags":["products"],"summary":"List the caller's company image rows so a client can diff its local","description":"offline cache against the server in one request instead of probing each\nimage individually (up to 25 concurrent GETs per the mobile bulk-download\npath this endpoint exists to relieve).\n\nSee [`ProductImageManifestRequest`] for the `since`/`ids`/`cursor` request\nshape and [`ProductImageManifestEntry`] for exactly what `bytes` and\n`available` mean in each mode: bulk listings are DB-only (cheap, no R2\ncalls, `available` reflects DB health only) and a bounded `ids` list gets\na real R2-verified `available`/`bytes` at the same per-image cost as\nfetching it directly.","operationId":"get_product_image_manifest","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductImageManifestRequest"}}},"required":true},"responses":{"200":{"description":"Manifest page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductImageManifestResponse"}}}},"400":{"description":"Bad request (invalid cursor or too many ids)"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/images/{image_id}":{"get":{"tags":["products"],"summary":"Get a product image by image ID, proxied from R2 storage.","description":"Supports optional `?size=thumb_200` or `?size=thumb_400` query parameter.\nFalls back to the original image if the requested thumbnail is not available.\nHonours `If-None-Match` and answers 304 (skipping the R2 fetch) when the\nclient's cached `ETag` is still current.","operationId":"get_product_image","parameters":[{"name":"image_id","in":"path","description":"Image ID","required":true,"schema":{"type":"string"}},{"name":"size","in":"query","description":"Optional size: thumb_200 or thumb_400","required":false,"schema":{"type":"string","nullable":true}},{"name":"If-None-Match","in":"header","description":"ETag from a previous response; a match returns 304","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Image bytes"},"304":{"description":"Not modified — cached ETag still valid"},"403":{"description":"Access denied"},"404":{"description":"Image not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["products"],"summary":"Delete an image","operationId":"delete_product_image","parameters":[{"name":"image_id","in":"path","description":"Image ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Image deleted"},"404":{"description":"Image not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"head":{"tags":["products"],"summary":"HEAD variant of [`get_product_image`] — identical lookup, ownership check,","description":"and ETag/If-None-Match handling, but the response never carries a body.\nLets a client learn `Content-Length` (and confirm the image still exists)\nbefore committing local disk to a bulk offline-image download.","operationId":"head_product_image","parameters":[{"name":"image_id","in":"path","description":"Image ID","required":true,"schema":{"type":"string"}},{"name":"size","in":"query","description":"Optional size: thumb_200 or thumb_400","required":false,"schema":{"type":"string","nullable":true}},{"name":"If-None-Match","in":"header","description":"ETag from a previous response; a match returns 304","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Headers only — Content-Length is the image size"},"304":{"description":"Not modified — cached ETag still valid"},"403":{"description":"Access denied"},"404":{"description":"Image not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/low-stock":{"get":{"tags":["crate::features::products::handlers"],"operationId":"get_low_stock_products","responses":{"200":{"description":"Low stock products","content":{"application/json":{"schema":{}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/popular":{"get":{"tags":["crate::features::products::handlers"],"operationId":"get_popular_products","responses":{"200":{"description":"Popular products","content":{"application/json":{"schema":{}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/quick-search":{"get":{"tags":["crate::features::products::handlers"],"operationId":"quick_search_products","parameters":[{"name":"search","in":"query","description":"Quick search term","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Quick search results","content":{"application/json":{"schema":{}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/search":{"get":{"tags":["products"],"operationId":"search_products","parameters":[{"name":"search","in":"query","description":"Search term","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Search results","content":{"application/json":{"schema":{}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/search/facets":{"get":{"tags":["products"],"operationId":"get_hybrid_search_facets","parameters":[{"name":"q","in":"query","description":"Search query","required":true,"schema":{"type":"string"}},{"name":"language","in":"query","description":"Preferred language code","required":false,"schema":{"type":"string","nullable":true}},{"name":"mode","in":"query","description":"Search mode: exact, keyword, semantic, hybrid","required":false,"schema":{"type":"string","nullable":true}},{"name":"category_id","in":"query","description":"Optional category filter","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Facet counts for search query","content":{"application/json":{"schema":{}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/search/hybrid":{"post":{"tags":["products"],"operationId":"hybrid_search_products","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HybridProductSearchRequest"}}},"required":true},"responses":{"200":{"description":"Hybrid search results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HybridProductSearchResponse"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/stats":{"get":{"tags":["crate::features::products::handlers"],"operationId":"get_product_stats","responses":{"200":{"description":"Product statistics","content":{"application/json":{"schema":{}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/{id}":{"get":{"tags":["products"],"operationId":"get_product","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Product details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebappProduct"}}}},"404":{"description":"Product not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["products"],"operationId":"update_product","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductInput"}}},"required":true},"responses":{"200":{"description":"Product updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebappProduct"}}}},"400":{"description":"Bad request"},"404":{"description":"Product not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["products"],"operationId":"delete_product","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Product deleted"},"404":{"description":"Product not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["products"],"operationId":"patch_product","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchProductInput"}}},"required":true},"responses":{"200":{"description":"Product patched","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebappProduct"}}}},"400":{"description":"Bad request"},"404":{"description":"Product not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/{id}/enrichment":{"get":{"tags":["products"],"operationId":"get_product_enrichment","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Enrichment detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnrichmentDetailResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Product not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/{id}/enrichment/approve":{"patch":{"tags":["products"],"operationId":"approve_product_enrichment","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnrichmentApprovalRequest"}}},"required":true},"responses":{"200":{"description":"Enrichment approval updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnrichmentApprovalResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Product not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/{id}/enrichment/job/cancel":{"post":{"tags":["products"],"operationId":"cancel_product_enrichment_job","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The queued run was cancelled (recorded as failed with 'Cancelled by user')","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnrichmentJobActionResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Product not found"},"409":{"description":"No queued run to cancel, or the run is already processing"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/{id}/enrichment/job/retry":{"post":{"tags":["products"],"operationId":"retry_product_enrichment_job","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"A new enrichment run was queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnrichmentJobActionResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Product not found"},"409":{"description":"Nothing to retry, or a run is already queued/processing"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/{id}/enrichment/run":{"post":{"tags":["products"],"operationId":"run_product_enrichment","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnrichmentRunRequest"}}},"required":true},"responses":{"200":{"description":"Enrichment queued or skipped","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnrichmentRunQueuedResponse"}}}},"400":{"description":"Real AI model is not configured"},"403":{"description":"Insufficient permissions"},"404":{"description":"Product not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/{id}/images":{"post":{"tags":["products"],"summary":"Upload an image for a specific product","operationId":"upload_product_image","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Image file","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/ProductImageUploadForm"}}},"required":true},"responses":{"201":{"description":"Image uploaded"},"400":{"description":"Bad request"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["products"],"summary":"Delete all images for a specific product","description":"Soft-deletes all non-deleted images for the product and removes files from R2 storage.\nUsed by bulk image import \"replace\" mode to clear existing images before uploading new ones.","operationId":"delete_all_product_images","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"All images deleted"},"404":{"description":"Product not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/{id}/similar":{"get":{"tags":["products"],"operationId":"get_similar_products","parameters":[{"name":"id","in":"path","description":"Product id","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max neighbours (1-100, default 10)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Semantically similar products","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimilarProductsResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Product not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/{id}/stock-history":{"get":{"tags":["Inventory"],"operationId":"get_product_stock_history","parameters":[{"name":"id","in":"path","description":"Product id","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Page size (1-500, default 50)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Rows to skip (default 0)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Stock movements for this product, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockMovementListResponse"}}}},"403":{"description":"Insufficient permissions"}}}},"/api/products/{id}/variants":{"get":{"tags":["Products"],"summary":"GET /api/products/{id}/variants","operationId":"list_product_variants","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Variants of the product, oldest first","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ProductVariantResponse"}}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Product not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["Products"],"summary":"POST /api/products/{id}/variants","operationId":"create_product_variant","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductVariantRequest"}}},"required":true},"responses":{"201":{"description":"Variant created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductVariantResponse"}}}},"400":{"description":"Validation error"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Product not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/{id}/variants/{variant_id}":{"delete":{"tags":["Products"],"summary":"DELETE /api/products/{id}/variants/{variant_id} — orders that sold the","description":"variant keep their snapshot (`variant_label`), so deleting is safe.","operationId":"delete_product_variant","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}},{"name":"variant_id","in":"path","description":"Variant ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Variant deleted"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Product or variant not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["Products"],"summary":"PATCH /api/products/{id}/variants/{variant_id}","operationId":"update_product_variant","parameters":[{"name":"id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}},{"name":"variant_id","in":"path","description":"Variant ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProductVariantRequest"}}},"required":true},"responses":{"200":{"description":"Variant updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductVariantResponse"}}}},"400":{"description":"Validation error"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Product or variant not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/{product_id}/modifier-groups":{"get":{"tags":["crate::features::modifier_groups::handlers"],"operationId":"list_product_modifier_groups","parameters":[{"name":"product_id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of modifier groups for product","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ModifierGroupWithOptions"}}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/products/{product_id}/modifier-groups/{group_id}":{"post":{"tags":["crate::features::modifier_groups::handlers"],"operationId":"attach_modifier_group_to_product","parameters":[{"name":"product_id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}},{"name":"group_id","in":"path","description":"Modifier Group ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachModifierGroupInput"}}},"required":true},"responses":{"201":{"description":"Modifier group attached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductModifierGroup"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["crate::features::modifier_groups::handlers"],"operationId":"detach_modifier_group_from_product","parameters":[{"name":"product_id","in":"path","description":"Product ID","required":true,"schema":{"type":"string"}},{"name":"group_id","in":"path","description":"Modifier Group ID","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Modifier group detached"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/public/catalogues":{"post":{"tags":["hosted-catalogues"],"summary":"POST /api/public/catalogues — start an anonymous draft.","operationId":"create_draft","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftCreated"}}}}}}},"/api/public/catalogues/by-slug/{slug}":{"get":{"tags":["hosted-catalogues"],"summary":"GET /api/public/catalogues/by-slug/{slug} — what a customer sees.","operationId":"read_public","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCatalogue"}}}}}}},"/api/public/catalogues/company/{company}/events":{"post":{"tags":["catalog-search"],"operationId":"record_event","parameters":[{"name":"company","in":"path","description":"Published company","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchEvent"}}},"required":true},"responses":{"204":{"description":"Event recorded"},"404":{"description":"Not published"}}}},"/api/public/catalogues/company/{company}/search":{"get":{"tags":["catalog-search"],"operationId":"search_public","parameters":[{"name":"company","in":"path","description":"Published company","required":true,"schema":{"type":"string"}},{"name":"q","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"category","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"product","in":"query","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Public product search","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogSearchResponse"}}}},"404":{"description":"Not published"}}}},"/api/public/catalogues/verify":{"get":{"tags":["hosted-catalogues"],"summary":"GET /api/public/catalogues/verify — kept only to forward stray links.","description":"This used to be where the emailed link landed and where publishing happened.\nIt no longer changes anything: a GET that publishes is fired by every link\nscanner that inspects the merchant's inbox. Publishing is now a POST from\nthe confirmation page, and this route exists so that a link from an older\nemail, or one someone copied out of a scanner log, still reaches that page\nrather than a 404.","operationId":"verify_publish","responses":{"302":{"description":"Redirects to the confirmation page"}}},"post":{"tags":["hosted-catalogues"],"summary":"POST /api/public/catalogues/verify — the confirmation page calls this.","description":"Publishing lives behind a POST so that reading the merchant's email cannot\npublish their catalogue. The token is still single use.","operationId":"confirm_publish","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmPublishRequest"}}},"required":true},"responses":{"200":{"description":"Published"}}}},"/api/public/catalogues/{id}":{"get":{"tags":["hosted-catalogues"],"summary":"GET /api/public/catalogues/{id} — the owning browser's view of its own draft.","operationId":"read_own","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogueState"}}}}}},"put":{"tags":["hosted-catalogues"],"summary":"PUT /api/public/catalogues/{id} — save the catalogue's contents.","operationId":"save_content","requestBody":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveContentRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogueState"}}}}}},"delete":{"tags":["hosted-catalogues"],"summary":"DELETE /api/public/catalogues/{id} — remove it and its images.","operationId":"delete_catalogue","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleMessage"}}}}}}},"/api/public/catalogues/{id}/extend":{"post":{"tags":["hosted-catalogues"],"summary":"POST /api/public/catalogues/{id}/extend — another 30 days.","operationId":"extend_catalogue","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogueState"}}}}}}},"/api/public/catalogues/{id}/images":{"post":{"tags":["hosted-catalogues"],"summary":"POST /api/public/catalogues/{id}/images — store one product photo.","operationId":"upload_image","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageStored"}}}}}}},"/api/public/catalogues/{id}/publish":{"post":{"tags":["hosted-catalogues"],"summary":"POST /api/public/catalogues/{id}/publish — ask for the link by email.","description":"This is where the anonymity ends, deliberately. Building and previewing cost\nnothing and identify nobody; putting images on a vender.cloud address is\nwhat requires an address we have round-tripped.","operationId":"request_publish","requestBody":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleMessage"}}}}}}},"/api/public/offers":{"get":{"tags":["billing"],"operationId":"listPublicOffers","parameters":[{"name":"lang","in":"query","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PublicOffer"}}}}}}}},"/api/public/offers/quote":{"get":{"tags":["billing"],"operationId":"quotePublicOffers","parameters":[{"name":"selections","in":"query","description":"JSON array of `OfferSelection` objects; the server loads all prices.","required":true,"schema":{"type":"string"}},{"name":"interval","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OfferInterval"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/super.offers.OfferQuote"}}}},"400":{"description":"Invalid selection"}}}},"/api/public/plans":{"get":{"tags":["Billing"],"summary":"GET /api/public/plans?lang=en|nl|es","description":"Unauthenticated: the marketing site's pricing page. Serves only\n`is_active AND is_public` rows and never the entitlement map or Stripe ids.","operationId":"listPublicPlans","parameters":[{"name":"lang","in":"query","description":"Locale for the display copy (`en`, `nl`, `es`). Falls back to `en`.","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Public pricing catalog, ordered by sort_order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPlansResponse"}}}},"500":{"description":"Database error"}}}},"/api/public/services":{"get":{"tags":["billing"],"operationId":"listPublicCommercialServices","parameters":[{"name":"lang","in":"query","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PublicCommercialService"}}}}}}}},"/api/purchase-orders":{"get":{"tags":["procurement"],"operationId":"listPurchaseOrders","parameters":[{"name":"search","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"status","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"supplier_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"location_id","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Purchase-order list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderListResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]},"post":{"tags":["procurement"],"operationId":"createPurchaseOrder","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePurchaseOrderRequest"}}},"required":true},"responses":{"201":{"description":"Purchase order created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderResponse"}}}},"400":{"description":"Invalid order"},"403":{"description":"Forbidden"},"409":{"description":"Document-number conflict"},"422":{"description":"Currency is not the company currency (exchange rates are not supported yet)"}},"security":[{"jwt":[]}]}},"/api/purchase-orders/{id}":{"get":{"tags":["procurement"],"operationId":"getPurchaseOrder","parameters":[{"name":"id","in":"path","description":"Purchase order ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Purchase order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/purchase-orders/{id}/approve":{"post":{"tags":["procurement"],"operationId":"approvePurchaseOrder","parameters":[{"name":"id","in":"path","description":"Purchase order ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderTransitionRequest"}}},"required":true},"responses":{"200":{"description":"Purchase order transitioned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Invalid state"}},"security":[{"jwt":[]}]}},"/api/purchase-orders/{id}/cancel":{"post":{"tags":["procurement"],"operationId":"cancelPurchaseOrder","parameters":[{"name":"id","in":"path","description":"Purchase order ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderTransitionRequest"}}},"required":true},"responses":{"200":{"description":"Purchase order transitioned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Invalid state"}},"security":[{"jwt":[]}]}},"/api/purchase-orders/{id}/close":{"post":{"tags":["procurement"],"operationId":"closePurchaseOrder","parameters":[{"name":"id","in":"path","description":"Purchase order ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderTransitionRequest"}}},"required":true},"responses":{"200":{"description":"Purchase order transitioned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Invalid state"}},"security":[{"jwt":[]}]}},"/api/purchase-orders/{id}/mark-sent":{"post":{"tags":["procurement"],"operationId":"markPurchaseOrderSent","parameters":[{"name":"id","in":"path","description":"Purchase order ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderTransitionRequest"}}},"required":true},"responses":{"200":{"description":"Purchase order transitioned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Invalid state"}},"security":[{"jwt":[]}]}},"/api/purchase-orders/{id}/submit":{"post":{"tags":["procurement"],"operationId":"submitPurchaseOrder","parameters":[{"name":"id","in":"path","description":"Purchase order ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderTransitionRequest"}}},"required":true},"responses":{"200":{"description":"Purchase order transitioned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseOrderResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Invalid state"}},"security":[{"jwt":[]}]}},"/api/query/{entity}/aggregate":{"post":{"tags":["crate::features::query::handlers"],"summary":"`POST /api/query/{entity}/aggregate` — tenancy-scoped `GROUP BY` /","description":"aggregate execution over any registered entity: the HTTP face of\n`shared::query::aggregate::compile_and_run`.\n\nThe company guard comes from the authenticated claims (never the body);\ndate buckets are computed in the company's configured timezone; row,\ngroup-cardinality and statement-timeout ceilings from `QuerySettings`\n(clamped to platform maxima) apply inside the engine.","operationId":"aggregate","parameters":[{"name":"entity","in":"path","description":"Entity key, e.g. sales_receipts","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AggregateRequest"}}},"required":true},"responses":{"200":{"description":"Aggregated groups","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AggregateResponse"}}}},"400":{"description":"Invalid filter or aggregation"},"403":{"description":"Insufficient permissions"},"404":{"description":"Unknown entity"}},"security":[{"bearer_auth":[]}]}},"/api/query/{entity}/facets":{"post":{"tags":["crate::features::query::handlers"],"summary":"`POST /api/query/{entity}/facets` — drill-down counts, each facet stripping","description":"its own conditions. Products only.","operationId":"compute_facets","parameters":[{"name":"entity","in":"path","description":"Entity key","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FacetsRequest"}}},"required":true},"responses":{"200":{"description":"Facet counts"}},"security":[{"bearer_auth":[]}]}},"/api/query/{entity}/ids":{"post":{"tags":["crate::features::query::handlers"],"summary":"`POST /api/query/{entity}/ids` — matching IDs for bulk operations, paged so","description":"a huge selection never materialises client-side. Products only.","operationId":"matching_ids","parameters":[{"name":"entity","in":"path","description":"Entity key","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdsRequest"}}},"required":true},"responses":{"200":{"description":"Matching product IDs"}},"security":[{"bearer_auth":[]}]}},"/api/query/{entity}/preview":{"post":{"tags":["crate::features::query::handlers"],"summary":"`POST /api/query/{entity}/preview` — a page of matching rows plus the total.","description":"Products only (the row shape is the typed product response).","operationId":"preview","parameters":[{"name":"entity","in":"path","description":"Entity key","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewRequest"}}},"required":true},"responses":{"200":{"description":"A page of matching rows"}},"security":[{"bearer_auth":[]}]}},"/api/query/{entity}/schema":{"get":{"tags":["crate::features::query::handlers"],"summary":"`GET /api/query/{entity}/schema` — the registry resolved for this company","description":"and its dynamic options (categories, for products). Drives the whole\nbuilder UI. Fields the caller lacks permission for are omitted entirely.","operationId":"get_schema","parameters":[{"name":"entity","in":"path","description":"Entity key, e.g. products or sales_receipts","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Query schema for the entity"},"403":{"description":"Insufficient permissions"},"404":{"description":"Unknown entity"}},"security":[{"bearer_auth":[]}]}},"/api/query/{entity}/validate":{"post":{"tags":["crate::features::query::handlers"],"summary":"`POST /api/query/{entity}/validate` — check and normalise a tree without","description":"running it. The editor calls this on every debounce.","operationId":"validate","parameters":[{"name":"entity","in":"path","description":"Entity key","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateRequest"}}},"required":true},"responses":{"200":{"description":"Validation result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateResponse"}}}}},"security":[{"bearer_auth":[]}]}},"/api/quotes":{"get":{"tags":["Quotes"],"operationId":"list_quotes","responses":{"200":{"description":"List of quotes for the company","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/QuoteListItem"}}}}},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["Quotes"],"operationId":"create_quote","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateQuoteRequest"}}},"required":true},"responses":{"201":{"description":"Quote created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteDetailResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/quotes/{id}":{"get":{"tags":["Quotes"],"operationId":"get_quote","parameters":[{"name":"id","in":"path","description":"Quote id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Quote with items","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteDetailResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Quote not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/quotes/{id}/approve":{"post":{"tags":["Quotes"],"operationId":"approve_quote","parameters":[{"name":"id","in":"path","description":"Quote id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Quote approved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteDetailResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Quote not found"},"409":{"description":"Quote not in a state that can be approved"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/quotes/{id}/convert":{"post":{"tags":["Quotes"],"operationId":"convert_quote","parameters":[{"name":"id","in":"path","description":"Quote id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Quote converted; new sales order created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConvertQuoteResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Quote not found"},"409":{"description":"Quote not in a state that can be converted"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/reports/cogs-ledger":{"get":{"tags":["Reports"],"summary":"`GET /api/reports/cogs-ledger` — see the module docs.","operationId":"get_cogs_ledger","parameters":[{"name":"start_date","in":"query","description":"Inclusive start: `YYYY-MM-DD` (company timezone) or RFC 3339.","required":false,"schema":{"type":"string","nullable":true}},{"name":"end_date","in":"query","description":"Inclusive end: `YYYY-MM-DD` (company timezone) or RFC 3339.","required":false,"schema":{"type":"string","nullable":true}},{"name":"kind","in":"query","description":"Only this allocation kind: `receipt`, `consume`, `reverse`, `adjust`, `transfer`.","required":false,"schema":{"type":"string","nullable":true}},{"name":"product_id","in":"query","description":"Only this product.","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","description":"Page size (1–1000, default 100).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Rows to skip.","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Cost ledger allocations, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CogsLedgerResponse"}}}},"400":{"description":"Invalid filters"},"403":{"description":"Forbidden"}},"security":[{"bearer_auth":[]}]}},"/api/reports/customer-analytics":{"post":{"tags":["Reports"],"operationId":"get_customer_analytics","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportFilters"}}},"required":true},"responses":{"200":{"description":"Customer analytics report with segmentation and insights. Date filters bound the order activity window (company timezone); category_id restricts to orders containing products of that category.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerAnalytics"}}}},"400":{"description":"Invalid filter values"},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/reports/gross-margin":{"get":{"tags":["Reports"],"summary":"`GET /api/reports/gross-margin` — see the module docs.","operationId":"get_gross_margin","parameters":[{"name":"start_date","in":"query","description":"Inclusive start: `YYYY-MM-DD` (company timezone) or RFC 3339.","required":false,"schema":{"type":"string","nullable":true}},{"name":"end_date","in":"query","description":"Inclusive end: `YYYY-MM-DD` (company timezone) or RFC 3339.","required":false,"schema":{"type":"string","nullable":true}},{"name":"group_by","in":"query","description":"Grouping; defaults to `product`.","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/MarginGroupBy"}],"nullable":true}},{"name":"location_id","in":"query","description":"Only this location.","required":false,"schema":{"type":"string","nullable":true}},{"name":"channel","in":"query","description":"Only this channel (`pos_sync`, `pos_online`, `horeca`, `admin_receipt`, `order_picking`).","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","description":"Page size (1–1000, default 100).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Rows to skip.","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Revenue, cost of goods and gross margin at the cost captured when stock left","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrossMarginResponse"}}}},"400":{"description":"Invalid filters"},"403":{"description":"Forbidden or inaccessible location"}},"security":[{"bearer_auth":[]}]}},"/api/reports/inventory":{"get":{"tags":["Reports"],"operationId":"get_inventory_report","parameters":[{"name":"start_date","in":"query","description":"Inclusive range start. `YYYY-MM-DD` (interpreted as start of that day\nin the company timezone) or an RFC 3339 instant.","required":false,"schema":{"type":"string","nullable":true}},{"name":"end_date","in":"query","description":"Inclusive range end. `YYYY-MM-DD` is interpreted as end of that day in\nthe company timezone; an RFC 3339 instant is an exclusive upper bound.","required":false,"schema":{"type":"string","nullable":true}},{"name":"category_id","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","description":"Maximum number of rows returned for the report's largest table.","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Number of rows to skip for the report's largest table.","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Inventory report with statistics. Date filters restrict to products created in the range (company timezone); category_id restricts to that category.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryReport"}}}},"400":{"description":"Invalid filter values"},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/reports/inventory-valuation":{"get":{"tags":["Reports"],"operationId":"getInventoryValuation","parameters":[{"name":"location_id","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"search","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"exceptions_only","in":"query","required":false,"schema":{"type":"boolean","nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Perpetual inventory valuation and reconciliation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryValuationResponse"}}}},"400":{"description":"Invalid filters"},"403":{"description":"Forbidden"},"500":{"description":"Database error"}},"security":[{"jwt":[]}]}},"/api/reports/product-cost-summary":{"get":{"tags":["Reports"],"summary":"`GET /api/reports/product-cost-summary` — see the module docs.","operationId":"get_product_cost_summary","parameters":[{"name":"product_id","in":"query","description":"Product id.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"One product's cost by location, last received cost and margin at its list prices","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductCostSummaryResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Product not found"}},"security":[{"bearer_auth":[]}]}},"/api/reports/purchasing-spend":{"get":{"tags":["Reports"],"summary":"`GET /api/reports/purchasing-spend` — see the module docs.","operationId":"get_purchasing_spend","parameters":[{"name":"start_date","in":"query","description":"Inclusive start: `YYYY-MM-DD` (company timezone) or RFC 3339.","required":false,"schema":{"type":"string","nullable":true}},{"name":"end_date","in":"query","description":"Inclusive end: `YYYY-MM-DD` (company timezone) or RFC 3339.","required":false,"schema":{"type":"string","nullable":true}},{"name":"group_by","in":"query","description":"Grouping; defaults to `supplier`.","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/SpendGroupBy"}],"nullable":true}},{"name":"limit","in":"query","description":"Page size (1–1000, default 100).","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Rows to skip.","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Posted goods receipts by supplier, product or month","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchasingSpendResponse"}}}},"400":{"description":"Invalid filters"},"403":{"description":"Forbidden"}},"security":[{"bearer_auth":[]}]}},"/api/reports/sales":{"get":{"tags":["Reports"],"operationId":"get_sales_report","parameters":[{"name":"start_date","in":"query","description":"Inclusive range start. `YYYY-MM-DD` (interpreted as start of that day\nin the company timezone) or an RFC 3339 instant.","required":false,"schema":{"type":"string","nullable":true}},{"name":"end_date","in":"query","description":"Inclusive range end. `YYYY-MM-DD` is interpreted as end of that day in\nthe company timezone; an RFC 3339 instant is an exclusive upper bound.","required":false,"schema":{"type":"string","nullable":true}},{"name":"category_id","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","description":"Maximum number of rows returned for the report's largest table.","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Number of rows to skip for the report's largest table.","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Sales report with revenue and customer statistics. Date filters bound the order range (company timezone); category_id restricts to orders containing products of that category. sales_by_period is bucketed by month in the company timezone, sorted ascending, defaulting to the last 12 months when no range is given.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SalesReport"}}}},"400":{"description":"Invalid filter values"},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/sales-orders":{"get":{"tags":["Sales Orders"],"operationId":"get_sales_orders","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"product_id","in":"query","description":"Filter by product ID (orders containing this product)","required":false,"schema":{"type":"string","nullable":true}},{"name":"account_id","in":"query","description":"Filter by account/customer ID","required":false,"schema":{"type":"string","nullable":true}},{"name":"status","in":"query","description":"Filter by order status (draft, submitted, processing, completed, cancelled)","required":false,"schema":{"type":"string","nullable":true}},{"name":"source","in":"query","description":"Origin channel filter: \"storefront\" (online store), \"\" (staff),\n… — matches `webapp_salesorder.source` exactly.","required":false,"schema":{"type":"string","nullable":true}},{"name":"search","in":"query","description":"Search by order ID or customer name","required":false,"schema":{"type":"string","nullable":true}},{"name":"date_from","in":"query","description":"Filter orders from this date (YYYY-MM-DD format)","required":false,"schema":{"type":"string","nullable":true}},{"name":"date_to","in":"query","description":"Filter orders until this date (YYYY-MM-DD format)","required":false,"schema":{"type":"string","nullable":true}},{"name":"include_deleted","in":"query","description":"Include soft-deleted orders (admin recovery view). Requires the\n`sales_orders.manage` permission; defaults to false.","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"List of sales orders","content":{"application/json":{"schema":{}}}},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["Sales Orders"],"operationId":"create_sales_order","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSalesOrderRequest"}}},"required":true},"responses":{"201":{"description":"Sales order created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSalesOrderResponse"}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/sales-orders/recent":{"get":{"tags":["Sales Orders"],"operationId":"get_recent_sales_orders","parameters":[{"name":"limit","in":"query","description":"Number of orders to return (default 20)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Recent sales orders","content":{"application/json":{"schema":{}}}},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/sales-orders/stats":{"get":{"tags":["Sales Orders"],"operationId":"get_sales_order_stats","responses":{"200":{"description":"Sales order statistics","content":{"application/json":{"schema":{}}}},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/sales-orders/{id}":{"get":{"tags":["Sales Orders"],"operationId":"get_sales_order","parameters":[{"name":"id","in":"path","description":"Sales order UUID","required":true,"schema":{"type":"string"}},{"name":"include_deleted","in":"query","description":"Include a soft-deleted order (admin recovery view). Requires the\n`sales_orders.manage` permission; defaults to false.","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Sales order details with items and customer info","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SalesOrderDetailResponse"}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Sales order not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["Sales Orders"],"operationId":"update_sales_order","parameters":[{"name":"id","in":"path","description":"Sales order UUID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSalesOrderRequest"}}},"required":true},"responses":{"200":{"description":"Sales order updated successfully","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request - validation errors"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Sales order not found"},"409":{"description":"Line items can no longer be replaced: the order is ready to ship or later, or has a non-void invoice"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["Sales Orders"],"operationId":"delete_sales_order","parameters":[{"name":"id","in":"path","description":"Sales order UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Sales order deleted successfully","content":{"application/json":{"schema":{}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Sales order not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/sales-orders/{id}/allowed-transitions":{"get":{"tags":["Sales Orders"],"summary":"GET /api/sales-orders/{id}/allowed-transitions — E-2.2 order state","description":"machine (ADR-002 §state machine). Company-scoped exactly like\n`get_sales_order` (no extra permission beyond an authenticated\n`UserClaims`): a cross-tenant id 404s the same way a direct order lookup\nwould.","operationId":"get_allowed_transitions","parameters":[{"name":"id","in":"path","description":"Sales order UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Current status and its legal next statuses","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllowedTransitionsResponse"}}}},"404":{"description":"Sales order not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/sales-orders/{id}/approve":{"post":{"tags":["Sales Orders"],"summary":"POST /api/sales-orders/{id}/approve — ADR-002 E-2.4 approval gate.","description":"Transitions a `pending_approval` order (created because its total\nexceeded `order_rules.require_approval_over_amount`) to `submitted`.\nRejection is the existing cancel path (`PUT /{id}` with\n`status: \"cancelled\"`, or `DELETE /{id}`) — `PendingApproval ->\nCancelled` is already a legal transition in the state machine\n(`domain::orders::status`), so no separate reject endpoint is needed.","operationId":"approve_sales_order","parameters":[{"name":"id","in":"path","description":"Sales order UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Order approved and transitioned to submitted","content":{"application/json":{"schema":{}}}},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Sales order not found"},"422":{"description":"Order is not pending_approval","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IllegalTransitionResponse"}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/sales-orders/{id}/refund":{"post":{"tags":["Sales Orders"],"summary":"Refund (part of) a storefront order. Card orders go back through Stripe","description":"(`POST /v1/refunds` on the order's PaymentIntent); pay-on-delivery orders\nonly record the amount handed back. `payment_status` becomes\n`partially_refunded` or `refunded`; the order status is left to the\nregular status flow (cancel separately if nothing ships).","operationId":"refund_sales_order","parameters":[{"name":"id","in":"path","description":"Sales order ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundSalesOrderRequest"}}},"required":true},"responses":{"200":{"description":"Refund recorded (and sent through Stripe for card orders)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundSalesOrderResponse"}}}},"400":{"description":"Invalid amount or nothing left to refund"},"403":{"description":"Forbidden - insufficient permissions"},"404":{"description":"Sales order not found"},"409":{"description":"Order is not a storefront order or was never paid"},"500":{"description":"Internal server error"},"502":{"description":"Stripe refused the refund"}},"security":[{"bearer_auth":[]}]}},"/api/sales-receipts":{"get":{"tags":["sales-receipts"],"summary":"Get list of sales receipts with pagination and filtering","operationId":"getSalesReceipts","parameters":[{"name":"page","in":"query","description":"Page number","required":false,"schema":{"type":"integer","format":"int32","nullable":true}},{"name":"limit","in":"query","description":"Items per page","required":false,"schema":{"type":"integer","format":"int32","nullable":true}},{"name":"product_id","in":"query","description":"Filter by product ID","required":false,"schema":{"type":"string","nullable":true}},{"name":"search","in":"query","description":"Search receipt number or ID","required":false,"schema":{"type":"string","nullable":true}},{"name":"start_date","in":"query","description":"Start date (ISO format)","required":false,"schema":{"type":"string","nullable":true}},{"name":"end_date","in":"query","description":"End date (ISO format)","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"List of sales receipts","content":{"application/json":{"schema":{}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["crate::features::sales_receipts::handlers"],"operationId":"createSalesReceipt","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SalesReceiptInput"}}},"required":true},"responses":{"201":{"description":"Sales receipt created","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/sales-receipts/export":{"get":{"tags":["sales-receipts"],"summary":"Bulk export sales receipts for analytics and reporting","operationId":"bulkSalesReceiptsExport","parameters":[{"name":"start_date","in":"query","description":"Start date (ISO format)","required":false,"schema":{"type":"string","nullable":true}},{"name":"end_date","in":"query","description":"End date (ISO format)","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Export successful","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PublicSalesReceiptResponse"}}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/sales-receipts/{id}":{"get":{"tags":["crate::features::sales_receipts::handlers"],"operationId":"get_sales_receipt","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Sales receipt details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSalesReceiptResponse"}}}},"403":{"description":"Insufficient permissions"},"404":{"description":"Sales receipt not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["crate::features::sales_receipts::handlers"],"operationId":"updateSalesReceipt","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSalesReceipt"}}},"required":true},"responses":{"200":{"description":"Sales receipt updated","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"403":{"description":"Insufficient permissions"},"404":{"description":"Sales receipt not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["crate::features::sales_receipts::handlers"],"operationId":"delete_sales_receipt","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Sales receipt deleted"},"403":{"description":"Insufficient permissions"},"404":{"description":"Sales receipt not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/settings":{"get":{"tags":["crate::features::settings::handlers"],"summary":"Read the authenticated company's settings with integration secrets masked.","operationId":"get_company_settings","responses":{"200":{"description":"Company settings retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanySettings"}}}},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["crate::features::settings::handlers"],"summary":"Merge and audit authorized company settings updates.","operationId":"update_company_settings","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCompanySettingsRequest"}}},"required":true},"responses":{"200":{"description":"Company settings updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanySettings"}}}},"400":{"description":"Bad request - invalid input data"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["crate::features::settings::handlers"],"summary":"Apply a partial company settings update through the shared audited handler.","operationId":"patch_company_settings","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCompanySettingsRequest"}}},"required":true},"responses":{"200":{"description":"Company settings updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanySettings"}}}},"400":{"description":"Bad request - invalid input data"},"403":{"description":"Forbidden - insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/settings/catalog-search/choices":{"get":{"tags":["catalog-search"],"operationId":"choices","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"category","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"product","in":"query","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Company catalog choices","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchChoices"}}}}},"security":[{"bearer_auth":[]}]}},"/api/settings/catalog-search/statistics":{"get":{"tags":["catalog-search"],"operationId":"statistics","responses":{"200":{"description":"Aggregated search usage","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SearchStatistic"}}}}},"403":{"description":"Insufficient permissions"}},"security":[{"bearer_auth":[]}]}},"/api/settings/logo":{"post":{"tags":["crate::features::settings::handlers"],"summary":"Upload company logo to R2","operationId":"uploadCompanyLogo","requestBody":{"content":{"application/json":{"schema":{"default":null,"nullable":true}}},"required":true},"responses":{"200":{"description":"Logo uploaded successfully","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request - invalid file"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/settings/numbering":{"get":{"tags":["settings"],"summary":"Get numbering settings for all entity types","operationId":"getNumberingSettings","responses":{"200":{"description":"Numbering settings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberingSettings"}}}},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/settings/numbering/{entity_type}":{"put":{"tags":["settings"],"summary":"Update numbering config for a specific entity type","operationId":"updateNumberingConfig","parameters":[{"name":"entity_type","in":"path","description":"Entity type: sales_order, sales_receipt, account, product","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateNumberingConfigRequest"}}},"required":true},"responses":{"200":{"description":"Updated numbering config","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityNumberConfig"}}}},"400":{"description":"Invalid entity type or request"},"403":{"description":"Insufficient permissions"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/stock-transfers":{"get":{"tags":["stock-transfers"],"operationId":"listStockTransfers","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"location_id","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"product_id","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTransferListResponse"}}}}},"security":[{"jwt":[]}]},"post":{"tags":["stock-transfers"],"operationId":"createStockTransfer","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateStockTransferRequest"}}},"required":true},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTransferResponse"}}}}},"security":[{"jwt":[]}]}},"/api/stock-transfers/{id}":{"get":{"tags":["stock-transfers"],"operationId":"getStockTransfer","parameters":[{"name":"id","in":"path","description":"Stock transfer ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTransferResponse"}}}}},"security":[{"jwt":[]}]}},"/api/stock-transfers/{id}/approve":{"post":{"tags":["stock-transfers"],"operationId":"approveStockTransfer","parameters":[{"name":"id","in":"path","description":"Stock transfer ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTransferTransitionRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTransferResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Invalid state"}},"security":[{"jwt":[]}]}},"/api/stock-transfers/{id}/cancel":{"post":{"tags":["stock-transfers"],"operationId":"cancelStockTransfer","parameters":[{"name":"id","in":"path","description":"Stock transfer ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTransferTransitionRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTransferResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Invalid state"}},"security":[{"jwt":[]}]}},"/api/stock-transfers/{id}/receive":{"post":{"tags":["stock-transfers"],"operationId":"receiveStockTransfer","parameters":[{"name":"id","in":"path","description":"Stock transfer ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTransferTransitionRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTransferResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Invalid state"}},"security":[{"jwt":[]}]}},"/api/stock-transfers/{id}/reject":{"post":{"tags":["stock-transfers"],"operationId":"rejectStockTransfer","parameters":[{"name":"id","in":"path","description":"Stock transfer ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTransferTransitionRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTransferResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Invalid state"}},"security":[{"jwt":[]}]}},"/api/stock-transfers/{id}/ship":{"post":{"tags":["stock-transfers"],"operationId":"shipStockTransfer","parameters":[{"name":"id","in":"path","description":"Stock transfer ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTransferTransitionRequest"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTransferResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Invalid state"}},"security":[{"jwt":[]}]}},"/api/stock/movements":{"get":{"tags":["Inventory"],"operationId":"list_stock_movements","parameters":[{"name":"limit","in":"query","description":"Page size (1-500, default 50)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","description":"Rows to skip (default 0)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"product_id","in":"query","description":"Filter by product","required":false,"schema":{"type":"string","nullable":true}},{"name":"location_id","in":"query","description":"Filter by storelocation id","required":false,"schema":{"type":"string","nullable":true}},{"name":"kind","in":"query","description":"Filter by movement kind (reason string)","required":false,"schema":{"type":"string","nullable":true}},{"name":"since","in":"query","description":"Inclusive lower bound on created_at (RFC 3339)","required":false,"schema":{"type":"string","nullable":true}},{"name":"until","in":"query","description":"Inclusive upper bound on created_at (RFC 3339)","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Stock movements, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockMovementListResponse"}}}},"403":{"description":"Insufficient permissions"}}}},"/api/storefront/{slug}/categories":{"get":{"tags":["Storefront"],"summary":"GET /api/storefront/{slug}/categories — all sellable categories for this store.","operationId":"storefront_categories_handler","parameters":[{"name":"slug","in":"path","description":"Storefront slug","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Category list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontCategorySummary"}}}}},"404":{"description":"Store not found or disabled"},"500":{"description":"Internal server error"}}}},"/api/storefront/{slug}/checkout":{"post":{"tags":["Storefront"],"summary":"POST /api/storefront/{slug}/checkout — server-side re-priced, stock-","description":"validated checkout. Creates a sales order (source=\"storefront\") and,\nfor card payment, a Stripe Checkout Session.","operationId":"storefront_checkout_handler","parameters":[{"name":"slug","in":"path","description":"Storefront slug","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontCheckoutRequest"}}},"required":true},"responses":{"201":{"description":"Order created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontCheckoutResponse"}}}},"400":{"description":"Bad request — invalid items, stock, or payment configuration"},"404":{"description":"Store not found or disabled"},"500":{"description":"Internal server error"}}}},"/api/storefront/{slug}/config":{"get":{"tags":["Storefront"],"summary":"GET /api/storefront/{slug}/config — public storefront configuration:","description":"theme, denormalized homepage sections, currency, payment/shipping options\n(never the Stripe secret key), SEO, and announcement bar.","operationId":"storefront_config_handler","parameters":[{"name":"slug","in":"path","description":"Storefront slug","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Storefront configuration","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontConfigResponse"}}}},"404":{"description":"Store not found or disabled"},"500":{"description":"Internal server error"}}}},"/api/storefront/{slug}/contact":{"post":{"tags":["Storefront"],"summary":"POST /api/storefront/{slug}/contact — a contact-form message. Opens a CRM","description":"case (subject \"Website message from …\") on the matching contact, with\nthe message as the first, non-internal comment, and notifies the tenant's\nnotification e-mail addresses best-effort.","operationId":"storefront_contact_handler","parameters":[{"name":"slug","in":"path","description":"Storefront slug","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontContactRequest"}}},"required":true},"responses":{"200":{"description":"Message received","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontFormResponse"}}}},"400":{"description":"Invalid input"},"404":{"description":"Store not found or disabled"},"500":{"description":"Internal server error"}}}},"/api/storefront/{slug}/newsletter":{"post":{"tags":["Storefront"],"summary":"POST /api/storefront/{slug}/newsletter — subscribe an email address.","description":"Upserts a CRM contact and grants email/marketing consent attributed to\nthe storefront, so campaigns can target the subscriber immediately.","operationId":"storefront_newsletter_handler","parameters":[{"name":"slug","in":"path","description":"Storefront slug","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontNewsletterRequest"}}},"required":true},"responses":{"200":{"description":"Subscribed (or already subscribed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontFormResponse"}}}},"400":{"description":"Invalid email"},"404":{"description":"Store not found or disabled"},"500":{"description":"Internal server error"}}}},"/api/storefront/{slug}/orders/{token}":{"get":{"tags":["Storefront"],"summary":"GET /api/storefront/{slug}/orders/{token} — public order status. The","description":"token (a UUID) is the only credential; no PII beyond what the buyer\nentered is returned, and an unknown token 404s the same as a malformed one.","operationId":"storefront_order_status_handler","parameters":[{"name":"slug","in":"path","description":"Storefront slug","required":true,"schema":{"type":"string"}},{"name":"token","in":"path","description":"Order public token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Public order status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontOrderStatusResponse"}}}},"404":{"description":"Store or order not found"},"500":{"description":"Internal server error"}}}},"/api/storefront/{slug}/orders/{token}/confirm-payment":{"post":{"tags":["Storefront"],"summary":"POST /api/storefront/{slug}/orders/{token}/confirm-payment — retrieve the","description":"Stripe Checkout Session with the company's secret key, verify it is\n`paid` AND matches the session id stored on the order (prevents forged\nconfirmations), then mark the order paid.","operationId":"storefront_confirm_payment_handler","parameters":[{"name":"slug","in":"path","description":"Storefront slug","required":true,"schema":{"type":"string"}},{"name":"token","in":"path","description":"Order public token","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontConfirmPaymentRequest"}}},"required":true},"responses":{"200":{"description":"Order payment status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontOrderStatusResponse"}}}},"400":{"description":"Session/order mismatch or payment not completed"},"404":{"description":"Store or order not found"},"500":{"description":"Internal server error"}}}},"/api/storefront/{slug}/pages/{handle}":{"get":{"tags":["Storefront"],"summary":"GET /api/storefront/{slug}/pages/{handle} — one published custom page","description":"(About, FAQ, Contact, …) with its sections denormalised exactly like the\nhomepage sections in `/config`. Unpublished pages 404.","operationId":"storefront_page_handler","parameters":[{"name":"slug","in":"path","description":"Storefront slug","required":true,"schema":{"type":"string"}},{"name":"handle","in":"path","description":"Page handle, e.g. `about`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Page with denormalised sections","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontPageResponse"}}}},"404":{"description":"Store, or page, not found"},"500":{"description":"Internal server error"}}}},"/api/storefront/{slug}/products":{"get":{"tags":["Storefront"],"summary":"GET /api/storefront/{slug}/products — paginated, searchable, sortable","description":"catalog of sellable products for this store.","operationId":"storefront_products_handler","parameters":[{"name":"slug","in":"path","description":"Storefront slug","required":true,"schema":{"type":"string"}},{"name":"search","in":"query","description":"Case-insensitive name/item-number substring","required":false,"schema":{"type":"string","nullable":true}},{"name":"category_id","in":"query","description":"Filter by category id","required":false,"schema":{"type":"string","nullable":true}},{"name":"sort","in":"query","description":"name_asc | name_desc | price_asc | price_desc | newest","required":false,"schema":{"type":"string","nullable":true}},{"name":"page","in":"query","description":"Page number (default 1)","required":false,"schema":{"type":"integer","format":"int32","nullable":true}},{"name":"page_size","in":"query","description":"Page size (default from store settings, max 100)","required":false,"schema":{"type":"integer","format":"int32","nullable":true}},{"name":"in_stock","in":"query","description":"true = only products with stock, whatever the store default","required":false,"schema":{"type":"boolean","nullable":true}}],"responses":{"200":{"description":"Paginated product list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontProductsResponse"}}}},"404":{"description":"Store not found or disabled"},"500":{"description":"Internal server error"}}}},"/api/storefront/{slug}/products/{id}":{"get":{"tags":["Storefront"],"summary":"GET /api/storefront/{slug}/products/{id} — full detail for one product.","operationId":"storefront_product_detail_handler","parameters":[{"name":"slug","in":"path","description":"Storefront slug","required":true,"schema":{"type":"string"}},{"name":"id","in":"path","description":"Product id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Product detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontProductDetail"}}}},"404":{"description":"Store or product not found"},"500":{"description":"Internal server error"}}}},"/api/storefront/{slug}/quote":{"post":{"tags":["Storefront"],"summary":"POST /api/storefront/{slug}/quote — totals for a cart, delivery choice and","description":"optional discount code, computed with the same rules as checkout. Never\ncreates anything; a rejected code comes back as `discount_error` with a\n200 so the checkout page can show it inline.","operationId":"storefront_quote_handler","parameters":[{"name":"slug","in":"path","description":"Storefront slug","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontQuoteRequest"}}},"required":true},"responses":{"200":{"description":"Server-computed totals","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorefrontQuoteResponse"}}}},"400":{"description":"Bad request — invalid items or delivery"},"404":{"description":"Store not found or disabled"},"500":{"description":"Internal server error"}}}},"/api/storefront/{slug}/stripe/webhook":{"post":{"tags":["Storefront"],"summary":"POST /api/storefront/{slug}/stripe/webhook — Stripe calls this for the","description":"merchant's Checkout Sessions. The raw body is verified against the\nstore's `payments.stripe_webhook_secret` (write-only setting), then\n`checkout.session.completed` / `…async_payment_succeeded` mark the order\npaid and `…async_payment_failed` / `…expired` mark it failed / expired —\nso an order is settled even when the buyer never returns to the store.","operationId":"storefront_stripe_webhook_handler","parameters":[{"name":"slug","in":"path","description":"Storefront slug","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}},"required":true},"responses":{"200":{"description":"Event received (acknowledged even when it concerns no order)"},"400":{"description":"Missing/invalid Stripe-Signature, webhook not configured, or unparseable event"},"404":{"description":"Store not found"},"500":{"description":"Internal server error"}}}},"/api/supplier-bills":{"get":{"tags":["procurement"],"operationId":"listSupplierBills","parameters":[{"name":"search","in":"query","description":"Matches the bill number or the supplier invoice number.","required":false,"schema":{"type":"string","nullable":true}},{"name":"status","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"supplier_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"purchase_order_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"overdue","in":"query","description":"Only bills with a balance whose due date has passed.","required":false,"schema":{"type":"boolean","nullable":true}},{"name":"open","in":"query","description":"Only bills with a balance (posted or partially paid).","required":false,"schema":{"type":"boolean","nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Supplier bills, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierBillListResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]},"post":{"tags":["procurement"],"operationId":"createSupplierBill","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSupplierBillRequest"}}},"required":true},"responses":{"201":{"description":"Draft bill created and matched","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierBillResponse"}}}},"400":{"description":"Invalid bill"},"403":{"description":"Forbidden"},"404":{"description":"Supplier, order or product not found"},"409":{"description":"Supplier invoice already entered"},"422":{"description":"Foreign currency or an order that cannot be billed"}},"security":[{"jwt":[]}]}},"/api/supplier-bills/aging":{"get":{"tags":["procurement"],"operationId":"getApAging","parameters":[{"name":"as_of","in":"query","description":"`YYYY-MM-DD`; defaults to today.","required":false,"schema":{"type":"string","nullable":true}},{"name":"supplier_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}}],"responses":{"200":{"description":"Accounts-payable aging per supplier","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApAgingResponse"}}}},"400":{"description":"Invalid date"},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]}},"/api/supplier-bills/received-not-billed":{"get":{"tags":["procurement"],"operationId":"getReceivedNotBilled","parameters":[{"name":"supplier_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Order lines received but not yet billed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceivedNotBilledResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]}},"/api/supplier-bills/{id}":{"get":{"tags":["procurement"],"operationId":"getSupplierBill","parameters":[{"name":"id","in":"path","description":"Supplier bill ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Supplier bill; a draft's match reflects today's receipts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierBillResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/supplier-bills/{id}/apply-credit":{"post":{"tags":["procurement"],"operationId":"applyVendorCreditToBill","parameters":[{"name":"id","in":"path","description":"Supplier bill ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplyVendorCreditRequest"}}},"required":true},"responses":{"200":{"description":"Credit applied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierBillResponse"}}}},"400":{"description":"Amount beyond the bill balance or the credit remaining"},"403":{"description":"Forbidden"},"404":{"description":"Credit not found"},"409":{"description":"Stale version"},"422":{"description":"Bill or credit not open"}},"security":[{"jwt":[]}]}},"/api/supplier-bills/{id}/approve-variance":{"post":{"tags":["procurement"],"operationId":"approveSupplierBillVariance","parameters":[{"name":"id","in":"path","description":"Supplier bill ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierBillActionRequest"}}},"required":true},"responses":{"200":{"description":"Variance approved; the bill can post","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierBillResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Not a draft with a variance"}},"security":[{"jwt":[]}]}},"/api/supplier-bills/{id}/post":{"post":{"tags":["procurement"],"operationId":"postSupplierBill","parameters":[{"name":"id","in":"path","description":"Supplier bill ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierBillActionRequest"}}},"required":true},"responses":{"200":{"description":"Bill posted to accounts payable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierBillResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Not a draft, or an unapproved variance"}},"security":[{"jwt":[]}]}},"/api/supplier-bills/{id}/void":{"post":{"tags":["procurement"],"operationId":"voidSupplierBill","parameters":[{"name":"id","in":"path","description":"Supplier bill ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierBillActionRequest"}}},"required":true},"responses":{"200":{"description":"Bill voided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierBillResponse"}}}},"400":{"description":"Reason missing"},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Paid, credited or revalued bills cannot be voided"}},"security":[{"jwt":[]}]}},"/api/supplier-payments":{"get":{"tags":["procurement"],"operationId":"listSupplierPayments","parameters":[{"name":"search","in":"query","description":"Matches the payment number or the reference.","required":false,"schema":{"type":"string","nullable":true}},{"name":"status","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"supplier_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"unapplied","in":"query","description":"Only payments with an unapplied amount.","required":false,"schema":{"type":"boolean","nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Supplier payments, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierPaymentListResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]},"post":{"tags":["procurement"],"operationId":"createSupplierPayment","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSupplierPaymentRequest"}}},"required":true},"responses":{"201":{"description":"Payment recorded and applied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierPaymentResponse"}}}},"400":{"description":"Invalid payment or allocation"},"403":{"description":"Forbidden"},"404":{"description":"Supplier or bill not found"},"422":{"description":"Foreign currency or a bill that cannot be paid"}},"security":[{"jwt":[]}]}},"/api/supplier-payments/{id}":{"get":{"tags":["procurement"],"operationId":"getSupplierPayment","parameters":[{"name":"id","in":"path","description":"Supplier payment ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Supplier payment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierPaymentResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/supplier-payments/{id}/apply":{"post":{"tags":["procurement"],"operationId":"applySupplierPayment","parameters":[{"name":"id","in":"path","description":"Supplier payment ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplySupplierPaymentRequest"}}},"required":true},"responses":{"200":{"description":"Unapplied amount applied to bills","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierPaymentResponse"}}}},"400":{"description":"Allocations exceed the unapplied amount"},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Payment void or a bill that cannot be paid"}},"security":[{"jwt":[]}]}},"/api/supplier-payments/{id}/void":{"post":{"tags":["procurement"],"operationId":"voidSupplierPayment","parameters":[{"name":"id","in":"path","description":"Supplier payment ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoidDocumentRequest"}}},"required":true},"responses":{"200":{"description":"Payment voided and taken off its bills","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierPaymentResponse"}}}},"400":{"description":"Reason missing"},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Already void"}},"security":[{"jwt":[]}]}},"/api/supplier-returns":{"get":{"tags":["supplier-returns"],"operationId":"listSupplierReturns","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"supplier_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"location_id","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierReturnListResponse"}}}}},"security":[{"jwt":[]}]},"post":{"tags":["supplier-returns"],"operationId":"createSupplierReturn","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSupplierReturnRequest"}}},"required":true},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierReturnResponse"}}}}},"security":[{"jwt":[]}]}},"/api/supplier-returns/{id}":{"get":{"tags":["supplier-returns"],"operationId":"getSupplierReturn","parameters":[{"name":"id","in":"path","description":"Supplier return ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierReturnResponse"}}}}},"security":[{"jwt":[]}]}},"/api/supplier-returns/{id}/approve":{"post":{"tags":["supplier-returns"],"operationId":"approveSupplierReturn","parameters":[{"name":"id","in":"path","description":"Supplier return ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierReturnTransitionRequest"}}},"required":true},"responses":{"200":{"description":"Supplier return transitioned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierReturnResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Invalid state"}},"security":[{"jwt":[]}]}},"/api/supplier-returns/{id}/close":{"post":{"tags":["supplier-returns"],"operationId":"closeSupplierReturn","parameters":[{"name":"id","in":"path","description":"Supplier return ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierReturnTransitionRequest"}}},"required":true},"responses":{"200":{"description":"Supplier return transitioned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierReturnResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Invalid state"}},"security":[{"jwt":[]}]}},"/api/supplier-returns/{id}/credit":{"post":{"tags":["supplier-returns"],"operationId":"creditSupplierReturn","parameters":[{"name":"id","in":"path","description":"Supplier return ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierReturnTransitionRequest"}}},"required":true},"responses":{"200":{"description":"Supplier return transitioned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierReturnResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Invalid state"}},"security":[{"jwt":[]}]}},"/api/supplier-returns/{id}/ship":{"post":{"tags":["supplier-returns"],"operationId":"shipSupplierReturn","parameters":[{"name":"id","in":"path","description":"Supplier return ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierReturnTransitionRequest"}}},"required":true},"responses":{"200":{"description":"Supplier return transitioned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierReturnResponse"}}}},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Invalid state"}},"security":[{"jwt":[]}]}},"/api/suppliers":{"get":{"tags":["procurement"],"operationId":"listSuppliers","parameters":[{"name":"search","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"status","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Supplier list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierListResponse"}}}},"400":{"description":"Invalid filters"},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]},"post":{"tags":["procurement"],"operationId":"createSupplier","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSupplierRequest"}}},"required":true},"responses":{"201":{"description":"Supplier created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierResponse"}}}},"400":{"description":"Invalid supplier"},"403":{"description":"Forbidden"},"409":{"description":"Supplier number conflict"}},"security":[{"jwt":[]}]}},"/api/suppliers/{id}":{"get":{"tags":["procurement"],"operationId":"getSupplier","parameters":[{"name":"id","in":"path","description":"Supplier ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Supplier","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Supplier not found"}},"security":[{"jwt":[]}]},"put":{"tags":["procurement"],"operationId":"updateSupplier","parameters":[{"name":"id","in":"path","description":"Supplier ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSupplierRequest"}}},"required":true},"responses":{"200":{"description":"Supplier updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierResponse"}}}},"400":{"description":"Invalid supplier"},"403":{"description":"Forbidden"},"404":{"description":"Supplier not found"},"409":{"description":"Supplier number conflict"}},"security":[{"jwt":[]}]},"delete":{"tags":["procurement"],"operationId":"deactivateSupplier","parameters":[{"name":"id","in":"path","description":"Supplier ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Supplier deactivated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Supplier not found"}},"security":[{"jwt":[]}]}},"/api/suppliers/{supplier_id}/products":{"get":{"tags":["procurement"],"operationId":"listSupplierProducts","parameters":[{"name":"supplier_id","in":"path","description":"Supplier ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"name":"search","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"active_only","in":"query","required":false,"schema":{"type":"boolean","nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Supplier purchasing terms","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierProductListResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Supplier not found"}},"security":[{"jwt":[]}]},"post":{"tags":["procurement"],"operationId":"createSupplierProduct","parameters":[{"name":"supplier_id","in":"path","description":"Supplier ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSupplierProductRequest"}}},"required":true},"responses":{"201":{"description":"Supplier product created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierProductResponse"}}}},"400":{"description":"Invalid purchasing terms"},"403":{"description":"Forbidden"},"404":{"description":"Supplier or product not found"},"409":{"description":"Duplicate supplier product"}},"security":[{"jwt":[]}]}},"/api/suppliers/{supplier_id}/products/{id}":{"get":{"tags":["procurement"],"operationId":"getSupplierProduct","parameters":[{"name":"supplier_id","in":"path","description":"Supplier ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"name":"id","in":"path","description":"Supplier product ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Supplier purchasing terms and cost history","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierProductResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Supplier product not found"}},"security":[{"jwt":[]}]},"put":{"tags":["procurement"],"operationId":"updateSupplierProduct","parameters":[{"name":"supplier_id","in":"path","description":"Supplier ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"name":"id","in":"path","description":"Supplier product ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSupplierProductRequest"}}},"required":true},"responses":{"200":{"description":"Supplier product updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierProductResponse"}}}},"400":{"description":"Invalid purchasing terms"},"403":{"description":"Forbidden"},"404":{"description":"Supplier product not found"},"409":{"description":"Stale version"}},"security":[{"jwt":[]}]}},"/api/suppliers/{supplier_id}/products/{id}/deactivate":{"post":{"tags":["procurement"],"operationId":"deactivateSupplierProduct","parameters":[{"name":"supplier_id","in":"path","description":"Supplier ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"name":"id","in":"path","description":"Supplier product ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierProductTransitionRequest"}}},"required":true},"responses":{"200":{"description":"Supplier product deactivated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierProductResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Supplier product not found"},"409":{"description":"Stale version"}},"security":[{"jwt":[]}]}},"/api/sync-live":{"get":{"tags":["Sync"],"summary":"Subscribe to change hints for the authenticated tenant only.","operationId":"sync_live","responses":{"200":{"description":"Server-sent refresh hints; reconnect and reconcile using durable cursors"},"403":{"description":"Selling permission or live-sync policy denied"},"503":{"description":"Live stream temporarily unavailable"}},"security":[{"bearer_auth":[]}]}},"/api/sync-pull":{"get":{"tags":["Sync"],"summary":"Sync Protocol - GET /api/sync-pull","description":"Implements sync protocol for offline-first mobile apps (legacy `WatermelonDB` compatibility)\n\nChanges:\n- Fixed: Parameter name from 'timestamp' to '`last_pulled_at`'\n- Fixed: Timestamp returned in milliseconds (not seconds)\n- Fixed: Returns created/updated/deleted arrays properly\n- Added: Deletion tracking support","operationId":"sync_pull","parameters":[{"name":"last_pulled_at","in":"query","description":"Timestamp in milliseconds of last sync","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"client","in":"query","description":"Client type: 'pos' skips B2B filters (no image/wholesale requirements)","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Sync data with created/updated/deleted records","content":{"application/json":{"schema":{"$ref":"#/components/schemas/crate.features.sync.dto.SyncPullResponse"}}}},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/sync-pull-batch":{"get":{"tags":["Sync"],"operationId":"sync_pull_batch","parameters":[{"name":"entity_type","in":"query","description":"Which entity to pull (companies|settings|categories|accounts|products|product_images|sales_orders|sales_order_items|gift_cards|product_groups|lookbooks)","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque cursor from previous batch. Omit on the first call.","required":false,"schema":{"type":"string","nullable":true}},{"name":"batch_size","in":"query","description":"Rows per batch (default 2000, max 5000)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"last_pulled_at","in":"query","description":"Timestamp in milliseconds of last sync. 0 = full sync (server suppresses tombstones)","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"client","in":"query","description":"Client type. 'pos' skips B2B catalog filters.","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"Batched sync data with cursor metadata","content":{"application/json":{"schema":{}}}},"400":{"description":"Missing/invalid entity_type or malformed cursor"},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/sync-push":{"post":{"tags":["Sync"],"summary":"Sync Protocol - POST /api/sync-push","description":"Receives changes from mobile app and saves them to database\n\nMobile sends pending orders that were created offline. Every order is FIRST\ndurably persisted raw into `mobile_order_inbox`, then validated and created\nin one transaction (order-durability §B1). Validation failures quarantine\nthe inbox row instead of losing the order.\n\nRequest body (top level, next to `changes`): optional\n`supports_quarantine: bool` (§B7). Clients that set it true receive\nper-order `quarantined` acks in `order_sync_results` (see `SyncOrderAck`);\nlegacy clients get those orders reported in the `errors` array as before.\nMobile uses `inserted_ids`/`order_sync_results` to mark orders as synced.","operationId":"sync_push","requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"description":"Sync push successful","content":{"application/json":{"schema":{},"example":{"conflicts":[],"errors":[],"inserted_ids":["order-123","order-456"],"order_sync_results":[{"client_order_id":"local-1","inbox_id":"7c9e6679-7425-40de-963d-2f4f0f1a6b10","sales_order_id":"order-123","status":"created"},{"client_order_id":"local-2","error":"Account not found or doesn't belong to your company","error_code":"account_not_found","inbox_id":"8d0f7780-8536-51ef-a74e-3f5f1f2b7c21","status":"quarantined"}],"success":true,"timestamp":1704729600000}}}},"400":{"description":"Bad request"},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/sync-push-gift-cards":{"post":{"tags":["Sync"],"operationId":"sync_push_gift_cards","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncPushGiftCardsRequest"}}},"required":true},"responses":{"200":{"description":"Per-entry results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncPushGiftCardsResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]}},"/api/sync-push-receipts":{"post":{"tags":["Sync"],"summary":"Receipt-native sync push endpoint for desktop POS.","description":"Internally maps to the existing sync-push processing path while using a\ndedicated request contract to avoid legacy order-shaped wrappers.","operationId":"sync_push_receipts","requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"description":"Receipt sync push successful","content":{"application/json":{"schema":{}}}},"400":{"description":"Bad request"},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/token/refresh":{"post":{"tags":["auth"],"summary":"Refresh JWT token","operationId":"refresh_token","responses":{"200":{"description":"Token refreshed successfully","content":{"application/json":{"schema":{}}}},"401":{"description":"Invalid or expired token"},"500":{"description":"Internal server error"}},"security":[{"jwt":[]}]}},"/api/user/change-password":{"post":{"tags":["crate::features::user_management::handlers"],"summary":"Change current user's password","operationId":"changePassword","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}},"required":true},"responses":{"200":{"description":"Password changed successfully"},"400":{"description":"Current password is incorrect"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/user/profile":{"get":{"tags":["crate::features::user_management::handlers"],"summary":"Get current user's profile","operationId":"getUserProfile","responses":{"200":{"description":"User profile retrieved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfileResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["crate::features::user_management::handlers"],"summary":"Update current user's profile","operationId":"updateUserProfile","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProfileRequest"}}},"required":true},"responses":{"200":{"description":"Profile updated successfully"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/users":{"get":{"tags":["crate::features::user_management::handlers"],"summary":"Get all users in the company (staff+ only)","operationId":"get_company_users","parameters":[{"name":"search","in":"query","description":"Search by name/email","required":false,"schema":{"type":"string","nullable":true}},{"name":"role","in":"query","description":"Filter by role","required":false,"schema":{"type":"string","nullable":true}},{"name":"order_by","in":"query","description":"Sort field","required":false,"schema":{"type":"string","nullable":true}},{"name":"order_dir","in":"query","description":"Sort direction","required":false,"schema":{"type":"string","nullable":true}}],"responses":{"200":{"description":"List of users","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserListEnvelope"}}}},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["crate::features::user_management::handlers"],"summary":"Create a new user in the company (staff+ only)","operationId":"create_user","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserRequest"}}},"required":true},"responses":{"201":{"description":"User created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserResponse"}}}},"400":{"description":"Bad request"},"403":{"description":"Forbidden"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/users/permissions/catalog":{"get":{"tags":["crate::features::user_management::handlers"],"operationId":"getRbacCatalog","responses":{"200":{"description":"RBAC catalog","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RbacCatalogResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"bearer_auth":[]}]}},"/api/users/reset-password":{"post":{"tags":["crate::features::user_management::handlers"],"summary":"Reset user password (staff+ only)","operationId":"resetUserPassword","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Password reset successfully"},"403":{"description":"Insufficient permissions"},"404":{"description":"User not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/users/{id}":{"get":{"tags":["crate::features::user_management::handlers"],"summary":"Get details of a specific user (staff+ only)","operationId":"get_user","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"User details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserListResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"put":{"tags":["crate::features::user_management::handlers"],"summary":"Update user details (staff+ only, with restrictions)","operationId":"update_user","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"integer","format":"int32"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserRequest"}}},"required":true},"responses":{"200":{"description":"User updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserMutationResponse"}}}},"400":{"description":"Bad request"},"403":{"description":"Forbidden"},"404":{"description":"Not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["crate::features::user_management::handlers"],"summary":"Delete user (staff+ only, cannot delete self)","operationId":"delete_user","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"User deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserMutationResponse"}}}},"400":{"description":"Bad request"},"403":{"description":"Forbidden"},"404":{"description":"Not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/users/{id}/access":{"put":{"tags":["crate::features::user_management::handlers"],"summary":"Update a user's access (role + active flag + permission override) atomically.","description":"Server-side guards, in order:\n1. caller needs `user.edit`\n2. self-target is forbidden — you cannot demote or disable yourself\n3. the role must be assignable (`UserRole::is_assignable`)\n4. privilege-escalation guard — the caller can only grant permissions they\nthemselves hold (applies to explicit overrides AND to the defaults of a\nnewly assigned role)\n5. only admins may assign the admin role or modify admin users","operationId":"updateUserAccess","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"integer","format":"int32"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserAccessRequest"}}},"required":true},"responses":{"200":{"description":"User access updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserListResponse"}}}},"400":{"description":"Bad request (unassignable role, conflicting override fields)"},"403":{"description":"Forbidden (missing permission, self-target, privilege escalation)"},"404":{"description":"Not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/users/{id}/permissions":{"put":{"tags":["crate::features::user_management::handlers"],"summary":"Update user permissions (admin only)","operationId":"update_user_permissions","parameters":[{"name":"id","in":"path","description":"User ID","required":true,"schema":{"type":"integer","format":"int32"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePermissionsRequest"}}},"required":true},"responses":{"200":{"description":"User permissions updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserPermissionsResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/api/vendor-credits":{"get":{"tags":["procurement"],"operationId":"listVendorCredits","parameters":[{"name":"search","in":"query","description":"Matches the credit number or the supplier reference.","required":false,"schema":{"type":"string","nullable":true}},{"name":"status","in":"query","required":false,"schema":{"type":"string","nullable":true}},{"name":"supplier_id","in":"query","required":false,"schema":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","format":"int64","nullable":true}}],"responses":{"200":{"description":"Vendor credits, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCreditListResponse"}}}},"403":{"description":"Forbidden"}},"security":[{"jwt":[]}]},"post":{"tags":["procurement"],"operationId":"createVendorCredit","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVendorCreditRequest"}}},"required":true},"responses":{"201":{"description":"Vendor credit recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCreditResponse"}}}},"400":{"description":"Invalid credit"},"403":{"description":"Forbidden"},"404":{"description":"Supplier not found"},"422":{"description":"Foreign currency"}},"security":[{"jwt":[]}]}},"/api/vendor-credits/{id}":{"get":{"tags":["procurement"],"operationId":"getVendorCredit","parameters":[{"name":"id","in":"path","description":"Vendor credit ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Vendor credit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCreditResponse"}}}},"403":{"description":"Forbidden"},"404":{"description":"Not found"}},"security":[{"jwt":[]}]}},"/api/vendor-credits/{id}/void":{"post":{"tags":["procurement"],"operationId":"voidVendorCredit","parameters":[{"name":"id","in":"path","description":"Vendor credit ID","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoidDocumentRequest"}}},"required":true},"responses":{"200":{"description":"Vendor credit voided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCreditResponse"}}}},"400":{"description":"Reason missing"},"403":{"description":"Forbidden"},"409":{"description":"Stale version"},"422":{"description":"Credit already applied or void"}},"security":[{"jwt":[]}]}},"/api/webhooks/event-types":{"get":{"tags":["webhooks"],"summary":"Every event type an endpoint may subscribe to. Served from the Rust","description":"registry so the admin UI can never offer an event the backend won't emit.","operationId":"list_webhook_event_types","responses":{"200":{"description":"Subscribable webhook event types","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEventTypeResponse"}}}}},"403":{"description":"Forbidden - insufficient permissions"}},"security":[{"bearer_auth":[]}]}},"/public/displays/pairing/{device_id}":{"get":{"tags":["digital_displays"],"summary":"TV polls this by its own stable `device_id` (no auth — the TV has no","description":"credentials). Returns a short code while unclaimed; once an admin claims\nit, returns the same payload shape `get_public_display` gives, so the TV\ncan start rendering without ever navigating away from this URL.","operationId":"get_pairing_status","parameters":[{"name":"device_id","in":"path","description":"Stable per-device UUID the kiosk box is configured with","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"description":"Pairing status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PairingStatusResponse"}}}}}}},"/public/displays/{token}":{"get":{"tags":["digital_displays"],"summary":"Get public display data by token (for TV displays, menu boards, etc.)","operationId":"get_public_display","parameters":[{"name":"token","in":"path","description":"Public display token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Display data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicDisplayDataResponse"}}}},"404":{"description":"Display not found"}}}},"/public/kiosk/pairing/{device_id}":{"get":{"tags":["crate::features::horeca::kiosk"],"summary":"`GET /public/kiosk/pairing/{device_id}` (and its `/api` alias) — no","description":"auth, the kiosk box has no credentials yet.","operationId":"pairing_poll","parameters":[{"name":"device_id","in":"path","description":"Stable per-device UUID the kiosk box is configured with","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Pairing status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KioskPairingStatusResponse"}}}},"500":{"description":"Internal server error"}}}}},"components":{"schemas":{"AccessTokenListItem":{"type":"object","required":["id","kind","label","masked_preview","status","permissions","created_by_user_id","created_at"],"properties":{"app_version":{"type":"string","nullable":true},"created_at":{"type":"string"},"created_by_name":{"type":"string","nullable":true},"created_by_user_id":{"type":"integer","format":"int32"},"device_name":{"type":"string","nullable":true},"expires_at":{"type":"string","nullable":true},"id":{"type":"string"},"installation_id":{"type":"string","nullable":true},"kind":{"type":"string"},"label":{"type":"string"},"last_used_at":{"type":"string","nullable":true},"masked_preview":{"type":"string"},"owner_name":{"type":"string","nullable":true},"owner_user_id":{"type":"integer","format":"int32","nullable":true},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"}},"platform":{"type":"string","nullable":true},"status":{"type":"string"}}},"AccountInput":{"type":"object","description":"Account creation/update input","required":["name","customer_type"],"properties":{"account_number":{"type":"string","description":"Caller-supplied customer number. Absent means \"assign one for me\" — the\ncompany-scoped sequence number is used instead, so a create with just a\nname succeeds instead of failing with `missing field account_number`.","nullable":true},"address":{"type":"string","nullable":true},"customer_type":{"type":"string"},"email":{"type":"string","nullable":true},"excluded":{"type":"boolean","nullable":true},"name":{"type":"string"},"phone":{"type":"string","nullable":true}}},"AccountSearchResult":{"type":"object","description":"Account search result (lightweight, for quick-search endpoint)","required":["id","name"],"properties":{"account_number":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"id":{"type":"string"},"name":{"type":"string"},"phone":{"type":"string","nullable":true}}},"AckPrintJobInput":{"type":"object","required":["status"],"properties":{"error":{"type":"string","nullable":true},"status":{"type":"string","description":"printed | failed."}}},"ActivatePipelineRequest":{"type":"object","required":["version_id"],"properties":{"version_id":{"type":"string"}}},"ActivityListResponse":{"type":"object","required":["items","total_count","page","page_size"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ActivityResponse"}},"page":{"type":"integer","format":"int64"},"page_size":{"type":"integer","format":"int64"},"total_count":{"type":"integer","format":"int64"}}},"ActivityResponse":{"type":"object","required":["id","entity_type","entity_id","activity_type","payload","occurred_at","created_at"],"properties":{"activity_type":{"type":"string"},"actor_user_id":{"type":"integer","format":"int32","nullable":true},"created_at":{"type":"string"},"entity_id":{"$ref":"#/components/schemas/Uuid"},"entity_type":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"occurred_at":{"type":"string"},"payload":{},"source_domain_event_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}}},"AddOrderItemInput":{"type":"object","required":["qty","unit_price"],"properties":{"course":{"type":"string","nullable":true},"modifiers_snapshot":{"description":"`[{group_id, option_id}, …]`. Validated against the groups actually\nattached to the product (min/max/required) and REPLACED in storage by\nthe server-resolved snapshot, which carries authoritative group and\noption names — that is what lets a kitchen ticket print \"no onion\".","nullable":true},"note":{"type":"string","nullable":true},"product_id":{"type":"string","nullable":true},"qty":{"type":"integer","format":"int32"},"seat_number":{"type":"integer","format":"int32","description":"Seat / cover this line belongs to. Additive and optional.","nullable":true},"unit_price":{"$ref":"#/components/schemas/BigDecimal"},"variant_id":{"type":"string","nullable":true}}},"AddSegmentMemberRequest":{"type":"object","required":["contact_id"],"properties":{"contact_id":{"$ref":"#/components/schemas/Uuid"}}},"AddSuppressionRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string"},"reason":{"type":"string","description":"unsubscribed | `hard_bounce` | complaint | manual (defaults to manual).","nullable":true}}},"AddToListRequest":{"type":"object","required":["product_id"],"properties":{"product_id":{"type":"string"},"quantity":{"type":"integer","format":"int32","nullable":true},"sort_order":{"type":"integer","format":"int32","nullable":true}}},"AdminCreateCatalogRuleRequest":{"type":"object","description":"Admin create request. Unlike the portal variant, the admin picks the target\naccount explicitly (the staff user is not scoped to a single account).","required":["account_id","rule_type"],"properties":{"account_id":{"type":"string"},"category_id":{"type":"string","nullable":true},"product_id":{"type":"string","nullable":true},"rule_type":{"type":"string"}}},"AdminMfaResetRequest":{"type":"object","description":"Clear another user's MFA (lost phone, staff turnover).","required":["user_id"],"properties":{"reason":{"type":"string","description":"Free-text justification, stored in the audit log. Optional on the wire,\nbut the admin UI asks for it — a reset is one of the few actions that can\ndowngrade another person's account security, so \"why\" is worth keeping.","nullable":true},"user_id":{"type":"integer","format":"int32"}}},"AdminMfaResetResponse":{"type":"object","required":["message","user_id","had_mfa"],"properties":{"had_mfa":{"type":"boolean","description":"True when the target actually had MFA to clear; false means the request\nwas a no-op, reported honestly instead of as a success."},"message":{"type":"string"},"user_id":{"type":"integer","format":"int32"}}},"AdminNavigationSection":{"type":"string","description":"Stable top-level sections in the tenant admin navigation. These are typed\nso stored settings and API clients cannot invent section identifiers.","enum":["overview","catalog","sales","pos","fulfillment","insights","settings"]},"AdminUpdateCatalogRuleRequest":{"type":"object","description":"Admin update request. Full-replace semantics matching the create request:\nthe caller supplies the target account, rule type and exactly the\nproduct/category target that should remain on the rule.","required":["account_id","rule_type"],"properties":{"account_id":{"type":"string"},"category_id":{"type":"string","nullable":true},"product_id":{"type":"string","nullable":true},"rule_type":{"type":"string"}}},"AdminUser":{"type":"object","required":["id","username","email","first_name","last_name","is_staff","is_active","is_superuser","is_platform_admin","date_joined"],"properties":{"company_id":{"type":"string","nullable":true},"company_name":{"type":"string","nullable":true},"date_joined":{"$ref":"#/components/schemas/chrono.NaiveDateTime"},"email":{"type":"string"},"first_name":{"type":"string"},"id":{"type":"integer","format":"int32"},"is_active":{"type":"boolean"},"is_platform_admin":{"type":"boolean","description":"Platform (internal-admin) access. Distinct from tenant `is_superuser`."},"is_staff":{"type":"boolean"},"is_superuser":{"type":"boolean"},"last_login":{"allOf":[{"$ref":"#/components/schemas/chrono.NaiveDateTime"}],"nullable":true},"last_name":{"type":"string"},"role":{"allOf":[{"$ref":"#/components/schemas/UserRole"}],"nullable":true},"username":{"type":"string"}}},"AdminUserListResponse":{"type":"object","required":["items","total","page","per_page","total_pages"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AdminUser"}},"page":{"type":"integer","format":"int64"},"per_page":{"type":"integer","format":"int64"},"total":{"type":"integer","format":"int64"},"total_pages":{"type":"integer","format":"int64"}}},"AdminUserMfaStatus":{"type":"object","description":"Another user's MFA state, for the admin users list.","required":["user_id","mfa_enabled","recovery_codes_remaining","mfa_required"],"properties":{"mfa_enabled":{"type":"boolean"},"mfa_required":{"type":"boolean","description":"Whether the company policy requires MFA for this user's role."},"recovery_codes_remaining":{"type":"integer","format":"int64"},"user_id":{"type":"integer","format":"int32"}}},"AggregateRequest":{"type":"object","description":"Body for `POST /api/query/{entity}/aggregate`.","required":["aggregation"],"properties":{"aggregation":{"type":"object","description":"Group-by dimensions (optionally date-bucketed), aggregate outputs,\noptional HAVING, optional ORDER BY, optional row cap."},"query":{"type":"object","description":"Pre-aggregation filter tree. Absent means \"match every row\"\n(still tenancy-scoped, always)."}}},"AggregateResponse":{"type":"object","description":"Response for `POST /api/query/{entity}/aggregate`.","required":["group_keys","aggregates","groups","rows","row_limit_applied","truncated","took_ms"],"properties":{"aggregates":{"type":"array","items":{"type":"string"},"description":"The aggregate aliases, in the aggregation's declared order."},"group_keys":{"type":"array","items":{"type":"string"},"description":"The group-by field keys, in the aggregation's declared order."},"groups":{"type":"array","items":{"$ref":"#/components/schemas/AggregateRowResponse"},"description":"The grouped rows, ordered per the aggregation's `order_by` when one\nwas given."},"row_limit_applied":{"type":"integer","format":"int64","description":"The row cap this run actually applied (the minimum of the request and\nevery configured/platform ceiling)."},"rows":{"type":"integer","description":"Number of grouped rows returned (`groups.len()`, for convenience).","minimum":0},"took_ms":{"type":"integer","format":"int64","description":"Wall-clock execution time, milliseconds.","minimum":0},"truncated":{"type":"boolean","description":"Whether the result may have been cut off at `row_limit_applied` —\na heuristic (true when `rows == row_limit_applied`), never a wrong\nnumber: narrowing the query is always safe."}}},"AggregateRowResponse":{"type":"object","description":"One grouped result row of an aggregate response.\n\nValues are positional, aligned with the response's `group_keys` /\n`aggregates` header arrays — positional on purpose: the same field may\nlegally be grouped under two different buckets (the heatmap shape), which\na `{field: value}` object could not represent without collisions.","required":["group","values"],"properties":{"group":{"type":"array","items":{"type":"object"},"description":"Group key values, in `group_keys` order. Date buckets are local\ncalendar labels (no UTC suffix); decimals are strings so money never\nrides a lossy JSON float."},"values":{"type":"array","items":{"type":"object"},"description":"Aggregate outputs, in `aggregates` order. Same encoding rules."}}},"AgreementCapacity":{"type":"object","required":["reps","office_users","tills","pos_locations"],"properties":{"office_users":{"type":"integer","format":"int32","minimum":0},"pos_locations":{"type":"integer","format":"int32","minimum":0},"reps":{"type":"integer","format":"int32","minimum":0},"tills":{"type":"integer","format":"int32","minimum":0}}},"AgreementManifest":{"type":"object","required":["features","families","capacity"],"properties":{"capacity":{"$ref":"#/components/schemas/AgreementCapacity"},"families":{"type":"array","items":{"$ref":"#/components/schemas/ProductFamily"}},"features":{"$ref":"#/components/schemas/OfferManifest"}}},"AgreementStatus":{"type":"string","enum":["draft","active","suspended","ended"]},"AiAutomationSettings":{"type":"object","description":"Company AI enrichment, review and search automation preferences.","required":["auto_extract_features","auto_generate_multilingual_keywords","auto_translate_searchable_fields","auto_update_search_index","auto_generate_exploded_images","require_review_before_publishing_ai_attributes","preferred_provider","fallback_provider","search_mode_default"],"properties":{"apply_attributes_on_approve":{"type":"boolean","description":"Whether approving enrichment applies its attribute suggestions."},"apply_category_on_approve":{"type":"boolean","description":"Whether approving enrichment applies its category suggestion."},"apply_description_on_approve":{"type":"boolean","description":"Apply-on-approve policy: which approved AI fields are written back onto\nthe product record (and thus propagate to POS/mobile/B2B via sync)."},"apply_product_type_on_approve":{"type":"boolean","description":"Write the approved canonical `product_type` onto the product record (and\nthus propagate to POS/mobile/B2B via sync) on approve."},"auto_extract_features":{"type":"boolean","description":"Preference for automatic product feature extraction."},"auto_generate_exploded_images":{"type":"boolean","description":"Preference for automatic exploded-view image generation."},"auto_generate_multilingual_keywords":{"type":"boolean","description":"Preference for automatic multilingual keyword generation."},"auto_translate_searchable_fields":{"type":"boolean","description":"Preference for automatic translation of searchable fields."},"auto_update_search_index":{"type":"boolean","description":"Preference for automatic search-index updates."},"completeness_weights":{"$ref":"#/components/schemas/CompletenessWeights"},"credit_cost_overrides":{"type":"object","description":"Per-provider credit-cost-per-usage-unit overrides (usage unit = one\ntarget language). Code defaults: offline providers 0, LLM providers 1.","additionalProperties":{"type":"number","format":"double"}},"custom_enrichment_fields":{"type":"object","description":"Additional text attributes: attribute code → extraction instructions.\nUnknown values remain null; approved values use the normal attribute workflow.","additionalProperties":{"type":"string"}},"custom_enrichment_instructions":{"type":"string","description":"Company-specific writing/extraction instructions, applied by LLM providers."},"default_target_languages":{"type":"array","items":{"type":"string"},"description":"Default languages targeted by enrichment and translation."},"embeddings_api_key":{"type":"string","description":"BYOK API key for the embeddings provider — stored in DB, masked\n(***configured***) in API responses exactly like `provider_api_key`.","nullable":true},"embeddings_model":{"type":"string","description":"Optional embeddings model override (e.g. \"voyage-3.5-lite\",\n\"text-embedding-3-small\"). None → provider default.","nullable":true},"embeddings_provider":{"type":"string","description":"Semantic-embeddings provider (Enrichment v2, Phase D). One of\n\"disabled\" | \"mock\" | \"voyage\" | \"openai\". Default \"disabled\" — NO\noutbound embedding call is ever made without explicit opt-in, and no key\nis required for the default. \"mock\" is only honored when the process\nruns with `AI_TRANSPORT=mock` (tests); production must pick voyage/openai."},"enable_vision_enrichment":{"type":"boolean","description":"Enable the vision colour-extraction pass (Enrichment v2, Phase E). When\ntrue, after the text provider runs, products that have an image but no\nconfident primary colour get the image sent to the BYOK vision model to\nfill the colour gap. Default false — no image is ever sent to a model\nwithout explicit opt-in. Per-field serde default keeps pre-existing\nstored configs deserializing cleanly (prod-safe)."},"fallback_provider":{"type":"string","description":"Fallback AI provider identifier."},"group_auto_approve_confidence":{"type":"number","format":"double","description":"Confidence at/above which the \"Approve all ≥ X\" bulk action in the Groups\nreview UI pre-selects proposed groups. Bounds 0.5–1.0 (validated on\nupdate). Default 0.9."},"grouping_enabled":{"type":"boolean","description":"Enable AI product-group proposals (Enrichment v2, Phase C). When true,\ncompleting an enrichment batch auto-enqueues a `group_proposal` job that\nsuggests parent/variant groupings for staff review. Default false until\na tenant reviews grouping quality. Per-field serde default keeps configs\nstored before this knob existed deserializing cleanly (prod-safe)."},"monthly_credit_limit":{"type":"number","format":"double","description":"Hard monthly credit cap; enqueue rejects once this month's completed\nruns have spent it. None = uncapped (default).","nullable":true},"preferred_provider":{"type":"string","description":"Preferred AI provider identifier."},"provider_api_key":{"type":"string","description":"BYOK API key — stored in DB, masked (***configured***) in API responses.","nullable":true},"provider_model":{"type":"string","description":"Optional model override, e.g. \"gpt-4o-mini\", \"claude-haiku-4-5\"","nullable":true},"require_review_before_publishing_ai_attributes":{"type":"boolean","description":"Whether AI-generated attributes require review before publication."},"search_mode_default":{"type":"string","description":"Default search mode preference."},"search_use_pgvector":{"type":"boolean","description":"Prefer the pgvector `embedding` column for hybrid search when it is\npresent, instead of the legacy hashed JSON path. Default false until\nbenchmarked per tenant."},"use_platform_credits":{"type":"boolean","description":"Run enrichment on the PLATFORM's provider key, metered against the\nplan's monthly `included_ai_credits` plus operator top-ups (\"managed\nmode\", default). Only consulted when no real `provider_api_key` is\nstored — a tenant's own key always wins (BYOK mode, own costs, no\nallowance). With this off and no key, live enrichment is unavailable.\nHeuristic providers are restricted to explicit test transport. See\n`products::services::platform_ai::effective_ai_mode`."},"vision_color_min_confidence":{"type":"number","format":"double","description":"Only run the vision pass when the text-derived primary colour is missing\nor its confidence is below this threshold. Bounds 0.0–1.0 (validated on\nupdate). Default 0.6."},"vision_model":{"type":"string","description":"Vision model used for colour extraction when `enable_vision_enrichment`\nis on. Sent to the same Anthropic Messages API as the text provider (the\ntenant's `provider_api_key` is reused when the preferred provider is\nClaude). Default \"claude-haiku-4-5\"."}}},"AiCreditSummary":{"type":"object","description":"A company's managed-AI position for the current calendar month. Serialised\nverbatim by the tenant usage endpoint and the internal-admin summary.","required":["included_credits_per_month","used_this_month","topup_credits_remaining","available_credits","period_start","extra_credits_price_cents_per_1000"],"properties":{"available_credits":{"type":"number","format":"double","description":"`max(0, included − used) + topup_credits_remaining`."},"extra_credits_price_cents_per_1000":{"type":"integer","format":"int32","description":"Price of a 1,000-credit top-up on the company's plan; 0 = not sold."},"included_credits_per_month":{"type":"integer","format":"int64","description":"Plan allowance per calendar month (0 = no managed allowance)."},"period_start":{"$ref":"#/components/schemas/DateTime"},"plan_code":{"type":"string","description":"Catalog code the subscription resolved to, when any.","nullable":true},"topup_credits_remaining":{"type":"integer","format":"int64","description":"Σ `credits_remaining` of unexpired top-ups."},"used_this_month":{"type":"number","format":"double","description":"Credits spent since `period_start` (every run, whatever the mode)."}}},"AiCreditTopup":{"type":"object","description":"One operator-granted credit pack.","required":["id","company_id","credits_granted","credits_remaining","source","created_at","updated_at"],"properties":{"company_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/DateTime"},"created_by":{"type":"string","description":"Operator identity (email) that granted the pack.","nullable":true},"credits_granted":{"type":"integer","format":"int32","description":"Credits in the pack when granted (immutable)."},"credits_remaining":{"type":"integer","format":"int32","description":"Credits still available; never below 0."},"expires_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"note":{"type":"string","nullable":true},"source":{"type":"string","description":"`operator` today."},"updated_at":{"$ref":"#/components/schemas/DateTime"}}},"AiMode":{"type":"string","description":"How a tenant's enrichment runs are provided and billed. Serialises\nlowercase (`byok` / `managed` / `offline`) — the usage endpoint reports it.","enum":["byok","managed","offline"]},"AllowedTransitionsResponse":{"type":"object","description":"Response body for `GET /api/sales-orders/{id}/allowed-transitions`\n(E-2.2 order state machine, ADR-002 §state machine).","required":["current_status","allowed_next"],"properties":{"allowed_next":{"type":"array","items":{"type":"string"}},"current_status":{"type":"string"}}},"AnalyticsResponse":{"type":"object","required":["totals","tenant_signups","user_signups","leads_by_status","subscriptions_by_status"],"properties":{"leads_by_status":{"type":"array","items":{"$ref":"#/components/schemas/LabeledCount"}},"subscriptions_by_status":{"type":"array","items":{"$ref":"#/components/schemas/LabeledCount"}},"tenant_signups":{"type":"array","items":{"$ref":"#/components/schemas/MonthPoint"},"description":"New tenants per month over the last 12 months (YYYY-MM)."},"totals":{"$ref":"#/components/schemas/AnalyticsTotals"},"user_signups":{"type":"array","items":{"$ref":"#/components/schemas/MonthPoint"},"description":"New users per month over the last 12 months (YYYY-MM)."}}},"AnalyticsTotals":{"type":"object","required":["total_tenants","active_tenants","total_users","total_leads","active_subscriptions"],"properties":{"active_subscriptions":{"type":"integer","format":"int64"},"active_tenants":{"type":"integer","format":"int64"},"total_leads":{"type":"integer","format":"int64"},"total_tenants":{"type":"integer","format":"int64"},"total_users":{"type":"integer","format":"int64"}}},"ApAgingBucket":{"type":"object","description":"One aging column. `from_days`/`to_days` are days past due, inclusive;\nthe \"current\" column (not yet due) has neither, the last has no `to_days`.","required":["key"],"properties":{"from_days":{"type":"integer","format":"int32","nullable":true},"key":{"type":"string"},"to_days":{"type":"integer","format":"int32","nullable":true}}},"ApAgingResponse":{"type":"object","required":["as_of","currency_code","buckets","rows","totals","total","unapplied_payments","open_credits","net_balance"],"properties":{"as_of":{"type":"string"},"buckets":{"type":"array","items":{"$ref":"#/components/schemas/ApAgingBucket"}},"currency_code":{"type":"string"},"net_balance":{"type":"string"},"open_credits":{"type":"string"},"rows":{"type":"array","items":{"$ref":"#/components/schemas/ApAgingRow"}},"total":{"type":"string"},"totals":{"type":"array","items":{"type":"string"},"description":"Column totals, aligned with `buckets`."},"unapplied_payments":{"type":"string"}}},"ApAgingRow":{"type":"object","required":["supplier_id","supplier_name","amounts","total","open_bills","unapplied_payments","open_credits","net_balance"],"properties":{"amounts":{"type":"array","items":{"type":"string"},"description":"Open bill balances per bucket, aligned with `ApAgingResponse.buckets`."},"net_balance":{"type":"string","description":"`total - unapplied_payments - open_credits`: what is owed net."},"open_bills":{"type":"integer","format":"int64"},"open_credits":{"type":"string","description":"Vendor credits not yet applied to a bill."},"supplier_id":{"$ref":"#/components/schemas/Uuid"},"supplier_name":{"type":"string"},"total":{"type":"string","description":"Sum of the open bill balances."},"unapplied_payments":{"type":"string","description":"Payments not yet applied to a bill."}}},"ApplySupplierPaymentRequest":{"type":"object","required":["version","allocations"],"properties":{"allocations":{"type":"array","items":{"$ref":"#/components/schemas/SupplierPaymentAllocationRequest"}},"version":{"type":"integer","format":"int32"}}},"ApplyVendorCreditRequest":{"type":"object","required":["version","vendor_credit_id","amount"],"properties":{"amount":{"type":"string"},"vendor_credit_id":{"$ref":"#/components/schemas/Uuid"},"version":{"type":"integer","format":"int32","description":"Version of the bill."}}},"AssignDeliveryRequest":{"type":"object","description":"Body of `POST /fulfillment/delivery/{id}/assign`.","properties":{"carrier":{"type":"string","description":"Carrier name, e.g. \"PostNL\", \"DHL\". Omit to leave unchanged.","nullable":true},"delivery_method":{"type":"string","nullable":true},"delivery_notes":{"type":"string","nullable":true},"driver_name":{"type":"string","nullable":true},"driver_user_id":{"type":"integer","format":"int32","description":"Stable tenant staff ID. Omit for an external courier or legacy text assignment.","nullable":true},"estimated_delivery":{"type":"string","description":"\"YYYY-MM-DD\"","nullable":true},"route_name":{"type":"string","nullable":true},"tracking_code":{"type":"string","description":"Carrier tracking code (shown to storefront buyers on their order page\nand in the \"on its way\" email). Omit to leave unchanged.","nullable":true}}},"AssignOpenOrderInput":{"type":"object","description":"Body for `POST /api/horeca/open-orders/{id}/assign` (POS-ship workstream E).\n`open_orders.assigned_user_id` existed; no endpoint could change it, so a\ntab could not be handed to another server mid-shift.","properties":{"assigned_user_id":{"type":"integer","format":"int32","description":"Target user id, or `null` to unassign.","nullable":true}}},"AssignThreadRequest":{"type":"object","properties":{"user_id":{"type":"integer","format":"int32","description":"User to assign the thread to; null/absent clears the assignment.","nullable":true}}},"AttachModifierGroupInput":{"type":"object","properties":{"sort_order":{"type":"integer","format":"int32","nullable":true}}},"AuditLogEntry":{"type":"object","required":["id","created_at","actor_id","method","endpoint","action","entity","company_id","success"],"properties":{"action":{"type":"string"},"actor_email":{"type":"string","description":"Denormalized actor e-mail extracted from metadata.","nullable":true},"actor_id":{"type":"string"},"company_id":{"type":"string"},"created_at":{"type":"string"},"endpoint":{"type":"string"},"entity":{"type":"string"},"entity_id":{"type":"string","nullable":true},"id":{"type":"integer","format":"int64"},"ip_address":{"type":"string","nullable":true},"method":{"type":"string"},"success":{"type":"boolean"}}},"AuditLogListResponse":{"type":"object","required":["items","total","page","per_page","total_pages"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AuditLogEntry"}},"page":{"type":"integer","format":"int64"},"per_page":{"type":"integer","format":"int64"},"total":{"type":"integer","format":"int64"},"total_pages":{"type":"integer","format":"int64"}}},"AuthUser":{"type":"object","required":["id","password","is_superuser","username","first_name","last_name","email","is_staff","is_active","date_joined"],"properties":{"date_joined":{"type":"string"},"email":{"type":"string"},"first_name":{"type":"string"},"id":{"type":"integer","format":"int32"},"is_active":{"type":"boolean"},"is_platform_admin":{"type":"boolean","description":"Platform (internal-admin) access flag. Distinct from the tenant `is_superuser`\nflag — only genuine Vender staff get this; tenant users never do."},"is_staff":{"type":"boolean"},"is_superuser":{"type":"boolean"},"last_login":{"type":"string","nullable":true},"last_name":{"type":"string"},"password":{"type":"string"},"username":{"type":"string"}}},"AuthenticatedUserDto":{"type":"object","required":["id","username","email","name","role","permissions","has_custom_permissions"],"properties":{"email":{"type":"string"},"has_custom_permissions":{"type":"boolean"},"id":{"type":"integer","format":"int32"},"name":{"type":"string"},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"}},"role":{"$ref":"#/components/schemas/UserRole"},"username":{"type":"string"}}},"BackupRecordResponse":{"type":"object","required":["id","company_id","backup_type"],"properties":{"backup_type":{"type":"string"},"company_id":{"type":"string"},"completed_at":{"type":"string","nullable":true},"created_by":{"type":"string","nullable":true},"error_message":{"type":"string","nullable":true},"id":{"type":"string"},"size_bytes":{"type":"integer","format":"int64","nullable":true},"started_at":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"storage_url":{"type":"string","nullable":true}}},"BigDecimal":{"type":"string","description":"Arbitrary precision decimal number represented as string","example":"123.45"},"BillingFeatureRow":{"type":"object","description":"One row of the operator-editable feature catalog.","required":["code","name","tier","price_cents","currency","is_active","sort_order","created_at","updated_at"],"properties":{"code":{"type":"string","description":"Matches the `Feature` enum's wire key, e.g. `pos.checkout`."},"created_at":{"$ref":"#/components/schemas/chrono.DateTime"},"currency":{"type":"string"},"description":{"type":"string","nullable":true},"is_active":{"type":"boolean"},"name":{"type":"string"},"price_cents":{"type":"integer","format":"int32"},"sort_order":{"type":"integer","format":"int32"},"tier":{"type":"string","description":"`core` | `standard` | `premium`. **Read-only** — owned by the enum and\nreset on every boot by the seeder. Exposed so the UI can show why a Core\nfeature cannot be removed from a plan."},"updated_at":{"$ref":"#/components/schemas/chrono.DateTime"}}},"BillingPlan":{"type":"object","required":["id","code","name","price_cents","currency","billing_interval","features","is_active","sort_order","created_at","updated_at","included_seats","extra_seat_price_cents","included_rep_devices","extra_rep_device_price_cents","trial_days","is_public","is_highlighted","is_trial_default","marketing","contact_sales","included_ai_credits","extra_ai_credits_price_cents"],"properties":{"billing_interval":{"type":"string"},"code":{"type":"string"},"contact_sales":{"type":"boolean","description":"Enterprise-style card: no self-serve price, contact CTA instead."},"created_at":{"$ref":"#/components/schemas/chrono.DateTime"},"currency":{"type":"string"},"description":{"type":"string","nullable":true},"extra_ai_credits_price_cents":{"type":"integer","format":"int32","description":"Price of a 1,000-credit AI top-up in cents; 0 = not sold."},"extra_rep_device_price_cents":{"type":"integer","format":"int32","description":"Price per additional rep device, per month, in cents."},"extra_seat_price_cents":{"type":"integer","format":"int32","description":"Price per additional seat, per month, in cents."},"features":{"type":"object"},"id":{"$ref":"#/components/schemas/Uuid"},"included_ai_credits":{"type":"integer","format":"int32","description":"AI enrichment credits included per calendar month (managed mode).\n0 = no managed allowance."},"included_rep_devices":{"type":"integer","format":"int32","description":"Rep-app devices included in the base price."},"included_seats":{"type":"integer","format":"int32","description":"Staff seats included in the base price."},"is_active":{"type":"boolean"},"is_highlighted":{"type":"boolean","description":"The \"most popular\" card on the pricing page."},"is_public":{"type":"boolean","description":"Shown on the public pricing page (`GET /api/public/plans`)."},"is_trial_default":{"type":"boolean","description":"New signups get their trial on this plan."},"marketing":{"type":"object","description":"Per-locale display copy\n(`{\"en\":{\"tagline\",\"bullets\":[],\"badge\",\"not_included\":[]},\"nl\":{..},\"es\":{..}}`).\nEntitlements live in `features`, never here."},"name":{"type":"string"},"price_cents":{"type":"integer","format":"int32"},"price_cents_annual":{"type":"integer","format":"int32","description":"TOTAL charged per YEAR in cents when billed annually (NOT per month).\n`None` = annual billing not offered.","nullable":true},"sku_limit":{"type":"integer","format":"int32","description":"`None` = unlimited.","nullable":true},"sort_order":{"type":"integer","format":"int32"},"stripe_price_id_annual":{"type":"string","description":"Stripe Price ID charged for this plan's ANNUAL interval. `None` falls\nback to the `STRIPE_PRICE`_* env vars (see `billing::plan_catalog`).","nullable":true},"stripe_price_id_monthly":{"type":"string","description":"Stripe Price ID charged for this plan's MONTHLY interval. `None` falls\nback to the `STRIPE_PRICE`_* env vars (see `billing::plan_catalog`).","nullable":true},"trial_days":{"type":"integer","format":"int32","description":"Trial length minted at signup when this plan is the trial default."},"updated_at":{"$ref":"#/components/schemas/chrono.DateTime"}}},"BillingSettings":{"type":"object","description":"Billing policy for one company.\n\nEvery value here is deliberately a setting rather than a `const`: these are\ncommercial policy, and policy has to move per tenant without a redeploy.\n\nNOTE: every field MUST carry its own `#[serde(default = ...)]` (the\ngroup-level `#[serde(default)]` only fires when the WHOLE group is absent).","properties":{"payment_failure_grace_days":{"type":"integer","format":"int64","description":"Days of continued access after a failed payment before the tenant is\nblocked. Default 7.\n\nThe window opens once per failure episode and is NOT extended by Stripe's\nretries — otherwise the deadline would keep sliding and the tenant would\nnever actually lapse."},"reminders_enabled":{"type":"boolean","description":"Whether this tenant receives billing reminder emails (trial ending,\ninvoice due, overdue, card expiring). Default true — reminders exist to\nprevent involuntary churn, so opting OUT should be the deliberate act.\n\nConsumed by the P4 dunning engine; currently stored but not yet read."}}},"BillingStatusResponse":{"type":"object","description":"Response from GET /api/billing/status","required":["plan_name","status","has_access","is_trial","trial_days_remaining","cancel_at_period_end"],"properties":{"cancel_at_period_end":{"type":"boolean"},"current_period_end":{"type":"string","nullable":true},"grace_until":{"type":"string","description":"Set while a post-payment-failure grace window is open. The frontend uses\nthis to warn *before* access is lost rather than after.\n\nAdditive field — safe for old clients, which ignore it (Backward\nCompatibility rule; mobile binaries call this endpoint for days after a\nbackend deploy).","nullable":true},"has_access":{"type":"boolean"},"is_comped":{"type":"boolean","description":"Operator-granted free contract. Surfaced so the UI can suppress upgrade\nprompts for a comped tenant."},"is_trial":{"type":"boolean"},"payment_method":{"type":"string","description":"How this tenant settles: `stripe_card` | `stripe_sepa` | `bank_transfer`\n| `manual` | `comped` | `external`. `None` = unclassified.\n\nThe UI needs this to stop offering Stripe-specific actions to tenants who\nhave no Stripe objects — \"Manage payment method\" opens the Stripe billing\nportal, which for an invoiced customer errors with \"No Stripe customer\nfound. Please complete checkout first.\", i.e. it tells a paying customer\nthey have not paid.","nullable":true},"plan_name":{"type":"string"},"status":{"type":"string"},"stripe_customer_id":{"type":"string","nullable":true},"trial_days_remaining":{"type":"integer","format":"int64"},"trial_ends_at":{"type":"string","nullable":true}}},"BuilderChannel":{"type":"string","description":"Which surface the builder is editing.","enum":["storefront","portal"]},"BulkCheckImageResult":{"type":"object","required":["item_number","existing_count","has_images"],"properties":{"existing_count":{"type":"integer","format":"int32"},"has_images":{"type":"boolean"},"item_number":{"type":"string"},"product_id":{"type":"string","nullable":true},"product_name":{"type":"string","nullable":true}}},"BulkCheckImagesRequest":{"type":"object","required":["item_numbers"],"properties":{"item_numbers":{"type":"array","items":{"type":"string"}},"match_field":{"type":"string","description":"Which product field to match against: \"`item_number`\" (default), \"code\",\n\"name\", or \"`external_id`\" (requires `source_system`)","nullable":true},"source_system":{"type":"string","description":"Source-system label for `match_field = \"external_id\"` lookups through\nthe `external_references` identity map.","nullable":true}}},"BulkCheckImagesResponse":{"type":"object","required":["results"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/BulkCheckImageResult"}}}},"BulkImportAccountRow":{"type":"object","properties":{"account_number":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"customer_type":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"excluded":{"type":"boolean","nullable":true},"external_id":{"type":"string","description":"Record id in the source system; used for identity matching when the\nrequest declares a `source_system`.","nullable":true},"name":{"type":"string","description":"Required when the row creates a new account.","nullable":true},"phone":{"type":"string","nullable":true}}},"BulkImportAccountsRequest":{"type":"object","required":["rows"],"properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/BulkImportAccountRow"}},"source_system":{"type":"string","description":"Source-system label (e.g. \"`legacy_pos`\"). When set, rows carrying an\n`external_id` match through the `external_references` identity map first.","nullable":true}}},"BulkImportCategoriesRequest":{"type":"object","required":["rows"],"properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/BulkImportCategoryRow"}},"source_system":{"type":"string","nullable":true}}},"BulkImportCategoryRow":{"type":"object","properties":{"excluded":{"type":"boolean","nullable":true},"external_id":{"type":"string","description":"Record id in the source system; used for identity matching when the\nrequest declares a `source_system`.","nullable":true},"name":{"type":"string","description":"Required when the row creates a new category.","nullable":true}}},"BulkImportResponse":{"type":"object","description":"Summary of an entity bulk import.","required":["created","updated","failed","results"],"properties":{"created":{"type":"integer","format":"int32","minimum":0},"failed":{"type":"integer","format":"int32","minimum":0},"results":{"type":"array","items":{"$ref":"#/components/schemas/BulkImportRowResult"}},"updated":{"type":"integer","format":"int32","minimum":0}}},"BulkImportRowResult":{"type":"object","description":"Per-row outcome of an entity bulk import (accounts, categories, ...).","required":["row","action"],"properties":{"action":{"type":"string","description":"\"created\" | \"updated\" | \"blocked\" | \"error\""},"entity_id":{"type":"string","description":"Local id of the created/updated entity (absent on error).","nullable":true},"field_conflicts":{"type":"array","items":{"type":"string"},"description":"Protected fields that changed independently in both systems."},"message":{"type":"string","nullable":true},"row":{"type":"integer","format":"int32","description":"1-based row number in the request.","minimum":0}}},"BulkProductData":{"type":"object","properties":{"category":{"type":"string","nullable":true},"category_external_id":{"type":"string","description":"Source-system id of the category/department (e.g. QBPOS `Department.ListID`).\nWhen present alongside the upload's `source_system`, this is matched via\n`external_references` instead of the fuzzy `category` name — this is what\nprevents duplicate categories when a source renames or abbreviates a\ndepartment name inconsistently between syncs.","nullable":true},"code":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"excluded":{"type":"string","description":"Active status from import. This value is interpreted as \"active\" (not excluded).\nAccepts common values like: active/inactive, true/false, yes/no, 1/0.","nullable":true},"external_id":{"type":"string","description":"Record id in the source system. Only used for matching when the upload\nsession declares a `source_system`; ignored (with a warning) otherwise.","nullable":true},"item_number":{"type":"string"},"msrp_price":{"allOf":[{"$ref":"#/components/schemas/bigdecimal.BigDecimal"}],"nullable":true},"name":{"type":"string","description":"Product name. Optional for stock-update imports; required for new product creation.","nullable":true},"qty":{"type":"integer","format":"int32","nullable":true},"regular_price":{"allOf":[{"$ref":"#/components/schemas/bigdecimal.BigDecimal"}],"nullable":true},"wholesale_price":{"allOf":[{"$ref":"#/components/schemas/bigdecimal.BigDecimal"}],"nullable":true}}},"BulkReassignOwnerRequest":{"type":"object","required":["from_user_id","to_user_id"],"properties":{"from_user_id":{"type":"integer","format":"int32","description":"The departing user whose records are handed over. May already be\ndeactivated — that is the typical case."},"to_user_id":{"type":"integer","format":"int32","description":"The receiving user; must be an active member of the company."}}},"BulkReassignOwnerResponse":{"type":"object","required":["contacts_reassigned","organizations_reassigned","deals_reassigned","cases_reassigned"],"properties":{"cases_reassigned":{"type":"integer","format":"int64"},"contacts_reassigned":{"type":"integer","format":"int64"},"deals_reassigned":{"type":"integer","format":"int64"},"organizations_reassigned":{"type":"integer","format":"int64"}}},"BulkUpsertExternalReferencesRequest":{"type":"object","required":["references"],"properties":{"references":{"type":"array","items":{"$ref":"#/components/schemas/UpsertExternalReferenceRequest"}}}},"BulkUpsertExternalReferencesResponse":{"type":"object","required":["upserted","errors"],"properties":{"errors":{"type":"array","items":{"type":"string"}},"upserted":{"type":"integer","format":"int32","minimum":0}}},"CampaignDetailResponse":{"type":"object","required":["id","name","segment_id","channel","status","sent_count","suppressed_count","created_at","updated_at"],"properties":{"body_template_ref":{"type":"string","nullable":true},"channel":{"type":"string"},"created_at":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"name":{"type":"string"},"scheduled_at":{"type":"string","nullable":true},"segment_id":{"$ref":"#/components/schemas/Uuid"},"sent_count":{"type":"integer","format":"int64"},"status":{"type":"string"},"subject":{"type":"string","nullable":true},"suppressed_count":{"type":"integer","format":"int64"},"updated_at":{"type":"string"}}},"CampaignRequest":{"type":"object","required":["name","segment_id"],"properties":{"body_template_ref":{"type":"string","description":"Reference to a stored template (D-001 component registry) — never\ninline freeform HTML.","nullable":true},"name":{"type":"string"},"scheduled_at":{"type":"string","description":"RFC 3339; present = scheduled, absent = draft.","nullable":true},"segment_id":{"$ref":"#/components/schemas/Uuid"},"subject":{"type":"string","nullable":true}}},"CampaignSendResponse":{"type":"object","required":["campaign_id","status","sent_count","suppressed_count"],"properties":{"campaign_id":{"$ref":"#/components/schemas/Uuid"},"sent_count":{"type":"integer","format":"int64"},"status":{"type":"string"},"suppressed_count":{"type":"integer","format":"int64"}}},"CapacityPrice":{"type":"object","required":["included"],"properties":{"extra_price":{"allOf":[{"$ref":"#/components/schemas/RecurringPrice"}],"nullable":true},"included":{"type":"integer","format":"int32","description":"Number included per base unit (company or location).","minimum":0},"maximum":{"type":"integer","format":"int32","description":"Maximum total quantity per base unit, including the included capacity.","nullable":true,"minimum":0}}},"CardDisplayConfig":{"type":"object","properties":{"description_display":{"$ref":"#/components/schemas/DescriptionDisplayMode"},"image_aspect_ratio":{"$ref":"#/components/schemas/CardImageAspectRatio"},"max_description_lines":{"type":"integer","format":"int32"},"show_category":{"type":"boolean"},"show_details_link":{"type":"boolean"},"show_image":{"type":"boolean"},"show_min_order_qty":{"type":"boolean"},"show_msrp_price":{"type":"boolean"},"show_name":{"type":"boolean"},"show_order_multiple":{"type":"boolean"},"show_price_labels":{"type":"boolean"},"show_regular_price":{"type":"boolean"},"show_stock_badge":{"type":"boolean"},"show_stock_quantity":{"type":"boolean"},"show_wholesale_price":{"type":"boolean"},"sku_display":{"$ref":"#/components/schemas/SkuDisplayMode"}}},"CardImageAspectRatio":{"type":"string","enum":["1:1","4:3","16:9"]},"CardSpacing":{"type":"string","enum":["compact","normal","spacious"]},"CaseCommentResponse":{"type":"object","required":["id","is_internal","body","created_at"],"properties":{"author_user_id":{"type":"integer","format":"int32","nullable":true},"body":{"type":"string"},"created_at":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"is_internal":{"type":"boolean"}}},"CaseDetailResponse":{"type":"object","required":["id","subject","priority","status","created_at","updated_at","comments"],"properties":{"assigned_to_user_id":{"type":"integer","format":"int32","nullable":true},"comments":{"type":"array","items":{"$ref":"#/components/schemas/CaseCommentResponse"}},"contact_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"created_at":{"type":"string"},"custom_fields":{"type":"object","description":"`field_definition.code` → typed value (A2 engine). Option-typed for\nschema additivity; always present on the wire.","additionalProperties":{},"nullable":true},"first_responded_at":{"type":"string","nullable":true},"first_response_due_at":{"type":"string","nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"organization_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"priority":{"type":"string"},"resolution_due_at":{"type":"string","nullable":true},"resolved_at":{"type":"string","nullable":true},"sla_policy_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"status":{"type":"string"},"subject":{"type":"string"},"updated_at":{"type":"string"}}},"CaseListItem":{"type":"object","required":["id","subject","priority","status","created_at","updated_at"],"properties":{"assigned_to_user_id":{"type":"integer","format":"int32","nullable":true},"contact_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"created_at":{"type":"string"},"first_response_due_at":{"type":"string","nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"organization_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"priority":{"type":"string"},"resolution_due_at":{"type":"string","nullable":true},"status":{"type":"string"},"subject":{"type":"string"},"updated_at":{"type":"string"}}},"CaseListResponse":{"type":"object","required":["items","total_count","page","page_size"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CaseListItem"}},"page":{"type":"integer","format":"int64"},"page_size":{"type":"integer","format":"int64"},"total_count":{"type":"integer","format":"int64"}}},"CasePriority":{"type":"string","description":"Case priority — mirrors the DB CHECK on `crm_cases.priority`.","enum":["low","normal","high","urgent"]},"CatalogDisplaySettings":{"type":"object","properties":{"b2b":{"$ref":"#/components/schemas/ChannelCatalogSettings"},"lookbook":{"$ref":"#/components/schemas/LookbookCatalogSettings"},"mobile":{"$ref":"#/components/schemas/ChannelCatalogSettings"},"pos":{"$ref":"#/components/schemas/ChannelCatalogSettings"}}},"CatalogResponseDto":{"type":"object","description":"Paginated catalog response — GET /api/portal/catalog","required":["results","count","min_qty"],"properties":{"count":{"type":"integer","format":"int64","description":"Total matching products (for pagination)"},"min_qty":{"type":"integer","format":"int32","description":"Company minimum order quantity setting"},"results":{"type":"array","items":{"$ref":"#/components/schemas/PortalProductDto"}}}},"CatalogRuleResponse":{"type":"object","required":["id","account_id","rule_type","created_at"],"properties":{"account_id":{"type":"string"},"category_id":{"type":"string","nullable":true},"created_at":{"type":"string"},"id":{"type":"string"},"product_id":{"type":"string","nullable":true},"rule_type":{"type":"string"}}},"CatalogSearchResponse":{"type":"object","required":["title","intro","accent","logo_url","currency","contact_email","action_label","analytics_enabled","products","categories","page","page_size","total"],"properties":{"accent":{"type":"string"},"action_label":{"type":"string"},"analytics_enabled":{"type":"boolean"},"categories":{"type":"array","items":{"$ref":"#/components/schemas/SearchCategory"}},"contact_email":{"type":"string"},"currency":{"type":"string"},"intro":{"type":"string"},"logo_url":{"type":"string"},"page":{"type":"integer","format":"int64"},"page_size":{"type":"integer","format":"int32","minimum":0},"products":{"type":"array","items":{"$ref":"#/components/schemas/SearchProduct"}},"title":{"type":"string"},"total":{"type":"integer","format":"int64"}}},"CatalogSearchSettings":{"type":"object","description":"Stored per company. Disabled until an administrator explicitly publishes.","properties":{"accent":{"type":"string","default":"#2563eb"},"action_label":{"type":"string","default":""},"analytics_enabled":{"type":"boolean","default":false},"analytics_retention_days":{"type":"integer","format":"int32","default":30},"boosted_product_ids":{"type":"array","items":{"type":"string"},"default":[]},"category_ids":{"type":"array","items":{"type":"string"},"default":[]},"contact_email":{"type":"string","default":""},"enabled":{"type":"boolean","default":false},"intro":{"type":"string","default":""},"logo_url":{"type":"string","default":""},"product_url_template":{"type":"string","description":"HTTPS product page URL containing {id} or {sku}; empty uses product details.","default":""},"results_per_page":{"type":"integer","format":"int32","default":12,"minimum":0},"show_out_of_stock":{"type":"boolean","default":true},"show_prices":{"type":"boolean","default":true},"synonyms":{"type":"string","description":"One comma-separated synonym group per line, e.g. sneakers, trainers.","default":""},"title":{"type":"string","default":""},"typo_threshold":{"type":"number","format":"double","default":0.45},"typo_tolerance":{"type":"boolean","default":true}}},"CatalogSortConfig":{"type":"object","properties":{"direction":{"$ref":"#/components/schemas/SortDirection"},"field":{"$ref":"#/components/schemas/CatalogSortField"}}},"CatalogSortField":{"type":"string","enum":["name","price","stock","item_number","created_at"]},"CatalogVisibilityConfig":{"type":"object","properties":{"min_stock_threshold":{"type":"integer","format":"int32"},"show_only_if_price_set":{"type":"boolean"},"show_only_if_wholesale_price_set":{"type":"boolean"},"show_only_with_description":{"type":"boolean"}}},"CatalogueState":{"type":"object","description":"What the owning browser receives, which additionally says where it stands.","required":["id","slug","kind","status","content","image_count","bytes_stored","view_count","extended_count"],"properties":{"bytes_stored":{"type":"integer","format":"int64"},"content":{},"email_hint":{"type":"string","description":"Present once publishing has been requested, so the page can say where\nthe link went without exposing the address to anyone else.","nullable":true},"expires_at":{"type":"string","nullable":true},"extended_count":{"type":"integer","format":"int32"},"id":{"type":"string"},"image_count":{"type":"integer","minimum":0},"kind":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string"},"view_count":{"type":"integer","format":"int32"}}},"CategoryBreakdown":{"type":"object","required":["category_name","product_count","total_value","avg_price"],"properties":{"avg_price":{"$ref":"#/components/schemas/BigDecimal"},"category_name":{"type":"string"},"product_count":{"type":"integer","format":"int64"},"total_value":{"$ref":"#/components/schemas/BigDecimal"}}},"CategoryInput":{"type":"object","description":"Category creation/update input","required":["name"],"properties":{"code":{"type":"string","nullable":true},"excluded":{"type":"boolean","nullable":true},"name":{"type":"string"},"parent_id":{"type":"string","nullable":true}}},"CategoryPayload":{"type":"object","description":"Category payload for product operations","properties":{"name":{"type":"string","nullable":true}}},"CategoryResponse":{"type":"object","description":"Category response with product count and alias info","required":["id","created_at","updated_at","name","company_id","excluded","level","lft","rght","tree_id","products_count","joined_count"],"properties":{"code":{"type":"string","nullable":true},"company_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/chrono.NaiveDateTime"},"excluded":{"type":"boolean"},"id":{"type":"string"},"joined_count":{"type":"integer","format":"int64"},"level":{"type":"integer","format":"int32"},"lft":{"type":"integer","format":"int32"},"merged_into_id":{"type":"string","nullable":true},"merged_into_name":{"type":"string","nullable":true},"name":{"type":"string"},"parent_id":{"type":"string","nullable":true},"products_count":{"type":"integer","format":"int64"},"rght":{"type":"integer","format":"int32"},"tree_id":{"type":"integer","format":"int32"},"updated_at":{"$ref":"#/components/schemas/chrono.NaiveDateTime"}}},"ChangePasswordRequest":{"type":"object","required":["current_password","new_password"],"properties":{"current_password":{"type":"string"},"new_password":{"type":"string"}}},"ChannelCatalogSettings":{"type":"object","properties":{"card":{"$ref":"#/components/schemas/CardDisplayConfig"},"category_sort":{"allOf":[{"$ref":"#/components/schemas/CatalogSortConfig"}],"nullable":true},"grid":{"$ref":"#/components/schemas/GridLayoutConfig"},"sort":{"$ref":"#/components/schemas/CatalogSortConfig"},"visibility":{"$ref":"#/components/schemas/CatalogVisibilityConfig"}}},"CheckoutAvailabilityReason":{"type":"string","enum":["ready","recovery_required","existing_billing","collection_unavailable"]},"CheckoutResponse":{"type":"object","description":"Response from POST /api/billing/checkout","required":["checkout_url"],"properties":{"checkout_url":{"type":"string"}}},"ClaimKioskDeviceInput":{"type":"object","required":["code","name"],"properties":{"code":{"type":"string"},"config":{"nullable":true},"name":{"type":"string"}}},"ClaimPairingCodeRequest":{"type":"object","required":["code","name"],"properties":{"code":{"type":"string"},"name":{"type":"string"}}},"ClientDownloadsError":{"type":"object","description":"Error response for unavailable or invalid release catalogues.","required":["error"],"properties":{"error":{"type":"string"}}},"ClientDownloadsResponse":{"type":"object","description":"Public release catalogue.","required":["schema_version","generated_at","releases"],"properties":{"generated_at":{"type":"string"},"releases":{"type":"array","items":{"$ref":"#/components/schemas/ClientRelease"}},"schema_version":{"type":"integer","format":"int32","minimum":0}}},"ClientRelease":{"type":"object","description":"Public release metadata returned to the marketing site.","required":["id","product","name","description","platform","architecture","version","filename","size_bytes","sha256","published_at","download_url"],"properties":{"architecture":{"type":"string"},"description":{"type":"string"},"download_url":{"type":"string","description":"Stable backend URL that redirects to the current public CDN object."},"filename":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"platform":{"type":"string"},"product":{"type":"string"},"published_at":{"type":"string"},"sha256":{"type":"string"},"size_bytes":{"type":"integer","format":"int64","minimum":0},"version":{"type":"string"}}},"CloseOrderInput":{"type":"object","properties":{"note":{"type":"string","nullable":true}}},"CloseOrderResponse":{"type":"object","description":"`POST /api/horeca/open-orders/{id}/close` response. Mirrors\n`handlers::close_open_order`'s `json!({\"success\": true, \"message\": \"Order\nclosed\"})` exactly.","required":["success","message"],"properties":{"message":{"type":"string"},"success":{"type":"boolean"}}},"CloseSessionRequest":{"type":"object","required":["ending_cash"],"properties":{"ending_cash":{"type":"string"}}},"CloseTableSessionInput":{"type":"object","properties":{"note":{"type":"string","nullable":true}}},"CogsKindTotal":{"type":"object","description":"Total cost for one allocation kind.","required":["kind","total_cost","count"],"properties":{"count":{"type":"integer","format":"int64","description":"Number of allocations."},"kind":{"type":"string","description":"Allocation kind."},"total_cost":{"type":"string","description":"Sum of total cost."}}},"CogsLedgerResponse":{"type":"object","description":"A page of the cost ledger, newest first.","required":["results","total","totals_by_kind","limit","offset"],"properties":{"limit":{"type":"integer","format":"int64","description":"Page size used."},"offset":{"type":"integer","format":"int64","description":"Offset used."},"results":{"type":"array","items":{"$ref":"#/components/schemas/CogsLedgerRow"},"description":"The page."},"total":{"type":"integer","format":"int64","description":"Rows across all pages."},"totals_by_kind":{"type":"array","items":{"$ref":"#/components/schemas/CogsKindTotal"},"description":"Sum of `total_cost` across all pages, per kind."}}},"CogsLedgerRow":{"type":"object","description":"One allocation in the cost ledger.","required":["id","created_at","kind","source_type","source_id","quantity","unit_cost","total_cost"],"properties":{"created_at":{"type":"string","description":"When it was posted (RFC 3339)."},"id":{"type":"string","description":"Allocation id."},"kind":{"type":"string","description":"`receipt`, `consume` (cost of goods sold), `reverse` (return), `adjust`, `transfer`."},"location_name":{"type":"string","description":"Location, when the allocation's stock movement names one.","nullable":true},"product_id":{"type":"string","description":"Product, when the allocation's stock movement names one.","nullable":true},"product_name":{"type":"string","description":"Product name.","nullable":true},"quantity":{"type":"string","description":"Units."},"source_id":{"type":"string","description":"Document id."},"source_line_id":{"type":"string","description":"Line within the document.","nullable":true},"source_type":{"type":"string","description":"Document type that posted it (e.g. `sales_receipt`, `goods_receipt`)."},"total_cost":{"type":"string","description":"Total cost."},"unit_cost":{"type":"string","description":"Unit cost."}}},"CollapsibleItem":{"type":"object","description":"One question/answer of a `collapsible_content` (FAQ) section.","properties":{"content":{"type":"string","description":"Rich text / markdown — rendered without raw HTML injection."},"heading":{"type":"string","description":"Heading displayed for the collapsible entry."},"id":{"type":"string","description":"Stable identifier of this configured entry."}}},"CommandResponse":{"type":"object","required":["message","command_id"],"properties":{"command_id":{"type":"string"},"message":{"type":"string"}}},"CommercialAgreement":{"type":"object","required":["id","company_id","revision","status","quote","selections","manifest","starts_at","ends_at","agreement_reference"],"properties":{"activated_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true},"agreement_reference":{"type":"string"},"company_id":{"type":"string"},"ends_at":{"$ref":"#/components/schemas/DateTime"},"id":{"$ref":"#/components/schemas/Uuid"},"manifest":{"$ref":"#/components/schemas/AgreementManifest"},"quote":{"$ref":"#/components/schemas/OfferQuote"},"revision":{"type":"integer","format":"int32"},"selections":{"type":"array","items":{"$ref":"#/components/schemas/OfferSelection"}},"starts_at":{"$ref":"#/components/schemas/DateTime"},"status":{"$ref":"#/components/schemas/AgreementStatus"}}},"CommercialCheckoutPolicy":{"type":"object","properties":{"allow_live_checkout":{"type":"boolean","description":"Test checkout and real collection have separate acceptance gates.","default":false},"automatic_tax":{"type":"boolean","default":true},"collect_tax_id":{"type":"boolean","default":true},"enabled":{"type":"boolean","description":"Platform-managed rollout switch; absent stored policy keeps checkout off.","default":false},"provider_timeout_seconds":{"type":"integer","format":"int64","default":30,"minimum":0}}},"CommercialCheckoutPolicyView":{"type":"object","required":["revision","policy","webhook_configured"],"properties":{"policy":{"$ref":"#/components/schemas/CommercialCheckoutPolicy"},"provider_mode":{"allOf":[{"$ref":"#/components/schemas/CommercialProviderMode"}],"nullable":true},"revision":{"type":"integer","format":"int32","minimum":0},"webhook_configured":{"type":"boolean","description":"Credential presence only. This does not claim a webhook was delivered."}}},"CommercialCurrencyRevenue":{"type":"object","required":["currency","active_agreements","annualised_contract_cents","monthly_equivalent_cents"],"properties":{"active_agreements":{"type":"integer","format":"int64","minimum":0},"annualised_contract_cents":{"type":"integer","format":"int64","description":"Exact annual quote total, or the monthly quote multiplied by twelve."},"currency":{"type":"string"},"monthly_equivalent_cents":{"type":"integer","format":"int64","description":"Annualised total divided by twelve, rounded once to the nearest cent."}}},"CommercialDashboard":{"type":"object","properties":{"open_pos_sessions":{"type":"integer","format":"int64","nullable":true},"orders_by_status":{"type":"object","description":"None means the module/permission is unavailable, not a zero count.","additionalProperties":{"type":"integer","format":"int64"},"nullable":true}}},"CommercialInterest":{"type":"object","properties":{"interval":{"allOf":[{"$ref":"#/components/schemas/OfferInterval"}],"nullable":true},"plan_code":{"type":"string","nullable":true},"products":{"type":"array","items":{"$ref":"#/components/schemas/ProductFamily"}},"selections":{"type":"array","items":{"$ref":"#/components/schemas/OfferSelection"}}}},"CommercialLicences":{"type":"object","required":["allocation","users","terminals"],"properties":{"agreement_id":{"allOf":[{"$ref":"#/components/schemas/uuid.Uuid"}],"nullable":true},"allocation":{"$ref":"#/components/schemas/LicenceAllocation"},"capacity":{"allOf":[{"$ref":"#/components/schemas/AgreementCapacity"}],"nullable":true},"locations":{"type":"array","items":{"$ref":"#/components/schemas/LicenceSubject"},"nullable":true},"terminals":{"type":"array","items":{"$ref":"#/components/schemas/LicenceSubject"}},"users":{"type":"array","items":{"$ref":"#/components/schemas/LicenceSubject"}}}},"CommercialProviderMode":{"type":"string","enum":["test","live"]},"CommercialRevenueReport":{"type":"object","required":["as_of","active_agreements","inactive_agreements","currencies","settlement_verified"],"properties":{"active_agreements":{"type":"integer","format":"int64","minimum":0},"as_of":{"$ref":"#/components/schemas/DateTime"},"currencies":{"type":"array","items":{"$ref":"#/components/schemas/CommercialCurrencyRevenue"},"description":"Independent currency totals. No exchange-rate conversion is implied."},"inactive_agreements":{"type":"integer","format":"int64","minimum":0},"settlement_verified":{"type":"boolean","description":"Manual acceptance currently has no verified receivable reconciliation."}}},"CommercialServiceRevision":{"type":"object","required":["id","publication","terms","marketing"],"properties":{"id":{"$ref":"#/components/schemas/Uuid"},"marketing":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/OfferCopy"}},"publication":{"$ref":"#/components/schemas/OfferPublication"},"terms":{"$ref":"#/components/schemas/CommercialServiceTerms"}}},"CommercialServiceTerms":{"type":"object","required":["code","revision","currency","from_price_cents"],"properties":{"code":{"type":"string"},"currency":{"type":"string"},"from_price_cents":{"type":"integer","format":"int64","description":"One-time starting estimate, excluding VAT. Final scope requires a quote."},"revision":{"type":"integer","format":"int32","minimum":0}}},"CommercialWorkspace":{"type":"object","required":["modular","has_access","families","enabled"],"properties":{"agreement_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"capacity":{"allOf":[{"$ref":"#/components/schemas/AgreementCapacity"}],"nullable":true},"commercial_interest":{"allOf":[{"$ref":"#/components/schemas/super.interest.CommercialInterest"}],"nullable":true},"enabled":{"type":"array","items":{"$ref":"#/components/schemas/Feature"},"description":"Complete explicit feature list for modular tenants; legacy clients retain\ntheir existing navigation until separately migrated."},"families":{"type":"array","items":{"$ref":"#/components/schemas/ProductFamily"}},"has_access":{"type":"boolean"},"modular":{"type":"boolean"}}},"CompanyAiCreditsResponse":{"type":"object","description":"A company's managed-AI position plus every pack ever granted to it.","required":["company_id","summary","topups"],"properties":{"company_id":{"type":"string"},"summary":{"$ref":"#/components/schemas/crate.features.billing.ai_credits.AiCreditSummary"},"topups":{"type":"array","items":{"$ref":"#/components/schemas/crate.features.billing.ai_credits.AiCreditTopup"},"description":"Every top-up ever granted, newest first (spent and expired included)."}}},"CompanySettings":{"type":"object","description":"Company configuration returned by the settings API and persisted for the tenant.","properties":{"address":{"type":"string","description":"Business address shown in company information.","nullable":true},"ai_automation":{"$ref":"#/components/schemas/AiAutomationSettings"},"billing":{"$ref":"#/components/schemas/BillingSettings"},"business_hours":{"type":"string","description":"Business opening hours as entered by an administrator.","nullable":true},"catalog_search":{"$ref":"#/components/schemas/crate.features.catalog_search.dto.CatalogSearchSettings"},"commercial_interest":{"allOf":[{"$ref":"#/components/schemas/crate.features.billing.interest.CommercialInterest"}],"nullable":true},"currency":{"type":"string","description":"NOTE: every non-Option field here MUST carry a `#[serde(default = ...)]`.\nStored `webapp_company.settings` blobs predate newer fields; a single\nmissing required key fails the WHOLE blob and silently resets EVERY\nsetting to defaults wherever the fallback pattern is used (e.g. the\nretention purge). Seen in prod 2026-07-23: legacy rows without\n`currency` reset all settings at deserialize time."},"currency_locale":{"type":"string","description":"Optional locale override for formatting monetary values.","nullable":true},"currency_symbol":{"type":"string","description":"Optional symbol override for displaying monetary values.","nullable":true},"date_format":{"type":"string","description":"Company preference for formatting dates."},"delivery_workflow":{"$ref":"#/components/schemas/DeliveryWorkflowSettings"},"diagnostics":{"$ref":"#/components/schemas/DiagnosticsSettings"},"digital_display":{"$ref":"#/components/schemas/DigitalDisplaySettings"},"email_notifications":{"type":"boolean","description":"Whether company email notifications are enabled."},"enrichment_worker":{"allOf":[{"$ref":"#/components/schemas/EnrichmentWorkerConfig"}],"nullable":true},"fulfillment_notifications":{"$ref":"#/components/schemas/FulfillmentNotificationSettings"},"horeca_ordering":{"$ref":"#/components/schemas/HorecaOrderingSettings"},"image_editor":{"$ref":"#/components/schemas/ImageEditorSettings"},"image_naming_patterns":{"type":"array","items":{"$ref":"#/components/schemas/ImageNamingPattern"},"description":"Saved filename matching patterns for image imports."},"import_settings":{"$ref":"#/components/schemas/ImportSettings"},"import_templates":{"$ref":"#/components/schemas/ImportTemplates"},"inventory_costing":{"$ref":"#/components/schemas/InventoryCostingSettings"},"inventory_counts":{"$ref":"#/components/schemas/InventoryCountSettings"},"inventory_reservation":{"$ref":"#/components/schemas/InventoryReservationSettings"},"invoice_settings":{"$ref":"#/components/schemas/InvoiceSettings"},"language":{"type":"string","description":"Preferred language for the company interface."},"live_sync":{"$ref":"#/components/schemas/super.live_sync.LiveSyncSettings"},"logo_url":{"type":"string","description":"URL of the company logo.","nullable":true},"low_stock_alerts":{"type":"boolean","description":"Whether low-stock notifications are enabled for the company."},"low_stock_threshold":{"type":"integer","format":"int32","description":"Stock quantity threshold used for low-stock notifications."},"mcp":{"$ref":"#/components/schemas/McpSettings"},"mcp_platform":{"allOf":[{"$ref":"#/components/schemas/McpPlatformConfig"}],"nullable":true},"navigation":{"$ref":"#/components/schemas/NavigationSettings"},"notification_emails":{"type":"string","description":"Company notification recipients; normalized by the settings repository.","nullable":true},"offline_images":{"$ref":"#/components/schemas/OfflineImagesSettings"},"onboarding":{"$ref":"#/components/schemas/OnboardingSettings"},"order_rules":{"$ref":"#/components/schemas/OrderRulesSettings"},"order_sync":{"$ref":"#/components/schemas/OrderSyncSettings"},"phone":{"type":"string","description":"Business contact telephone number.","nullable":true},"pipeline_limits":{"$ref":"#/components/schemas/PipelineLimitsSettings"},"portal_design":{"$ref":"#/components/schemas/PortalDesignSettings"},"pos_cash":{"$ref":"#/components/schemas/PosCashSettings"},"pos_catalogue":{"$ref":"#/components/schemas/PosCatalogueSettings"},"pos_lists":{"$ref":"#/components/schemas/PosListsSettings"},"pos_pricing":{"$ref":"#/components/schemas/PosPricingSettings"},"pos_reports":{"$ref":"#/components/schemas/PosReportsSettings"},"pos_security":{"$ref":"#/components/schemas/PosSecuritySettings"},"pos_shifts":{"$ref":"#/components/schemas/PosShiftSettings"},"pos_terminal_ui":{"$ref":"#/components/schemas/PosTerminalUiSettings"},"purchasing":{"$ref":"#/components/schemas/PurchasingSettings"},"qbpos_platform":{"allOf":[{"$ref":"#/components/schemas/QbposPlatformConfig"}],"nullable":true},"query":{"$ref":"#/components/schemas/QuerySettings"},"quickbooks_pos":{"$ref":"#/components/schemas/QuickBooksPosSettings"},"receipt_settings":{"$ref":"#/components/schemas/ReceiptSettings"},"receiving":{"$ref":"#/components/schemas/ReceivingSettings"},"reporting":{"$ref":"#/components/schemas/ReportingSettings"},"sales_order_email":{"$ref":"#/components/schemas/SalesOrderEmailSettings"},"security_policy":{"$ref":"#/components/schemas/SecurityPolicySettings"},"sms_notifications":{"type":"boolean","description":"Whether company SMS notifications are enabled."},"storefront":{"$ref":"#/components/schemas/StorefrontSettings"},"tax_rate":{"type":"number","format":"double","description":"Company default tax rate.","nullable":true},"time_format":{"type":"string","description":"Company preference for formatting times."},"timezone":{"type":"string","description":"Company timezone used when interpreting and displaying local times."}}},"CompletePickingRequest":{"type":"object","properties":{"notes":{"type":"string","nullable":true}}},"CompletenessWeights":{"type":"object","description":"Weights for the per-product completeness score. Every field carries its own\nserde default (see the `ImportSettings` NOTE above: group-level default only\nfires when the whole object is absent).","properties":{"attributes":{"type":"integer","format":"int32","description":"Weight assigned to product attributes in the product completeness score."},"category":{"type":"integer","format":"int32","description":"Weight assigned to category assignment in the product completeness score."},"description":{"type":"integer","format":"int32","description":"Weight assigned to the description in the product completeness score."},"enrichment":{"type":"integer","format":"int32","description":"Weight assigned to enrichment in the product completeness score."},"images":{"type":"integer","format":"int32","description":"Weight assigned to product images in the product completeness score."},"translations":{"type":"integer","format":"int32","description":"Weight assigned to translations in the product completeness score."}}},"ConfirmPublishRequest":{"type":"object","description":"The body of a publish confirmation.","required":["token"],"properties":{"token":{"type":"string","description":"The token from the emailed link."}}},"ConnectMailboxRequest":{"type":"object","required":["provider","email_address"],"properties":{"display_name":{"type":"string","nullable":true},"email_address":{"type":"string"},"imap_config":{"allOf":[{"$ref":"#/components/schemas/ImapConnectionInput"}],"nullable":true},"oauth_code":{"type":"string","description":"OAuth authorization code — gmail/outlook (not yet available, A8b).","nullable":true},"provider":{"$ref":"#/components/schemas/MailboxProvider"},"shared":{"type":"boolean"},"sync_direction":{"type":"string","description":"`read_only` (default) | `two_way`","nullable":true}}},"ContactConsentEntry":{"type":"object","description":"One consent state row: (channel, purpose) → status.","required":["channel","purpose","status"],"properties":{"channel":{"type":"string","description":"email | sms | phone."},"purpose":{"type":"string","description":"marketing | transactional | support."},"status":{"type":"string","description":"granted | revoked | `never_granted`."}}},"ContactConsentsResponse":{"type":"object","required":["contact_id","consents"],"properties":{"consents":{"type":"array","items":{"$ref":"#/components/schemas/ContactConsentEntry"}},"contact_id":{"$ref":"#/components/schemas/Uuid"}}},"ContactDetailResponse":{"type":"object","required":["id","company_id","first_name","last_name","source","created_at","updated_at"],"properties":{"company_id":{"type":"string"},"created_at":{"type":"string"},"custom_fields":{"type":"object","description":"`field_definition.code` → typed value, assembled server-side (A2).\nAlways present on the wire; typed Option only so the `OpenAPI` schema\nstays additive for clients generated before A2.","additionalProperties":{},"nullable":true},"first_name":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"job_title":{"type":"string","nullable":true},"last_name":{"type":"string"},"lifecycle_stage_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"merged_into_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"organization_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"owner_user_id":{"type":"integer","format":"int32","nullable":true},"portal_user_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"primary_email":{"type":"string","nullable":true},"primary_phone":{"type":"string","nullable":true},"source":{"type":"string"},"updated_at":{"type":"string"}}},"ContactImportReportResponse":{"type":"object","required":["id","status","total_rows","imported","skipped_duplicates","errored","errors","created_at","updated_at"],"properties":{"created_at":{"type":"string"},"errored":{"type":"integer","format":"int32"},"errors":{"description":"First 100 row-level errors: [{row, message}]. Counters keep\ncounting past the cap."},"id":{"$ref":"#/components/schemas/Uuid"},"imported":{"type":"integer","format":"int32"},"skipped_duplicates":{"type":"integer","format":"int32"},"status":{"type":"string","description":"queued | running | completed | failed"},"total_rows":{"type":"integer","format":"int32"},"updated_at":{"type":"string"}}},"ContactListItem":{"type":"object","required":["id","first_name","last_name","created_at","updated_at"],"properties":{"created_at":{"type":"string"},"first_name":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"job_title":{"type":"string","nullable":true},"last_activity_at":{"type":"string","description":"Most recent `crm_activities.occurred_at` for this contact; null until\nthe activity timeline (A2) starts writing rows.","nullable":true},"last_name":{"type":"string"},"lifecycle_stage_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"organization_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"organization_name":{"type":"string","nullable":true},"owner_user_id":{"type":"integer","format":"int32","nullable":true},"primary_email":{"type":"string","nullable":true},"updated_at":{"type":"string"}}},"ContactListResponse":{"type":"object","required":["items","total_count","page","page_size"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ContactListItem"}},"page":{"type":"integer","format":"int64"},"page_size":{"type":"integer","format":"int64"},"total_count":{"type":"integer","format":"int64"}}},"ContractRow":{"type":"object","required":["id","company_id","status","currency","billing_interval","starts_at","auto_renew","created_at","updated_at"],"properties":{"auto_renew":{"type":"boolean"},"billing_interval":{"type":"string"},"company_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/chrono.DateTime"},"currency":{"type":"string"},"ends_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"notes":{"type":"string","nullable":true},"payment_method":{"type":"string","nullable":true},"plan_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"price_cents":{"type":"integer","format":"int32","nullable":true},"starts_at":{"$ref":"#/components/schemas/chrono.DateTime"},"status":{"type":"string"},"updated_at":{"$ref":"#/components/schemas/chrono.DateTime"}}},"ConvertQuoteResponse":{"type":"object","description":"Response returned by POST /api/quotes/{id}/convert.","required":["id","status","converted_sales_order_id","sales_order_id"],"properties":{"converted_sales_order_id":{"type":"string"},"id":{"type":"string"},"sales_order_id":{"type":"string","description":"Alias of `converted_sales_order_id` for convenience."},"status":{"type":"string"}}},"CreateAccessTokenRequest":{"type":"object","required":["label","permissions"],"properties":{"expires_at":{"type":"string","nullable":true},"label":{"type":"string"},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"}}}},"CreateAccessTokenResponse":{"type":"object","required":["message","token","access_token"],"properties":{"access_token":{"$ref":"#/components/schemas/AccessTokenListItem"},"message":{"type":"string"},"token":{"type":"string"}}},"CreateAgreementRevision":{"type":"object","required":["company_id","selections","interval","starts_at","ends_at","agreement_reference","idempotency_key"],"properties":{"agreement_reference":{"type":"string","description":"A durable reference to the agreed quote/invoice. Never a payment claim."},"company_id":{"type":"string"},"ends_at":{"$ref":"#/components/schemas/DateTime"},"idempotency_key":{"$ref":"#/components/schemas/Uuid"},"interval":{"$ref":"#/components/schemas/OfferInterval"},"selections":{"type":"array","items":{"$ref":"#/components/schemas/OfferSelection"}},"starts_at":{"$ref":"#/components/schemas/DateTime"}}},"CreateAiCreditTopupRequest":{"type":"object","required":["credits"],"properties":{"credits":{"type":"integer","format":"int32","description":"Credits to grant, `1..=1_000_000`."},"expires_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"note":{"type":"string","description":"Free-text reason (invoice number, deal, goodwill …).","nullable":true}}},"CreateBackupRequest":{"type":"object","required":["backup_type"],"properties":{"backup_type":{"type":"string"}}},"CreateCaseCommentRequest":{"type":"object","required":["body"],"properties":{"body":{"type":"string"},"is_internal":{"type":"boolean","description":"Internal notes are invisible to any future portal-facing case view."}}},"CreateCaseRequest":{"type":"object","required":["subject","priority"],"properties":{"assigned_to_user_id":{"type":"integer","format":"int32","nullable":true},"contact_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"organization_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"priority":{"$ref":"#/components/schemas/CasePriority"},"subject":{"type":"string"}}},"CreateChannelOrderInput":{"type":"object","description":"Shared body shape for both QR (`POST /api/horeca-public/t/{table_token}/orders`)\nand kiosk (`POST /api/horeca/kiosk/orders`) order creation.","required":["idempotency_key"],"properties":{"customer_name":{"type":"string","nullable":true},"customer_phone":{"type":"string","nullable":true},"idempotency_key":{"type":"string","description":"Client-generated UUID. A retried/duplicated POST with the same key\nreturns the original order instead of creating a second one — see\n`public_handlers`/`kiosk` module docs for exactly how this is\nenforced."},"items":{"type":"array","items":{"$ref":"#/components/schemas/CreateChannelOrderItemInput"}},"order_type":{"type":"string","description":"kiosk only: `dine_in` | takeaway | pickup. QR orders are always\n`dine_in` (the table is the whole point of scanning its code) and\nignore this field.","nullable":true},"table_tent_number":{"type":"string","description":"Staff-assigned table-tent / pager number handed to a kiosk\ntakeaway/pickup customer with no floor-plan table (Horeca Ordering\nSuite wave B4). Stored on `open_orders.table_tent_number` and echoed\nback in `PublicOrderStatusResponse`/the KDS DTOs so staff/kitchen can\ncall the right number. QR orders already have a real table\n(`table_object_id`), so this is realistically kiosk-only, but nothing\nrejects it on the QR path — it's just an optional passthrough field.","nullable":true}}},"CreateChannelOrderItemInput":{"type":"object","required":["product_id","qty"],"properties":{"modifiers_snapshot":{"nullable":true},"note":{"type":"string","nullable":true},"product_id":{"type":"string"},"qty":{"type":"integer","format":"int32"}}},"CreateChannelOrderResponse":{"type":"object","required":["open_order_id","public_token","status"],"properties":{"open_order_id":{"type":"string"},"order_number":{"type":"string","nullable":true},"public_token":{"$ref":"#/components/schemas/uuid.Uuid"},"status":{"type":"string"}}},"CreateCheckoutRequest":{"type":"object","description":"Request to POST /api/billing/checkout","required":["price_key"],"properties":{"cancel_url":{"type":"string","description":"Where to redirect on cancel","nullable":true},"price_key":{"type":"string","description":"\"`starter_monthly`\" | \"`growth_monthly`\" | \"`scale_monthly`\"\n| \"`starter_annual`\" | \"`growth_annual`\" | \"`scale_annual`\""},"success_url":{"type":"string","description":"Where to redirect after successful checkout","nullable":true}}},"CreateCollectionAssignmentRequest":{"type":"object","properties":{"account_id":{"type":"string","nullable":true},"ends_at":{"type":"string","nullable":true},"group_id":{"type":"string","nullable":true},"starts_at":{"type":"string","nullable":true},"visibility_mode":{"type":"string","nullable":true}}},"CreateCommercialService":{"type":"object","required":["terms","marketing"],"properties":{"marketing":{"type":"object","description":"Scope and exclusions are editable in all supported customer languages.","additionalProperties":{"$ref":"#/components/schemas/OfferCopy"}},"terms":{"$ref":"#/components/schemas/CommercialServiceTerms"}}},"CreateContactRequest":{"type":"object","required":["first_name","last_name"],"properties":{"first_name":{"type":"string"},"job_title":{"type":"string","nullable":true},"last_name":{"type":"string"},"lifecycle_stage_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"organization_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"owner_user_id":{"type":"integer","format":"int32","nullable":true},"primary_email":{"type":"string","nullable":true},"primary_phone":{"type":"string","nullable":true},"source":{"$ref":"#/components/schemas/CrmRecordSource"}}},"CreateContractRequest":{"type":"object","required":["company_id"],"properties":{"auto_renew":{"type":"boolean","nullable":true},"billing_interval":{"type":"string","nullable":true},"company_id":{"type":"string"},"currency":{"type":"string","nullable":true},"ends_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"notes":{"type":"string","nullable":true},"payment_method":{"type":"string","description":"`stripe_card` | `stripe_sepa` | `bank_transfer` | `manual` | `comped` | `external`.","nullable":true},"plan_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"price_cents":{"type":"integer","format":"int32","description":"`None` = inherit the plan's price. `0` is valid and means free — the two\nare different and must stay distinguishable.","nullable":true},"starts_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"status":{"type":"string","description":"`draft` | `active` | `suspended` | `ended`. Defaults to `draft` so a\nhalf-filled contract cannot accidentally grant access the moment it is\ncreated.","nullable":true}}},"CreateCustomerReturnLineRequest":{"type":"object","required":["original_sales_receipt_item_id","quantity","unit_refund","disposition","reason_code"],"properties":{"disposition":{"type":"string"},"notes":{"type":"string","nullable":true},"original_sales_receipt_item_id":{"type":"string"},"quantity":{"type":"integer","format":"int32"},"reason_code":{"type":"string"},"unit_refund":{"type":"string"}}},"CreateCustomerReturnRequest":{"type":"object","required":["return_number","original_sales_receipt_id","location_id","refund_method","lines"],"properties":{"lines":{"type":"array","items":{"$ref":"#/components/schemas/CreateCustomerReturnLineRequest"}},"location_id":{"type":"string"},"original_sales_receipt_id":{"type":"string"},"reason":{"type":"string","nullable":true},"refund_method":{"type":"string"},"return_number":{"type":"string"}}},"CreateDealRequest":{"type":"object","required":["name","pipeline_id","stage_id"],"properties":{"amount":{"type":"string","description":"Decimal string or number; must be >= 0.","nullable":true},"currency":{"type":"string","description":"ISO 4217 (3 letters). Falls back to unset when absent.","nullable":true},"expected_close_date":{"type":"string","description":"ISO 8601 date (YYYY-MM-DD).","nullable":true},"name":{"type":"string"},"organization_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"owner_user_id":{"type":"integer","format":"int32","nullable":true},"pipeline_id":{"$ref":"#/components/schemas/Uuid"},"primary_contact_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"stage_id":{"$ref":"#/components/schemas/Uuid"}}},"CreateDocumentFromOrderRequest":{"type":"object","description":"Body of `POST /fulfillment/orders/{order_id}/documents` — the derived\ncreate flow (E-5.2). Totals are NEVER taken from the request: they are\ncomputed server-side from the order's line-item price snapshots. The\n`subtotal`/`tax_amount`/`total_amount` fields below exist only so a naive\nclient that still sends them doesn't hard-fail deserialization — any\nvalue present here is logged with `warn!` and then discarded.","required":["document_type"],"properties":{"document_type":{"type":"string","description":"One of `invoice` | `delivery_note` | `credit_note`."},"due_date":{"type":"string","description":"\"YYYY-MM-DD\"","nullable":true},"notes":{"type":"string","nullable":true},"parent_invoice_id":{"type":"string","description":"Required, and only meaningful, when `document_type == \"credit_note\"`:\nthe id of the invoice document this credit note corrects.","nullable":true},"subtotal":{"description":"Ignored — see struct doc. Present only to detect + warn on a client\nthat still sends client-supplied totals.","nullable":true},"tax_amount":{"nullable":true},"total_amount":{"nullable":true}}},"CreateFlagRequest":{"type":"object","required":["flag_key"],"properties":{"description":{"type":"string","nullable":true},"enabled":{"type":"boolean","nullable":true},"flag_key":{"type":"string"}}},"CreateFloorPlanInput":{"type":"object","required":["name"],"properties":{"background_image_url":{"type":"string","nullable":true},"height_px":{"type":"integer","format":"int32","nullable":true},"name":{"type":"string"},"objects":{"type":"array","items":{"$ref":"#/components/schemas/CreateFloorPlanObjectInput"}},"room_label":{"type":"string","nullable":true},"width_px":{"type":"integer","format":"int32","nullable":true}}},"CreateFloorPlanObjectInput":{"type":"object","required":["x","y"],"properties":{"color":{"type":"string","nullable":true},"height":{"type":"integer","format":"int32","nullable":true},"id":{"type":"string","nullable":true},"label":{"type":"string","nullable":true},"object_type":{"type":"string","nullable":true},"rotation":{"type":"integer","format":"int32","nullable":true},"seats":{"type":"integer","format":"int32","nullable":true},"width":{"type":"integer","format":"int32","nullable":true},"x":{"type":"integer","format":"int32"},"y":{"type":"integer","format":"int32"}}},"CreateGoodsReceiptLineRequest":{"type":"object","required":["purchase_order_line_id","accepted_qty","unit_cost"],"properties":{"accepted_qty":{"type":"string"},"damaged_qty":{"type":"string"},"discrepancy_reason":{"type":"string","nullable":true},"expires_at":{"type":"string","nullable":true},"lot_number":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"purchase_order_line_id":{"$ref":"#/components/schemas/Uuid"},"quarantine_qty":{"type":"string"},"rejected_qty":{"type":"string"},"serial_numbers":{},"tax_amount":{"type":"string"},"unit_cost":{"type":"string"}}},"CreateGoodsReceiptRequest":{"type":"object","required":["goods_receipt_number","purchase_order_id","lines"],"properties":{"freight_total":{"type":"string"},"goods_receipt_number":{"type":"string"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/CreateGoodsReceiptLineRequest"}},"notes":{"type":"string","nullable":true},"purchase_order_id":{"$ref":"#/components/schemas/Uuid"},"received_at":{"type":"string","nullable":true},"supplier_document_number":{"type":"string","nullable":true}}},"CreateHelpArticleRequest":{"type":"object","required":["slug","title","content","category"],"properties":{"app_target":{"type":"string","nullable":true},"category":{"type":"string"},"content":{"type":"string"},"is_published":{"type":"boolean","nullable":true},"slug":{"type":"string"},"sort_order":{"type":"integer","format":"int32","nullable":true},"title":{"type":"string"}}},"CreateImageDocumentRequest":{"type":"object","description":"Request body for `POST /api/image-documents`.","required":["scene_json","source_refs"],"properties":{"name":{"type":"string","description":"Defaults to \"\" when omitted, matching the column default — the\nstandalone studio may not have named the document yet.","nullable":true},"output_image_id":{"type":"string","description":"Optional — set once the scene has been rendered back to the gallery.","nullable":true},"product_id":{"type":"string","description":"Optional — a standalone studio document belongs to no product.","nullable":true},"scene_json":{"type":"object","description":"fabric.js scene serialization. Required: an editor document without a\nscene has nothing to re-open."},"source_refs":{"type":"object","description":"Defaults to an empty array when omitted."}}},"CreateInternalUserRequest":{"type":"object","required":["username","email","first_name","last_name","company_id","role"],"properties":{"company_id":{"type":"string"},"email":{"type":"string"},"first_name":{"type":"string"},"is_active":{"type":"boolean","nullable":true},"is_platform_admin":{"type":"boolean","description":"Grant platform (internal-admin) access. Defaults to false.","nullable":true},"last_name":{"type":"string"},"password":{"type":"string","nullable":true},"role":{"$ref":"#/components/schemas/UserRole"},"send_welcome_email":{"type":"boolean","nullable":true},"username":{"type":"string"}}},"CreateInternalUserResponse":{"type":"object","required":["user","temporary_password","email_sent","message"],"properties":{"email_sent":{"type":"boolean"},"message":{"type":"string"},"temporary_password":{"type":"string"},"user":{"$ref":"#/components/schemas/AdminUser"}}},"CreateInventoryCountRequest":{"type":"object","required":["location_id"],"properties":{"idempotency_key":{"type":"string","description":"Stable terminal-generated retry key. Reusing the key returns the\noriginal session instead of duplicating the physical count.","nullable":true},"location_id":{"type":"string"},"notes":{"type":"string","nullable":true},"product_ids":{"type":"array","items":{"type":"string"},"description":"Optional product scope for cycle counts. When omitted, the session\nsnapshots the complete active catalogue at the selected location.","nullable":true}}},"CreateLookbookRequest":{"type":"object","required":["name"],"properties":{"cover_image_url":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"ends_at":{"type":"string","nullable":true},"is_active":{"type":"boolean","nullable":true},"name":{"type":"string"},"position":{"type":"integer","format":"int32","nullable":true},"starts_at":{"type":"string","nullable":true},"subtitle":{"type":"string","nullable":true}}},"CreateMarketingImageRequest":{"type":"object","required":["image_url"],"properties":{"image_url":{"type":"string"},"order_index":{"type":"integer","format":"int32","nullable":true},"title":{"type":"string","nullable":true}}},"CreateModifierGroupInput":{"type":"object","required":["name"],"properties":{"is_required":{"type":"boolean","nullable":true},"max_selections":{"type":"integer","format":"int32","nullable":true},"min_selections":{"type":"integer","format":"int32","nullable":true},"name":{"type":"string"},"options":{"type":"array","items":{"$ref":"#/components/schemas/CreateModifierOptionInput"}},"selection_type":{"type":"string","nullable":true}}},"CreateModifierOptionInput":{"type":"object","required":["name"],"properties":{"is_default":{"type":"boolean","nullable":true},"name":{"type":"string"},"price_adjustment":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"sort_order":{"type":"integer","format":"int32","nullable":true}}},"CreateModularCheckout":{"type":"object","required":["selections","interval","idempotency_key"],"properties":{"idempotency_key":{"$ref":"#/components/schemas/Uuid"},"interval":{"$ref":"#/components/schemas/OfferInterval"},"selections":{"type":"array","items":{"$ref":"#/components/schemas/OfferSelection"}}}},"CreateOfferRevision":{"type":"object","required":["terms","manifest","marketing"],"properties":{"manifest":{"$ref":"#/components/schemas/OfferManifest"},"marketing":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/OfferCopy"}},"plan_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"provider_prices":{"type":"object","additionalProperties":{"type":"string"}},"terms":{"$ref":"#/components/schemas/OfferTerms"}}},"CreateOpenOrderInput":{"type":"object","description":"Body for `POST /api/horeca/open-orders`.\n\nEvery field except `table_session_id` is additive (POS-ship workstream E)\nand optional: an old client sending `{\"table_session_id\": \"...\"}` — or\n`{}` — still behaves exactly as before, because each new field falls back\nto the same default the handler hardcoded previously.\n\nThe extra fields are what makes a **takeaway / pickup** order creatable\nfrom the till at all: before them the handler always wrote\n`order_type = dine_in` with no customer name and no tent number, so the\nonly order a POS could open was a dine-in tab bound to a table (audit 05\ngap (e)).","properties":{"customer_name":{"type":"string","nullable":true},"customer_phone":{"type":"string","nullable":true},"order_type":{"type":"string","description":"`dine_in` (default) | `takeaway` | `pickup`. Unrecognised values are\nrejected with 422 rather than silently coerced — see\n`crate::features::horeca::status::OrderType`.","nullable":true},"table_session_id":{"type":"string","nullable":true},"table_tent_number":{"type":"string","description":"Staff-assigned table-tent / pager number for an order with no\nfloor-plan table.","nullable":true}}},"CreateOrderResponseDto":{"type":"object","description":"Response after successfully creating an order — POST /api/portal/orders","required":["message","order_id","total"],"properties":{"message":{"type":"string"},"order_id":{"type":"string"},"order_number":{"type":"string","description":"Human-readable sequential order number, if the company has numbering\nconfigured. Additive field — absent on responses from older backends.","nullable":true},"total":{"type":"number","format":"double"}}},"CreateOrganizationRequest":{"type":"object","required":["name"],"properties":{"domain":{"type":"string","nullable":true},"industry_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"lifecycle_stage_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"linked_account_id":{"type":"string","description":"Link to an existing customer account (`webapp_account.id`).\nNULL = prospect.","nullable":true},"name":{"type":"string"},"owner_user_id":{"type":"integer","format":"int32","nullable":true},"size_range":{"type":"string","description":"One of `1-10 | 11-50 | 51-200 | 201-1000 | 1000+`.","nullable":true},"source":{"$ref":"#/components/schemas/CrmRecordSource"}}},"CreatePipelineRequest":{"type":"object","required":["name"],"properties":{"description":{"type":"string","nullable":true},"name":{"type":"string"},"trigger_event_type":{"type":"string","description":"One of `models::domain_event::domain_event_type`, or omitted for a\nmanual-only pipeline.","nullable":true}}},"CreatePipelineVersionRequest":{"type":"object","description":"`POST /api/pipelines/{id}/versions` body. `graph` is validated server-side\n(acyclic, known node kinds, declared ports, RBAC) before it is ever stored\n— see `handlers::validate_graph`.","required":["graph"],"properties":{"graph":{}}},"CreatePlanRequest":{"type":"object","required":["code","name"],"properties":{"billing_interval":{"type":"string","nullable":true},"code":{"type":"string"},"contact_sales":{"type":"boolean","nullable":true},"currency":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"extra_ai_credits_price_cents":{"type":"integer","format":"int32","description":"Price of a 1,000-credit top-up in cents. Defaults to 0 (not sold).","nullable":true},"extra_rep_device_price_cents":{"type":"integer","format":"int32","nullable":true},"extra_seat_price_cents":{"type":"integer","format":"int32","nullable":true},"features":{"type":"object","nullable":true},"included_ai_credits":{"type":"integer","format":"int32","description":"AI credits included per calendar month. Defaults to 0.","nullable":true},"included_rep_devices":{"type":"integer","format":"int32","nullable":true},"included_seats":{"type":"integer","format":"int32","nullable":true},"is_active":{"type":"boolean","nullable":true},"is_highlighted":{"type":"boolean","nullable":true},"is_public":{"type":"boolean","nullable":true},"is_trial_default":{"type":"boolean","nullable":true},"marketing":{"type":"object","description":"Per-locale display copy; must be a JSON object. Defaults to `{}`.","nullable":true},"name":{"type":"string"},"price_cents":{"type":"integer","format":"int32","nullable":true},"price_cents_annual":{"type":"integer","format":"int32","description":"TOTAL per YEAR in cents. Absent/null = annual billing not offered.","nullable":true},"sku_limit":{"type":"integer","format":"int32","description":"Absent/null = unlimited.","nullable":true},"sort_order":{"type":"integer","format":"int32","nullable":true},"stripe_price_id_annual":{"type":"string","description":"Stripe Price ID for the annual interval. Empty/whitespace is stored as\nNULL (= \"not configured, use the env fallback\").","nullable":true},"stripe_price_id_monthly":{"type":"string","description":"Stripe Price ID for the monthly interval. Empty/whitespace is stored as\nNULL (= \"not configured, use the env fallback\").","nullable":true},"trial_days":{"type":"integer","format":"int32","description":"Defaults to `billing::services::DEFAULT_TRIAL_DAYS`.","nullable":true}}},"CreatePortalOrderRequest":{"type":"object","required":["items"],"properties":{"client_order_id":{"type":"string","description":"Client-generated id making the create idempotent. A portal client that\nloses the response on a timeout can safely retry: the second call\nreturns the order created by the first instead of duplicating it.\nAdditive/optional — omit for one-shot calls.","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/OrderItemRequest"}},"note":{"type":"string","nullable":true}}},"CreatePortalReturnRequest":{"type":"object","description":"Create a return request — POST /api/portal/returns","required":["order_id","items"],"properties":{"customer_note":{"type":"string","description":"Free-text note from the customer","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/PortalReturnLineRequest"}},"order_id":{"type":"string","description":"The caller's own order the return is raised against"},"reason":{"type":"string","description":"Overall reason (max 50 chars); defaults to \"other\"","nullable":true}}},"CreatePortalUserRequest":{"type":"object","description":"Create portal user request (admin-only)","required":["email","password","first_name","last_name","account_id"],"properties":{"account_id":{"type":"string"},"email":{"type":"string"},"first_name":{"type":"string"},"is_active":{"type":"boolean","description":"Whether the account is active on creation (defaults to true)","nullable":true},"last_name":{"type":"string"},"password":{"type":"string"},"username":{"type":"string","description":"Unique username for login (lowercase, no spaces)","nullable":true}}},"CreatePrinterProfileInput":{"type":"object","required":["name","printer_type","connection_type"],"properties":{"connection_config":{"nullable":true},"connection_type":{"type":"string"},"id":{"type":"string","description":"Existing profile id, when the client is editing rather than creating.\n\nAdditive and optional: the deployed admin does not send it, and a payload\nwithout it still works exactly as before (matched by name). Sending it is\nwhat lets a profile be RENAMED without being treated as a delete plus an\ninsert — which used to change its id and orphan everything pointing at it.","nullable":true},"is_default":{"type":"boolean","nullable":true},"name":{"type":"string"},"printer_type":{"type":"string"}}},"CreateProductCollectionRequest":{"type":"object","required":["name"],"properties":{"description":{"type":"string","nullable":true},"ends_at":{"type":"string","nullable":true},"is_active":{"type":"boolean","nullable":true},"name":{"type":"string"},"starts_at":{"type":"string","nullable":true}}},"CreateProductVariantRequest":{"type":"object","description":"Body of `POST /api/products/{id}/variants`.","required":["name"],"properties":{"attributes":{"description":"Free-form attribute map, e.g. `{\"size\": \"M\", \"colour\": \"Blue\"}`.","nullable":true},"cost_price":{"type":"string","nullable":true},"name":{"type":"string","description":"Shopper-facing label, e.g. \"M / Blue\"."},"price":{"type":"string","description":"Retail price for this variant (decimal string); omit = product price.","nullable":true},"sku":{"type":"string","description":"Variant SKU (unique per company is recommended, not enforced).","nullable":true}}},"CreatePurchaseOrderLineRequest":{"type":"object","required":["product_id","description","purchase_uom","units_per_purchase","ordered_qty","unit_cost"],"properties":{"description":{"type":"string"},"discount_amount":{"type":"string"},"expected_date":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"ordered_qty":{"type":"string"},"product_id":{"type":"string"},"purchase_uom":{"type":"string"},"supplier_product_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"supplier_sku":{"type":"string","nullable":true},"tax_amount":{"type":"string"},"unit_cost":{"type":"string"},"units_per_purchase":{"type":"string"},"variant_id":{"type":"string","nullable":true}}},"CreatePurchaseOrderRequest":{"type":"object","required":["purchase_order_number","supplier_id","location_id","currency_code","order_date","lines"],"properties":{"currency_code":{"type":"string"},"expected_date":{"type":"string","nullable":true},"freight_total":{"type":"string"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/CreatePurchaseOrderLineRequest"}},"location_id":{"type":"string"},"notes":{"type":"string","nullable":true},"order_date":{"type":"string"},"purchase_order_number":{"type":"string"},"supplier_id":{"$ref":"#/components/schemas/Uuid"},"supplier_reference":{"type":"string","nullable":true}}},"CreateQuoteRequest":{"type":"object","description":"Create quote request. Sent by the \"Save as Quote\" action.","required":["account_id","items"],"properties":{"account_id":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/QuoteItemInput"}},"note":{"type":"string","nullable":true}}},"CreateSalesOrderRequest":{"type":"object","description":"Create sales order request","required":["account_id","items"],"properties":{"account_id":{"type":"string"},"client_order_id":{"type":"string","description":"Client-generated id making the create idempotent. An offline client that\nloses the response can safely retry: the second call returns the order\ncreated by the first instead of duplicating it. Omit for one-shot calls.","nullable":true},"date":{"allOf":[{"$ref":"#/components/schemas/NaiveDateTime"}],"nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/SalesOrderItemInput"}},"note":{"type":"string","nullable":true}}},"CreateSalesOrderResponse":{"type":"object","description":"Create sales order response","required":["id","status","created_at"],"properties":{"created_at":{"type":"string"},"id":{"type":"string"},"status":{"type":"string"}}},"CreateSavedListRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}},"CreateStockTransferRequest":{"type":"object","required":["transfer_number","from_location_id","to_location_id","product_id","quantity"],"properties":{"from_location_id":{"type":"string"},"notes":{"type":"string","nullable":true},"product_id":{"type":"string"},"quantity":{"type":"integer","format":"int32"},"to_location_id":{"type":"string"},"transfer_number":{"type":"string"},"variant_id":{"type":"string","nullable":true}}},"CreateSupplierBillLineRequest":{"type":"object","description":"One line of a new supplier bill.","required":["quantity","unit_cost"],"properties":{"description":{"type":"string","description":"Defaults to the purchase-order line's description.","nullable":true},"product_id":{"type":"string","description":"Product billed; defaults to the purchase-order line's product.","nullable":true},"purchase_order_line_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"quantity":{"type":"string","description":"Quantity billed, in the purchase unit of the order line."},"tax_amount":{"type":"string"},"unit_cost":{"type":"string","description":"Unit cost billed, excluding tax."},"variant_id":{"type":"string","description":"Variant billed; defaults to the purchase-order line's variant.","nullable":true}}},"CreateSupplierBillRequest":{"type":"object","description":"A supplier invoice to enter as a draft bill.","required":["supplier_id","supplier_invoice_number","bill_date","currency_code","lines"],"properties":{"bill_date":{"type":"string","description":"`YYYY-MM-DD`."},"currency_code":{"type":"string"},"due_date":{"type":"string","description":"`YYYY-MM-DD`; defaults to the bill date plus the supplier's payment terms.","nullable":true},"freight_total":{"type":"string"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/CreateSupplierBillLineRequest"}},"notes":{"type":"string","nullable":true},"purchase_order_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"supplier_id":{"$ref":"#/components/schemas/Uuid"},"supplier_invoice_number":{"type":"string","description":"The supplier's own invoice number; unique per supplier among live bills."}}},"CreateSupplierPaymentRequest":{"type":"object","required":["supplier_id","payment_date","method","currency_code","amount"],"properties":{"allocations":{"type":"array","items":{"$ref":"#/components/schemas/SupplierPaymentAllocationRequest"},"description":"Bills this payment settles. Whatever is not allocated stays on account."},"amount":{"type":"string"},"currency_code":{"type":"string"},"method":{"type":"string","description":"One of the company's `purchasing.supplier_payment_methods`."},"notes":{"type":"string","nullable":true},"payment_date":{"type":"string","description":"`YYYY-MM-DD`."},"reference":{"type":"string","nullable":true},"supplier_id":{"$ref":"#/components/schemas/Uuid"}}},"CreateSupplierProductRequest":{"type":"object","required":["product_id","purchase_uom","units_per_purchase"],"properties":{"effective_from":{"type":"string","nullable":true},"effective_to":{"type":"string","nullable":true},"is_preferred":{"type":"boolean"},"lead_time_days":{"type":"integer","format":"int32","nullable":true},"minimum_order_qty":{"type":"string","nullable":true},"order_multiple":{"type":"string","nullable":true},"product_id":{"type":"string"},"purchase_uom":{"type":"string"},"quoted_unit_cost":{"type":"string","nullable":true},"supplier_barcode":{"type":"string","nullable":true},"supplier_description":{"type":"string","nullable":true},"supplier_sku":{"type":"string","nullable":true},"units_per_purchase":{"type":"string"},"variant_id":{"type":"string","nullable":true}}},"CreateSupplierRequest":{"type":"object","required":["supplier_number","name","currency_code","payment_terms_days"],"properties":{"account_number":{"type":"string","nullable":true},"accounting_mapping":{},"billing_address":{"nullable":true},"contacts":{},"currency_code":{"type":"string"},"email":{"type":"string","nullable":true},"lead_time_days":{"type":"integer","format":"int32","nullable":true},"legal_name":{"type":"string","nullable":true},"name":{"type":"string"},"notes":{"type":"string","nullable":true},"payment_terms_days":{"type":"integer","format":"int32"},"phone":{"type":"string","nullable":true},"shipping_address":{"nullable":true},"supplier_number":{"type":"string"},"tax_identifier":{"type":"string","nullable":true}}},"CreateSupplierReturnLineRequest":{"type":"object","required":["goods_receipt_line_id","quantity","unit_cost","reason_code"],"properties":{"goods_receipt_line_id":{"$ref":"#/components/schemas/Uuid"},"notes":{"type":"string","nullable":true},"quantity":{"type":"string"},"reason_code":{"type":"string"},"unit_cost":{"type":"string"}}},"CreateSupplierReturnRequest":{"type":"object","required":["supplier_return_number","supplier_id","location_id","lines"],"properties":{"lines":{"type":"array","items":{"$ref":"#/components/schemas/CreateSupplierReturnLineRequest"}},"location_id":{"type":"string"},"notes":{"type":"string","nullable":true},"supplier_id":{"$ref":"#/components/schemas/Uuid"},"supplier_reference":{"type":"string","nullable":true},"supplier_return_number":{"type":"string"}}},"CreateTenantRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}},"CreateTokenRequest":{"type":"object","description":"Token creation request","required":["user_id"],"properties":{"user_id":{"type":"integer","format":"int32"}}},"CreateUserRequest":{"type":"object","required":["username","email","first_name","last_name","role"],"properties":{"email":{"type":"string"},"first_name":{"type":"string"},"is_active":{"type":"boolean","nullable":true},"last_name":{"type":"string"},"role":{"$ref":"#/components/schemas/UserRole"},"username":{"type":"string"}}},"CreateUserResponse":{"type":"object","required":["id","username","temporary_password","message"],"properties":{"id":{"type":"integer","format":"int32"},"message":{"type":"string"},"temporary_password":{"type":"string"},"username":{"type":"string"}}},"CreateVendorCreditRequest":{"type":"object","required":["supplier_id","credit_date","currency_code","amount"],"properties":{"amount":{"type":"string"},"credit_date":{"type":"string","description":"`YYYY-MM-DD`."},"currency_code":{"type":"string"},"notes":{"type":"string","nullable":true},"supplier_id":{"$ref":"#/components/schemas/Uuid"},"supplier_reference":{"type":"string","description":"The supplier's credit-note number.","nullable":true}}},"CreatedRunResponse":{"type":"object","required":["run_id","dry_run","status"],"properties":{"dry_run":{"type":"boolean"},"run_id":{"type":"string"},"status":{"type":"string"}}},"CrmFieldEntityType":{"type":"string","description":"Entity types that can carry custom fields — mirrors the DB CHECK on\n`crm_field_definitions.entity_type`.","enum":["contact","organization","deal","case"]},"CrmFieldType":{"type":"string","description":"Field data types. Mirrors the DB CHECK constraint on\n`crm_field_definitions.field_type` — \"no freeform fields\" is a hard rule,\nso the enum is closed here AND in the database.","enum":["TEXT","NUMBER","BOOLEAN","DATE","DATETIME","CURRENCY","PICKLIST_SINGLE","PICKLIST_MULTI","USER_REF","ENTITY_REF"]},"CrmManualActivityType":{"type":"string","description":"Activity kinds a rep can log manually. The DB CHECK additionally allows\n`stage_changed`, `consent_changed`, `domain_event` and `system`, which\nonly system write paths (merge trails, the domain-event ingest) may use —\nthe manual-log endpoint rejects them.","enum":["note","call","meeting","task","email"]},"CrmPicklistOption":{"type":"object","description":"One admin-defined picklist option. End users only ever pick from these,\nnever type free text.","required":["value","label"],"properties":{"label":{"type":"string"},"value":{"type":"string"}}},"CrmRecordSource":{"type":"string","description":"Where a CRM record came from. Stored as VARCHAR backed by a CHECK\nconstraint (`chk_crm_contacts_source` / `chk_crm_organizations_source`);\nthis enum is the single Rust-side authority for the allowed values.","enum":["manual","import","campaign","api","email_sync"]},"CrmSortDir":{"type":"string","description":"Sort direction for CRM list endpoints.","enum":["asc","desc"]},"CustomerAnalytics":{"type":"object","required":["customer_segments","top_customers","retention_metrics","geographic_distribution","summary","top_customers_total"],"properties":{"customer_segments":{"type":"array","items":{"$ref":"#/components/schemas/CustomerSegment"}},"customer_type_distribution":{"type":"array","items":{"$ref":"#/components/schemas/CustomerTypeData"},"description":"Distribution of order revenue by account `customer_type`. Same data as\nthe deprecated `geographic_distribution`, honestly named.\n\n`Option` so the `OpenAPI` schema stays strictly additive (no newly\nrequired field on an existing schema — `scripts/check_breaking_api.sh`\ngate); the handler always emits `Some`.","nullable":true},"geographic_distribution":{"type":"array","items":{"$ref":"#/components/schemas/GeoData"},"description":"Deprecated: this was never geographic data — it is the distribution by\n`customer_type`, relabeled as `region`. Use\n`customer_type_distribution` instead; this field keeps emitting the\nsame data for backward compatibility.","deprecated":true},"retention_metrics":{"$ref":"#/components/schemas/RetentionMetrics"},"summary":{"$ref":"#/components/schemas/CustomerAnalyticsSummary"},"top_customers":{"type":"array","items":{"$ref":"#/components/schemas/TopCustomer"}},"top_customers_total":{"type":"integer","format":"int64"}}},"CustomerAnalyticsSummary":{"type":"object","required":["total_customers","active_customers","new_customers_period","avg_customer_lifetime_value"],"properties":{"active_customers":{"type":"integer","format":"int64"},"avg_customer_lifetime_value":{"$ref":"#/components/schemas/BigDecimal"},"new_customers_period":{"type":"integer","format":"int64"},"total_customers":{"type":"integer","format":"int64"}}},"CustomerGroupMemberResponse":{"type":"object","required":["id","name"],"properties":{"account_number":{"type":"string","nullable":true},"id":{"type":"string"},"name":{"type":"string"}}},"CustomerGroupResponse":{"type":"object","required":["id","company_id","name","member_count","price_list_ids","created_at","updated_at"],"properties":{"company_id":{"type":"string"},"created_at":{"type":"string"},"default_min_order_qty":{"type":"integer","format":"int32","nullable":true},"description":{"type":"string","nullable":true},"id":{"type":"string"},"member_count":{"type":"integer","format":"int64"},"name":{"type":"string"},"price_list_ids":{"type":"array","items":{"type":"string"}},"updated_at":{"type":"string"}}},"CustomerReturnLineResponse":{"type":"object","required":["id","original_sales_receipt_item_id","line_number","product_id","quantity","unit_refund","refund_total","disposition","reason_code"],"properties":{"disposition":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"line_number":{"type":"integer","format":"int32"},"notes":{"type":"string","nullable":true},"original_sales_receipt_item_id":{"type":"string"},"product_id":{"type":"string"},"quantity":{"type":"integer","format":"int32"},"reason_code":{"type":"string"},"refund_total":{"type":"string"},"unit_refund":{"type":"string"}}},"CustomerReturnListResponse":{"type":"object","required":["results","total","limit","offset"],"properties":{"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/CustomerReturnResponse"}},"total":{"type":"integer","format":"int64"}}},"CustomerReturnResponse":{"type":"object","required":["id","return_number","original_sales_receipt_id","account_id","location_id","status","refund_method","refund_amount","created_at","updated_at","version","lines"],"properties":{"account_id":{"type":"string"},"created_at":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/CustomerReturnLineResponse"}},"location_id":{"type":"string"},"original_sales_receipt_id":{"type":"string"},"posted_at":{"type":"string","nullable":true},"reason":{"type":"string","nullable":true},"refund_amount":{"type":"string"},"refund_method":{"type":"string"},"return_number":{"type":"string"},"status":{"type":"string"},"updated_at":{"type":"string"},"version":{"type":"integer","format":"int32"}}},"CustomerSales":{"type":"object","required":["customer_name","order_count","total_spent"],"properties":{"customer_name":{"type":"string"},"order_count":{"type":"integer","format":"int64"},"total_spent":{"$ref":"#/components/schemas/BigDecimal"}}},"CustomerSegment":{"type":"object","required":["segment_name","customer_count","revenue_percentage","avg_order_value","description"],"properties":{"avg_order_value":{"$ref":"#/components/schemas/BigDecimal"},"customer_count":{"type":"integer","format":"int64"},"description":{"type":"string"},"revenue_percentage":{"type":"number","format":"double"},"segment_name":{"type":"string"}}},"CustomerTypeData":{"type":"object","description":"Order-revenue distribution bucketed by account `customer_type`.","required":["customer_type","customer_count","total_revenue","avg_order_value"],"properties":{"avg_order_value":{"$ref":"#/components/schemas/BigDecimal"},"customer_count":{"type":"integer","format":"int64"},"customer_type":{"type":"string"},"total_revenue":{"$ref":"#/components/schemas/BigDecimal"}}},"DashboardCountKpi":{"type":"object","required":["current","previous","delta_abs"],"properties":{"current":{"type":"integer","format":"int64"},"delta_abs":{"type":"integer","format":"int64"},"delta_pct":{"type":"number","format":"double","nullable":true},"previous":{"type":"integer","format":"int64"}}},"DashboardInventorySummary":{"type":"object","required":["low_stock_count","out_of_stock_count"],"properties":{"low_stock_count":{"type":"integer","format":"int64"},"out_of_stock_count":{"type":"integer","format":"int64"}}},"DashboardKpis":{"type":"object","required":["orders_7d","revenue_7d","avg_order_value_7d"],"properties":{"avg_order_value_7d":{"$ref":"#/components/schemas/DashboardNumberKpi"},"orders_7d":{"$ref":"#/components/schemas/DashboardCountKpi"},"revenue_7d":{"$ref":"#/components/schemas/DashboardNumberKpi"}}},"DashboardNumberKpi":{"type":"object","required":["current","previous","delta_abs"],"properties":{"current":{"type":"number","format":"double"},"delta_abs":{"type":"number","format":"double"},"delta_pct":{"type":"number","format":"double","nullable":true},"previous":{"type":"number","format":"double"}}},"DashboardPickingSummary":{"type":"object","required":["pending_count","in_progress_count","completed_count"],"properties":{"completed_count":{"type":"integer","format":"int64"},"in_progress_count":{"type":"integer","format":"int64"},"pending_count":{"type":"integer","format":"int64"}}},"DashboardStats":{"type":"object","required":["total_tenants","active_tenants","total_users","active_users"],"properties":{"active_tenants":{"type":"integer","format":"int64"},"active_users":{"type":"integer","format":"int64"},"total_tenants":{"type":"integer","format":"int64"},"total_users":{"type":"integer","format":"int64"}}},"DashboardValuesResponse":{"type":"object","required":["customers_count","products_count","recent_sales","recent_sales_orders","kpis","inventory","picking"],"properties":{"customers_count":{"type":"integer","format":"int64"},"inventory":{"$ref":"#/components/schemas/DashboardInventorySummary"},"kpis":{"$ref":"#/components/schemas/DashboardKpis"},"picking":{"$ref":"#/components/schemas/DashboardPickingSummary"},"products_count":{"type":"integer","format":"int64"},"recent_sales":{"type":"array","items":{}},"recent_sales_orders":{"type":"array","items":{}},"sales_window_from":{"type":"string","description":"Exact UTC bounds used by the sales KPIs and their drill-downs.","nullable":true},"sales_window_to":{"type":"string","nullable":true}}},"DatasetEnrichRequest":{"type":"object","properties":{"enrichment_config":{"nullable":true}}},"DatasetInput":{"type":"object","required":["data_set_format","data_file","size"],"properties":{"data_file":{"type":"string"},"data_set_format":{"type":"string"},"size":{"type":"integer","format":"int32"},"source_type":{"type":"string","nullable":true}}},"DateTime":{"type":"string","description":"Date-time in ISO 8601 format","example":"2024-01-15T10:30:00Z"},"DealDetailResponse":{"type":"object","required":["id","name","pipeline_id","stage_id","created_at","updated_at","stage_history"],"properties":{"amount":{"type":"string","nullable":true},"closed_at":{"type":"string","nullable":true},"created_at":{"type":"string"},"currency":{"type":"string","nullable":true},"custom_fields":{"type":"object","description":"`field_definition.code` → typed value (A2 engine). Option-typed for\nschema additivity; always present on the wire.","additionalProperties":{},"nullable":true},"expected_close_date":{"type":"string","nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"lost_reason_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"name":{"type":"string"},"organization_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"owner_user_id":{"type":"integer","format":"int32","nullable":true},"pipeline_id":{"$ref":"#/components/schemas/Uuid"},"primary_contact_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"stage_history":{"type":"array","items":{"$ref":"#/components/schemas/DealStageHistoryEntry"}},"stage_id":{"$ref":"#/components/schemas/Uuid"},"updated_at":{"type":"string"}}},"DealListItem":{"type":"object","required":["id","name","pipeline_id","stage_id","created_at","updated_at"],"properties":{"amount":{"type":"string","nullable":true},"closed_at":{"type":"string","nullable":true},"created_at":{"type":"string"},"currency":{"type":"string","nullable":true},"expected_close_date":{"type":"string","nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"name":{"type":"string"},"organization_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"organization_name":{"type":"string","nullable":true},"owner_user_id":{"type":"integer","format":"int32","nullable":true},"pipeline_id":{"$ref":"#/components/schemas/Uuid"},"stage_id":{"$ref":"#/components/schemas/Uuid"},"updated_at":{"type":"string"}}},"DealListResponse":{"type":"object","required":["items","total_count","page","page_size"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/DealListItem"}},"page":{"type":"integer","format":"int64"},"page_size":{"type":"integer","format":"int64"},"total_count":{"type":"integer","format":"int64"}}},"DealPipelineRequest":{"type":"object","required":["name","stages"],"properties":{"is_default":{"type":"boolean"},"name":{"type":"string"},"stages":{"type":"array","items":{"$ref":"#/components/schemas/DealPipelineStageInput"}}}},"DealPipelineResponse":{"type":"object","required":["id","name","is_default","is_active","stages","created_at","updated_at"],"properties":{"created_at":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"is_active":{"type":"boolean"},"is_default":{"type":"boolean"},"name":{"type":"string"},"stages":{"type":"array","items":{"$ref":"#/components/schemas/DealPipelineStageResponse"}},"updated_at":{"type":"string"}}},"DealPipelineStageInput":{"type":"object","required":["code","label"],"properties":{"code":{"type":"string","description":"Stable machine code, unique within the pipeline (e.g. `qualified`)."},"is_lost":{"type":"boolean"},"is_won":{"type":"boolean"},"label":{"type":"string"},"probability_percent":{"type":"integer","format":"int32","description":"0–100."},"sort_order":{"type":"integer","format":"int32"}}},"DealPipelineStageResponse":{"type":"object","required":["id","code","label","sort_order","probability_percent","is_won","is_lost"],"properties":{"code":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"is_lost":{"type":"boolean"},"is_won":{"type":"boolean"},"label":{"type":"string"},"probability_percent":{"type":"integer","format":"int32"},"sort_order":{"type":"integer","format":"int32"}}},"DealStageHistoryEntry":{"type":"object","required":["to_stage_id","occurred_at"],"properties":{"changed_by_user_id":{"type":"integer","format":"int32","nullable":true},"from_stage_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"occurred_at":{"type":"string"},"to_stage_id":{"$ref":"#/components/schemas/Uuid"}}},"DeleteAccessTokenResponse":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}},"DeliveryDetailsResponse":{"type":"object","required":["order_id","can_attach_proof","can_assign","can_dispatch","can_complete"],"properties":{"can_assign":{"type":"boolean"},"can_attach_proof":{"type":"boolean"},"can_complete":{"type":"boolean"},"can_dispatch":{"type":"boolean"},"delivery_method":{"type":"string","nullable":true},"delivery_status":{"type":"string","nullable":true},"driver_name":{"type":"string","nullable":true},"driver_user_id":{"type":"integer","format":"int32","nullable":true},"estimated_delivery":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"order_id":{"type":"string"},"pod_url":{"type":"string","nullable":true}}},"DeliveryOptionsResponse":{"type":"object","required":["staff","settings"],"properties":{"settings":{"$ref":"#/components/schemas/crate.features.settings.dto.DeliveryWorkflowSettings"},"staff":{"type":"array","items":{"$ref":"#/components/schemas/DeliveryStaffOption"}}}},"DeliveryStaffOption":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","format":"int32"},"name":{"type":"string"}}},"DeliveryWorkflowSettings":{"type":"object","description":"Company-scoped delivery evidence and picking queue preferences.","properties":{"picking_page_size":{"type":"integer","format":"int64","description":"Number of picking queue entries requested per page.","default":25},"proof_max_bytes":{"type":"integer","format":"int64","description":"Maximum upload size in bytes for delivery evidence.","default":10485760,"minimum":0},"proof_max_pixels":{"type":"integer","format":"int64","description":"Maximum pixel count for delivery evidence images.","default":40000000,"minimum":0}}},"DescriptionDisplayMode":{"type":"string","enum":["none","truncated","full"]},"DeviceDiagnosticsListResponse":{"type":"object","required":["items","total","limit","offset"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/DeviceDiagnosticsSummary"}},"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"total":{"type":"integer","format":"int64"}}},"DeviceDiagnosticsSummary":{"type":"object","description":"List row (bundle omitted — fetch the detail endpoint for it).","required":["id","company_id","user_id","trigger","created_at"],"properties":{"company_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/chrono.DateTime"},"device_info":{"nullable":true},"id":{"type":"string"},"trigger":{"type":"string"},"user_id":{"type":"integer","format":"int32"}}},"DeviceDiagnosticsUploadRequest":{"type":"object","description":"Upload request: the JSON diagnostic bundle plus metadata.","required":["bundle","trigger"],"properties":{"bundle":{"description":"The full diagnostic bundle (log ring, sync metadata, outbox snapshot,\npending counts, settings snapshot, DB health report)."},"device_info":{"description":"App version/build, device model + OS, device id if available.","nullable":true},"trigger":{"type":"string","description":"One of: manual | `auto_crash` | `auto_reset` | `auto_quarantine` |\n`auto_integrity_fail` | `support_request`."}}},"DeviceDiagnosticsUploadResponse":{"type":"object","description":"Upload response: reference id the user can read to support.","required":["id","trigger","created_at"],"properties":{"created_at":{"$ref":"#/components/schemas/chrono.DateTime"},"id":{"type":"string"},"trigger":{"type":"string"}}},"DiagnosticTrigger":{"type":"string","description":"Why a device uploaded a diagnostic bundle. Mirrors the\n`device_diagnostics_trigger_check` DB constraint.","enum":["manual","auto_crash","auto_reset","auto_quarantine","auto_integrity_fail","support_request"]},"DiagnosticsSettings":{"type":"object","description":"Per-company device diagnostics tuning (order-durability plan §B8). Stored\ninside the company settings JSON; absent config falls back to `Default` and\nnever breaks.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level `#[serde(default)]` only fires when the WHOLE group is\nmissing).","properties":{"auto_upload_enabled":{"type":"boolean","description":"Whether devices may auto-upload diagnostic bundles on critical events\n(crash, reset, quarantine, integrity failure). Manual and\nsupport-requested uploads are always allowed. Default true."},"diagnostics_retention_days":{"type":"integer","format":"int32","description":"Days an uploaded diagnostics row is retained before purge. Default 30.","minimum":0},"max_db_upload_mb":{"type":"integer","format":"int32","description":"Max size (MB) of a future DB-file blob upload. Currently unused by the\nbackend (bundle-only uploads); wired now so tenants configure it\nwithout a redeploy once blob upload ships. Default 50.","minimum":0},"max_uploads_per_device_per_day":{"type":"integer","format":"int32","description":"Max diagnostic uploads accepted per user per UTC day. Default 10.","minimum":0}}},"DigitalDisplaySettings":{"type":"object","description":"Per-company digital display (TV signage) tuning. Stored inside the company\nsettings JSON; absent config falls back to `Default` and never breaks.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level `#[serde(default)]` only fires when the WHOLE group is\nmissing).","properties":{"max_image_upload_mb":{"type":"integer","format":"int32","description":"Max size (MB) of an image uploaded as display content. Default 10.","minimum":0},"max_video_upload_mb":{"type":"integer","format":"int32","description":"Max size (MB) of a video uploaded as display content. Default 200.","minimum":0},"viewer_refresh_seconds":{"type":"integer","format":"int32","description":"How often (seconds) the public TV viewer re-fetches its content.\nDefault 60.","minimum":0}}},"DiscountRuleResponse":{"type":"object","required":["id","company_id","name","discount_type","value","applies_to","is_stackable","priority","created_at","updated_at"],"properties":{"active_from_time":{"type":"string","nullable":true},"active_to_time":{"type":"string","nullable":true},"active_weekdays":{"type":"string","nullable":true},"applies_to":{"type":"string"},"company_id":{"type":"string"},"config":{"nullable":true},"created_at":{"type":"string"},"discount_type":{"type":"string"},"ends_at":{"type":"string","nullable":true},"id":{"type":"string"},"is_active":{"type":"boolean","nullable":true},"is_stackable":{"type":"boolean"},"kind":{"type":"string","nullable":true},"min_order_qty":{"type":"integer","format":"int32","nullable":true},"min_order_value":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"name":{"type":"string"},"priority":{"type":"integer","format":"int32"},"starts_at":{"type":"string","nullable":true},"target_id":{"type":"string","nullable":true},"target_name":{"type":"string","nullable":true},"updated_at":{"type":"string"},"value":{"$ref":"#/components/schemas/BigDecimal"}}},"DisplayResponse":{"type":"object","required":["id","company_id","name","display_type","mode","is_public","slide_duration_seconds","transition_type","is_active","created_at","updated_at"],"properties":{"assigned_terminal_ids":{"type":"array","items":{"$ref":"#/components/schemas/Uuid"},"description":"Assigned terminal IDs (for POS displays)","nullable":true},"company_id":{"type":"string","description":"Company id is a varchar in the DB (legacy companies use non-UUID ids),\nso it is exposed as a plain string like other features do."},"content_count":{"type":"integer","format":"int64","description":"Current content items assigned to this display","nullable":true},"created_at":{"type":"string"},"display_type":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"is_active":{"type":"boolean"},"is_public":{"type":"boolean"},"last_sync_at":{"type":"string","nullable":true},"layout_config":{"nullable":true},"mode":{"type":"string"},"name":{"type":"string"},"public_token":{"type":"string","nullable":true},"slide_duration_seconds":{"type":"integer","format":"int32"},"theme":{"nullable":true},"transition_type":{"type":"string"},"updated_at":{"type":"string"}}},"DomainEventListResponse":{"type":"object","description":"Paginated list response for `GET /api/events`.","required":["items","total","page","per_page","total_pages"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/DomainEventResponse"}},"page":{"type":"integer","format":"int64"},"per_page":{"type":"integer","format":"int64"},"total":{"type":"integer","format":"int64"},"total_pages":{"type":"integer","format":"int64"}}},"DomainEventQuery":{"type":"object","description":"Query parameters for `GET /api/events`.","properties":{"dispatched":{"type":"boolean","description":"Filter by dispatch status: `true` = already fanned out\n(`dispatched_at IS NOT NULL`), `false` = still pending.","nullable":true},"entity_id":{"type":"string","description":"Filter by `entity_id`.","nullable":true},"entity_type":{"type":"string","description":"Filter by `entity_type` (e.g. \"product\").","nullable":true},"event_type":{"type":"string","description":"Filter by exact `event_type` (e.g. \"product.created\"). See\n`models::domain_event::domain_event_type` for the allowed catalogue.","nullable":true},"from":{"type":"string","description":"ISO-8601 lower bound on `occurred_at` (e.g. \"2025-01-01T00:00:00Z\").","nullable":true},"page":{"type":"integer","format":"int64","nullable":true},"per_page":{"type":"integer","format":"int64","nullable":true},"to":{"type":"string","description":"ISO-8601 upper bound on `occurred_at`.","nullable":true}}},"DomainEventResponse":{"type":"object","description":"A single domain event returned by the read API.","required":["id","company_id","event_type","entity_type","entity_id","payload","occurred_at","attempts"],"properties":{"actor_user_id":{"type":"integer","format":"int32","nullable":true},"attempts":{"type":"integer","format":"int32"},"causation_id":{"type":"string","nullable":true},"company_id":{"type":"string"},"correlation_id":{"type":"string","nullable":true},"dispatched_at":{"type":"string","nullable":true},"entity_id":{"type":"string"},"entity_type":{"type":"string"},"event_type":{"type":"string"},"id":{"type":"string"},"last_error":{"type":"string","nullable":true},"next_retry_at":{"type":"string","nullable":true},"occurred_at":{"type":"string"},"payload":{}}},"DraftCreated":{"type":"object","description":"Handed back once, at creation. The token is never retrievable again.","required":["id","slug","edit_token"],"properties":{"edit_token":{"type":"string","description":"Possession of this is authority to edit or delete. Shown once."},"id":{"type":"string"},"slug":{"type":"string"}}},"EffectiveMfaPolicy":{"type":"object","description":"The fully-resolved MFA position for one user.","required":["mfa_mode","mfa_required","mfa_setup_required","mfa_challenge_required","can_disable_mfa","step_up_freshness_minutes","require_mfa_for_sensitive_actions","policy_source"],"properties":{"can_disable_mfa":{"type":"boolean","description":"Whether this user may turn their own MFA off right now."},"mfa_challenge_required":{"type":"boolean","description":"A confirmed factor exists, so login must challenge for it. True even when\n`mfa_required` is false: opting in means being challenged."},"mfa_mode":{"$ref":"#/components/schemas/MfaMode"},"mfa_required":{"type":"boolean","description":"Whether MFA is mandatory for this specific user."},"mfa_setup_required":{"type":"boolean","description":"Mandatory but not yet enrolled — the user must be pushed through setup\nbefore they get a session."},"policy_source":{"$ref":"#/components/schemas/PolicySource"},"require_mfa_for_sensitive_actions":{"type":"boolean","description":"Whether sensitive actions require a fresh step-up."},"step_up_freshness_minutes":{"type":"integer","format":"int32","description":"How long a step-up re-authentication stays fresh, in minutes."}}},"EmailLogEntry":{"type":"object","required":["id","recipient","subject","status","transport","sent_at"],"properties":{"error":{"type":"string","nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"recipient":{"type":"string"},"sent_at":{"$ref":"#/components/schemas/chrono.DateTime"},"status":{"type":"string"},"subject":{"type":"string"},"template":{"type":"string","nullable":true},"transport":{"type":"string"}}},"EmailLogListResponse":{"type":"object","required":["items","total","page","per_page","total_pages"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/EmailLogEntry"}},"page":{"type":"integer","format":"int64"},"per_page":{"type":"integer","format":"int64"},"total":{"type":"integer","format":"int64"},"total_pages":{"type":"integer","format":"int64"}}},"EmailMessageResponse":{"type":"object","required":["id","provider_message_id","direction","from_address","sent_at","has_attachments","recipients"],"properties":{"body_html":{"type":"string","nullable":true},"body_text":{"type":"string","nullable":true},"direction":{"type":"string"},"from_address":{"type":"string"},"has_attachments":{"type":"boolean"},"id":{"$ref":"#/components/schemas/Uuid"},"provider_message_id":{"type":"string"},"recipients":{"type":"array","items":{"$ref":"#/components/schemas/EmailRecipientEntry"}},"sent_at":{"type":"string"}}},"EmailRecipientEntry":{"type":"object","required":["role","address"],"properties":{"address":{"type":"string"},"contact_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"role":{"type":"string"}}},"EmailThreadDetailResponse":{"type":"object","required":["id","mailbox_id","status","last_message_at","messages"],"properties":{"assigned_to_user_id":{"type":"integer","format":"int32","nullable":true},"contact_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"last_message_at":{"type":"string"},"mailbox_id":{"$ref":"#/components/schemas/Uuid"},"messages":{"type":"array","items":{"$ref":"#/components/schemas/EmailMessageResponse"}},"provider_thread_id":{"type":"string","nullable":true},"status":{"type":"string"},"subject":{"type":"string","nullable":true}}},"EmailThreadListResponse":{"type":"object","required":["items","total_count","page","page_size"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/EmailThreadResponse"}},"page":{"type":"integer","format":"int64"},"page_size":{"type":"integer","format":"int64"},"total_count":{"type":"integer","format":"int64"}}},"EmailThreadResponse":{"type":"object","required":["id","mailbox_id","status","last_message_at","message_count"],"properties":{"assigned_to_user_id":{"type":"integer","format":"int32","nullable":true},"contact_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"last_message_at":{"type":"string"},"mailbox_id":{"$ref":"#/components/schemas/Uuid"},"message_count":{"type":"integer","format":"int64"},"provider_thread_id":{"type":"string","nullable":true},"status":{"type":"string"},"subject":{"type":"string","nullable":true}}},"EmbeddingsBackfillResponse":{"type":"object","description":"Result of enqueuing an embeddings backfill batch.","required":["enqueued","embeddings_enabled"],"properties":{"embeddings_enabled":{"type":"boolean","description":"False when embeddings are disabled — nothing was enqueued."},"enqueued":{"type":"integer","format":"int64","description":"Number of embed jobs enqueued (products already in flight are skipped)."}}},"EnrichmentBatchCancelResponse":{"type":"object","required":["batch_id","cancelled_jobs"],"properties":{"batch_id":{"type":"string"},"cancelled_jobs":{"type":"integer","format":"int64"}}},"EnrichmentBatchCreateRequest":{"type":"object","properties":{"collection_id":{"type":"string","description":"Enrich every product in this collection.","nullable":true},"force":{"type":"boolean"},"product_ids":{"type":"array","items":{"type":"string"},"description":"Explicit product ids; mutually exclusive with `collection_id`.","nullable":true},"target_languages":{"type":"array","items":{"type":"string"},"nullable":true}}},"EnrichmentBatchCreateResponse":{"type":"object","required":["queued","skipped","failed"],"properties":{"batch_id":{"type":"string","nullable":true},"failed":{"type":"integer","format":"int64"},"queued":{"type":"integer","format":"int64"},"skipped":{"type":"integer","format":"int64"}}},"EnrichmentBatchListQuery":{"type":"object","properties":{"limit":{"type":"integer","format":"int64","nullable":true},"offset":{"type":"integer","format":"int64","nullable":true}}},"EnrichmentBatchListResponse":{"type":"object","required":["items","total_count"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/EnrichmentBatchSummary"}},"total_count":{"type":"integer","format":"int64"}}},"EnrichmentBatchSummary":{"type":"object","required":["id","source","status","total_jobs","queued","claimed","processing","completed","failed","cancelled","dead","created_at"],"properties":{"cancelled":{"type":"integer","format":"int64"},"cancelled_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"claimed":{"type":"integer","format":"int64"},"completed":{"type":"integer","format":"int64"},"completed_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"created_at":{"$ref":"#/components/schemas/chrono.DateTime"},"dead":{"type":"integer","format":"int64"},"failed":{"type":"integer","format":"int64"},"id":{"type":"string"},"processing":{"type":"integer","format":"int64"},"queued":{"type":"integer","format":"int64"},"recipe_id":{"type":"string","nullable":true},"source":{"type":"string"},"source_ref":{"type":"string","nullable":true},"status":{"type":"string","description":"running | completed | cancelled"},"total_jobs":{"type":"integer","format":"int32"}}},"EnrichmentCompletenessFacets":{"type":"object","required":["missing_description","no_images","uncategorized","without_attributes","untranslated","never_enriched","needs_review"],"properties":{"missing_description":{"type":"integer","format":"int64"},"needs_review":{"type":"integer","format":"int64"},"never_enriched":{"type":"integer","format":"int64"},"no_images":{"type":"integer","format":"int64"},"uncategorized":{"type":"integer","format":"int64"},"untranslated":{"type":"integer","format":"int64","description":"Products covering fewer projection languages than the company default."},"without_attributes":{"type":"integer","format":"int64"}}},"EnrichmentCompletenessResponse":{"type":"object","required":["total_products","average_score","facets"],"properties":{"average_score":{"type":"number","format":"double","description":"Weighted catalog-health score, 0-100."},"facets":{"$ref":"#/components/schemas/EnrichmentCompletenessFacets"},"total_products":{"type":"integer","format":"int64"}}},"EnrichmentJobRequeueResponse":{"type":"object","description":"Result of requeuing a dead/stuck job (single or bulk).","required":["requeued"],"properties":{"requeued":{"type":"integer","format":"int64","description":"Number of jobs returned to the queue (1 for a single requeue)."}}},"EnrichmentJobResponse":{"type":"object","description":"A single durable `enrichment_jobs` row, for the admin Queue view.","required":["id","product_id","status","attempts","max_attempts","created_at","updated_at"],"properties":{"attempts":{"type":"integer","format":"int32"},"created_at":{"$ref":"#/components/schemas/chrono.DateTime"},"id":{"type":"string"},"last_error":{"type":"string","nullable":true},"max_attempts":{"type":"integer","format":"int32"},"product_id":{"type":"string"},"product_name":{"type":"string","description":"Product name at query time; null if the product row is gone.","nullable":true},"status":{"type":"string"},"updated_at":{"$ref":"#/components/schemas/chrono.DateTime"}}},"EnrichmentJobsResponse":{"type":"object","description":"Envelope for the durable-job listing.","required":["items","total_count","page","page_size"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/EnrichmentJobResponse"}},"page":{"type":"integer","format":"int64"},"page_size":{"type":"integer","format":"int64"},"total_count":{"type":"integer","format":"int64","description":"Total matching jobs across all pages."}}},"EnrichmentQueueHealthResponse":{"type":"object","description":"At-a-glance health of the durable enrichment queue for one company.","required":["depth","in_flight","dead"],"properties":{"dead":{"type":"integer","format":"int64","description":"Jobs in the dead-letter state (retry budget exhausted)."},"depth":{"type":"integer","format":"int64","description":"Jobs waiting to be claimed (status = queued)."},"in_flight":{"type":"integer","format":"int64","description":"Jobs currently claimed or processing."},"last_completed_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"oldest_queued_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true}}},"EnrichmentSummaryResponse":{"type":"object","required":["status"],"properties":{"approved_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"last_completed_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"last_error":{"type":"string","nullable":true},"last_run_id":{"type":"string","nullable":true},"last_started_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"model":{"type":"string","nullable":true},"provider":{"type":"string","nullable":true},"status":{"type":"string"}}},"EnrichmentUsageByKind":{"type":"object","description":"Current-month credit spend split by job kind (Enrichment v2, Phase E). Sums\nto `current_month_credits`. Vision spend is folded into the text-enrichment\nruns' `credit_cost` but tracked separately, so `enrichment` here already\nexcludes the vision portion.","required":["enrichment","embeddings","vision"],"properties":{"embeddings":{"type":"number","format":"double","description":"Semantic-embedding credits."},"enrichment":{"type":"number","format":"double","description":"Text enrichment credits (excluding the vision portion)."},"vision":{"type":"number","format":"double","description":"Vision colour-extraction credits."}}},"EnrichmentUsageQuery":{"type":"object","properties":{"months":{"type":"integer","format":"int32","description":"How many months back to aggregate (default 3, max 24).","nullable":true}}},"EnrichmentUsageResponse":{"type":"object","required":["items","current_month_credits","by_kind","ai_mode","platform_available","included_credits_per_month","used_this_month","topup_credits_remaining","available_credits","period_start","extra_credits_price_cents_per_1000"],"properties":{"ai_mode":{"$ref":"#/components/schemas/crate.features.products.services.platform_ai.AiMode"},"available_credits":{"type":"number","format":"double","description":"`max(0, included − used) + topup_credits_remaining` — what managed-mode\nruns may still spend this month."},"by_kind":{"$ref":"#/components/schemas/EnrichmentUsageByKind"},"current_month_credits":{"type":"number","format":"double"},"extra_credits_price_cents_per_1000":{"type":"integer","format":"int32","description":"Price of a 1,000-credit top-up on the company's plan; 0 = not sold."},"included_credits_per_month":{"type":"integer","format":"int64","description":"Plan allowance per calendar month (managed mode); 0 without a plan row."},"items":{"type":"array","items":{"$ref":"#/components/schemas/EnrichmentUsageRow"}},"monthly_credit_limit":{"type":"number","format":"double","nullable":true},"period_start":{"$ref":"#/components/schemas/chrono.DateTime"},"platform_available":{"type":"boolean","description":"This deployment has a platform provider key, so managed mode is\npossible for tenants without their own key."},"topup_credits_remaining":{"type":"integer","format":"int64","description":"Σ remaining credits of unexpired operator top-ups."},"used_this_month":{"type":"number","format":"double","description":"Credits spent since `period_start` (all modes)."}}},"EnrichmentUsageRow":{"type":"object","required":["month","provider","runs","units","credits"],"properties":{"credits":{"type":"number","format":"double"},"month":{"type":"string","description":"Calendar month, \"YYYY-MM\"."},"provider":{"type":"string"},"runs":{"type":"integer","format":"int64"},"units":{"type":"integer","format":"int64","description":"Usage units (one unit = one target language on one run)."}}},"EnrichmentWorkerConfig":{"type":"object","description":"Enrichment worker configuration for a single tenant. Stored inside the\ncompany settings JSON; falls back to `Default` when a tenant has never\nbeen configured.","required":["worker_poll_interval_secs","worker_max_concurrent","worker_stale_timeout_minutes","enabled"],"properties":{"enabled":{"type":"boolean","description":"Whether the enrichment worker is enabled."},"worker_backoff_base_secs":{"type":"integer","format":"int32","description":"Base backoff in seconds; the retry delay doubles each attempt from here.\nBounds 5–600."},"worker_heartbeat_interval_secs":{"type":"integer","format":"int32","description":"How often an in-flight job refreshes its heartbeat. Bounds 5–120."},"worker_max_attempts":{"type":"integer","format":"int32","description":"Retry budget for a job (attempt 1 + retries). Bounds 1–10.\nPer-field serde default keeps configs stored before this knob existed\ndeserializing cleanly (prod-safe)."},"worker_max_concurrent":{"type":"integer","format":"int32","description":"Maximum number of concurrent enrichment jobs."},"worker_poll_interval_secs":{"type":"integer","format":"int32","description":"Interval in seconds between enrichment queue polls."},"worker_stale_timeout_minutes":{"type":"integer","format":"int32","description":"Age in minutes after which an unresponsive enrichment job is stale."}}},"EnrichmentWorklistItem":{"type":"object","required":["id","name","item_number","score","has_description","image_count","has_category","attribute_count","language_count"],"properties":{"attribute_count":{"type":"integer","format":"int64"},"enrichment_status":{"type":"string","nullable":true},"has_category":{"type":"boolean"},"has_description":{"type":"boolean"},"id":{"type":"string"},"image_count":{"type":"integer","format":"int64"},"item_number":{"type":"string"},"language_count":{"type":"integer","format":"int64"},"name":{"type":"string"},"score":{"type":"number","format":"double","description":"Per-product completeness score, 0-100, using the company weights."}}},"EnrichmentWorklistQuery":{"type":"object","properties":{"gap":{"type":"string","description":"Gap filter: `missing_description` | `no_images` | uncategorized |\n`without_attributes` | untranslated | `never_enriched` | `needs_review`","nullable":true},"limit":{"type":"integer","format":"int64","nullable":true},"offset":{"type":"integer","format":"int64","nullable":true},"search":{"type":"string","nullable":true}}},"EnrichmentWorklistResponse":{"type":"object","required":["items","total_count"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/EnrichmentWorklistItem"}},"total_count":{"type":"integer","format":"int64"}}},"EntityNumberConfig":{"type":"object","required":["entity_type","prefix","pad_width","start_at","current_counter","preview"],"properties":{"current_counter":{"type":"integer","format":"int64"},"entity_type":{"$ref":"#/components/schemas/EntityType"},"pad_width":{"type":"integer","format":"int32"},"prefix":{"type":"string"},"preview":{"type":"string","description":"Example of the next number that will be generated"},"start_at":{"type":"integer","format":"int64"}}},"EntityType":{"type":"string","enum":["sales_order","sales_receipt","account","product","category","quote","gift_card","stock_adjustment","invoice","delivery_note","credit_note","horeca_order","supplier_bill","supplier_payment","vendor_credit"]},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"EscalateCaseRequest":{"type":"object","properties":{"reason":{"type":"string","description":"Optional note recorded with the escalation.","nullable":true}}},"ExternalEntityType":{"type":"string","description":"Kinds of local entities that can carry external source-system IDs.\n\nThe HTTP API currently accepts the importable entity kinds\n(`product`, `account`, `category`) because it validates that the target\nentity exists. The remaining variants exist for the integrations engine,\nwhich validates its own entities before persisting references.","enum":["product","account","category","sales_order","invoice","payment","tax_rate"]},"ExternalReferenceResponse":{"type":"object","required":["id","entity_type","entity_id","source_system","external_id","created_at","updated_at"],"properties":{"created_at":{"type":"string"},"entity_id":{"type":"string"},"entity_type":{"type":"string"},"external_id":{"type":"string"},"id":{"type":"string"},"source_system":{"type":"string"},"updated_at":{"type":"string"}}},"FacetResponse":{"type":"object","description":"Facet counts for one field, serialised.","required":["field","values"],"properties":{"field":{"type":"string","description":"The facet field key."},"values":{"type":"array","items":{"$ref":"#/components/schemas/FacetValueResponse"},"description":"Counted values, most populous first."}}},"FacetValueResponse":{"type":"object","description":"One counted facet value, serialised.","required":["value","count"],"properties":{"count":{"type":"integer","format":"int64","description":"Match count under the sibling-stripped query."},"value":{"type":"string","description":"The value as stored in the AST."}}},"FacetsRequest":{"type":"object","description":"Body for `POST /api/query/{entity}/facets`.","required":["query"],"properties":{"only":{"type":"array","items":{"type":"string"},"description":"Restrict to these facet field keys; all facetable fields when absent.","nullable":true},"query":{"type":"object","description":"The filter tree the facets are computed against (each facet strips its\nown conditions)."}}},"FavoriteResponse":{"type":"object","required":["id","account_id","product_id","created_at"],"properties":{"account_id":{"type":"string"},"created_at":{"type":"string"},"id":{"type":"string"},"product_id":{"type":"string"},"product_name":{"type":"string","nullable":true},"unit_price":{"type":"number","format":"double","description":"The account's resolved (price-list-aware) unit price — the same\neffective price the order path books. `None` when the product no\nlonger exists for the company. Additive field.","nullable":true}}},"Feature":{"type":"string","description":"An atomic, billable capability. One per product surface a Plan can grant or withhold.\n\nSerialized form is `group.capability` (e.g. `pos.checkout`), matching the `Permission`\nconvention so the two read as siblings in the admin UI.","enum":["core.auth","core.settings","core.profile","core.users","core.audit","core.dashboard","core.onboarding","core.billing","core.table_views","core.diagnostics","catalogue.products","catalogue.categories","catalogue.modifier_groups","catalogue.pricing","catalogue.tax_currency","catalogue.images","catalogue.documents","inventory.stock","inventory.locations","inventory.datasets","inventory.procurement","inventory.counts","sales.orders","sales.quotes","sales.receipts","sales.accounts","sales.fulfillment","sales.picking","sales.gift_cards","pos.checkout","pos.terminals","pos.z_reports","pos.horeca","pos.kiosk","pos.qr_ordering","pos.kds","channels.b2b_portal","channels.catalog_rules","channels.storefront","channels.digital_display","channels.marketing_images","data.reports","data.query","data.backups","data.sync","data.integrations","data.webhooks","data.pipelines","data.external_refs","data.access_tokens","data.mcp","crm.contacts","crm.deals","crm.activities","crm.cases","crm.campaigns","crm.inbox","crm.automation","platform.custom_entities","platform.custom_ui","platform.modules","platform.marketplace","platform.reports"]},"FeatureFlag":{"type":"object","required":["id","flag_key","enabled","created_at","updated_at"],"properties":{"created_at":{"$ref":"#/components/schemas/chrono.DateTime"},"description":{"type":"string","nullable":true},"enabled":{"type":"boolean"},"flag_key":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"updated_at":{"$ref":"#/components/schemas/chrono.DateTime"}}},"FieldDefinitionRequest":{"type":"object","required":["entity_type","code","label","field_type"],"properties":{"code":{"type":"string","description":"Machine key, unique per (company, `entity_type`). Immutable after create."},"entity_ref_target":{"type":"string","description":"Required for `ENTITY_REF`: contact | organization | deal | case | product | account.","nullable":true},"entity_type":{"$ref":"#/components/schemas/CrmFieldEntityType"},"field_type":{"$ref":"#/components/schemas/CrmFieldType"},"is_required":{"type":"boolean"},"label":{"type":"string"},"options":{"type":"array","items":{"$ref":"#/components/schemas/CrmPicklistOption"},"description":"Required for `PICKLIST_SINGLE` / `PICKLIST_MULTI`; forbidden otherwise.","nullable":true},"sort_order":{"type":"integer","format":"int32"}}},"FieldDefinitionResponse":{"type":"object","required":["id","entity_type","code","label","field_type","is_required","is_active","sort_order","created_at","updated_at"],"properties":{"code":{"type":"string"},"created_at":{"type":"string"},"entity_ref_target":{"type":"string","nullable":true},"entity_type":{"type":"string"},"field_type":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"is_active":{"type":"boolean"},"is_required":{"type":"boolean"},"label":{"type":"string"},"options":{"nullable":true},"sort_order":{"type":"integer","format":"int32"},"updated_at":{"type":"string"}}},"FieldLayoutItemInput":{"type":"object","required":["field_definition_id","section"],"properties":{"field_definition_id":{"$ref":"#/components/schemas/Uuid"},"is_required_override":{"type":"boolean","description":"Layout may tighten requiredness; the definition's `is_required` is the floor.","nullable":true},"section":{"type":"string","description":"Admin-typed section label grouping fields (e.g. \"Basic Info\")."},"sort_order":{"type":"integer","format":"int32"}}},"FieldLayoutItemResponse":{"type":"object","required":["field_definition_id","section","sort_order"],"properties":{"field_definition_id":{"$ref":"#/components/schemas/Uuid"},"is_required_override":{"type":"boolean","nullable":true},"section":{"type":"string"},"sort_order":{"type":"integer","format":"int32"}}},"FieldLayoutRequest":{"type":"object","description":"PUT body for the default layout of an entity type: replaces all items\ntransactionally, creating the layout row if missing.","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/FieldLayoutItemInput"}},"name":{"type":"string","nullable":true}}},"FieldLayoutResponse":{"type":"object","required":["id","entity_type","name","is_default","items"],"properties":{"entity_type":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"is_default":{"type":"boolean"},"items":{"type":"array","items":{"$ref":"#/components/schemas/FieldLayoutItemResponse"}},"name":{"type":"string"}}},"FieldValuesRequest":{"type":"object","description":"PUT body: map of `field_definition_id` → JSON value. Provided keys are\nset (validated against the definition's type); an explicit `null` clears\nthe value; absent keys are left untouched.","required":["values"],"properties":{"values":{"type":"object","additionalProperties":{}}}},"FieldValuesResponse":{"type":"object","required":["entity_type","entity_id","values"],"properties":{"entity_id":{"$ref":"#/components/schemas/Uuid"},"entity_type":{"type":"string"},"values":{"type":"object","description":"`field_definition.code` → typed JSON value.","additionalProperties":{}}}},"FloorPlan":{"type":"object","required":["id","company_id","name","width_px","height_px","created_at","updated_at"],"properties":{"background_image_url":{"type":"string","nullable":true},"company_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/DateTime"},"height_px":{"type":"integer","format":"int32"},"id":{"type":"string"},"name":{"type":"string"},"room_label":{"type":"string","nullable":true},"updated_at":{"$ref":"#/components/schemas/DateTime"},"width_px":{"type":"integer","format":"int32"}}},"FloorPlanObject":{"type":"object","required":["id","floor_plan_id","company_id","object_type","seats","x","y","width","height","rotation","created_at","updated_at"],"properties":{"color":{"type":"string","nullable":true},"company_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/DateTime"},"floor_plan_id":{"type":"string"},"height":{"type":"integer","format":"int32"},"id":{"type":"string"},"label":{"type":"string","nullable":true},"object_type":{"type":"string"},"rotation":{"type":"integer","format":"int32"},"seats":{"type":"integer","format":"int32"},"updated_at":{"$ref":"#/components/schemas/DateTime"},"width":{"type":"integer","format":"int32"},"x":{"type":"integer","format":"int32"},"y":{"type":"integer","format":"int32"}}},"FloorPlanObjectWithStatus":{"type":"object","required":["object"],"properties":{"current_session":{"allOf":[{"$ref":"#/components/schemas/crate.models.horeca.TableSession"}],"nullable":true},"object":{"$ref":"#/components/schemas/crate.models.horeca.FloorPlanObject"},"order_summary":{"allOf":[{"$ref":"#/components/schemas/TableOrderSummary"}],"nullable":true}}},"FloorPlanStateResponse":{"type":"object","description":"`GET /api/horeca/floor-plans/{id}/state` response. Mirrors\n`handlers::get_floor_plan_state`'s `json!({\"floor_plan\":.., \"objects\":..,\n\"objects_with_status\":..})` exactly.","required":["floor_plan","objects","objects_with_status"],"properties":{"floor_plan":{"$ref":"#/components/schemas/crate.models.horeca.FloorPlan"},"objects":{"type":"array","items":{"$ref":"#/components/schemas/crate.models.horeca.FloorPlanObject"}},"objects_with_status":{"type":"array","items":{"$ref":"#/components/schemas/FloorPlanObjectWithStatus"}}}},"ForcedEnrolStartRequest":{"type":"object","description":"Start TOTP enrolment on the forced path, where the caller has no session yet:\n`/api/login` returned `mfa_setup_required` plus this token instead of a JWT.\n\nThe in-session equivalent (`POST /api/auth/mfa/totp/setup`, from the profile\npage) takes no body — it identifies the user from claims.","required":["setup_token"],"properties":{"setup_token":{"type":"string"}}},"ForcedEnrolVerifyRequest":{"type":"object","description":"Complete enrolment on the forced path. A success here also issues the session\nthe user was denied at login — otherwise forced enrolment would dead-end on\nthe sign-in screen.","required":["setup_token","code"],"properties":{"code":{"type":"string","description":"The 6-digit code currently shown by the authenticator app."},"setup_token":{"type":"string"}}},"FrequentProductResponse":{"type":"object","description":"Product record used by account frequent-products endpoint.","required":["id","created_at","updated_at","name","description","code","code_type","company_id","item_number","msrp_price","qty","regular_price","wholesale_price","images","order_count","last_ordered_at"],"properties":{"category_id":{"type":"string","nullable":true},"category_name":{"type":"string","nullable":true},"code":{"type":"string"},"code_type":{"type":"string"},"company_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/chrono.NaiveDateTime"},"description":{"type":"string"},"id":{"type":"string"},"images":{"type":"array","items":{"$ref":"#/components/schemas/ProductImageResponse"}},"item_number":{"type":"string"},"last_ordered_at":{"$ref":"#/components/schemas/chrono.NaiveDateTime"},"msrp_price":{"$ref":"#/components/schemas/BigDecimal"},"name":{"type":"string"},"order_count":{"type":"integer","format":"int32"},"qty":{"type":"integer","format":"int32"},"regular_price":{"$ref":"#/components/schemas/BigDecimal"},"updated_at":{"$ref":"#/components/schemas/chrono.NaiveDateTime"},"wholesale_price":{"$ref":"#/components/schemas/BigDecimal"}}},"FulfillmentDocumentResponse":{"type":"object","required":["id","company_id","order_id","document_type","document_number","created_at","updated_at"],"properties":{"company_id":{"type":"string"},"created_at":{"type":"string"},"created_by":{"type":"string","nullable":true},"document_number":{"type":"string"},"document_type":{"type":"string","description":"Normalized on read: `invoice` | `delivery_note` | `credit_note` |\n`other` (legacy free-text values collapse to `other` — see\n[`DocumentType::from_stored_str`])."},"due_date":{"type":"string","nullable":true},"id":{"type":"string"},"notes":{"type":"string","nullable":true},"order_id":{"type":"string"},"order_number":{"type":"string","nullable":true},"parent_invoice_id":{"type":"string","description":"The invoice this credit note corrects. `None` for every non-credit-note\ndocument, and for legacy rows created before this linkage existed.","nullable":true},"payment_status":{"type":"string","description":"Derived `unpaid | partial | paid | overpaid` (E-5.3). `None` for\nevery non-invoice document and for invoices never issued.","nullable":true},"pdf_url":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"subtotal":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"tax_amount":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"total_amount":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"updated_at":{"type":"string"}}},"FulfillmentNotificationSettings":{"type":"object","description":"Customer notification preferences for completed deliveries.\n\nWhether/how the customer is emailed when their order is marked delivered.\nFollows the `sales_order_email` / `ai_automation` grouping pattern:\nnested inside `CompanySettings` (`webapp_company.settings` JSONB), code\nholds the default, a stored value overrides, absent falls back to the\ndefault and never breaks deserialization of the rest of the blob.","properties":{"delivered_email_enabled":{"type":"boolean","description":"Send a \"your order was delivered\" email to the order's account email\nwhen a shipment transitions to delivered. Default OFF (additive\nopt-in) — see the struct doc above."}}},"FulfillmentPolicyResponse":{"type":"object","required":["allow_partial_picking","require_reason_for_shortage","require_note_for_shortage","remaining_quantity_behavior","allow_product_substitution","require_approval_for_substitution","allow_mixed_fulfillment","require_approval_for_mixed_fulfillment","allow_manual_picking","allow_barcode_scanning","camera_scanning_optional","allow_over_picking","allow_negative_stock","allow_add_off_order_products","allow_remove_lines","require_note_for_added_product","require_note_for_removed_line","default_picking_view"],"properties":{"allow_add_off_order_products":{"type":"boolean"},"allow_barcode_scanning":{"type":"boolean"},"allow_manual_picking":{"type":"boolean"},"allow_mixed_fulfillment":{"type":"boolean"},"allow_negative_stock":{"type":"boolean"},"allow_over_picking":{"type":"boolean"},"allow_partial_picking":{"type":"boolean"},"allow_product_substitution":{"type":"boolean"},"allow_remove_lines":{"type":"boolean"},"camera_scanning_optional":{"type":"boolean"},"default_picking_view":{"type":"string"},"max_over_pick_percent":{"type":"number","format":"double","description":"Max over-pick allowance in percent on top of the ordered qty\n(e.g. 10.0 = up to 110% of a line's qty). `null` = no cap configured\n(unlimited over-pick when `allow_over_picking` is true). Additive —\nold clients ignore it.","nullable":true},"remaining_quantity_behavior":{"type":"string"},"require_approval_for_mixed_fulfillment":{"type":"boolean"},"require_approval_for_substitution":{"type":"boolean"},"require_note_for_added_product":{"type":"boolean"},"require_note_for_removed_line":{"type":"boolean"},"require_note_for_shortage":{"type":"boolean"},"require_reason_for_shortage":{"type":"boolean"}}},"GeoData":{"type":"object","required":["region","customer_count","total_revenue","avg_order_value"],"properties":{"avg_order_value":{"$ref":"#/components/schemas/BigDecimal"},"customer_count":{"type":"integer","format":"int64"},"region":{"type":"string"},"total_revenue":{"$ref":"#/components/schemas/BigDecimal"}}},"GetProductsByItemNumbersRequest":{"type":"object","required":["item_numbers"],"properties":{"item_numbers":{"type":"array","items":{"type":"string"}}}},"GetProductsByItemNumbersResponse":{"type":"object","required":["products"],"properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/ProductByItemNumber"}}}},"GetSalesReceiptResponse":{"type":"object","description":"Full sales receipt response with items","required":["receipt","items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SalesReceiptItemResponse"}},"receipt":{"$ref":"#/components/schemas/SalesReceiptDetailResponse"}}},"GoodsReceiptLineResponse":{"type":"object","required":["id","line_number","product_id","description","accepted_qty","rejected_qty","damaged_qty","quarantine_qty","purchase_uom","units_per_purchase","unit_cost","tax_amount","line_total"],"properties":{"accepted_qty":{"type":"string"},"damaged_qty":{"type":"string"},"description":{"type":"string"},"discrepancy_reason":{"type":"string","nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"line_number":{"type":"integer","format":"int32"},"line_total":{"type":"string"},"product_id":{"type":"string"},"purchase_order_line_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"purchase_uom":{"type":"string"},"quarantine_qty":{"type":"string"},"rejected_qty":{"type":"string"},"tax_amount":{"type":"string"},"unit_cost":{"type":"string"},"units_per_purchase":{"type":"string"},"variant_id":{"type":"string","nullable":true}}},"GoodsReceiptListResponse":{"type":"object","required":["results","total","limit","offset"],"properties":{"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/GoodsReceiptResponse"}},"total":{"type":"integer","format":"int64"}}},"GoodsReceiptResponse":{"type":"object","required":["id","goods_receipt_number","supplier_id","location_id","status","received_at","currency_code","subtotal","tax_total","freight_total","total","discrepancy_status","version","lines"],"properties":{"currency_code":{"type":"string"},"discrepancy_status":{"type":"string"},"freight_total":{"type":"string"},"goods_receipt_number":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/GoodsReceiptLineResponse"}},"location_id":{"type":"string"},"notes":{"type":"string","nullable":true},"posted_at":{"type":"string","nullable":true},"purchase_order_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"received_at":{"type":"string"},"status":{"type":"string"},"subtotal":{"type":"string"},"supplier_document_number":{"type":"string","nullable":true},"supplier_id":{"$ref":"#/components/schemas/Uuid"},"tax_total":{"type":"string"},"total":{"type":"string"},"version":{"type":"integer","format":"int32"}}},"GoogleAuthRequest":{"type":"object","description":"Google authentication request (login — existing user)","required":["id_token"],"properties":{"id_token":{"type":"string","description":"Google ID token from Google Identity Services"}}},"GoogleAuthResponse":{"type":"object","description":"Google auth response (login/signup)","required":["message","token","is_new_user","user"],"properties":{"is_new_user":{"type":"boolean"},"message":{"type":"string"},"token":{"type":"string"},"user":{"$ref":"#/components/schemas/GoogleAuthUser"}}},"GoogleAuthUser":{"type":"object","description":"Google auth user info","required":["id","username","email","name","role","permissions","company_id","location_ids","has_custom_permissions"],"properties":{"company_id":{"type":"string"},"email":{"type":"string"},"has_custom_permissions":{"type":"boolean"},"id":{"type":"integer","format":"int32"},"location_ids":{"type":"array","items":{"type":"string"}},"name":{"type":"string"},"permissions":{"type":"array","items":{"type":"string"}},"role":{"type":"string"},"username":{"type":"string"}}},"GoogleSignupRequest":{"type":"object","description":"Google signup request (new user registration via Google)","required":["id_token","company_name"],"properties":{"commercial_interest":{"allOf":[{"$ref":"#/components/schemas/crate.features.billing.interest.CommercialInterest"}],"nullable":true},"company_name":{"type":"string","description":"Company name — required because we still need business info"},"id_token":{"type":"string","description":"Google ID token from Google Identity Services"},"utm_campaign":{"type":"string","nullable":true},"utm_medium":{"type":"string","nullable":true},"utm_source":{"type":"string","description":"Optional UTM tracking","nullable":true}}},"GraphValidationErrorResponse":{"type":"object","description":"A single graph-validation failure returned to the caller, describing the\noffending node or edge concretely rather than a bare boolean.","required":["error","issues"],"properties":{"error":{"type":"string"},"issues":{"type":"array","items":{"type":"string"},"description":"Every issue as human-readable text (node-scoped ones prefixed with\nthe node id), graph-level ones included."},"node_issues":{"type":"array","items":{"$ref":"#/components/schemas/NodeIssueResponse"},"description":"The node-scoped subset, structured for canvas highlighting. Absent\nwhen every issue is graph-level.","nullable":true}}},"GridLayoutConfig":{"type":"object","properties":{"columns_large":{"type":"integer","format":"int32"},"columns_small":{"type":"integer","format":"int32"},"page_size":{"type":"integer","format":"int32"},"spacing":{"$ref":"#/components/schemas/CardSpacing"}}},"GrossMarginResponse":{"type":"object","description":"The gross-margin report.","required":["group_by","results","totals","total","limit","offset"],"properties":{"coverage_start":{"type":"string","description":"First costed sale this company has (RFC 3339); sales before it carry\nno cost snapshot and are not in this report.","nullable":true},"group_by":{"$ref":"#/components/schemas/MarginGroupBy"},"limit":{"type":"integer","format":"int64","description":"Page size used."},"offset":{"type":"integer","format":"int64","description":"Offset used."},"results":{"type":"array","items":{"$ref":"#/components/schemas/GrossMarginRow"},"description":"One page of groups, largest revenue first (days: chronological)."},"total":{"type":"integer","format":"int64","description":"Number of groups across all pages."},"totals":{"$ref":"#/components/schemas/GrossMarginTotals"}}},"GrossMarginRow":{"type":"object","description":"One group of the gross-margin report.","required":["key","label","quantity","revenue","revenue_costed","cogs","gross_margin","lines","missing_cost_lines"],"properties":{"cogs":{"type":"string","description":"Cost of goods of the lines that have a known cost."},"detail":{"type":"string","description":"Secondary label (item number for products), if any.","nullable":true},"gross_margin":{"type":"string","description":"`revenue_costed - cogs`."},"key":{"type":"string","description":"Group identity (product id, category id, channel, location id, account id or date)."},"label":{"type":"string","description":"Human label for the group."},"lines":{"type":"integer","format":"int64","description":"Stock-issuing lines in the group."},"margin_percent":{"type":"string","description":"`gross_margin / revenue_costed × 100`, `None` when nothing was costed.","nullable":true},"missing_cost_lines":{"type":"integer","format":"int64","description":"Lines sold with no known cost (excluded from the margin, not counted as zero)."},"quantity":{"type":"string","description":"Net units sold (returns subtract)."},"revenue":{"type":"string","description":"Revenue of every line in the group."},"revenue_costed":{"type":"string","description":"Revenue of the lines that have a known cost (the margin basis)."}}},"GrossMarginTotals":{"type":"object","description":"Totals across every group (all pages).","required":["revenue","revenue_costed","cogs","gross_margin","lines","missing_cost_lines"],"properties":{"cogs":{"type":"string","description":"Total cost of goods."},"gross_margin":{"type":"string","description":"Total gross margin."},"lines":{"type":"integer","format":"int64","description":"Total lines."},"margin_percent":{"type":"string","description":"Overall margin percentage, `None` when nothing was costed.","nullable":true},"missing_cost_lines":{"type":"integer","format":"int64","description":"Total lines without a known cost."},"revenue":{"type":"string","description":"Total revenue."},"revenue_costed":{"type":"string","description":"Revenue with a known cost."}}},"HealthCheckItem":{"type":"object","required":["name","status","message"],"properties":{"latency_ms":{"type":"integer","format":"int64","description":"Round-trip latency for checks that perform I/O (e.g. the DB ping).","nullable":true,"minimum":0},"message":{"type":"string","description":"Human-readable, non-sensitive detail."},"name":{"type":"string","description":"Machine-readable check name, e.g. \"database\", \"email\"."},"status":{"type":"string","description":"One of: \"healthy\" | \"unhealthy\" | \"configured\" | \"`not_configured`\"."}}},"HealthVersion":{"type":"object","required":["api","environment"],"properties":{"api":{"type":"string"},"branch":{"type":"string","nullable":true},"build_time":{"type":"string","nullable":true},"commit":{"type":"string","nullable":true},"environment":{"type":"string"}}},"HeartbeatRequest":{"type":"object","required":["status"],"properties":{"app_version":{"type":"string","nullable":true},"logged_in_user_id":{"type":"integer","format":"int32","nullable":true},"logged_in_user_name":{"type":"string","nullable":true},"status":{"type":"string"}}},"HelpArticleResponse":{"type":"object","required":["id","slug","title","content","category"],"properties":{"app_target":{"type":"string","nullable":true},"category":{"type":"string"},"content":{"type":"string"},"created_at":{"type":"string","nullable":true},"id":{"type":"string"},"is_published":{"type":"boolean","nullable":true},"slug":{"type":"string"},"sort_order":{"type":"integer","format":"int32","nullable":true},"title":{"type":"string"},"updated_at":{"type":"string","nullable":true}}},"HomeSpeakerSettings":{"type":"object","properties":{"enabled":{"type":"boolean","default":true},"muted_emails":{"type":"array","items":{"type":"string"},"description":"Exact email addresses excluded from notifications (including production smoke tests).","default":[]},"payment_enabled":{"type":"boolean","default":true},"payment_sound":{"allOf":[{"$ref":"#/components/schemas/SpeakerSound"}],"default":"cash_register"},"signup_enabled":{"type":"boolean","default":true},"signup_sound":{"allOf":[{"$ref":"#/components/schemas/SpeakerSound"}],"default":"music_box"},"timeout_seconds":{"type":"integer","format":"int64","default":10,"minimum":0}}},"HorecaBillingSettings":{"type":"object","description":"Bill presentation: service charge and tips (POS-ship workstream E).","properties":{"max_tip_percent":{"type":"integer","format":"int32","description":"Hard ceiling on a tip, as a percentage of the goods total, enforced by\n`settle_open_order`. A mistyped `500` on a `50` bill is a chargeback,\nand a tip larger than the bill is nearly always a keying error — but\n\"nearly always\" is why this is a setting and not a constant: a bar\nthat runs a tip jar may legitimately want it higher.\n\nDefault 100 (a tip may equal but not exceed the bill). `0` disables\nthe ceiling entirely."},"service_charge_label":{"type":"string","description":"Line label printed for the service charge."},"service_charge_percent":{"type":"number","format":"double","description":"Percent added to the bill as a service charge. `None` = no service\ncharge, which is the default and the current behaviour.","nullable":true},"tip_enabled":{"type":"boolean","description":"Whether hospitality billing supports tips."},"tip_presets_percent":{"type":"array","items":{"type":"integer","format":"int32"},"description":"Percentages offered as one-tap tip buttons."}}},"HorecaKdsSettings":{"type":"object","description":"Kitchen display refresh and order-aging thresholds.","properties":{"aging_late_seconds":{"type":"integer","format":"int32","description":"Order age in seconds that triggers the late indication."},"aging_warn_seconds":{"type":"integer","format":"int32","description":"Order age in seconds that triggers the warning indication."},"poll_seconds":{"type":"integer","format":"int32","description":"Interval in seconds between kitchen display refreshes."}}},"HorecaKioskSettings":{"type":"object","description":"Kiosk presentation, ordering and payment preferences.","properties":{"idle_timeout_seconds":{"type":"integer","format":"int32","description":"Idle duration in seconds before the kiosk resets its session."},"languages":{"type":"array","items":{"type":"string"},"description":"Languages offered by the kiosk."},"order_number_reset":{"type":"string","description":"'daily' | 'never' — whether the kiosk order number counter resets\nat midnight (design §3.1 \"daily-reset rule configurable\")."},"payment_modes":{"type":"array","items":{"type":"string"},"description":"Payment modes offered for kiosk orders."},"theme":{"$ref":"#/components/schemas/HorecaKioskTheme"},"tip_prompt_enabled":{"type":"boolean","description":"Whether the kiosk displays a tip prompt."},"upsell_category_id":{"type":"string","description":"Optional category used for kiosk upsell suggestions.","nullable":true}}},"HorecaKioskTheme":{"type":"object","description":"Optional kiosk branding and attract-screen media.","properties":{"attract_media_urls":{"type":"array","items":{"type":"string"},"description":"Media URLs displayed by the kiosk attract screen."},"logo_url":{"type":"string","description":"Optional kiosk logo URL.","nullable":true},"primary_color":{"type":"string","description":"Optional kiosk primary colour override.","nullable":true}}},"HorecaOfflineSettings":{"type":"object","description":"What the till may do with tables while the cloud is unreachable\n(POS-ship workstream E; audit 05 §5(f)).\n\nOpen orders are cloud-authoritative on purpose: inventing a local\nopen-order id and reconciling it later is how double-charges happen. So\noffline the READ path degrades to a cache and the WRITE path is blocked\nloudly.","properties":{"allow_held_fallback":{"type":"boolean","description":"Allow the cashier to fall back to a local HELD retail receipt labelled\nwith the table, to be settled when the cloud returns. The table never\nbecomes an `open_order`; the kitchen gets no ticket (printing is\nserver-routed), which is why this is honest rather than a silent\ndegradation."},"cache_ttl_seconds":{"type":"integer","format":"int32","description":"How long a cached floor-plan snapshot is still shown (greyed, with a\n\"last seen\" banner) before the till stops offering it at all."}}},"HorecaOrderingSettings":{"type":"object","description":"Hospitality ordering preferences shared by kiosk, QR, waiter and kitchen surfaces.\n\nHoreca Ordering Suite (kiosk/QR/waiter/KDS) tunables — design doc §7.\nEvery default below reproduces a `// SETTINGS(horeca_ordering...)`-marked\nhardcoded constant this group replaces (see\n`features::horeca::public_handlers::MAX_ORDER_LINES`/`MAX_QTY_PER_LINE`\ndocs — those two are the only ones an existing handler actually reads\ntoday; the rest are forward-declared for the surfaces that consume them\n(kiosk/service/store apps) once those wire up to this group).\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level default only fires when the WHOLE group is missing).","properties":{"billing":{"$ref":"#/components/schemas/HorecaBillingSettings"},"enabled":{"type":"boolean","description":"Master switch for the whole horeca ordering surface (POS-ship\nworkstream E).\n\nDefaults to **false** so that a stored blob written before this field\nexisted — i.e. every current tenant — keeps behaving exactly as it does\ntoday, and, crucially, so a RETAIL client never sees a floor plan, a\ntables screen or a course picker appear on their till after an update.\nTurning horeca on is a deliberate act."},"kds":{"$ref":"#/components/schemas/HorecaKdsSettings"},"kiosk":{"$ref":"#/components/schemas/HorecaKioskSettings"},"offline":{"$ref":"#/components/schemas/HorecaOfflineSettings"},"pos":{"$ref":"#/components/schemas/HorecaPosSettings"},"pos_inbox_poll_seconds":{"type":"integer","format":"int32","description":"Desktop POS incoming-orders panel poll interval (design §4.4)."},"qr":{"$ref":"#/components/schemas/HorecaQrSettings"},"routing":{"$ref":"#/components/schemas/HorecaRoutingSettings"},"voids":{"$ref":"#/components/schemas/HorecaVoidSettings"},"waiter":{"$ref":"#/components/schemas/HorecaWaiterSettings"}}},"HorecaPosSettings":{"type":"object","description":"Till-side horeca behaviour (POS-ship workstream E). Every value here was a\nhardcoded constant or an unwritten assumption before.","properties":{"allow_takeaway":{"type":"boolean","description":"Offer \"New takeaway\" on the tables screen."},"block_z_close_with_open_tables":{"type":"boolean","description":"Block closing a Z report while non-terminal orders exist. When false,\nthe till still LISTS the open tables — it just doesn't stop the close."},"default_course":{"type":"string","description":"Course pre-selected when a line is added. Empty = use\n`waiter.default_course`."},"floor_poll_seconds":{"type":"integer","format":"int32","description":"How often the till re-polls floor-plan state, in seconds."},"require_manager_for_void":{"type":"boolean","description":"Require manager approval before voiding a line that has already been\nsent to the kitchen."},"show_running_total":{"type":"boolean","description":"Show a running total on each occupied table tile. Some operators\nconsider the amount on a wall-mounted screen a privacy problem."}}},"HorecaQrSettings":{"type":"object","description":"Preferences for customer ordering through table QR codes.","properties":{"auto_accept":{"type":"boolean","description":"Whether submitted QR orders are accepted automatically."},"enabled":{"type":"boolean","description":"Whether ordering through table QR codes is enabled."},"mode":{"type":"string","description":"'order' | '`menu_only`'."},"payment_modes":{"type":"array","items":{"type":"string"},"description":"Payment modes offered for QR orders."},"require_name":{"type":"boolean","description":"Whether QR ordering requires a customer name."},"status_poll_seconds":{"type":"integer","format":"int32","description":"Interval in seconds between order-status refreshes."}}},"HorecaRoutingSettings":{"type":"object","description":"Routing preferences for hospitality orders.","properties":{"auto_accept_sources":{"type":"array","items":{"type":"string"},"description":"Sources (pos|waiter|kiosk|qr|storefront) that skip the `accepted`\nstep and go straight from `placed` to `in_prep` (design §3.2 \"accepted\nis skipped when tenant config says auto-accept (per source)\"). Empty\nby default — every order needs an explicit staff accept."},"max_order_lines":{"type":"integer","format":"int32","description":"Max distinct line items a single QR/kiosk order may contain.\nReproduces the pre-existing hardcoded\n`public_handlers::MAX_ORDER_LINES` constant."},"max_qty_per_line":{"type":"integer","format":"int32","description":"Max qty for a single line item. Reproduces the pre-existing hardcoded\n`public_handlers::MAX_QTY_PER_LINE` constant."},"pos_inbox_enabled":{"type":"boolean","description":"Whether hospitality orders are routed to the POS inbox."}}},"HorecaVoidSettings":{"type":"object","description":"Void policy (POS-ship workstream E; audit 05 finding B3).","properties":{"allow_after_served":{"type":"boolean","description":"Allow voiding a line already marked `served`. Defaults to **true** to\npreserve the permissiveness of the pre-existing hard-delete path (which\nhad no guard at all); operators who want served lines frozen turn it\noff."},"reasons":{"type":"array","items":{"type":"string"},"description":"Reasons offered as one-tap buttons on the till's void dialog. A free\ntext reason is always allowed as well."}}},"HorecaWaiterSettings":{"type":"object","description":"Waiter ordering, course and floor-view preferences.","properties":{"course_labels":{"type":"array","items":{"type":"string"},"description":"Course labels offered to waiting staff."},"default_course":{"type":"string","description":"Course assigned when no different course is selected."},"floor_poll_seconds":{"type":"integer","format":"int32","description":"Interval in seconds between floor-view refreshes."},"require_pin":{"type":"boolean","description":"Whether waiter access requires a staff PIN."}}},"HybridProductSearchRequest":{"type":"object","required":["query"],"properties":{"category_id":{"type":"string","nullable":true},"facets":{"nullable":true},"has_images":{"type":"boolean","description":"When true, only return products that have at least one non-deleted image."},"in_stock_only":{"type":"boolean"},"include_excluded":{"type":"boolean"},"language":{"type":"string","nullable":true},"limit":{"type":"integer","format":"int64","nullable":true},"mode":{"type":"string","nullable":true},"offset":{"type":"integer","format":"int64","nullable":true},"query":{"type":"string"}}},"HybridProductSearchResponse":{"type":"object","required":["items","total_count","facet_counts"],"properties":{"facet_counts":{},"items":{"type":"array","items":{"$ref":"#/components/schemas/HybridProductSearchResultItem"}},"total_count":{"type":"integer","format":"int64"}}},"HybridProductSearchResultItem":{"type":"object","required":["product","score","keyword_score","vector_score","reasons"],"properties":{"keyword_score":{"type":"number","format":"double"},"product":{"$ref":"#/components/schemas/ProductWithImagesResponse"},"reasons":{"type":"array","items":{"type":"string"}},"score":{"type":"number","format":"double"},"vector_score":{"type":"number","format":"double"}}},"IdsRequest":{"type":"object","description":"Body for `POST /api/query/{entity}/ids`.","required":["query"],"properties":{"limit":{"type":"integer","format":"int64","description":"Page size; clamped to a hard ceiling.","nullable":true},"offset":{"type":"integer","format":"int64","description":"Row offset, for keyset-style paging over large selections.","nullable":true},"query":{"type":"object","description":"The filter tree selecting the products."}}},"IllegalTransitionResponse":{"type":"object","description":"Body for a 422 illegal-transition response (design §3.2: \"illegal\ntransitions → 422 with `allowed_next`\").","required":["error","current_status","allowed_next"],"properties":{"allowed_next":{"type":"array","items":{"type":"string"}},"current_status":{"type":"string"},"error":{"type":"string"}}},"ImageDocumentListResponse":{"type":"object","required":["items","total_count","page","page_size"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ImageDocumentResponse"}},"page":{"type":"integer","format":"int64"},"page_size":{"type":"integer","format":"int64"},"total_count":{"type":"integer","format":"int64","description":"Total matching documents across all pages."}}},"ImageDocumentResponse":{"type":"object","required":["id","company_id","name","scene_json","source_refs","version","created_at","updated_at"],"properties":{"company_id":{"type":"string","description":"Varchar passthrough — `company_id` is a plain string column (legacy\ncompanies use non-UUID ids), same as `digital_display::DisplayResponse`."},"created_at":{"$ref":"#/components/schemas/DateTime"},"created_by":{"type":"integer","format":"int32","nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"name":{"type":"string"},"output_image_id":{"type":"string","nullable":true},"product_id":{"type":"string","nullable":true},"scene_json":{"type":"object","description":"fabric.js scene serialization."},"source_refs":{"type":"object","description":"Which product images / assets the scene was built from."},"updated_at":{"$ref":"#/components/schemas/DateTime"},"version":{"type":"integer","format":"int32"}}},"ImageEditorSettings":{"type":"object","description":"Image-editor tuning (image editor, Phase 0) — admin-configurable per\ncompany; code holds the defaults. Nothing here is a hardcoded policy\nconstant, so a tenant can be retuned without a redeploy.\n\nEvery non-Option field carries a `#[serde(default = ...)]`: a stored\n`webapp_company.settings` blob written before this group existed simply has\nno `image_editor` key, and a single missing key would otherwise fail the\nWHOLE blob and silently reset every unrelated setting to its default.","properties":{"brand_colors":{"type":"array","items":{"type":"string"},"description":"Brand colours offered in the editor's swatches. Empty → the generic\npalette. Present-null tolerated so a cleared field cannot fail the blob."},"brand_fonts":{"type":"array","items":{"type":"string"},"description":"Font families offered for text objects. Empty → the built-in stack."},"default_canvas_preset":{"type":"string","description":"Canvas preset selected on open. Default `square` — the catalog shape."},"default_export_format":{"type":"string","description":"Export format when the user does not pick one: `png` | `jpeg` | `webp`.\nDefault `png` (lossless, and preserves transparency after a background\nremoval — a JPEG default would silently flatten cutouts onto black)."},"enable_background_removal":{"type":"boolean","description":"Whether the in-browser background remover is offered. Default true: it\nruns locally (`BiRefNet` via transformers.js), costs no credits and makes\nno outbound API call, so there is no spend reason to disable it."},"jpeg_quality":{"type":"integer","format":"int32","description":"JPEG/WebP quality, 1–100. Ignored for PNG. Default 90."},"max_canvas_px":{"type":"integer","format":"int32","description":"Largest canvas edge, in pixels. Bounds both memory use in the browser\nand the size of what gets uploaded back. Default 4096."},"max_layers":{"type":"integer","format":"int32","description":"Maximum objects on one canvas. A runaway scene is slow to render and\nslow to serialize. Default 100."},"max_source_images":{"type":"integer","format":"int32","description":"Maximum source images that may be pulled into a single document.\nDefault 20."}}},"ImageExistenceResult":{"type":"object","required":["item_number","has_images"],"properties":{"has_images":{"type":"boolean"},"item_number":{"type":"string"},"product_id":{"type":"string","nullable":true}}},"ImageNamingPattern":{"type":"object","description":"Saved filename pattern used to match imported images to products.","required":["id","name","pattern","match_field","created_at","updated_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp stored with the pattern."},"id":{"type":"string","description":"Identifier of the saved image pattern."},"is_default":{"type":"boolean","description":"Pre-selected in the image importer when the page loads. At most one\npattern should carry this flag; the admin UI enforces exclusivity.\nSerde-defaulted so patterns stored before this field existed still load."},"match_field":{"type":"string","description":"Which product field to match against: \"`item_number`\", \"code\", or \"name\""},"name":{"type":"string","description":"Display name used to select the pattern."},"pattern":{"type":"string","description":"The pattern template, e.g. \"{`ITEM_NUMBER`}_Def\" or \"{`ITEM_NUMBER`}_*\""},"updated_at":{"type":"string","description":"Most recent modification timestamp stored with the pattern."}}},"ImageStored":{"type":"object","required":["url","key","width","height","bytes"],"properties":{"bytes":{"type":"integer","format":"int64"},"height":{"type":"integer","format":"int32","minimum":0},"key":{"type":"string"},"url":{"type":"string"},"width":{"type":"integer","format":"int32","minimum":0}}},"ImapConnectionInput":{"type":"object","required":["host","port","username","app_password"],"properties":{"app_password":{"type":"string","description":"Sealed at rest (D-013) — never returned by any endpoint."},"host":{"type":"string"},"port":{"type":"integer","format":"int32","minimum":0},"username":{"type":"string"}}},"ImpersonateResponse":{"type":"object","required":["token","user","impersonated"],"properties":{"impersonated":{"type":"boolean","description":"Always true — flags this as an operator-minted impersonation session."},"token":{"type":"string","description":"A tenant-user JWT for the target user. Use as a normal bearer token."},"user":{"$ref":"#/components/schemas/ImpersonatedUser"}}},"ImpersonatedUser":{"type":"object","required":["id","email","name","company_id"],"properties":{"company_id":{"type":"string"},"email":{"type":"string"},"id":{"type":"integer","format":"int32"},"name":{"type":"string"}}},"ImportAttributeCount":{"type":"object","description":"Change count for a single attribute (e.g. `regular_price` → 812).","required":["attribute","count"],"properties":{"attribute":{"type":"string"},"count":{"type":"integer","format":"int64"}}},"ImportChangeListResponse":{"type":"object","required":["items","total"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ImportChangeRow"}},"total":{"type":"integer","format":"int64","description":"Total rows matching the filter, for the pager."}}},"ImportChangeRow":{"type":"object","description":"One field-level change: item, attribute, old → new.","required":["id","item_number","description","attribute","old_value","new_value"],"properties":{"attribute":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"item_number":{"type":"string"},"new_value":{"type":"string"},"old_value":{"type":"string"}}},"ImportContactsRequest":{"type":"object","required":["csv"],"properties":{"csv":{"type":"string","description":"CSV with a header row. Recognised columns: `first_name`, `last_name`\n(required), `primary_email`, `primary_phone`, `job_title`. ≤5 `MiB`, ≤100k\nrows per run."}}},"ImportContactsResponse":{"type":"object","required":["import_id","status"],"properties":{"import_id":{"$ref":"#/components/schemas/Uuid"},"status":{"type":"string"}}},"ImportError":{"type":"object","required":["message"],"properties":{"line":{"type":"integer","format":"int32","nullable":true},"message":{"type":"string"}}},"ImportItemCount":{"type":"object","description":"A product and how many of its fields the run changed.","required":["item_number","description","change_count"],"properties":{"change_count":{"type":"integer","format":"int64"},"description":{"type":"string"},"item_number":{"type":"string"}}},"ImportRunListResponse":{"type":"object","required":["items","total"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ImportRunSummary"}},"total":{"type":"integer","format":"int64","description":"Total runs matching the filter, for the pager."}}},"ImportRunReportResponse":{"type":"object","description":"Everything the report header needs in one request.","required":["run","attribute_breakdown","top_items","errors","detail_available"],"properties":{"attribute_breakdown":{"type":"array","items":{"$ref":"#/components/schemas/ImportAttributeCount"},"description":"Changes grouped by attribute, biggest first."},"detail_available":{"type":"boolean","description":"False when the company has `store_import_reports` off — the UI then\nexplains why there is no detail instead of showing an empty table."},"errors":{"type":"array","items":{"$ref":"#/components/schemas/ImportError"}},"run":{"$ref":"#/components/schemas/ImportRunSummary"},"top_items":{"type":"array","items":{"$ref":"#/components/schemas/ImportItemCount"},"description":"Products with the most changed fields in this run."}}},"ImportRunSummary":{"type":"object","description":"One import run, as shown in the reports list and at the top of a report.","required":["id","upload_id","file_name","file_size","import_type","status","total_records","successful","failed","warnings","changes_count","changes_truncated","created_at","notified_emails","user_id"],"properties":{"changes_count":{"type":"integer","format":"int32","description":"Real number of field-level changes the run produced."},"changes_truncated":{"type":"boolean","description":"True when stored detail was capped by `report_max_change_rows`, so the\nUI can say \"showing first N of M\" instead of under-reporting."},"completed_at":{"type":"string","nullable":true},"created_at":{"type":"string"},"duration_seconds":{"type":"integer","format":"int64","description":"Wall-clock seconds from start to completion; None while still running.","nullable":true},"enrichment_batch_id":{"type":"string","description":"Enrichment batch queued by the import→enrich handoff, when opted in.","nullable":true},"failed":{"type":"integer","format":"int32"},"file_name":{"type":"string"},"file_size":{"type":"integer","format":"int64"},"id":{"type":"string","description":"History row UUID — the id used in report URLs."},"import_type":{"type":"string","description":"Which pipeline produced the run: \"products\", \"stock\", \"images\"."},"notified_emails":{"type":"array","items":{"type":"string"},"description":"Recipients the completion email reached (empty when none was sent)."},"source_system":{"type":"string","description":"Source-system label from the upload, when the caller supplied one.","nullable":true},"status":{"type":"string"},"successful":{"type":"integer","format":"int32"},"total_records":{"type":"integer","format":"int32"},"upload_id":{"type":"string","description":"Upload session id; also accepted in place of `id` when fetching a run."},"user_id":{"type":"integer","format":"int32"},"user_name":{"type":"string","description":"Display name of the user who ran the import.","nullable":true},"warnings":{"type":"integer","format":"int32"}}},"ImportSettings":{"type":"object","description":"Per-company import tuning. Stored inside the company settings JSON; absent\nconfig falls back to `Default` and never breaks.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`.\nOld companies already have `{\"bulk_row_limit\": N}` persisted; without a\nper-field default, deserializing those rows would fail once a new field\nexists (the group-level `#[serde(default)]` only fires when the WHOLE group\nis missing).","properties":{"bulk_row_limit":{"type":"integer","format":"int32","description":"Max rows accepted per bulk request (accounts/categories bulk-import and\nexternal-references bulk upsert)."},"email_attach_changelist":{"type":"boolean","description":"Attach the full change list as a CSV file to the import-completion\nemail. Default true."},"email_attachment_max_rows":{"type":"integer","format":"int32","description":"Max rows written to the CSV attachment (SES has a ~10 MB raw-message\nlimit). Beyond this the CSV is truncated with a trailing note. Default\n50000."},"email_max_inline_rows":{"type":"integer","format":"int32","description":"Max change rows rendered inline in the import-completion email before\nthe rest is truncated to the attachment. Keeps the email small enough\nnot to crash mail clients. Default 100."},"email_send_on_automated_imports":{"type":"boolean","description":"Send per-upload completion emails for automated connector imports.\nConnectors process large syncs as many bounded upload chunks, so enabling\nthis can intentionally produce one email per chunk. Default false; the\nconnector run history remains the aggregate operational record."},"email_send_on_zero_changes":{"type":"boolean","description":"Send the completion email even when an import produced zero real\nchanges (and no errors). Default false — silence no-op runs."},"price_decimal_places":{"type":"integer","format":"int32","description":"Decimal places prices are rounded to on ingest. Rounding on import is\nwhat stops sub-cent churn (e.g. a source emitting 9.386 rewriting a\nstored 9.39 on every run). Default 2 (currency minor unit)."},"price_min_change":{"type":"number","format":"double","description":"Optional extra suppression: after rounding, ignore a price change whose\nabsolute delta is below this value. 0 disables it (rounding alone\ngoverns). Applies to regular/wholesale/msrp price fields."},"price_rounding_mode":{"type":"string","description":"Rounding strategy applied at `price_decimal_places`: \"`half_up`\" (retail\nconvention, default) or \"`half_even`\" (banker's rounding)."},"report_max_change_rows":{"type":"integer","format":"int32","description":"Max change rows persisted per run. Beyond this the run is marked\n`changes_truncated` and the UI says so rather than under-reporting.\nGuards against a 65k-product full-resync writing millions of rows.\nDefault 50000 (matches the CSV attachment cap)."},"report_retention_days":{"type":"integer","format":"int32","description":"Days of import-report detail to keep. A sweep after each import deletes\nchange rows older than this. 0 disables the sweep (keep forever).\nDefault 90."},"store_import_reports":{"type":"boolean","description":"Persist the per-row change list to `inventory_import_changes` so the\nadmin Import Reports page can show what an import actually changed.\nOff = counts and errors only (the email still works). Default true."}}},"ImportTemplate":{"type":"object","description":"Named field mapping reused for a company import.","required":["id","name","import_type","mapping","created_at","updated_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp stored with the template."},"description":{"type":"string","description":"Optional explanation of when to use this mapping.","nullable":true},"id":{"type":"string","description":"Identifier of the saved mapping template."},"import_type":{"type":"string","description":"Import category associated with this template."},"mapping":{"type":"object","description":"Mapping from destination field names to source column names.","additionalProperties":{"type":"string"}},"name":{"type":"string","description":"Display name used to select the template."},"updated_at":{"type":"string","description":"Most recent modification timestamp stored with the template."}}},"ImportTemplates":{"type":"object","description":"Saved import mappings grouped by the kind of data being imported.","required":["products","stock_updates","images"],"properties":{"images":{"type":"array","items":{"$ref":"#/components/schemas/ImportTemplate"},"description":"Templates for importing product images."},"products":{"type":"array","items":{"$ref":"#/components/schemas/ImportTemplate"},"description":"Templates for importing product records."},"stock_updates":{"type":"array","items":{"$ref":"#/components/schemas/ImportTemplate"},"description":"Templates for updating stock quantities."}}},"InboxActionResponse":{"type":"object","description":"Outcome of a reprocess / resolve / discard action on an inbox row.","required":["id","status"],"properties":{"error":{"type":"string","nullable":true},"error_code":{"allOf":[{"$ref":"#/components/schemas/QuarantineCode"}],"nullable":true},"id":{"type":"string"},"sales_order_id":{"type":"string","nullable":true},"status":{"$ref":"#/components/schemas/InboxStatus"}}},"InboxStatus":{"type":"string","description":"Lifecycle status of a `mobile_order_inbox` row (order-durability §B1).\nMirrors the DB CHECK constraint — keep both in sync.","enum":["received","processed","quarantined","duplicate","resolved","discarded"]},"InitializeOpeningCostRequest":{"type":"object","required":["location_id","product_id","expected_stock_quantity","unit_cost"],"properties":{"expected_stock_quantity":{"type":"string"},"location_id":{"type":"string"},"notes":{"type":"string","nullable":true},"product_id":{"type":"string"},"unit_cost":{"type":"string"},"variant_id":{"type":"string","nullable":true}}},"InitializeOpeningCostResponse":{"type":"object","required":["operation_id","location_id","product_id","quantity_on_hand","average_unit_cost","inventory_value","cost_version","accounting_event_created"],"properties":{"accounting_event_created":{"type":"boolean"},"average_unit_cost":{"type":"string"},"cost_version":{"type":"integer","format":"int64"},"inventory_value":{"type":"string"},"location_id":{"type":"string"},"operation_id":{"$ref":"#/components/schemas/Uuid"},"product_id":{"type":"string"},"quantity_on_hand":{"type":"string"},"variant_id":{"type":"string","nullable":true}}},"InternalOfferRevision":{"type":"object","required":["id","publication","terms","manifest","marketing","provider_prices"],"properties":{"id":{"$ref":"#/components/schemas/Uuid"},"manifest":{"$ref":"#/components/schemas/OfferManifest"},"marketing":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/OfferCopy"}},"plan_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"provider_prices":{"type":"object","additionalProperties":{"type":"string"}},"publication":{"$ref":"#/components/schemas/OfferPublication"},"terms":{"$ref":"#/components/schemas/OfferTerms"}}},"InventoryCostExceptionRow":{"type":"object","description":"One `inventory_cost_exception` queued by the cost ledger.","required":["id","created_at","reason","source_type","source_id"],"properties":{"created_at":{"type":"string","description":"When the exception was queued (RFC 3339)."},"id":{"$ref":"#/components/schemas/Uuid"},"item_number":{"type":"string","description":"Product item number, when the product still exists.","nullable":true},"location_id":{"type":"string","description":"Location involved, when recorded.","nullable":true},"location_name":{"type":"string","description":"Current location name, when the location still exists.","nullable":true},"product_id":{"type":"string","description":"Product involved, when recorded.","nullable":true},"product_name":{"type":"string","description":"Current product name, when the product still exists.","nullable":true},"quantity":{"type":"integer","format":"int64","description":"Units involved, when recorded.","nullable":true},"reason":{"type":"string","description":"e.g. `missing_inventory_cost`, `oversold_cost_ledger`,\n`return_without_cost`, `adjustment_without_cost`."},"source_id":{"type":"string","description":"Document id that raised it."},"source_type":{"type":"string","description":"Document type that raised it (e.g. `sales_receipt`)."}}},"InventoryCostExceptionsResponse":{"type":"object","description":"A page of cost exceptions, newest first.","required":["items","total","limit","offset"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/InventoryCostExceptionRow"},"description":"The page."},"limit":{"type":"integer","format":"int64","description":"Page size used."},"offset":{"type":"integer","format":"int64","description":"Offset used."},"total":{"type":"integer","format":"int64","description":"Matching exceptions across all pages."}}},"InventoryCostingSettings":{"type":"object","description":"Company perpetual inventory costing policy.\n\nPer-company perpetual inventory-costing policy (E-3.0, G024\nreconciliation). Drives `features::procurement::cost_ledger` and\n`features::inventory_counts::service`. The only supported `method` today\nis `\"weighted_average\"` (see `features::procurement::costing`); the field\nstays a string so a future costing method is additive, not a breaking\nenum change. Stored inside the company settings JSON; absent config falls\nback to `Default` (costing OFF) so existing tenants are unaffected until\nthey opt in.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`.","properties":{"allow_negative_inventory":{"type":"boolean","description":"Allow transfers and supplier returns to drive the cost ledger's\nquantity/value negative instead of rejecting them. Sales are never\nblocked by the cost ledger: an oversold sale posts at the current\naverage and is flagged as a cost exception either way."},"block_sales_without_cost":{"type":"boolean","description":"Reject a sale issue when no cost state exists yet for the product\n(i.e. it was never received or given an opening balance)."},"direct_quantity_edits":{"type":"string","description":"What a direct quantity change (product form, inventory import) does\nwhile costing is on: `\"allow_costed\"` posts it to the cost ledger at\nthe current average; `\"block\"` refuses it, so stock only moves through\nreceipts, counts and transfers. Ignored while costing is off."},"enabled":{"type":"boolean","description":"Master switch: track perpetual weighted-average cost and post COGS on\nsale. Default OFF — existing tenants keep today's behavior (no cost\ntracking) until they opt in (F-5)."},"method":{"type":"string","description":"Costing method. Only `\"weighted_average\"` is implemented."},"money_scale":{"type":"integer","format":"int64","description":"Decimal scale (places) money amounts are rounded to inside the cost\nledger."},"require_original_sale_location_for_returns":{"type":"boolean","description":"Require a customer return to name the original sale's location as its\nrestock location (prevents a return silently restocking a different\nstore's cost layers). Default OFF — existing tenants are unaffected."}}},"InventoryCountLineResponse":{"type":"object","required":["id","product_id","item_number","product_name","version"],"properties":{"baseline_quantity":{"type":"string","nullable":true},"counted_at":{"type":"string","nullable":true},"counted_by_user_id":{"type":"integer","format":"int32","nullable":true},"counted_quantity":{"type":"string","nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"item_number":{"type":"string"},"product_id":{"type":"string"},"product_name":{"type":"string"},"unit_cost":{"type":"string","nullable":true},"variance_quantity":{"type":"string","nullable":true},"variance_value":{"type":"string","nullable":true},"variant_id":{"type":"string","nullable":true},"version":{"type":"integer","format":"int64"}}},"InventoryCountListResponse":{"type":"object","required":["results","total","limit","offset"],"properties":{"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/InventoryCountResponse"}},"total":{"type":"integer","format":"int64"}}},"InventoryCountResponse":{"type":"object","required":["id","company_id","location_id","count_number","status","blind_count","version","created_by_user_id","created_at","updated_at","total_lines","counted_lines","variance_lines","total_variance_value","lines"],"properties":{"approved_at":{"type":"string","nullable":true},"approved_by_user_id":{"type":"integer","format":"int32","nullable":true},"blind_count":{"type":"boolean"},"cancelled_at":{"type":"string","nullable":true},"company_id":{"type":"string"},"count_number":{"type":"string"},"counted_lines":{"type":"integer","minimum":0},"created_at":{"type":"string"},"created_by_user_id":{"type":"integer","format":"int32"},"id":{"$ref":"#/components/schemas/Uuid"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/InventoryCountLineResponse"}},"location_id":{"type":"string"},"notes":{"type":"string","nullable":true},"posted_at":{"type":"string","nullable":true},"posted_by_user_id":{"type":"integer","format":"int32","nullable":true},"status":{"type":"string"},"submitted_at":{"type":"string","nullable":true},"submitted_by_user_id":{"type":"integer","format":"int32","nullable":true},"total_lines":{"type":"integer","minimum":0},"total_variance_value":{"type":"string"},"updated_at":{"type":"string"},"variance_lines":{"type":"integer","minimum":0},"version":{"type":"integer","format":"int64"}}},"InventoryCountSettings":{"type":"object","description":"Company physical inventory count policy.\n\nPer-company physical-inventory count policy (E-3.0, G024 reconciliation).\nStored inside the company settings JSON; absent config falls back to\n`Default` (zero tolerance, no forced approval) so existing tenants are\nunaffected.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`.","properties":{"blind_count_default":{"type":"boolean","description":"New count sessions default to blind counting (counters cannot see the\nsystem quantity while counting)."},"quantity_variance_approval_threshold":{"type":"string","description":"Decimal string quantity variance tolerated before a count line\nrequires approval."},"require_approval_for_variance":{"type":"boolean","description":"Require manager approval before posting a count session with a\nvariance outside tolerance."},"value_variance_approval_threshold":{"type":"string","description":"Decimal string value variance tolerated before a count line requires\napproval."}}},"InventoryCountTransitionRequest":{"type":"object","required":["expected_version"],"properties":{"expected_version":{"type":"integer","format":"int64"},"notes":{"type":"string","nullable":true}}},"InventoryHistoryItem":{"type":"object","required":["id","upload_id","file_name","file_size","total_records","successful","failed","warnings","status","created_at","error_summary"],"properties":{"completed_at":{"type":"string","nullable":true},"created_at":{"type":"string"},"error_summary":{"type":"array","items":{"$ref":"#/components/schemas/ImportError"}},"failed":{"type":"integer","format":"int32"},"file_name":{"type":"string"},"file_size":{"type":"integer","format":"int64"},"id":{"type":"string"},"status":{"type":"string"},"successful":{"type":"integer","format":"int32"},"total_records":{"type":"integer","format":"int32"},"upload_id":{"type":"string"},"warnings":{"type":"integer","format":"int32"}}},"InventoryHistoryResponse":{"type":"object","required":["history"],"properties":{"history":{"type":"array","items":{"$ref":"#/components/schemas/InventoryHistoryItem"}}}},"InventoryImportResult":{"type":"object","required":["upload_id","status","total_processed","successful","failed","errors","processing_time_seconds","completed_at"],"properties":{"completed_at":{"type":"string"},"errors":{"type":"array","items":{"type":"string"}},"failed":{"type":"integer","format":"int64","minimum":0},"processing_time_seconds":{"type":"number","format":"double"},"status":{"type":"string"},"successful":{"type":"integer","format":"int64","minimum":0},"total_processed":{"type":"integer","format":"int64","minimum":0},"upload_id":{"type":"string"}}},"InventoryProgressResponse":{"type":"object","required":["upload_id","status","processed_records","total_records","percentage_complete","estimated_time_remaining","errors","last_updated"],"properties":{"errors":{"type":"array","items":{"type":"string"}},"estimated_time_remaining":{"type":"integer","format":"int64","minimum":0},"last_updated":{"type":"string"},"percentage_complete":{"type":"number","format":"double"},"processed_records":{"type":"integer","format":"int64","minimum":0},"status":{"type":"string"},"total_records":{"type":"integer","format":"int64","minimum":0},"upload_id":{"type":"string"}}},"InventoryReport":{"type":"object","required":["total_products","total_value","low_stock_count","out_of_stock_count","categories_breakdown","categories_total"],"properties":{"categories_breakdown":{"type":"array","items":{"$ref":"#/components/schemas/CategoryBreakdown"}},"categories_total":{"type":"integer","format":"int64"},"low_stock_count":{"type":"integer","format":"int64"},"out_of_stock_count":{"type":"integer","format":"int64"},"total_products":{"type":"integer","format":"int64"},"total_value":{"$ref":"#/components/schemas/BigDecimal"}}},"InventoryReservationSettings":{"type":"object","description":"Company stock reservation policy for sales orders.\n\nPer-company stock reservation (\"committed stock\") tuning. A sales order\nreserves its quantities at creation; the reservation is consumed when\npicking completes and released on cancel/delete. Available-to-sell =\n`qty - reserved_qty`. Stored inside the company settings JSON; absent\nconfig falls back to `Default` and never breaks.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level default only fires when the WHOLE group is missing).","properties":{"block_orders_exceeding_available":{"type":"boolean","description":"Validate new orders against available (qty - reserved) instead of raw\nqty. Still subordinate to the existing overselling flags\n(`portal_settings.allow_out_of_stock_orders`,\n`fulfillment_policies.allow_negative_stock`). Default ON."},"enabled":{"type":"boolean","description":"Master switch: reserve stock when a sales order is created. Default ON."},"show_reserved_on_mobile":{"type":"boolean","description":"Ship reserved quantities to mobile/POS devices over sync so catalogues\ncan show \"N reserved\" next to stock. Default ON."}}},"InventoryUploadFileForm":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"CSV file to import."}}},"InventoryUploadRequest":{"type":"object","required":["file_name","file_size","content_type"],"properties":{"content_type":{"type":"string"},"enrich_after_import":{"type":"boolean","description":"When true, products created/updated by this import are queued into an\nenrichment batch after the import completes (import → enrich handoff)."},"file_name":{"type":"string"},"file_size":{"type":"integer","format":"int64","minimum":0},"source_system":{"type":"string","description":"Optional source-system label (e.g. \"`legacy_pos`\", \"`exact_online`\").\nWhen set, rows carrying an `external_id` column are matched through the\n`external_references` identity map before falling back to `item_number`.","nullable":true}}},"InventoryUploadResponse":{"type":"object","required":["upload_id","upload_url","estimated_time_seconds"],"properties":{"estimated_time_seconds":{"type":"integer","format":"int64","minimum":0},"upload_id":{"type":"string"},"upload_url":{"type":"string"}}},"InventoryValuationItem":{"type":"object","required":["location_id","location_name","product_id","product_name","item_number","stock_quantity","cost_quantity","quantity_variance","average_unit_cost","inventory_value","expected_value","value_variance","valuation_status"],"properties":{"average_unit_cost":{"type":"string"},"cost_quantity":{"type":"string"},"cost_version":{"type":"integer","format":"int64","nullable":true},"costing_method":{"type":"string","nullable":true},"expected_value":{"type":"string"},"inventory_value":{"type":"string"},"item_number":{"type":"string"},"location_id":{"type":"string"},"location_name":{"type":"string"},"product_id":{"type":"string"},"product_name":{"type":"string"},"quantity_variance":{"type":"string"},"stock_quantity":{"type":"string"},"updated_at":{"type":"string","nullable":true},"valuation_status":{"type":"string"},"value_variance":{"type":"string"},"variant_id":{"type":"string","nullable":true}}},"InventoryValuationResponse":{"type":"object","required":["results","summary","total","limit","offset","as_of"],"properties":{"as_of":{"type":"string"},"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/InventoryValuationItem"}},"summary":{"$ref":"#/components/schemas/InventoryValuationSummary"},"total":{"type":"integer","format":"int64"}}},"InventoryValuationSummary":{"type":"object","required":["total_stock_quantity","total_cost_quantity","total_inventory_value","total_expected_value","total_quantity_variance","total_value_variance","exception_count","missing_cost_count","quantity_mismatch_count","value_mismatch_count"],"properties":{"exception_count":{"type":"integer","format":"int64"},"missing_cost_count":{"type":"integer","format":"int64"},"quantity_mismatch_count":{"type":"integer","format":"int64"},"total_cost_quantity":{"type":"string"},"total_expected_value":{"type":"string"},"total_inventory_value":{"type":"string"},"total_quantity_variance":{"type":"string"},"total_stock_quantity":{"type":"string"},"total_value_variance":{"type":"string"},"value_mismatch_count":{"type":"integer","format":"int64"}}},"InvoiceListResponse":{"type":"object","description":"Response from GET /api/billing/invoices.\n\nA tenant with no Stripe customer (bank transfer, manual, comped, trial)\ngets `{ \"items\": [] }` with 200 — having no Stripe invoices is a normal\nstate, not an error.","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceSummary"}}}},"InvoicePaymentResponse":{"type":"object","required":["id","invoice_id","amount","payment_method","voided"],"properties":{"amount":{"$ref":"#/components/schemas/BigDecimal"},"id":{"type":"string"},"idempotency_key":{"type":"string","nullable":true},"invoice_id":{"type":"string"},"paid_at":{"type":"string","nullable":true},"payment_method":{"type":"string"},"recorded_by":{"type":"string","nullable":true},"reference":{"type":"string","nullable":true},"void_reason":{"type":"string","nullable":true},"voided":{"type":"boolean","description":"Void, not delete (E-5.3, ADR-006): a voided payment stays visible\nhere forever, just excluded from the paid total."},"voided_at":{"type":"string","nullable":true}}},"InvoiceSettings":{"type":"object","description":"Company display details for fulfilment document PDFs.\n\nNeutral, tenant-configurable fields rendered onto fulfillment document\nPDFs (invoice / delivery note / credit note headers and footer). See\n`CompanySettings.invoice_settings` doc above — legal/jurisdiction fields\nare a founder decision (F-2) not yet made, so this group intentionally\ncarries only cosmetic display fields today. All-empty defaults keep the\nPDF renderer working (falls back to `webapp_company.name`) for every\ntenant that never configures this.","properties":{"allow_overpayment":{"type":"boolean","description":"E-5.3: whether recording a payment larger than the invoice's\nremaining balance is allowed. Default OFF (additive — absent in every\nstored settings blob predating this field, which deserializes to\n`false` via `#[serde(default)]` and never breaks existing tenants).\nWhen off, `record_payment_handler` rejects an overpaying request with\n422 instead of silently accepting it."},"company_address":{"type":"string","description":"Company postal address shown under the display name."},"company_display_name":{"type":"string","description":"Company display name shown in the PDF header. Empty = fall back to\n`webapp_company.name`."},"footer_text":{"type":"string","description":"Free-text footer line (e.g. payment terms, bank details)."},"tax_id":{"type":"string","description":"Tax identifier / VAT number shown in the header. Neutral label\n(\"Tax ID\") until jurisdiction-specific labeling is decided (F-2)."}}},"InvoiceSummary":{"type":"object","description":"One Stripe invoice, trimmed to what the billing page renders.\n\nAmounts are in the currency's **minor units** (cents for USD/EUR),\nexactly as Stripe reports them; the frontend formats using the invoice's\nown `currency`, never an assumed one.","required":["id","amount_due","amount_paid","currency"],"properties":{"amount_due":{"type":"integer","format":"int64","description":"Amount due, minor units."},"amount_paid":{"type":"integer","format":"int64","description":"Amount paid, minor units."},"created":{"type":"string","description":"RFC 3339 creation time.","nullable":true},"currency":{"type":"string","description":"Lowercase ISO currency code as Stripe sends it (e.g. `usd`, `eur`)."},"hosted_invoice_url":{"type":"string","description":"Stripe-hosted invoice page (viewable without auth).","nullable":true},"id":{"type":"string","description":"Stripe invoice id (`in_...`)."},"invoice_pdf":{"type":"string","description":"Direct PDF download link.","nullable":true},"number":{"type":"string","description":"Human-facing invoice number (e.g. `A1B2C3-0001`); absent on drafts.","nullable":true},"period_end":{"type":"string","description":"RFC 3339 billing period end.","nullable":true},"period_start":{"type":"string","description":"RFC 3339 billing period start.","nullable":true},"status":{"type":"string","description":"Stripe invoice status: `draft` | `open` | `paid` | `uncollectible` | `void`.","nullable":true}}},"JobListResponse":{"type":"object","required":["jobs","dead_letter_count"],"properties":{"dead_letter_count":{"type":"integer","format":"int64"},"jobs":{"type":"array","items":{"$ref":"#/components/schemas/JobView"}}}},"JobView":{"type":"object","required":["id","kind","payload","status","run_at","attempts","max_attempts","created_at"],"properties":{"attempts":{"type":"integer","format":"int32"},"company_id":{"type":"string","nullable":true},"completed_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true},"created_at":{"$ref":"#/components/schemas/DateTime"},"id":{"$ref":"#/components/schemas/Uuid"},"idempotency_key":{"type":"string","nullable":true},"kind":{"type":"string"},"last_error":{"type":"string","nullable":true},"locked_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true},"locked_by":{"type":"string","nullable":true},"max_attempts":{"type":"integer","format":"int32"},"payload":{},"run_at":{"$ref":"#/components/schemas/DateTime"},"status":{"type":"string"}}},"JoinCategoryPayload":{"type":"object","description":"Payload for joining a category into another (non-destructive alias)","required":["target_id"],"properties":{"target_id":{"type":"string","description":"ID of the category that will become the canonical target"}}},"KdsBumpInput":{"type":"object","description":"`POST /api/horeca/kds/bump`. `scope` selects what `to_status` applies to:\n`item` (needs `item_id`), `course` (needs `course`), or `order` (every\nitem on the order). See `handlers::kds_bump` doc for the exact\nper-item/per-order transition rules.","required":["open_order_id","scope","to_status"],"properties":{"course":{"type":"string","description":"Required when `scope = \"course\"`.","nullable":true},"item_id":{"type":"string","description":"Required when `scope = \"item\"`.","nullable":true},"open_order_id":{"type":"string"},"scope":{"type":"string","description":"item | course | order."},"to_status":{"type":"string","description":"preparing | ready | served."}}},"KdsBumpResponse":{"type":"object","required":["success","order_status","items_bumped","items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/crate.models.horeca.OpenOrderItem"}},"items_bumped":{"type":"integer","minimum":0},"order_status":{"type":"string","description":"The order's status AFTER this bump — may have advanced (e.g. to\n`in_prep`/`ready`) as a side effect; see `handlers::kds_bump` doc."},"success":{"type":"boolean"}}},"KdsOrderDto":{"type":"object","required":["id","status","source","order_type","items"],"properties":{"customer_display":{"type":"string","description":"Horeca Ordering Suite wave B4: a single display string for \"who is\nthis order for\" — `customer_name` when set, else `table_tent_number`\nformatted as \"Table tent #N\", else `None`. Kitchen staff need ONE\nglanceable field regardless of which channel took the order.","nullable":true},"id":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/KdsOrderItemDto"}},"order_number":{"type":"string","nullable":true},"order_type":{"type":"string"},"placed_at":{"type":"string","nullable":true},"source":{"type":"string"},"status":{"type":"string"},"table_label":{"type":"string","description":"Horeca Ordering Suite wave B4: the floor-plan object's `label`\n(joined `table_session -> floor_plan_objects`). `None` for orders\nwith no table session (kiosk takeaway/pickup) or an unlabeled table.","nullable":true},"table_session_id":{"type":"string","nullable":true}}},"KdsOrderItemDto":{"type":"object","required":["id","qty","status"],"properties":{"course":{"type":"string","nullable":true},"id":{"type":"string"},"note":{"type":"string","nullable":true},"product_id":{"type":"string","nullable":true},"product_name":{"type":"string","description":"Horeca Ordering Suite wave B4: `webapp_product.name` at query time\n(not a snapshot — mirrors `MenuProductDto`, a renamed/deleted product\nshows its current name, same as the rest of this module). `None` when\nthe product was hard-deleted or the item has no `product_id`.","nullable":true},"qty":{"type":"integer","format":"int32"},"sent_at":{"type":"string","description":"Horeca Ordering Suite wave B4: when this item's course was sent to\nthe kitchen (`open_order_items.sent_at`), RFC3339.","nullable":true},"status":{"type":"string"}}},"KdsQueueResponse":{"type":"object","description":"`cursor` is the `updated_at` of the most-recently-seen order (RFC3339),\nechoed back so the next poll's `since` is exactly this response's high\nwatermark — same delta-cursor shape as the design doc's poll-cheap\nrequirement (§3.7).","required":["orders","cursor"],"properties":{"cursor":{"type":"string"},"orders":{"type":"array","items":{"$ref":"#/components/schemas/KdsOrderDto"}},"removed_order_ids":{"type":"array","items":{"type":"string"},"description":"Horeca Ordering Suite wave B4 (item 5c): ids of orders that\ntransitioned to a TERMINAL status (`completed`/`cancelled`) since\n`since` — present so a KDS poller can remove a ticket it's already\nshowing instead of never hearing about the order again once the\nexisting `orders` filter (non-terminal only) starts excluding it.\nEmpty on a full snapshot request (`since` omitted) since there is\nnothing to \"remove\" from a client with no prior state."}}},"KioskDevice":{"type":"object","required":["id","company_id","name","pairing_code","status","created_at","updated_at"],"properties":{"access_token_id":{"allOf":[{"$ref":"#/components/schemas/uuid.Uuid"}],"nullable":true},"company_id":{"type":"string"},"config":{"nullable":true},"created_at":{"$ref":"#/components/schemas/DateTime"},"id":{"type":"string"},"last_seen_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true},"name":{"type":"string"},"pairing_code":{"type":"string"},"status":{"type":"string","description":"pending | claimed | revoked."},"updated_at":{"$ref":"#/components/schemas/DateTime"}}},"KioskDeviceResponse":{"type":"object","required":["id","name","status","created_at"],"properties":{"config":{"nullable":true},"created_at":{"type":"string"},"id":{"type":"string"},"last_seen_at":{"type":"string","nullable":true},"name":{"type":"string"},"status":{"type":"string"}}},"KioskDisplayConfigDto":{"type":"object","description":"Kiosk theming/behavior config embedded in the kiosk `MenuResponse`\n(Horeca Ordering Suite wave B4, item 4). Mirrors\n`settings::dto::HorecaKioskSettings`/`HorecaKioskTheme` — see those for\nthe admin-configurable source of truth and defaults.","required":["attract_media_urls","languages","idle_timeout_seconds","tip_prompt_enabled"],"properties":{"attract_media_urls":{"type":"array","items":{"type":"string"}},"idle_timeout_seconds":{"type":"integer","format":"int32"},"languages":{"type":"array","items":{"type":"string"}},"logo_url":{"type":"string","nullable":true},"primary_color":{"type":"string","nullable":true},"tip_prompt_enabled":{"type":"boolean"}}},"KioskPairingStatusResponse":{"type":"object","required":["status"],"properties":{"code":{"type":"string","nullable":true},"kiosk_device_id":{"type":"string","nullable":true},"status":{"type":"string","description":"pending | claimed."},"token":{"type":"string","description":"Delivered exactly once — see `kiosk.rs` module docs. Absent on every\npoll after the first that observes `status = \"claimed\"`.","nullable":true}}},"LabeledCount":{"type":"object","required":["label","count"],"properties":{"count":{"type":"integer","format":"int64"},"label":{"type":"string"}}},"LeadListResponse":{"type":"object","required":["items","total","page","per_page","total_pages"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/LeadSummary"}},"page":{"type":"integer","format":"int64"},"per_page":{"type":"integer","format":"int64"},"total":{"type":"integer","format":"int64"},"total_pages":{"type":"integer","format":"int64"}}},"LeadSummary":{"type":"object","required":["id","lead_type","status","first_name","last_name","email","created_at"],"properties":{"city":{"type":"string","nullable":true},"company_name":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"created_at":{"$ref":"#/components/schemas/chrono.DateTime"},"email":{"type":"string"},"first_name":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"last_name":{"type":"string"},"lead_type":{"type":"string"},"role":{"type":"string","nullable":true},"status":{"type":"string"},"team_size":{"type":"string","nullable":true},"utm_campaign":{"type":"string","nullable":true},"utm_medium":{"type":"string","nullable":true},"utm_source":{"type":"string","nullable":true}}},"LicenceAllocation":{"type":"object","required":["revision","assignments"],"properties":{"assignments":{"type":"array","items":{"$ref":"#/components/schemas/LicenceAssignment"}},"revision":{"type":"integer","format":"int32"}}},"LicenceAssignment":{"type":"object","required":["kind","subject_id"],"properties":{"device_id":{"type":"string","description":"Stable installation ID for a rep; terminal ID for a till.","nullable":true},"kind":{"$ref":"#/components/schemas/LicenceKind"},"location_id":{"type":"string","description":"Required for tills, absent for user licences.","nullable":true},"subject_id":{"type":"string","description":"User ID for rep/office; registered terminal UUID for till."}}},"LicenceKind":{"type":"string","enum":["rep","office","till"]},"LicenceSubject":{"type":"object","required":["id","label"],"properties":{"device_id":{"type":"string","nullable":true},"id":{"type":"string"},"label":{"type":"string"},"location_id":{"type":"string","nullable":true}}},"LifecycleStageRequest":{"type":"object","required":["code","label","sort_order"],"properties":{"code":{"type":"string","description":"Stable machine code, unique per company (e.g. `lead`, `mql`)."},"label":{"type":"string"},"sort_order":{"type":"integer","format":"int32"}}},"LifecycleStageResponse":{"type":"object","required":["id","code","label","sort_order","is_active","created_at","updated_at"],"properties":{"code":{"type":"string"},"created_at":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"is_active":{"type":"boolean"},"label":{"type":"string"},"sort_order":{"type":"integer","format":"int32"},"updated_at":{"type":"string"}}},"LineResolutionInput":{"type":"object","required":["sales_order_line_id","resolution_type","picked_quantity","remaining_quantity"],"properties":{"note":{"type":"string","nullable":true},"picked_quantity":{"type":"integer","format":"int32"},"reason_code":{"type":"string","nullable":true},"remaining_quantity":{"type":"integer","format":"int32"},"resolution_type":{"type":"string"},"sales_order_line_id":{"type":"string"},"substitute_product_id":{"type":"string","nullable":true},"substitute_quantity":{"type":"integer","format":"int32","nullable":true}}},"LinkAccountRequest":{"type":"object","required":["account_id"],"properties":{"account_id":{"type":"string","description":"`webapp_account.id` to link this organization to."}}},"ListAccessTokensResponse":{"type":"object","required":["results"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/AccessTokenListItem"}}}},"ListPaymentsResponse":{"type":"object","description":"Response shape for `GET /fulfillment/documents/{id}/payments`.","required":["payments","payment_status","balance_due","total_amount"],"properties":{"balance_due":{"$ref":"#/components/schemas/BigDecimal"},"payment_status":{"type":"string","description":"`unpaid | partial | paid | overpaid`."},"payments":{"type":"array","items":{"$ref":"#/components/schemas/InvoicePaymentResponse"}},"total_amount":{"$ref":"#/components/schemas/BigDecimal"}}},"ListPosTerminalsResponse":{"type":"object","required":["results","total"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/PosTerminalListItem"}},"total":{"type":"integer","format":"int64"}}},"LiveSyncSettings":{"type":"object","description":"Controls server-side change checks and the till's safety-net sync interval.","properties":{"connection_lifetime_seconds":{"type":"integer","format":"int32","description":"Maximum stream lifetime before reconnecting through authentication middleware.","default":300,"minimum":0},"enabled":{"type":"boolean","description":"Allow tills to select live updates. Existing interval choices remain valid.","default":true},"fallback_interval_ms":{"type":"integer","format":"int32","description":"Periodic full cursor reconciliation, including changes without domain events.","default":60000,"minimum":0},"poll_interval_ms":{"type":"integer","format":"int32","description":"Minimum delay between lightweight server change checks for a connected till.","default":2000,"minimum":0}}},"LoadSampleDataRequest":{"type":"object","description":"Body for `POST /api/onboarding/sample-data`.\n\nEntirely optional: an empty body loads the default pack, so callers written\nbefore this existed are unaffected.","properties":{"pack":{"type":"string","description":"Vertical id to load. Unknown values are rejected with 400.","nullable":true}}},"LogActivityRequest":{"type":"object","required":["entity_type","entity_id","activity_type"],"properties":{"activity_type":{"$ref":"#/components/schemas/CrmManualActivityType"},"entity_id":{"$ref":"#/components/schemas/Uuid"},"entity_type":{"$ref":"#/components/schemas/CrmFieldEntityType"},"occurred_at":{"type":"string","description":"RFC 3339; defaults to now. Offline clients send their local capture\ntime here.","nullable":true},"payload":{"description":"Structured activity body (e.g. `{\"note\": \"...\"}`). Must be a JSON\nobject; capped at 16 `KiB` serialized.","nullable":true}}},"LoginRequest":{"type":"object","description":"Login request payload","required":["username","password"],"properties":{"password":{"type":"string"},"username":{"type":"string"}}},"LogoItem":{"type":"object","description":"One logo of a `logo_list` section.","properties":{"alt":{"type":"string","description":"Alternative text describing the logo image."},"id":{"type":"string","description":"Stable identifier of this configured entry."},"image_url":{"type":"string","description":"URL of the image displayed with this content."},"link":{"type":"string","description":"Destination opened when this entry is selected."}}},"LookbookCatalogSettings":{"type":"object","description":"Lookbook display settings.\n\nA lookbook is largely \"the catalogue rendered with different display\nsettings over a curated set\", so it reuses `ChannelCatalogSettings` verbatim\nfor card/grid/sort rather than duplicating those knobs. The one thing it\ncannot inherit is the stock policy: `show_only_in_stock` and\n`allow_out_of_stock_orders` are top-level `portal_settings` fields shared by\nevery channel, so a lookbook needs its own.\n\nThe nesting (`lookbook.channel` / `lookbook.stock`) is deliberate rather\nthan `#[serde(flatten)]`, which utoipa cannot describe cleanly and which\nwould muddy the generated TypeScript client.","properties":{"channel":{"$ref":"#/components/schemas/ChannelCatalogSettings"},"enabled":{"type":"boolean","description":"Per-tenant kill switch. When false the mobile app hides the lookbook\nentry point entirely and the sync stops sending lookbook rows."},"price_mode":{"$ref":"#/components/schemas/LookbookPriceMode"},"stock":{"$ref":"#/components/schemas/LookbookStockPolicy"}}},"LookbookDetailResponse":{"type":"object","required":["id","company_id","name","position","is_active","created_at","updated_at","items"],"properties":{"company_id":{"type":"string"},"cover_image_url":{"type":"string","nullable":true},"created_at":{"type":"string"},"description":{"type":"string","nullable":true},"ends_at":{"type":"string","nullable":true},"id":{"type":"string"},"is_active":{"type":"boolean"},"items":{"type":"array","items":{"$ref":"#/components/schemas/LookbookItemResponse"},"description":"Ordered by `(position, id)`. The tiebreak matters: two items can share a\nposition after a partial reorder, and without it the lookbook would come\nback in a different order on each read."},"name":{"type":"string"},"position":{"type":"integer","format":"int32"},"published_at":{"type":"string","nullable":true},"starts_at":{"type":"string","nullable":true},"subtitle":{"type":"string","nullable":true},"updated_at":{"type":"string"}}},"LookbookItemInput":{"type":"object","description":"One entry in a reorder request.","required":["product_id"],"properties":{"note":{"type":"string","nullable":true},"product_id":{"type":"string"},"section_label":{"type":"string","nullable":true}}},"LookbookItemResponse":{"type":"object","description":"One product inside a lookbook, in curated order.","required":["id","product_id","position"],"properties":{"id":{"type":"string"},"note":{"type":"string","description":"Free-text note the merchandiser attached, shown alongside the product.","nullable":true},"position":{"type":"integer","format":"int32","description":"Curated order within the lookbook."},"product_id":{"type":"string"},"section_label":{"type":"string","description":"Optional editorial heading this item sits under, e.g.\n\"Fishermen · Nets & Line\". `None` means it continues the preceding run.","nullable":true}}},"LookbookPriceMode":{"type":"string","description":"Which price a lookbook shows.\n\n`resolved_prices` is keyed by account and the mobile refresh early-returns\nwhen no account is selected, so a lookbook opened before the rep picks the\nstore has no customer price to show.","enum":["customer_resolved","base_fallback"]},"LookbookResponse":{"type":"object","description":"A lookbook in list form. Deliberately does NOT carry its items — a list of\ntwenty lookbooks should not drag every product with it.","required":["id","company_id","name","position","is_active","product_count","products_without_image_count","created_at","updated_at"],"properties":{"company_id":{"type":"string"},"cover_image_url":{"type":"string","nullable":true},"created_at":{"type":"string"},"description":{"type":"string","nullable":true},"ends_at":{"type":"string","nullable":true},"id":{"type":"string"},"is_active":{"type":"boolean"},"name":{"type":"string"},"position":{"type":"integer","format":"int32","description":"Order among the tenant's lookbooks, ascending."},"product_count":{"type":"integer","format":"int64"},"products_without_image_count":{"type":"integer","format":"int64","description":"How many of this lookbook's products have no image. Surfaced so the\nbuilder can warn before publish — a lookbook is a visual artifact, and\nimage coverage across the catalogue is patchy enough that the ordinary\ncatalogue filters image-less products out by default."},"published_at":{"type":"string","description":"`None` means draft. Only published lookbooks sync to a rep's device.","nullable":true},"starts_at":{"type":"string","nullable":true},"subtitle":{"type":"string","nullable":true},"updated_at":{"type":"string"}}},"LookbookStockDisplay":{"type":"string","description":"How much stock information a lookbook card reveals.","enum":["hidden","badge","exact_qty"]},"LookbookStockLocationMode":{"type":"string","description":"Which location's stock the out-of-stock badge reflects.\n\n`stock_levels` is per-location, so \"not in stock\" is meaningless without\nsaying where. A rep working out of one warehouse should not be told an item\nis available because a different branch has it.","enum":["assigned_location","company_wide"]},"LookbookStockPolicy":{"type":"object","description":"Stock policy for lookbooks.\n\nA lookbook exists precisely so a rep can present a themed range *including*\nitems that are currently out of stock, so `show_out_of_stock` defaults to\n`true` — the opposite of the ordinary catalogue, where `show_only_in_stock`\ndefaults to `true` and the per-device override can only tighten it\n(see `frontend/apps/mobile/app/(tabs)/catalogue.tsx`).\n\nPresentation stays honest by construction: an out-of-stock item is shown\n*labelled*, and whether the rep may actually take the order is a separate\ntoggle that defaults to off. Showing a product is a merchandising decision;\npromising delivery is a commercial one.","properties":{"allow_out_of_stock_orders":{"type":"boolean"},"default_lead_time_days":{"type":"integer","format":"int32"},"out_of_stock_label":{"type":"string"},"show_lead_time":{"type":"boolean"},"show_out_of_stock":{"type":"boolean"},"stock_display":{"$ref":"#/components/schemas/LookbookStockDisplay"},"stock_location_mode":{"$ref":"#/components/schemas/LookbookStockLocationMode"}}},"MailboxListResponse":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/MailboxResponse"}}}},"MailboxProvider":{"type":"string","enum":["gmail","outlook","imap"]},"MailboxResponse":{"type":"object","required":["id","provider","email_address","shared","sync_direction","sync_interval_minutes","connection_status","created_at","updated_at"],"properties":{"connection_status":{"type":"string"},"created_at":{"type":"string"},"display_name":{"type":"string","nullable":true},"email_address":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"last_error_message":{"type":"string","nullable":true},"last_sync_at":{"type":"string","nullable":true},"provider":{"type":"string"},"shared":{"type":"boolean"},"sync_direction":{"type":"string"},"sync_interval_minutes":{"type":"integer","format":"int32"},"updated_at":{"type":"string"}}},"ManageCollectionItemsRequest":{"type":"object","required":["product_ids"],"properties":{"product_ids":{"type":"array","items":{"type":"string"}}}},"MarginGroupBy":{"type":"string","description":"How the gross-margin report is grouped.","enum":["product","category","channel","location","customer","day"]},"MarkDeliveredRequest":{"type":"object","description":"Body of `POST /fulfillment/delivery/{id}/delivered`.","properties":{"delivery_notes":{"type":"string","nullable":true},"proof_of_delivery_url":{"type":"string","nullable":true}}},"MarkOutForDeliveryRequest":{"type":"object","description":"Optional body of `POST /fulfillment/delivery/{id}/out-for-delivery`.","properties":{"carrier":{"type":"string","nullable":true},"tracking_code":{"type":"string","nullable":true}}},"MarketingImageListResponse":{"type":"object","required":["results","total"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/MarketingImageResponse"}},"total":{"type":"integer","format":"int64"}}},"MarketingImageResponse":{"type":"object","required":["id","company_id","image_url","order_index","is_active","created_at","updated_at"],"properties":{"company_id":{"type":"string"},"created_at":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"image_url":{"type":"string"},"is_active":{"type":"boolean"},"order_index":{"type":"integer","format":"int32"},"title":{"type":"string","nullable":true},"updated_at":{"type":"string"}}},"McpPlatformConfig":{"type":"object","description":"Platform-managed MCP entitlement overrides for one company.\n\nPlatform-managed MCP override for a single tenant — written by operators via\ninternal-admin (/api/admin/mcp/config), READ-ONLY for tenants. Every field\nis `Option`: `None` = \"no override, defer to the plan default\". Stored\ninside the company settings JSON alongside the tenant-editable `mcp` group.","properties":{"disabled_tools":{"type":"array","items":{"type":"string"},"description":"Per-tool kill switch — tool names explicitly disabled for this tenant\neven when otherwise available."},"enabled_tools":{"type":"array","items":{"type":"string"},"description":"Per-tool kill switch — tool names explicitly enabled for this tenant\n(required to turn ON Class-B destructive tools, which are default-OFF)."},"force_enabled":{"type":"boolean","description":"Operator force-enable/disable MCP for this tenant regardless of plan.\n`None` = defer to the plan ceiling.","nullable":true},"max_tokens":{"type":"integer","format":"int32","description":"Cap on active MCP tokens for this tenant. `None` = plan/global default.","nullable":true,"minimum":0},"write_override":{"type":"boolean","description":"Operator override for write access. `None` = defer to the plan ceiling.","nullable":true}}},"McpSettings":{"type":"object","description":"Company MCP access preferences, bounded by plan and operator entitlements.\n\nNOTE: every field MUST carry its own `#[serde(default = ...)]` (the\ngroup-level `#[serde(default)]` only fires when the WHOLE group is absent).","properties":{"allow_write":{"type":"boolean","description":"Whether the customer opts into exposing write tools. Clamped to the\nplan/operator write ceiling at runtime. Default false."},"enabled":{"type":"boolean","description":"Whether the customer has opted into MCP access. Default false."},"require_write_confirmation":{"type":"boolean","description":"Advisory: annotate write-tool responses as requiring confirmation. This\nis a UX hint, NOT a security control. Default true."}}},"MenuCategoryDto":{"type":"object","required":["id","name","sort_order"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"parent_id":{"type":"string","nullable":true},"sort_order":{"type":"integer","format":"int32","description":"Display order. Sourced from `webapp_category.lft` (the MPTT\nnested-set left boundary) rather than a dedicated column — `lft` is\nalready monotonically increasing in tree-traversal/display order for\ncategories at the same level, and no `sort_order` column exists on\n`webapp_category` today. Not a literal admin-configurable field;\njust an ordering hint so clients don't render raw, unordered API\nresponse order."}}},"MenuModifierGroupDto":{"type":"object","required":["id","name","min_selections","is_required","options"],"properties":{"id":{"type":"string"},"is_required":{"type":"boolean"},"max_selections":{"type":"integer","format":"int32","nullable":true},"min_selections":{"type":"integer","format":"int32"},"name":{"type":"string"},"options":{"type":"array","items":{"$ref":"#/components/schemas/MenuModifierOptionDto"}}}},"MenuModifierOptionDto":{"type":"object","required":["id","name","price_adjustment","is_default"],"properties":{"id":{"type":"string"},"is_default":{"type":"boolean","description":"Sourced from `modifier_options.is_default` — the option pre-selected\nfor the customer when the group renders (Horeca Ordering Suite wave\nB4)."},"name":{"type":"string"},"price_adjustment":{"$ref":"#/components/schemas/BigDecimal"}}},"MenuProductDto":{"type":"object","required":["id","name","description","price","modifier_groups"],"properties":{"category_id":{"type":"string","nullable":true},"description":{"type":"string"},"id":{"type":"string"},"image_url":{"type":"string","description":"First product image (thumbnail preferred), same resolution as\n`features::storefront::service::load_first_images` — a public CDN\nURL via `shared::utils::build_image_url`, not proxy/auth-gated\n(verified: storefront's public catalog uses the same helper with no\nbearer token involved, unlike the admin/mobile image paths which\nroute through an authenticated proxy).","nullable":true},"modifier_groups":{"type":"array","items":{"$ref":"#/components/schemas/MenuModifierGroupDto"}},"name":{"type":"string"},"price":{"$ref":"#/components/schemas/BigDecimal"}}},"MenuResponse":{"type":"object","description":"Menu response shared by the QR table endpoint and the authenticated\nkiosk endpoint. Channel-scoped visibility (design §7 item 5,\n`product_channel_availability`) does not exist yet — see module docs on\nthe handlers that build this — so both channels currently see every\nnon-deleted, non-excluded product, same set `excluded.eq(false)` already\nhides from every other listing in this codebase (products/categories/\naccounts handlers).","required":["categories","products","currency"],"properties":{"categories":{"type":"array","items":{"$ref":"#/components/schemas/MenuCategoryDto"}},"currency":{"type":"string","description":"Sourced from `CompanySettings.currency`/`currency_symbol` — same\nfields `features::storefront::service::resolve_storefront` reads for\nthe public storefront config response."},"currency_symbol":{"type":"string","nullable":true},"display_config":{"allOf":[{"$ref":"#/components/schemas/KioskDisplayConfigDto"}],"nullable":true},"products":{"type":"array","items":{"$ref":"#/components/schemas/MenuProductDto"}}}},"MergeAccountsPayload":{"type":"object","description":"Account merge payload","required":["target_customer_id","merge_customer_ids"],"properties":{"merge_customer_ids":{"type":"array","items":{"type":"string"}},"target_customer_id":{"type":"string"}}},"MergeCategoriesPayload":{"type":"object","description":"Category merge payload","required":["target_id","merge_ids"],"properties":{"merge_ids":{"type":"array","items":{"type":"string"}},"target_id":{"type":"string"}}},"MergeContactsRequest":{"type":"object","required":["source_id"],"properties":{"source_id":{"$ref":"#/components/schemas/Uuid"}}},"MergeOrganizationsRequest":{"type":"object","required":["source_id"],"properties":{"source_id":{"$ref":"#/components/schemas/Uuid"}}},"MfaChallengeRequest":{"type":"object","description":"Answer a login challenge with a second factor.","required":["challenge_token"],"properties":{"challenge_token":{"type":"string","description":"The opaque token `/api/login` returned instead of a JWT."},"code":{"type":"string","description":"A 6-digit TOTP code. Either this or `recovery_code` is required.","nullable":true},"recovery_code":{"type":"string","description":"A single-use recovery code, for when the authenticator is unavailable.","nullable":true}}},"MfaDisableRequest":{"type":"object","description":"Turn your own MFA off. Requires proof of possession, not just a live session.","properties":{"code":{"type":"string","description":"Current TOTP code. Either this or `recovery_code` is required.","nullable":true},"recovery_code":{"type":"string","nullable":true}}},"MfaMode":{"type":"string","description":"How hard the company pushes multi-factor authentication on its staff.\n\nThis is the \"companywide enforce vs. user opt-in\" switch. It is deliberately\na three-state enum rather than a bool: \"everyone must\" and \"nobody has to\"\nare the easy cases, but the common real-world position is \"the people who can\nchange prices, refund money and export the customer list must; the seasonal\nfloor staff sharing a tablet need not\".","enum":["optional","required","admin_only"]},"MfaStatusResponse":{"type":"object","description":"A user's current MFA position.","required":["mfa_enabled","has_totp","recovery_codes_remaining","effective_policy","mfa_available"],"properties":{"effective_policy":{"$ref":"#/components/schemas/EffectiveMfaPolicy"},"factor_label":{"type":"string","description":"Human label for the enrolled factor.","nullable":true},"has_totp":{"type":"boolean","description":"Whether that factor is TOTP (the only kind implemented today)."},"mfa_available":{"type":"boolean","description":"Whether this deployment can encrypt MFA secrets at all (see\n`crypto::key_available`). False means enrolment will be refused, and the\nUI should say so up front rather than after the user scans a QR code."},"mfa_enabled":{"type":"boolean","description":"Whether a confirmed factor exists."},"recovery_codes_remaining":{"type":"integer","format":"int64","description":"How many unused recovery codes remain."}}},"MobileLoginMfaChallengeResponse":{"type":"object","description":"Returned by mobile-login when the password was accepted but a second factor is\nstill owed. Carries NO token — that is the whole point.\n\n`status` is the discriminator: existing clients that only read `token` will\nfail to parse this and surface an error, which is the safe direction. Clients\nthat understand `status` prompt for a code and re-post.","required":["status","message"],"properties":{"challenge_token":{"type":"string","description":"Present for `mfa_required`. Re-post it with a code.","nullable":true},"message":{"type":"string"},"mfa_methods":{"type":"array","items":{"type":"string"},"description":"Which factors the challenge accepts.","nullable":true},"status":{"type":"string","description":"Always `\"mfa_required\"` or `\"mfa_setup_required\"`."}}},"MobileLoginRequest":{"type":"object","required":["username","password","installation_id"],"properties":{"app_version":{"type":"string","nullable":true},"challenge_token":{"type":"string","description":"Second-factor round trip. When this endpoint answers with\n`status: \"mfa_required\"`, the client re-posts the SAME request plus the\nreturned `challenge_token` and one of `code` / `recovery_code`.\n\nCarried on the same request rather than a separate endpoint because the\ndevice fields (`installation_id`, `platform`, …) are needed to mint the\ndevice token either way — a separate endpoint would have to duplicate all\nof them, and any field it forgot would silently change which token row got\nrotated.","nullable":true},"code":{"type":"string","description":"6-digit TOTP code, sent with `challenge_token`.","nullable":true},"device_name":{"type":"string","nullable":true},"installation_id":{"type":"string"},"password":{"type":"string"},"platform":{"type":"string","nullable":true},"recovery_code":{"type":"string","description":"Single-use recovery code, alternative to `code`.","nullable":true},"username":{"type":"string"}}},"MobileLoginResponse":{"type":"object","required":["message","token","token_kind","user"],"properties":{"company_id":{"type":"string","description":"The signed-in user's company.\n\nDeliberately NOT added to `AuthenticatedUserDto`, which is shared with the\nweb login where the JWT already carries it. The mobile/desktop token is an\nOPAQUE access token (`vnd_<lookup>_<secret>`), not a JWT, so its holder\ncannot read claims out of it — anything the client needs has to be in this\nbody. The desktop was reading `user.company_id`, which has never existed on\nthis DTO, and silently storing `\"\"` for every till."},"expires_at":{"type":"string","nullable":true},"location_ids":{"type":"array","items":{"type":"string"},"description":"Locations this user is assigned to. EMPTY for admins, who are not scoped to\nspecific locations — an empty list means \"not location-scoped\", not \"no\naccess\". The desktop was reading `user.location_ids`, which likewise has\nnever existed here, so its location picker could never appear."},"message":{"type":"string"},"token":{"type":"string"},"token_kind":{"type":"string"},"user":{"$ref":"#/components/schemas/AuthenticatedUserDto"}}},"MobileOrderInboxDetail":{"type":"object","description":"Detail response for one inbox row: every stored column plus a `resolved`\nhuman-readable view of the raw payload.","required":["id","company_id","user_id","client_order_id","raw_payload","status","received_at","resolved"],"properties":{"client_order_id":{"type":"string"},"company_id":{"type":"string"},"device_info":{"nullable":true},"error_code":{"type":"string","nullable":true},"error_detail":{"nullable":true},"id":{"type":"string"},"processed_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"raw_payload":{},"received_at":{"$ref":"#/components/schemas/chrono.DateTime"},"resolved":{"$ref":"#/components/schemas/ResolvedInboxOrder"},"resolved_by":{"type":"integer","format":"int32","nullable":true},"sales_order_id":{"type":"string","nullable":true},"status":{"type":"string"},"user_id":{"type":"integer","format":"int32"}}},"MobileOrderInboxListResponse":{"type":"object","required":["items","total","limit","offset"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/MobileOrderInboxSummary"}},"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"total":{"type":"integer","format":"int64"}}},"MobileOrderInboxSummary":{"type":"object","description":"List row for the inbox management view (`raw_payload` omitted — fetch the\ndetail endpoint for it). Includes lightweight resolved fields (customer name,\nsales rep, item count, order total) so the list is human-readable without\nopening each row.","required":["id","user_id","client_order_id","status","received_at","item_count","total_qty"],"properties":{"account_name":{"type":"string","description":"Resolved customer/account name from the order payload, if resolvable.","nullable":true},"client_order_id":{"type":"string"},"error_code":{"type":"string","nullable":true},"id":{"type":"string"},"item_count":{"type":"integer","format":"int64","description":"Number of distinct line items in the order payload."},"processed_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"received_at":{"$ref":"#/components/schemas/chrono.DateTime"},"resolved_by":{"type":"integer","format":"int32","nullable":true},"sales_order_id":{"type":"string","nullable":true},"sales_rep":{"type":"string","description":"Resolved sales-rep display name for `user_id`, if resolvable.","nullable":true},"status":{"type":"string"},"total_amount":{"type":"string","description":"Order total (sum of qty × unit price from the payload), as a decimal string.","nullable":true},"total_qty":{"type":"integer","format":"int64","description":"Total units ordered across all line items."},"user_id":{"type":"integer","format":"int32"}}},"ModifierGroup":{"type":"object","required":["id","company_id","name","selection_type","min_selections","is_required","created_at","updated_at"],"properties":{"company_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/DateTime"},"id":{"type":"string"},"is_required":{"type":"boolean"},"max_selections":{"type":"integer","format":"int32","nullable":true},"min_selections":{"type":"integer","format":"int32"},"name":{"type":"string"},"selection_type":{"type":"string"},"updated_at":{"$ref":"#/components/schemas/DateTime"}}},"ModifierGroupWithOptions":{"allOf":[{"$ref":"#/components/schemas/ModifierGroup"},{"type":"object","required":["options"],"properties":{"options":{"type":"array","items":{"$ref":"#/components/schemas/ModifierOption"}}}}],"description":"Response struct that bundles a modifier group with its options"},"ModifierOption":{"type":"object","required":["id","modifier_group_id","company_id","name","price_adjustment","is_default","sort_order","created_at","updated_at"],"properties":{"company_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/DateTime"},"id":{"type":"string"},"is_default":{"type":"boolean"},"modifier_group_id":{"type":"string"},"name":{"type":"string"},"price_adjustment":{"$ref":"#/components/schemas/BigDecimal"},"sort_order":{"type":"integer","format":"int32"},"updated_at":{"$ref":"#/components/schemas/DateTime"}}},"ModularCheckoutAvailability":{"type":"object","required":["can_start_new_checkout","reason"],"properties":{"can_start_new_checkout":{"type":"boolean","description":"Advisory only: the purchase write revalidates eligibility under the company lock."},"current_checkout":{"allOf":[{"$ref":"#/components/schemas/ModularCheckoutView"}],"nullable":true},"reason":{"$ref":"#/components/schemas/CheckoutAvailabilityReason"}}},"ModularCheckoutStatus":{"type":"string","enum":["reserved","open","completed","expired"]},"ModularCheckoutView":{"type":"object","required":["id","idempotency_key","status","quote","payment_reconciled"],"properties":{"checkout_url":{"type":"string","nullable":true},"expires_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"idempotency_key":{"$ref":"#/components/schemas/Uuid"},"payment_reconciled":{"type":"boolean","description":"A session return or completion is not evidence of reconciled paid access."},"quote":{"$ref":"#/components/schemas/OfferQuote"},"status":{"$ref":"#/components/schemas/ModularCheckoutStatus"}}},"MonthPoint":{"type":"object","required":["month","count"],"properties":{"count":{"type":"integer","format":"int64"},"month":{"type":"string"}}},"MoveDealStageRequest":{"type":"object","required":["to_stage_id"],"properties":{"required_field_values":{"type":"object","description":"Custom-field values applied atomically with the move (field\nengine semantics: definition ids → JSON values).","additionalProperties":{}},"to_stage_id":{"$ref":"#/components/schemas/Uuid"}}},"MoveOpenOrderInput":{"type":"object","description":"Body for `POST /api/horeca/open-orders/{id}/move` (POS-ship workstream E).\n\nMoves a tab to a different table, or off the floor entirely\n(`table_session_id: null` — e.g. converting a walked-out dine-in tab into\na takeaway order). `open_orders.table_session_id` was always a plain\nnullable FK, so the data model already allowed this; nothing exposed it.","properties":{"order_type":{"type":"string","description":"When moving onto a table, also set the order type (a tab moved off the\nfloor is usually becoming `takeaway`). Optional; unchanged if absent.","nullable":true},"table_session_id":{"type":"string","description":"Target table session, or `null` to detach from any table.","nullable":true}}},"MulticolumnItem":{"type":"object","description":"One column of a `multicolumn` section (feature / value-proposition block).","properties":{"icon":{"type":"string","description":"Lucide icon name (e.g. \"truck\", \"shield-check\"). Empty = no icon."},"id":{"type":"string","description":"Stable identifier of this configured entry."},"image_url":{"type":"string","description":"URL of the image displayed with this content."},"link":{"type":"string","description":"Destination opened when this entry is selected."},"text":{"type":"string","description":"Supporting copy displayed with this content."},"title":{"type":"string","description":"Display heading for this content."}}},"MutationResponse":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}},"NaiveDateTime":{"type":"string","description":"Date-time in ISO 8601 format without timezone","example":"2024-01-15T10:30:00"},"NavigationSettings":{"type":"object","description":"Company preferences for the admin sidebar and quick-access work zone.","properties":{"enabled_sections":{"type":"array","items":{"$ref":"#/components/schemas/AdminNavigationSection"},"description":"Admin sidebar sections enabled for the company."},"work_zone":{"$ref":"#/components/schemas/WorkZoneSettings"}}},"NodeIssueResponse":{"type":"object","description":"One validation issue attributed to a specific graph node, so the canvas\ncan highlight the offending node instead of showing a wall of text.","required":["node_id","message"],"properties":{"code":{"type":"string","description":"Stable machine code (e.g. \"`missing_required`\") for structured issues,\nso the admin UI can translate; absent for free-form messages (cron\nparse errors, script compile errors, SSRF refusals) where `message`\nis the only rendering.","nullable":true},"message":{"type":"string","description":"English fallback text — always present, always accurate."},"node_id":{"type":"string"},"params":{"description":"Interpolation values for `code` (field names, kinds, ports…).","nullable":true}}},"NodeSpecResponse":{"type":"object","description":"One node kind in the palette, filtered to kinds the caller may use (see\n`handlers::list_nodes_handler`).","required":["kind","category","title","description","input_schema","output_schema","output_ports","credit_cost_hint","side_effect"],"properties":{"category":{"type":"string"},"credit_cost_hint":{"type":"integer","format":"int32","minimum":0},"description":{"type":"string"},"input_schema":{},"kind":{"type":"string"},"output_ports":{"type":"array","items":{"$ref":"#/components/schemas/OutputPortSpecResponse"},"description":"Empty for single-output kinds: every outgoing edge is taken regardless\nof `source_port`."},"output_schema":{},"required_permission":{"type":"string","nullable":true},"side_effect":{"type":"string"},"title":{"type":"string"}}},"NumberingSettings":{"type":"object","required":["configs"],"properties":{"configs":{"type":"array","items":{"$ref":"#/components/schemas/EntityNumberConfig"}}}},"OfferCopy":{"type":"object","required":["name","description","included","excluded"],"properties":{"description":{"type":"string"},"excluded":{"type":"array","items":{"type":"string"}},"included":{"type":"array","items":{"type":"string"}},"name":{"type":"string"}}},"OfferFeatureChoice":{"type":"object","required":["feature","name","core"],"properties":{"core":{"type":"boolean"},"feature":{"$ref":"#/components/schemas/Feature"},"name":{"type":"string"},"required_by":{"type":"array","items":{"$ref":"#/components/schemas/ProductFamily"},"description":"Complete-workflow requirements; optional for old API consumers."}}},"OfferInterval":{"type":"string","enum":["monthly","annual"]},"OfferManifest":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"array","items":{"$ref":"#/components/schemas/Feature"}},"limits":{"type":"object","additionalProperties":{}}}},"OfferPublication":{"type":"string","enum":["draft","evaluation","available","retired"]},"OfferQuote":{"type":"object","required":["currency","interval","subtotal_cents","lines"],"properties":{"currency":{"type":"string"},"interval":{"$ref":"#/components/schemas/OfferInterval"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/QuoteLine"}},"subtotal_cents":{"type":"integer","format":"int64","description":"Excludes applicable tax, hardware, acquiring and separate services."}}},"OfferSelection":{"type":"object","required":["code","revision"],"properties":{"base_quantity":{"type":"integer","format":"int32","minimum":0},"code":{"type":"string"},"extra_office_users":{"type":"integer","format":"int32","minimum":0},"extra_reps":{"type":"integer","format":"int32","minimum":0},"extra_tills":{"type":"integer","format":"int32","minimum":0},"revision":{"type":"integer","format":"int32","minimum":0}}},"OfferTerms":{"type":"object","required":["code","revision","family","currency","base_price","maximum_base_quantity","reps","office_users","tills"],"properties":{"base_price":{"$ref":"#/components/schemas/RecurringPrice"},"code":{"type":"string"},"currency":{"type":"string"},"exclusive_group":{"type":"string","description":"Offers in one group replace one another; they cannot be double-charged.","nullable":true},"family":{"$ref":"#/components/schemas/ProductFamily"},"incompatible_families":{"type":"array","items":{"$ref":"#/components/schemas/ProductFamily"},"description":"Applies in either direction to other selections, including older revisions\nwith no replacement group. Empty preserves the old combination rules."},"maximum_base_quantity":{"type":"integer","format":"int32","minimum":0},"office_users":{"$ref":"#/components/schemas/CapacityPrice"},"reps":{"$ref":"#/components/schemas/CapacityPrice"},"revision":{"type":"integer","format":"int32","minimum":0},"tills":{"$ref":"#/components/schemas/CapacityPrice"},"workflow_validation":{"$ref":"#/components/schemas/OfferWorkflowValidation"}}},"OfferWorkflowValidation":{"type":"string","description":"Older stored revisions retain their original validation semantics.","enum":["legacy","complete_workflow"]},"OfflineImagesSettings":{"type":"object","description":"Product-image caching preferences for offline mobile use.\n\nPer-company tuning for how sales-rep mobile devices (iPads) cache product\nimages for offline use (the offline-images plan). Stored inside the\ncompany settings JSON; absent config falls back to `Default` and never\nbreaks.\n\nDefaults below are chosen to match the CURRENT hardcoded mobile behavior\nexactly, so shipping this group changes nothing until a tenant opts to\ntune it:\n- `auto_prime_after_sync` / `auto_prime_requires_unmetered` mirror\n`useImagePrime`'s `enabled`/`requireUnmetered` option defaults\n(`frontend/apps/mobile/hooks/useImagePrime.ts`).\n- `download_timeout_ms` / `batch_timeout_ms` mirror `DOWNLOAD_TIMEOUT_MS`\n(15000) / `BATCH_DOWNLOAD_TIMEOUT_MS` (45000) in\n`frontend/apps/mobile/services/cache/image-cache.ts`.\n- `max_concurrency_ceiling` mirrors the high-RAM-tier value of\n`imageDownloadConcurrencyMax` (12) in\n`frontend/apps/mobile/utils/device-capabilities.ts` — the per-device\nceiling (3 on low-RAM iPads) still applies underneath this company-level\nceiling (effective concurrency = min(device ceiling, this value)).\n- `retry_max_attempts` / `retry_backoff_base_ms` / `retry_backoff_cap_ms`\nmirror `DEFAULT_PRIME_CONFIG` / `BACKOFF_BASE_MS` / `BACKOFF_CAP_MS` in\n`frontend/apps/mobile/services/cache/image-prime-queue.ts`.\n- `max_cache_bytes_override` defaults to `None` (defer to the per-device\ntier ceiling in `device-capabilities.ts` / the 4 `GiB` default in\n`constants/app.config.ts` `IMAGE_CACHE.MAX_SIZE_BYTES`) — when set, this is\nan ADDITIONAL ceiling; the device tier remains a hard ceiling regardless\n(effective cap = min(device tier ceiling, this value)).\n- `verify_interval_hours`, `min_free_disk_bytes`,\n`thumbnails_required_offline`, and `readiness_warn_threshold_pct` are\nNET-NEW: there is no existing hardcoded mobile equivalent, so these are\nforward-looking knobs for the follow-up that wires the mobile app to\nthis group.\n\nNOT YET WIRED to the mobile app — `frontend/apps/mobile` still reads its\nown hardcoded constants listed above. A follow-up change points that code\nat this settings group.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level `#[serde(default)]` only fires when the WHOLE group is\nmissing) — see the invariants comment at the top of `CompanySettings`.","properties":{"auto_prime_after_sync":{"type":"boolean","description":"Automatically start downloading product images after a data sync\ncompletes, without the rep tapping anything. Default true."},"auto_prime_requires_unmetered":{"type":"boolean","description":"Only auto-prime on an unmetered connection (Wi-Fi/ethernet) so a rep on\ncellular never silently burns their data plan. Default true."},"batch_timeout_ms":{"type":"integer","format":"int32","description":"Timeout for a background batch/thumbnail download, in milliseconds.\nDefault 45000."},"download_timeout_ms":{"type":"integer","format":"int32","description":"Timeout for a single on-demand image download (the rep is actively\nwaiting on it), in milliseconds. Default 15000."},"max_cache_bytes_override":{"type":"integer","format":"int64","description":"Optional company-level cap (bytes) on the on-device image cache. This\nis ADDITIONAL to, never a replacement for, the per-device tier\nceiling — the effective cap applied on-device must be\n`min(device_tier_ceiling, this_value)` once wired. `None` (default) =\ndefer entirely to the device tier ceiling.","nullable":true},"max_concurrency_ceiling":{"type":"integer","format":"int32","description":"Company-level ceiling on parallel image downloads. The on-device tier\nceiling still applies underneath this — effective concurrency =\n`min(device_tier_ceiling, this_value)`. Default 12."},"min_free_disk_bytes":{"type":"integer","format":"int64","description":"Priming refuses to start (or pauses) once free device storage drops\nbelow this many bytes, so the cache never fills the disk out from\nunder the OS and other apps. Default 500 `MiB` (`524_288_000`)."},"readiness_warn_threshold_pct":{"type":"integer","format":"int32","description":"Readiness percentage (0-100, validated on update) below which the\nadmin/rep UI shows a warning that the offline catalog is incomplete.\nDefault 90."},"retry_backoff_base_ms":{"type":"integer","format":"int32","description":"Base retry backoff, doubled per attempt, in milliseconds. Default\n30000."},"retry_backoff_cap_ms":{"type":"integer","format":"int32","description":"Ceiling on the exponential retry backoff, in milliseconds. Default\n21600000 (6 hours)."},"retry_max_attempts":{"type":"integer","format":"int32","description":"Maximum attempts for one queued prime job (first try + retries).\nDefault 6."},"thumbnails_required_offline":{"type":"boolean","description":"Whether a device is considered \"offline ready\" only when thumbnails\n(not necessarily full-resolution images) are cached for every catalog\nitem. Default true — thumbnails are small and are what the\npicker/catalog grid actually renders; full images remain best-effort."},"verify_interval_hours":{"type":"integer","format":"int32","description":"How often a background pass re-verifies cached images still match the\nserver version (catches silent corruption/eviction), in hours. Default\n24 (once a day)."}}},"OnboardingSettings":{"type":"object","description":"Post-signup onboarding wizard — what the tenant answered, and the knobs that\ncontrol how the wizard behaves.\n\nThe raw answers also live in `onboarding_state.profile`; what lands here is\nthe durable consequence of those answers, so every value stays editable in\nSettings afterward rather than being reachable only through the wizard.","properties":{"enabled_channels":{"type":"array","items":{"$ref":"#/components/schemas/SalesChannel"},"description":"How this company sells. Empty = not answered yet, which the UI treats as\n\"show everything\" rather than \"show nothing\"."},"min_products_for_vertical_choice":{"type":"integer","format":"int32","description":"How many products a pack needs before the \"what do you sell?\" question\noffers it."},"sample_pack":{"type":"string","description":"Which sample pack was loaded, if any.","nullable":true},"vertical":{"type":"string","description":"The vertical the tenant picked, recorded even while the question is\nhidden so the answer is available once packs grow.","nullable":true},"wizard_enabled":{"type":"boolean","description":"Kill switch. Off = new users go straight to the dashboard."}}},"OnboardingStateResponse":{"type":"object","required":["company_id","current_step","completed_steps","skipped_steps"],"properties":{"available_packs":{"type":"array","items":{"$ref":"#/components/schemas/SamplePackOption"},"description":"Packs with enough products to be worth offering as a choice, newest\nthreshold applied. The \"what do you sell?\" question renders only when\nthis holds two or more entries."},"company_id":{"type":"string"},"completed_steps":{"type":"array","items":{"type":"string"}},"current_step":{"type":"string"},"has_business_activity":{"type":"boolean","description":"True when existing business records make an automatic welcome redirect inappropriate.","nullable":true},"profile":{"description":"Raw wizard answers, e.g.\n`{\"vertical\":\"casacesar\",\"channels\":[\"pos\"],\"catalogue_source\":\"sample\"}`.\n`{}` before the wizard is answered."},"profile_completed_at":{"type":"string","description":"`None` means the tenant has not finished the wizard, which is what login\nuses to decide whether to redirect to `/app/welcome`. Every company that\nexisted when the wizard shipped was backfilled, so only genuinely new\nsignups see it.","nullable":true},"sample_data":{"$ref":"#/components/schemas/SampleDataSummaryResponse"},"sample_data_available":{"type":"boolean","description":"Whether the sample-catalogue offer still applies to this company —\n`false` once it holds any live product. The admin onboarding page gates\nthe \"load sample data\" card on this so it stops offering an action that\n`POST /api/onboarding/sample-data` would refuse with a 409.\n\nOptional so older clients that never sent/expected it keep deserializing."},"skipped_steps":{"type":"array","items":{"type":"string"}},"updated_at":{"type":"string","nullable":true}}},"OnlineStoreAiAction":{"type":"string","description":"What the merchant asks the assistant to do.","enum":["generate_homepage","rewrite_section","suggest_theme","generate_page","generate_seo"]},"OnlineStoreAiProposal":{"type":"object","description":"Everything the assistant may propose. Each field is optional; the builder\nshows what came back and applies only what the merchant accepts.","properties":{"announcement_text":{"type":"string","nullable":true},"notes":{"type":"string","description":"Short explanation of the choices, shown in the assistant panel."},"page":{"allOf":[{"$ref":"#/components/schemas/StorefrontPage"}],"nullable":true},"sections":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontSection"},"nullable":true},"seo":{"allOf":[{"$ref":"#/components/schemas/StorefrontSeoSettings"}],"nullable":true},"store_name":{"type":"string","nullable":true},"theme":{"allOf":[{"$ref":"#/components/schemas/StorefrontTheme"}],"nullable":true}}},"OnlineStoreAiRequest":{"type":"object","required":["action"],"properties":{"action":{"$ref":"#/components/schemas/OnlineStoreAiAction"},"channel":{"$ref":"#/components/schemas/BuilderChannel"},"current_theme":{"allOf":[{"$ref":"#/components/schemas/StorefrontTheme"}],"nullable":true},"language":{"type":"string","description":"ISO language for generated copy (en | nl | es). Default: the company\nlanguage, falling back to English.","nullable":true},"page_kind":{"type":"string","description":"`about` | `contact` | `faq` | `shipping` (`generate_page`).","nullable":true},"prompt":{"type":"string","description":"Free-form brief from the merchant (\"a cosy bakery in Utrecht, warm\ntones, we deliver on Saturdays\"). Optional for every action."},"section":{"allOf":[{"$ref":"#/components/schemas/StorefrontSection"}],"nullable":true}}},"OnlineStoreAiResponse":{"type":"object","required":["action","mode","provider","model","credits_charged","proposal"],"properties":{"action":{"$ref":"#/components/schemas/OnlineStoreAiAction"},"credits_charged":{"type":"number","format":"double","description":"Credits charged to the monthly allowance (managed mode only)."},"mode":{"type":"string","description":"`byok` | `managed` | `offline` — how this proposal was produced."},"model":{"type":"string"},"proposal":{"$ref":"#/components/schemas/OnlineStoreAiProposal"},"provider":{"type":"string"},"warning":{"type":"string","description":"Non-fatal notice, e.g. why the LLM was skipped and the offline\ngenerator answered instead."}}},"OnlineStoreAiStatusResponse":{"type":"object","required":["mode","provider","model","llm_available","credit_cost"],"properties":{"credit_cost":{"type":"number","format":"double","description":"Credits one builder request costs in managed mode."},"credits_remaining":{"type":"number","format":"double","description":"Managed mode: credits left this month (allowance + top-ups − spent).","nullable":true},"llm_available":{"type":"boolean","description":"True when an LLM will answer (BYOK key set, or managed mode with\ncredits left)."},"mode":{"type":"string","description":"`byok` | `managed` | `offline`."},"model":{"type":"string"},"provider":{"type":"string"}}},"OnlineStoreChecklistItem":{"type":"object","description":"One row of the \"ready to launch\" checklist on the Online Store overview.","required":["key","done"],"properties":{"done":{"type":"boolean"},"key":{"type":"string","description":"Stable key the admin maps to a label + link: `enabled`, `slug`,\n`products`, `sections`, `payments`, `delivery`, `contact`, `policies`."}}},"OnlineStoreMediaResponse":{"type":"object","required":["url","key","size_bytes","kind"],"properties":{"key":{"type":"string"},"kind":{"type":"string","description":"`image` | `video`."},"size_bytes":{"type":"integer","minimum":0},"url":{"type":"string","description":"Public URL to store in the section (CDN when configured)."}}},"OnlineStorePreviewRequest":{"type":"object","description":"A draft to render exactly as the live store would — sections are\ndenormalised server-side (products, categories, sanitised custom HTML)\nso the builder canvas shows real data without persisting anything.","properties":{"channel":{"$ref":"#/components/schemas/BuilderChannel"},"page_handle":{"type":"string","description":"Also render this custom page (by handle) so the canvas can show it.","nullable":true},"portal_design":{"allOf":[{"$ref":"#/components/schemas/PortalDesignSettings"}],"nullable":true},"storefront":{"allOf":[{"$ref":"#/components/schemas/StorefrontSettings"}],"nullable":true}}},"OnlineStorePreviewResponse":{"type":"object","required":["config"],"properties":{"config":{"$ref":"#/components/schemas/StorefrontConfigResponse"},"page":{"allOf":[{"$ref":"#/components/schemas/StorefrontPageResponse"}],"nullable":true}}},"OnlineStoreStatusResponse":{"type":"object","required":["enabled","slug","store_name","store_url","url_template","product_count","category_count","section_count","page_count","checklist","order_count","portal_custom_homepage","portal_section_count"],"properties":{"category_count":{"type":"integer","format":"int64"},"checklist":{"type":"array","items":{"$ref":"#/components/schemas/OnlineStoreChecklistItem"}},"enabled":{"type":"boolean"},"order_count":{"type":"integer","format":"int64","description":"Storefront orders (source = \"storefront\"), all time."},"page_count":{"type":"integer","format":"int64"},"portal_custom_homepage":{"type":"boolean","description":"B2B portal: whether the custom homepage is switched on."},"portal_section_count":{"type":"integer","format":"int64"},"product_count":{"type":"integer","format":"int64","description":"Sellable products in the catalogue."},"section_count":{"type":"integer","format":"int64","description":"Homepage sections currently saved."},"slug":{"type":"string"},"store_name":{"type":"string"},"store_url":{"type":"string","description":"Canonical public URL for this store (empty until a slug is set)."},"url_template":{"type":"string","description":"`STORE_PUBLIC_URL_TEMPLATE` with `{slug}` left in, for the admin's\nlive URL preview while the merchant types."}}},"OpenOrder":{"type":"object","required":["id","company_id","status","created_at","updated_at","source","order_type","payment_state"],"properties":{"accepted_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true},"assigned_user_id":{"type":"integer","format":"int32","nullable":true},"bill_requested_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true},"company_id":{"type":"string"},"completed_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true},"created_at":{"$ref":"#/components/schemas/DateTime"},"customer_name":{"type":"string","nullable":true},"customer_phone":{"type":"string","nullable":true},"id":{"type":"string"},"order_number":{"type":"string","nullable":true},"order_type":{"type":"string","description":"`dine_in` | takeaway | pickup. See\n`crate::features::horeca::status::OrderType`."},"payment_state":{"type":"string","description":"unpaid | pending | paid | refunded — independent of `status`. See\n`crate::features::horeca::status::PaymentState`."},"placed_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true},"public_token":{"allOf":[{"$ref":"#/components/schemas/uuid.Uuid"}],"nullable":true},"ready_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true},"receipt_id":{"type":"string","description":"FK -> `webapp_salesreceipt.id`, set by `settle_open_order`. Presence of\nthis value is the idempotency guard for settle.","nullable":true},"source":{"type":"string","description":"pos | waiter | kiosk | qr | storefront (Horeca Ordering Suite wave\nB1). See `crate::features::horeca::status::OrderSource`."},"status":{"type":"string"},"table_session_id":{"type":"string","nullable":true},"table_tent_number":{"type":"string","description":"Horeca Ordering Suite wave B4: optional staff-assigned table-tent /\npager number for orders with no floor-plan table (kiosk\ntakeaway/pickup mainly).","nullable":true},"updated_at":{"$ref":"#/components/schemas/DateTime"}}},"OpenOrderBillingQuote":{"type":"object","description":"The bill-presentation half of a quote (POS-ship H8).","required":["service_charge_configured","service_charge_label","service_charge_amount","total_with_service_charge","cash_due_with_service_charge","tip_enabled","tip_presets_percent","max_tip_percent"],"properties":{"cash_due_with_service_charge":{"type":"string","description":"`total_with_service_charge` rounded to the merchant's smallest\ncirculating coin."},"max_tip_percent":{"type":"integer","format":"int32","description":"Ceiling the server enforces, as a percentage of the goods total. `0`\nmeans no ceiling."},"service_charge_amount":{"type":"string","description":"The resolved amount for this basket, in the same menu-price basis the\npro-forma prints. `\"0.00\"` when not configured."},"service_charge_configured":{"type":"boolean","description":"Whether a service charge is configured at all. `false` means the till\nshows no service-charge line and must not send\n`apply_service_charge: true` (the server would 422)."},"service_charge_label":{"type":"string","description":"Line label to print, e.g. `\"Service\"`."},"service_charge_percent":{"type":"string","description":"The configured percentage, as a decimal string, or `null`.","nullable":true},"tip_enabled":{"type":"boolean","description":"Whether to show the tip step at all."},"tip_presets_percent":{"type":"array","items":{"type":"integer","format":"int32"},"description":"One-tap tip percentages."},"total_with_service_charge":{"type":"string","description":"`total` with the service charge added. Equal to `total` when none is\nconfigured."}}},"OpenOrderItem":{"type":"object","required":["id","open_order_id","company_id","qty","unit_price","created_at","updated_at","status"],"properties":{"company_id":{"type":"string"},"course":{"type":"string","nullable":true},"created_at":{"$ref":"#/components/schemas/DateTime"},"id":{"type":"string"},"modifiers_snapshot":{"nullable":true},"note":{"type":"string","nullable":true},"open_order_id":{"type":"string"},"printer_target":{"type":"string","nullable":true},"product_id":{"type":"string","nullable":true},"qty":{"type":"integer","format":"int32"},"seat_number":{"type":"integer","format":"int32","description":"POS-ship workstream E: seat / cover this line belongs to (1-based,\nwaiter-assigned). NULL when the venue doesn't track covers.","nullable":true},"sent_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true},"settled_receipt_id":{"type":"string","description":"POS-ship workstream E / H8: the receipt that has paid for this line.\n`None` = still unpaid, and therefore still claimable by a settle.","nullable":true},"status":{"type":"string","description":"pending | sent | preparing | ready | served | voided (KDS bump\ngranularity). See\n`crate::features::horeca::status::OpenOrderItemStatus`."},"unit_price":{"$ref":"#/components/schemas/BigDecimal"},"updated_at":{"$ref":"#/components/schemas/DateTime"},"variant_id":{"type":"string","nullable":true},"voided_by_user_id":{"type":"integer","format":"int32","nullable":true},"voided_reason":{"type":"string","nullable":true}}},"OpenOrderQuotePart":{"type":"object","description":"The part of a split bill this quote priced (POS-ship H8).\n\nExists because the till could not ask. `settle` has taken `item_ids` /\n`split` since H8; the quote could not, so the desktop bill dialog fell back\nto the whole-tab total for every split — \"split equally, 2 ways\" showed the\ncashier XCG 5.89 for a 5.89 tab and would have taken the whole bill from the\nfirst guest. Verified on the Windows till, 2026-09-04.\n\nEvery figure here is produced by `settle::price_settlement_plan`, the same\nfunction the settle transaction runs, over the same basket and policy — so\nthis part's `amount_due` is cents-exact what settling this part charges.","required":["split_mode","item_ids","subtotal","tax_amount","tax_lines","total","cash_due","service_charge_amount","tip_amount","amount_due","cash_amount_due"],"properties":{"amount_due":{"type":"string","description":"`total + tip_amount` — what a card must be charged for this part. THIS\nis the figure the payment keypad demands."},"cash_amount_due":{"type":"string","description":"`cash_due + tip_amount` — what a cash drawer must take. A tip is never\nrounded, so it is added after the rounding, exactly as settle does it."},"cash_due":{"type":"string","description":"`total` rounded to the merchant's smallest circulating coin."},"item_ids":{"type":"array","items":{"type":"string"},"description":"The lines this part's price is derived from: the chosen subset for a\nby-items split, the whole remaining tab for an equal part."},"part_index":{"type":"integer","format":"int32","description":"Which zero-based part this is, for an `equal_part` quote.","nullable":true},"parts":{"type":"integer","format":"int32","description":"How many ways the bill is split, for an `equal_part` quote.","nullable":true},"service_charge_amount":{"type":"string","description":"The service charge folded into this part. `\"0.00\"` when none."},"service_charge_label":{"type":"string","description":"The label that would be printed for it, or `null` when no charge\napplies to this part.","nullable":true},"split_mode":{"type":"string","description":"`full` | `items` | `equal_part` — how the server read the request."},"subtotal":{"type":"string"},"tax_amount":{"type":"string"},"tax_lines":{"type":"array","items":{"$ref":"#/components/schemas/crate.features.horeca.settle.SettleTaxLineResponse"},"description":"Per-rate breakdown for THIS part — apportioned for an equal part, so a\nsplit receipt still prints a legal VAT breakdown."},"tip_amount":{"type":"string","description":"The proposed tip, validated against this part's ceiling. `\"0.00\"` when\nthe caller proposed none."},"total":{"type":"string","description":"Goods total for this part, service charge included when it was\nrequested. Tip excluded, and NOT cash-rounded."}}},"OpenOrderQuoteResponse":{"type":"object","description":"`GET /api/horeca/open-orders/{id}/quote` response — what this order costs,\npriced by the SERVER, without settling anything.\n\nExists because the till was showing a cashier an amount due it had computed\nfrom its own tax math. On a company with no tax rate configured it applied\na 21% fallback and displayed 5.42 for an order the server then charged 4.48\nfor. The number a cashier is told to collect and the number the server takes\nmust come from the same arithmetic; this endpoint runs\n`settle`'s own pricing pass (`price_open_order_lines`) so they cannot drift.\n\nRead-only: no receipt, no stock movement, no change to the order.","required":["open_order_id","subtotal","tax_amount","total","tax_lines","cash_due","cash_rounding_adjustment","billing"],"properties":{"billing":{"$ref":"#/components/schemas/OpenOrderBillingQuote"},"cash_due":{"type":"string","description":"What a CASH payment must be: `total` rounded to the merchant's smallest\ncirculating coin (`pos_pricing.cash_rounding_increment`). Equal to\n`total` when no increment is configured.\n\nThe till offers this as the cash amount instead of rounding `total`\nitself. A live sale showed why: the till rounded 12.74 to 12.75, the\nserver charged 12.74, and the extra cent came back as change — money a\nretail sale would have rounded away."},"cash_rounding_adjustment":{"type":"string","description":"`cash_due - total`, signed. Positive means the merchant collects the\nfraction; negative means it is forgone. Bounded by half an increment."},"equal_split_parts":{"type":"integer","format":"int32","description":"Recorded number of ways for the existing split; absent if not recorded.","nullable":true},"equal_split_started":{"type":"boolean","description":"Once an equal share is paid, finish equal shares or pay the balance.\nItem selection would otherwise charge the shared goods again.\nAbsent on older servers.","nullable":true},"open_order_id":{"type":"string"},"part":{"allOf":[{"$ref":"#/components/schemas/OpenOrderQuotePart"}],"nullable":true},"remaining":{"allOf":[{"$ref":"#/components/schemas/OpenOrderRemainingQuote"}],"nullable":true},"subtotal":{"type":"string","description":"Net, pre-tax, as a decimal string."},"tax_amount":{"type":"string"},"tax_lines":{"type":"array","items":{"$ref":"#/components/schemas/crate.features.horeca.settle.SettleTaxLineResponse"},"description":"Per-rate breakdown; a single blended VAT line is not legal on a\nmixed-rate basket."},"total":{"type":"string","description":"What the customer owes — `subtotal + tax_amount`."}}},"OpenOrderRemainingQuote":{"type":"object","description":"The tab priced over the lines nobody has paid for yet.\n\nThe flat fields on `OpenOrderQuoteResponse` price EVERY sellable line,\nsettled or not. That was harmless while a tab could only be settled whole;\nonce a by-items split can claim half the lines it means the bill dialog\nre-opens for the second guest still showing the first guest's food in the\n\"Order total\". The old fields keep their meaning — the deployed till, the\nwaiter app and the pro-forma printer all read them — and this block carries\nthe honest figure alongside.\n\nDeliberately the same SHAPE as the fields it shadows, so a client can pick\na basis (`quote.remaining ?? quote`) instead of branching per figure.","required":["subtotal","tax_amount","total","tax_lines","cash_due","cash_rounding_adjustment","service_charge_amount","total_with_service_charge","cash_due_with_service_charge","unsettled_item_count","settled_item_count"],"properties":{"cash_due":{"type":"string"},"cash_due_with_service_charge":{"type":"string"},"cash_rounding_adjustment":{"type":"string"},"service_charge_amount":{"type":"string","description":"The service charge resolved against the REMAINING basket — a percentage\nof what is left, not of a tab already part-paid. `\"0.00\"` when none is\nconfigured or nothing is left."},"settled_item_count":{"type":"integer","format":"int32","description":"Sellable lines an earlier part of a split already claimed. `0` on every\ntab that has not been partially settled, where this block is identical\nto the flat fields above."},"subtotal":{"type":"string"},"tax_amount":{"type":"string"},"tax_lines":{"type":"array","items":{"$ref":"#/components/schemas/crate.features.horeca.settle.SettleTaxLineResponse"},"description":"Per-rate breakdown of the unpaid lines only."},"total":{"type":"string","description":"`subtotal + tax_amount` — what this table still owes."},"total_with_service_charge":{"type":"string"},"unsettled_item_count":{"type":"integer","format":"int32","description":"Sellable lines still unpaid. `0` means the tab is fully settled and\nevery figure here is `\"0.00\"` — the honest answer, not an error."}}},"OpenOrderWithItems":{"type":"object","description":"`GET /api/horeca/open-orders/{id}` response. Mirrors\n`handlers::get_open_order`'s `json!({\"order\":.., \"items\":..})` exactly.","required":["order","items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/crate.models.horeca.OpenOrderItem"}},"order":{"$ref":"#/components/schemas/crate.models.horeca.OpenOrder"}}},"OpenSessionRequest":{"type":"object","required":["terminal_id","starting_cash"],"properties":{"notes":{"type":"string","nullable":true},"starting_cash":{"type":"string"},"terminal_id":{"type":"string"}}},"OpenTableSessionInput":{"type":"object","properties":{"note":{"type":"string","nullable":true},"party_size":{"type":"integer","format":"int32","nullable":true}}},"OrderItemRequest":{"type":"object","required":["product_id","qty","price"],"properties":{"price":{"type":"string","description":"Price as decimal string, e.g. \"9.99\""},"product_id":{"type":"string"},"qty":{"type":"integer","format":"int32"}}},"OrderLineResolutionResponse":{"type":"object","required":["id","sales_order_id","sales_order_line_id","resolution_type","ordered_quantity","picked_quantity","remaining_quantity","approval_status"],"properties":{"approval_status":{"type":"string"},"id":{"type":"string"},"note":{"type":"string","nullable":true},"ordered_quantity":{"type":"integer","format":"int32"},"picked_quantity":{"type":"integer","format":"int32"},"reason_code":{"type":"string","nullable":true},"remaining_quantity":{"type":"integer","format":"int32"},"resolution_type":{"type":"string"},"sales_order_id":{"type":"string"},"sales_order_line_id":{"type":"string"},"substitute_product_id":{"type":"string","nullable":true},"substitute_quantity":{"type":"integer","format":"int32","nullable":true}}},"OrderRulesSettings":{"type":"object","description":"Company rules governing sales-order creation.\n\nPer-company order-creation policy (ADR-002, E-2.4): MOQ enforcement and\nthe optional order-approval gate. Stored inside the company settings\nJSON; absent config falls back to `Default` (both OFF) so existing\ntenants see NO behavior change until they opt in from the admin UI.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level default only fires when the WHOLE group is missing).","properties":{"enforce_moq":{"type":"boolean","description":"When true, `domain::orders::create_order` (staff/mobile-sync/POS/etc.)\nand the customer-portal order handler reject a line whose qty is\nbelow `max(product.min_order_qty, account.min_order_qty)`. Default\nFALSE — preserves today's behavior (MOQ columns exist but are\ncurrently unenforced) for every tenant that has never opened this\nsettings group."},"require_approval_over_amount":{"type":"number","format":"double","description":"When set, a newly-created order whose total exceeds this amount is\ncreated with status `pending_approval` instead of `submitted` and\nmust go through `POST /api/sales-orders/{id}/approve`\n(`sales_orders.manage` permission) before it can progress. `None`\n(default) means the gate is off — every order submits normally,\nexactly like before this field existed.\n\n`f64`, not `BigDecimal`: matches the existing `tax_rate: Option<f64>`\nprecedent in `CompanySettings` for a settings-level monetary/percent\nthreshold — `BigDecimal` isn't used anywhere else in this file and\nisn't worth introducing for a single admin-configured threshold.","nullable":true}}},"OrderSyncSettings":{"type":"object","description":"Per-company mobile order sync tuning (order-durability plan §B6). Stored\ninside the company settings JSON; absent config falls back to `Default` and\nnever breaks.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(see the `ImportSettings` NOTE above: the group-level `#[serde(default)]`\nonly fires when the WHOLE group is missing).","properties":{"deleted_order_retention_days":{"type":"integer","format":"int32","description":"Days a soft-deleted sales order is retained before hard purge. Default 90.","minimum":0},"inbox_retention_days":{"type":"integer","format":"int32","description":"Days processed/duplicate inbox rows are retained. Default 90.","minimum":0},"max_push_batch_size":{"type":"integer","format":"int32","description":"Max sales orders (and receipts) accepted per sync-push request. Orders\nbeyond the cap get a retriable per-order error — never silent\ntruncation. Default 500.","minimum":0},"quarantine_notify_email_enabled":{"type":"boolean","description":"Email company admins when a pushed order is quarantined. Default false\n(the sending pipeline ships in a later phase; the toggle is wired now\nso tenants opt in without a redeploy)."}}},"OrdersResponseDto":{"type":"object","description":"Paginated orders list response — GET /api/portal/orders","required":["results","count"],"properties":{"count":{"type":"integer","format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/PortalOrderDto"}}}},"OrganizationDetailResponse":{"type":"object","required":["id","company_id","name","source","created_at","updated_at"],"properties":{"company_id":{"type":"string"},"created_at":{"type":"string"},"custom_fields":{"type":"object","description":"`field_definition.code` → typed value, assembled server-side (A2).\nAlways present on the wire; typed Option only so the `OpenAPI` schema\nstays additive for clients generated before A2.","additionalProperties":{},"nullable":true},"domain":{"type":"string","nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"industry_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"lifecycle_stage_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"linked_account_id":{"type":"string","nullable":true},"merged_into_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"name":{"type":"string"},"owner_user_id":{"type":"integer","format":"int32","nullable":true},"size_range":{"type":"string","nullable":true},"source":{"type":"string"},"updated_at":{"type":"string"}}},"OrganizationListItem":{"type":"object","required":["id","name","created_at","updated_at"],"properties":{"created_at":{"type":"string"},"domain":{"type":"string","nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"industry_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"last_activity_at":{"type":"string","description":"Most recent `crm_activities.occurred_at` for this organization; null\nuntil the activity timeline (A2) starts writing rows.","nullable":true},"lifecycle_stage_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"linked_account_id":{"type":"string","nullable":true},"name":{"type":"string"},"owner_user_id":{"type":"integer","format":"int32","nullable":true},"size_range":{"type":"string","nullable":true},"updated_at":{"type":"string"}}},"OrganizationListResponse":{"type":"object","required":["items","total_count","page","page_size"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationListItem"}},"page":{"type":"integer","format":"int64"},"page_size":{"type":"integer","format":"int64"},"total_count":{"type":"integer","format":"int64"}}},"OutputPortSpecResponse":{"type":"object","description":"One named output port a routing node exposes (`logic.branch`,\n`logic.gate`). The effective port name is the node config's `config_key`\nvalue when present, else `default` — the canvas derives its source handles\nfrom this, and save-time validation rejects edges on other port names.","required":["config_key","default"],"properties":{"config_key":{"type":"string"},"default":{"type":"string"}}},"PagingOptions":{"type":"object","properties":{"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"}}},"PairingStatusResponse":{"type":"object","description":"Response to the TV's poll of its own `device_id`. While `status ==\n\"pending\"`, `code`/`expires_at` are set and `display` is absent; once an\nadmin claims the code, `status == \"claimed\"` and `display` carries the\nsame payload [`PublicDisplayDataResponse`] the long-URL viewer gets, so\nthe TV can render in place without navigating away from its fixed URL.","required":["status"],"properties":{"code":{"type":"string","nullable":true},"display":{"allOf":[{"$ref":"#/components/schemas/PublicDisplayDataResponse"}],"nullable":true},"expires_at":{"type":"string","nullable":true},"status":{"type":"string"}}},"PasswordResetConfirmRequest":{"type":"object","description":"Password reset confirmation request","required":["token","new_password"],"properties":{"new_password":{"type":"string"},"token":{"type":"string"}}},"PasswordResetConfirmResponse":{"type":"object","description":"Password reset confirmation response","required":["message","success"],"properties":{"message":{"type":"string"},"success":{"type":"boolean"}}},"PasswordResetRequest":{"type":"object","description":"Password reset request","required":["email"],"properties":{"email":{"type":"string"}}},"PasswordResetResponse":{"type":"object","description":"Password reset response","required":["message","email"],"properties":{"email":{"type":"string"},"message":{"type":"string"}}},"PatchAccountInput":{"type":"object","description":"Account patch input (partial updates)","properties":{"account_number":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"customer_type":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"excluded":{"type":"boolean","nullable":true},"name":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true}}},"PatchCategoryInput":{"type":"object","description":"Category patch input (partial updates)","properties":{"code":{"type":"string","nullable":true},"excluded":{"type":"boolean","nullable":true},"name":{"type":"string","nullable":true},"parent_id":{"type":"string","nullable":true}}},"PatchProductInput":{"type":"object","description":"Product patch input (partial updates)","properties":{"category":{"allOf":[{"$ref":"#/components/schemas/CategoryPayload"}],"nullable":true},"code":{"type":"string","nullable":true},"code_type":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"excluded":{"type":"boolean","nullable":true},"expected_updated_at":{"allOf":[{"$ref":"#/components/schemas/chrono.NaiveDateTime"}],"nullable":true},"item_number":{"type":"string","nullable":true},"msrp_price":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"name":{"type":"string","nullable":true},"qty":{"type":"integer","format":"int32","nullable":true},"regular_price":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"wholesale_price":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true}}},"PaymentActionResponse":{"type":"object","description":"Response shape for both record-payment and void-payment: the payment row\nplus the invoice's freshly-recomputed derived state, so the caller never\nhas to make a second request to learn the new balance.","required":["payment","payment_status","balance_due","replayed"],"properties":{"balance_due":{"$ref":"#/components/schemas/BigDecimal"},"payment":{"$ref":"#/components/schemas/InvoicePaymentResponse"},"payment_status":{"type":"string","description":"`unpaid | partial | paid | overpaid`."},"replayed":{"type":"boolean","description":"`true` when this call replayed an existing payment via\n`idempotency_key` rather than recording a new one."}}},"Permission":{"type":"string","enum":["workspace.view","access_tokens.create","access_tokens.delete","access_tokens.revoke","access_tokens.rotate","access_tokens.view","accounts.manage","accounts.view","audit.push","auth.manage_tokens","categories.manage","categories.view","company.settings","customers.edit","customers.manage","customers.view","data.enrichment","data.export","data.import","gift_cards.create","gift_cards.manage","gift_cards.redeem","gift_cards.view","horeca.manage","horeca.serve","horeca.waive_service_charge","integrations.delete","integrations.field_mappings_manage","integrations.force_sync","integrations.manage","integrations.sync_execute","integrations.sync_logs_view","integrations.view","locations.manage","locations.view","orders.pick","pos.cash_movement","pos.discount","pos.discount_over_threshold","pos.manage","pos.no_sale","pos.price_override","pos.refund","pos.reveal_gift_card","pos.sell","pos.void","pos_shifts.manage","pos_terminals.manage","pos_terminals.view","products.edit","products.manage","products.view","reports.manage","reports.view","sales_orders.create","sales_orders.manage","sales_orders.view","sales_receipts.create","sales_receipts.manage","sales_receipts.view","stock.transfer","suppliers.view","suppliers.manage","purchasing.view","purchasing.create","purchasing.manage","purchasing.approve","purchasing.receive","inventory_cost.view","inventory_cost.manage","inventory_count.view","inventory_count.manage","inventory_count.approve","price_lists.view","price_lists.manage","lookbooks.view","lookbooks.manage","customer_groups.view","customer_groups.manage","discounts.view","discounts.manage","fulfillment.view","fulfillment.manage","webhooks.view","webhooks.manage","events.view","pipelines.view","pipelines.manage","tax.view","tax.manage","user.create","user.delete","user.edit","user.view_directory","crm_contacts.view","crm_contacts.manage","crm_deals.view","crm_deals.manage","crm_deal_pipelines.manage","crm_activities.view","crm_activities.log","crm_cases.view","crm_cases.manage","crm_campaigns.view","crm_campaigns.manage","crm_campaigns.send","crm_inbox.view","crm_inbox.manage","crm_inbox.reply","platform_modules.view","platform_modules.manage","platform_modules.publish","platform_entities.manage","platform_reports.view","platform_reports.manage","platform_ui.manage"]},"PermissionCatalogGroup":{"type":"object","required":["key","label","permissions"],"properties":{"key":{"$ref":"#/components/schemas/PermissionGroup"},"label":{"type":"string"},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/PermissionCatalogItem"}}}},"PermissionCatalogItem":{"type":"object","required":["permission","label","description"],"properties":{"description":{"type":"string"},"label":{"type":"string"},"permission":{"$ref":"#/components/schemas/Permission"}}},"PermissionGroup":{"type":"string","enum":["users","company","audit","products","customers","accounts","categories","sales","data","access_tokens","locations","picking","pos","horeca","gift_cards","integrations","pricing","discounts","fulfillment","webhooks","events","pipelines","tax","procurement","inventory_counts","crm","platform"]},"PickItemDetail":{"type":"object","required":["sales_order_item_id","product_id","product_name","sku","ordered_quantity","picked_quantity"],"properties":{"barcode":{"type":"string","nullable":true},"location":{"type":"string","nullable":true},"ordered_quantity":{"type":"integer","format":"int32"},"picked_quantity":{"type":"integer","format":"int64"},"product_id":{"type":"string"},"product_name":{"type":"string"},"sales_order_item_id":{"type":"string"},"sku":{"type":"string"}}},"PickingActionResponse":{"type":"object","required":["message","order_id","status"],"properties":{"message":{"type":"string"},"order_id":{"type":"string"},"status":{"type":"string"}}},"PickingAdjustmentInput":{"type":"object","description":"One picking adjustment from the client: an added off-order product\n(`adjustment_type = \"added_item\"`, with `product_id` + `quantity`) or a\nscratched line (`adjustment_type = \"removed_line\"`, with `sales_order_line_id`).\n`id` is client-supplied so offline replay upserts idempotently.","required":["id","adjustment_type"],"properties":{"adjustment_type":{"type":"string"},"id":{"type":"string"},"note":{"type":"string","nullable":true},"product_id":{"type":"string","nullable":true},"quantity":{"type":"integer","format":"int32"},"reason_code":{"type":"string","nullable":true},"sales_order_line_id":{"type":"string","nullable":true}}},"PickingAdjustmentResponse":{"type":"object","required":["id","sales_order_id","adjustment_type","quantity","created_by","created_at","updated_at"],"properties":{"adjustment_type":{"type":"string"},"created_at":{"type":"string"},"created_by":{"type":"integer","format":"int32"},"id":{"type":"string"},"note":{"type":"string","nullable":true},"product_id":{"type":"string","nullable":true},"quantity":{"type":"integer","format":"int32"},"reason_code":{"type":"string","nullable":true},"sales_order_id":{"type":"string"},"sales_order_line_id":{"type":"string","nullable":true},"updated_at":{"type":"string"}}},"PickingDetailsResponseDto":{"type":"object","required":["order_id","items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PickItemDetail"}},"order_id":{"type":"string"},"order_number":{"type":"string","nullable":true},"picking_priority":{"type":"string","nullable":true},"picking_status":{"type":"string","nullable":true},"picking_zone":{"type":"string","nullable":true}}},"PickingNotesResponse":{"type":"object","required":["order_id","session_id"],"properties":{"notes":{"type":"string","nullable":true},"order_id":{"type":"string"},"session_id":{"type":"string"}}},"PickingStatusResponse":{"type":"object","required":["order_id","picking_status"],"properties":{"notes":{"type":"string","nullable":true},"order_id":{"type":"string"},"picked_items":{"type":"integer","format":"int32","nullable":true},"picking_priority":{"type":"string","nullable":true},"picking_status":{"type":"string"},"picking_zone":{"type":"string","nullable":true},"session_id":{"type":"string","nullable":true},"session_status":{"type":"string","nullable":true},"total_items":{"type":"integer","format":"int32","nullable":true}}},"PinAccess":{"type":"object","description":"Tenant-bound authorization result for a terminal-local PIN mutation.","required":["company_id","employee_id","allowed"],"properties":{"allowed":{"type":"boolean","description":"True only after permission, tenant and escalation checks pass."},"company_id":{"type":"string","description":"Authenticated company whose user may be changed."},"employee_id":{"type":"integer","format":"int32","description":"Target employee account identifier."}}},"PipelineLimitsSettings":{"type":"object","description":"Per-company pipeline dispatcher + webhook-delivery tuning. Stored inside the\ncompany settings JSON; absent config falls back to `Default` and never\nbreaks. Consumed by `services::event_dispatcher`.\n\nNOTE: every field here MUST carry its own `#[serde(default = ...)]` — the\ngroup-level `#[serde(default)]` only fires when the WHOLE group is missing.\nA single missing key would otherwise fail the entire `CompanySettings` blob and\nsilently reset every setting (prod incident 2026-07-23).","properties":{"ai_enrich_timeout_secs":{"type":"integer","format":"int32","description":"Wall-clock budget (seconds) for one `ai.text.enrich` node execution —\nthe synchronous provider chain runs on a helper thread and the node\nfails with a clear timeout error when this elapses, freeing the\npipeline worker slot (a hung LLM provider can no longer hold it\nforever). The orphaned enrichment may still complete in the background\nand land on the product; the run row records what happened. Bounds\n10–3600, clamped at use. Default 300 (5 min — enrichment does multiple\nprovider calls incl. a vision pass, so it needs far more headroom than\n`http_timeout_secs`)."},"backoff_base_secs":{"type":"integer","format":"int32","description":"Base retry backoff in seconds; the delay doubles each attempt from here,\ncapped at 900s. Bounds 1–3600."},"batch_size":{"type":"integer","format":"int32","description":"Max domain-events (and webhook deliveries) claimed per tenant per tick.\nBounds 1–1000."},"email_allowed_recipients":{"type":"array","items":{"type":"string"},"description":"Recipient allowlist for the `notify.email` node. EMPTY = unrestricted\n(the node already requires explicit config or the company's own\n`notification_emails`, and a deny-by-default here would silently break\nevery tenant's already-saved email node — same no-silent-break contract\nas `script_enabled`). When populated, every recipient of every send\nmust match an entry or the node fails with a clear error naming the\nrejected address. Entry forms, case-insensitive: a full address\n(`ops@example.com`) matches exactly; `@example.com` (leading @)\nmatches any address at that domain."},"email_max_recipients":{"type":"integer","format":"int32","description":"Max recipients one `notify.email` send may address. Over the cap the\nnode fails clearly (never silently truncates). Bounds 1–500, clamped\nat use. Default 25."},"enabled":{"type":"boolean","description":"Master switch — when false the dispatcher skips this tenant entirely."},"http_allowed_hosts":{"type":"array","items":{"type":"string"},"description":"SSRF allowlist for the `http.request` node: the set of external hosts a\npipeline may call outbound. DENY BY DEFAULT — an EMPTY list means the\n`http.request` node cannot call anything at all (every request is\nrefused). A platform/company admin populates this via the normal company\nsettings update path. Matching is host-only (scheme/port/path ignored)\nand case-insensitive: an entry `api.example.com` matches exactly that\nhost; an entry written as `.example.com` (leading dot) matches\n`example.com` and any sub-domain of it (suffix rule). Populating this\nlist is necessary but NOT sufficient — even an allowlisted host is still\nrefused at execution time if it resolves to a private/loopback/\nlink-local/unique-local IP (SSRF + DNS-rebinding guard in\n`nodes::http_request`)."},"http_timeout_secs":{"type":"integer","format":"int32","description":"Per-request HTTP timeout for a webhook POST, in seconds. Bounds 1–120."},"max_attempts":{"type":"integer","format":"int32","description":"Delivery attempts before an event / webhook delivery is dead-lettered\n(attempt 1 + retries). Bounds 1–20."},"max_causation_depth":{"type":"integer","format":"int32","description":"Hard cap on causation-chain depth, applied regardless of the\npipeline-id cycle check. Bounds 1–1000."},"max_concurrent":{"type":"integer","format":"int32","description":"Max webhook deliveries `POSTed` concurrently per tenant per tick.\nBounds 1–64."},"max_concurrent_script_compiles":{"type":"integer","format":"int32","description":"Max `code.script` compiles one company may have running concurrently\n(across in-flight `POST /versions` requests) — each compile spawns an\nOS subprocess (the Javy CLI) and is bounded by\n`script_compile_timeout_secs`, so an unbounded number of concurrent\ncompiles from one tenant could still exhaust the process/blocking-pool\nbudget even though no single compile blocks a request thread anymore.\nA request that would exceed the limit is rejected with 429 rather than\nqueued indefinitely. Bounds\n1–[`PLATFORM_MAX_CONCURRENT_SCRIPT_COMPILES`]. Default 2."},"max_credits_per_run":{"type":"integer","format":"int32","description":"Max credits one run may spend before it is force-failed mid-execution.\nBounds 1–1000000."},"max_nodes_per_graph":{"type":"integer","format":"int32","description":"Max total nodes a single pipeline graph version may declare. Rejected\nat `POST /api/pipelines/{id}/versions` with a clear 400 naming the\nlimit — a request-reachable path must never spend unbounded work\nvalidating (and, for `code.script` nodes, compiling) an arbitrarily\nlarge graph. Bounds 1–[`PLATFORM_MAX_NODES_PER_GRAPH`]. Default 200."},"max_runs_per_hour":{"type":"integer","format":"int32","description":"Max pipeline runs QUEUED per tenant per rolling hour. A matched trigger\nbeyond this cap records a `skipped` run instead of queuing. Bounds\n1–100000."},"max_script_nodes_per_graph":{"type":"integer","format":"int32","description":"Max `code.script` nodes a single pipeline graph version may declare.\nNarrower than `max_nodes_per_graph` on purpose: each one triggers a\nsave-time Javy compile (an OS subprocess, up to\n`script_compile_timeout_secs` each), so this is the knob that actually\nbounds worst-case save-time compile cost. Bounds\n1–[`PLATFORM_MAX_SCRIPT_NODES_PER_GRAPH`]. Default 20."},"max_script_source_bytes":{"type":"integer","format":"int32","description":"Max bytes of JS source one `code.script` node's `source` field may\ncontain, checked at save time before Javy ever sees it (a large source\nmakes for a slower compile). Bounds\n256–[`PLATFORM_MAX_SCRIPT_SOURCE_BYTES`]. Default 65536 (64 `KiB`)."},"max_steps_per_run":{"type":"integer","format":"int32","description":"Max node executions in a single run (guards a pathological graph or a\nfan-out explosion). Bounds 1–10000."},"max_wall_clock_secs":{"type":"integer","format":"int32","description":"Max wall-clock seconds a single run may take before it is force-failed.\nBounds 1–86400."},"pipeline_execution_enabled":{"type":"boolean","description":"Per-tenant kill switch for pipeline EXECUTION. When false a matched\ntrigger records a `skipped` run and nothing executes. Distinct from\n`enabled` (the webhook-dispatcher master switch) so a tenant can keep\nwebhooks while pausing pipelines (or vice-versa)."},"poll_interval_secs":{"type":"integer","format":"int32","description":"How often the dispatcher polls the outbox, in seconds. Bounds 1–3600."},"reconciler_batch_size":{"type":"integer","format":"int32","description":"Max missed events backfilled per entity type per tenant per sweep.\nBounds 1–10000."},"reconciler_enabled":{"type":"boolean","description":"Per-tenant kill switch for the outbox reconciler. When false the\nreconciler skips this tenant entirely. Independent of `enabled` (the\nwebhook-dispatcher master switch)."},"reconciler_interval_secs":{"type":"integer","format":"int32","description":"How often the reconciler sweeps for missed events, in seconds. Bounds\n30–86400. (Loop cadence is global via env; this documents/tunes the\nintended default — see `services::event_reconciler`.)"},"reconciler_lookback_secs":{"type":"integer","format":"int32","description":"How far back the reconciler looks for entities missing an event, in\nseconds. An entity older than this is assumed to have been reconciled (or\npermanently lost) already and is not re-scanned. Bounds 60–604800."},"response_body_cap_bytes":{"type":"integer","format":"int32","description":"Max bytes of a webhook response body stored on the delivery row.\nBounds 0–65536."},"script_compile_timeout_secs":{"type":"integer","format":"int64","description":"Max wall-clock seconds one `javy build` invocation (the save-time\ncompile of a `code.script` node's source) may run before it is killed\nand the save rejected as a compile timeout. Was a hardcoded `const\nCOMPILE_TIMEOUT` in `script/compile.rs`; now company-tunable. Bounds\n1–[`PLATFORM_SCRIPT_COMPILE_TIMEOUT_SECS`] (enforced in\n`settings::services` and clamped again at the call site in\n`pipelines::handlers`, so a value that somehow reached storage\nout-of-range still cannot exceed the platform ceiling at run time).\nDefault 30 (the previous hardcoded value — unchanged behaviour for\nevery tenant until an admin opts to tune it)."},"script_enabled":{"type":"boolean","description":"Master switch for the `code.script` node. When false the node refuses to\nrun (clear `NodeError`, no wasm instantiated) for this tenant, regardless\nof `pipeline_execution_enabled`. Default TRUE: `code.script` already ran\nunconditionally before this knob existed, and this settings group's own\nrule is \"absent config falls back to default and never breaks\" — a\ndefault of `false` here would silently break every tenant's\nalready-saved `code.script` node the moment this field's default fires\n(i.e. on any settings blob that predates this field). Defaulting to\n`false` would be \"safer\" in isolation but violates that no-silent-break\ncontract, so `true` is the deliberate choice; a platform/company admin\ncan still opt a tenant OUT explicitly."},"script_fuel":{"type":"integer","format":"int64","description":"Wasm fuel budget for one `code.script` run (roughly, executed\ninstructions); exhaustion aborts the script. Bounds\n1–[`PLATFORM_SCRIPT_FUEL`] (enforced in `settings::services` and\nclamped again — floored at 0 to fail safe on a stored negative,\nceilinged at the platform max — at the call site in\n`pipelines::nodes::code_script::script_limits_from_settings`). Default\n100,000,000."},"script_max_output_bytes":{"type":"integer","format":"int64","description":"Max bytes of stdout a `code.script` run's guest may write before the run\nis failed as `OutputTooLarge`. Bounds 0–[`PLATFORM_SCRIPT_MAX_OUTPUT_BYTES`]\n(enforced in `settings::services` and clamped again — floored at 0 to\nfail safe on a stored negative, ceilinged at the platform max — at the\ncall site in `pipelines::nodes::code_script::script_limits_from_settings`).\nDefault 65,536 (64 `KiB`)."},"script_max_writes":{"type":"integer","format":"int32","description":"Max `product.update` write intents a single `code.script` run (with\n`writes_enabled: true`) may return; over the cap fails the run clearly.\nBounds 0–[`PLATFORM_SCRIPT_MAX_WRITES`] (enforced in `settings::services`\nand clamped again — floored at 0, ceilinged at the platform max — at the\ncall site in `pipelines::nodes::code_script`). Default 20."},"script_memory_bytes":{"type":"integer","format":"int64","description":"Max linear memory (bytes) one `code.script` run's wasm instance may\ngrow to. Bounds 1–[`PLATFORM_SCRIPT_MEMORY_BYTES`] (enforced in\n`settings::services` and clamped again at the call site in\n`pipelines::nodes::code_script::script_limits_from_settings`). Default\n67,108,864 (64 `MiB`)."},"script_wall_clock_ms":{"type":"integer","format":"int64","description":"Wall-clock budget (milliseconds) for one `code.script` run, enforced via\na wasmtime epoch timer — the reliable bound even for a script blocked\nsomewhere not burning fuel. Bounds\n1–[`PLATFORM_SCRIPT_WALL_CLOCK_MS`] (enforced in `settings::services`\nand clamped again — floored at 0 to fail safe on a stored negative,\nceilinged at the platform max — at the call site in\n`pipelines::nodes::code_script::script_limits_from_settings`, so a\nvalue that somehow reached storage out-of-range still cannot exceed the\nplatform ceiling at run time). Default 2000 (2s)."}}},"PipelineResponse":{"type":"object","required":["id","company_id","name","enabled","created_at","updated_at","webhook_configured"],"properties":{"active_version_id":{"type":"string","nullable":true},"company_id":{"type":"string"},"created_at":{"type":"string"},"created_by":{"type":"integer","format":"int32","nullable":true},"description":{"type":"string","nullable":true},"enabled":{"type":"boolean"},"id":{"type":"string"},"name":{"type":"string"},"trigger_event_type":{"type":"string","nullable":true},"updated_at":{"type":"string"},"webhook_configured":{"type":"boolean","description":"Whether an inbound webhook trigger is configured. The token itself is\nonly ever returned by the generate/rotate endpoint."}}},"PipelineRunDetailResponse":{"type":"object","description":"`GET /api/pipelines/runs/{runId}` — the run inspector: the run row plus\nevery step it executed, in execution order.","required":["run","steps"],"properties":{"run":{"$ref":"#/components/schemas/PipelineRunResponse"},"steps":{"type":"array","items":{"$ref":"#/components/schemas/PipelineStepRunResponse"}}}},"PipelineRunResponse":{"type":"object","required":["id","company_id","pipeline_id","pipeline_version_id","trigger_type","status","dry_run","created_at","attempts","max_attempts","scheduled_at"],"properties":{"actor_user_id":{"type":"integer","format":"int32","description":"The user whose action queued this run (manual caller, replayer, or the\ntriggering event's actor). Null for schedule fires / webhook calls.","nullable":true},"attempts":{"type":"integer","format":"int32"},"causation_id":{"type":"string","nullable":true},"claimed_at":{"type":"string","nullable":true},"claimed_by":{"type":"string","nullable":true},"company_id":{"type":"string"},"created_at":{"type":"string"},"credits_spent":{"type":"string","nullable":true},"dry_run":{"type":"boolean"},"error":{"type":"string","nullable":true},"finished_at":{"type":"string","nullable":true},"heartbeat_at":{"type":"string","nullable":true},"id":{"type":"string"},"max_attempts":{"type":"integer","format":"int32"},"pipeline_id":{"type":"string"},"pipeline_version_id":{"type":"string"},"scheduled_at":{"type":"string"},"started_at":{"type":"string","nullable":true},"status":{"type":"string"},"trigger_event_id":{"type":"string","nullable":true},"trigger_payload":{"description":"The injected `{{ trigger.* }}` payload for a manual test run, if any.","nullable":true},"trigger_type":{"type":"string"}}},"PipelineStepRunResponse":{"type":"object","required":["id","run_id","node_id","node_kind","status"],"properties":{"credits_spent":{"type":"string","nullable":true},"duration_ms":{"type":"integer","format":"int32","nullable":true},"error":{"type":"string","nullable":true},"finished_at":{"type":"string","nullable":true},"id":{"type":"string"},"input_json":{"nullable":true},"node_id":{"type":"string"},"node_kind":{"type":"string"},"output_json":{"nullable":true},"resolved_expr_json":{"nullable":true},"run_id":{"type":"string"},"started_at":{"type":"string","nullable":true},"status":{"type":"string"}}},"PipelineUsageResponse":{"type":"object","description":"One calendar month of pipeline usage for the tenant — the metering\nrollup billing can invoice from. Skipped runs (throttled/loop-guarded)\nnever executed and are excluded everywhere.","required":["month","runs_total","steps_total","credits_total","runs_by_trigger_type","credits_by_node_kind"],"properties":{"credits_by_node_kind":{"description":"Executed-step credit totals keyed by node kind (decimal strings)."},"credits_total":{"type":"string","description":"Total credits spent by the month's runs (base node charges + metered\nAI spend), as a decimal string."},"month":{"type":"string","description":"The month aggregated, `YYYY-MM` (UTC)."},"runs_by_trigger_type":{"description":"Non-skipped run counts keyed by trigger type\n(`event`/`manual`/`schedule`/`webhook`)."},"runs_total":{"type":"integer","format":"int64","description":"Non-skipped runs created in the month."},"steps_total":{"type":"integer","format":"int64","description":"Successfully executed steps in the month."}}},"PipelineVersionResponse":{"type":"object","required":["id","pipeline_id","version","graph","created_at"],"properties":{"created_at":{"type":"string"},"created_by":{"type":"integer","format":"int32","nullable":true},"graph":{},"id":{"type":"string"},"pipeline_id":{"type":"string"},"version":{"type":"integer","format":"int32"}}},"PlanFeatureRow":{"type":"object","required":["feature_code","enabled","limits","tier","name"],"properties":{"enabled":{"type":"boolean"},"feature_code":{"type":"string"},"limits":{"type":"object"},"name":{"type":"string"},"tier":{"type":"string","description":"Denormalized from `billing_feature` so the composer UI can render and\ndisable Core rows without a second round trip."}}},"PodUploadForm":{"type":"object","description":"Multipart form shape for `POST /fulfillment/delivery/{id}/pod`, mirroring\n`products::image_handlers::ProductImageUploadForm`.","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"Proof-of-delivery image file to upload."}}},"PodUploadResponse":{"type":"object","required":["shipment_id","pod_url"],"properties":{"pod_url":{"type":"string"},"shipment_id":{"type":"string"}}},"PolicySource":{"type":"string","description":"Why MFA is (or isn't) in force for a user — surfaced so the UI can explain\nitself instead of just greying out a button. \"Your company requires this\" and\n\"you switched this on yourself\" need very different copy.","enum":["company_policy","user_opt_in","default"]},"PortalCartCheckoutRequest":{"type":"object","description":"Convert the server-side cart into an order — POST /api/portal/cart/checkout","properties":{"client_order_id":{"type":"string","description":"Client-generated id making the checkout idempotent across retries —\nsame contract as POST /api/portal/orders","nullable":true},"note":{"type":"string","description":"Overrides the cart note when non-empty","nullable":true}}},"PortalCartItemDto":{"type":"object","description":"A cart line with resolved pricing — GET/PUT /api/portal/cart","required":["product_id","product_name","qty","unit_price","line_total","available_qty"],"properties":{"available_qty":{"type":"integer","format":"int32","description":"Current stock on hand for the product"},"image_url":{"type":"string","nullable":true},"line_total":{"type":"number","format":"double"},"product_id":{"type":"string"},"product_name":{"type":"string"},"qty":{"type":"integer","format":"int32"},"unit_price":{"type":"number","format":"double","description":"Effective unit price for THIS account (price-list aware — the same\nresolution order creation uses, never raw wholesale)"}}},"PortalCartLineRequest":{"type":"object","description":"One line of a PUT /api/portal/cart replace request","required":["product_id","qty"],"properties":{"product_id":{"type":"string"},"qty":{"type":"integer","format":"int32","description":"Must be >= 1 — removal is expressed by omitting the line"}}},"PortalCartResponseDto":{"type":"object","description":"The portal user's cart — GET/PUT /api/portal/cart","required":["items","note","total"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PortalCartItemDto"}},"note":{"type":"string"},"total":{"type":"number","format":"double","description":"Sum of line totals at effective prices"},"updated_at":{"type":"string","description":"Last modification time; null when the user has no cart yet","nullable":true}}},"PortalDesignSettings":{"type":"object","description":"Optional builder-designed homepage for the B2B customer portal.\n\nOff (the default) keeps today's behaviour: `/portal` redirects straight to\nthe catalogue. On, the portal renders `sections` (the same section engine\nas the B2C storefront, priced for the signed-in account) as its landing\npage. Colours and logo still come from `portal_settings`.","properties":{"sections":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontSection"},"description":"Sections displayed on the custom B2B portal homepage."},"show_catalog_link":{"type":"boolean","description":"Keep the \"Browse catalogue\" shortcut in the portal header even when\nthe custom homepage is on."},"template_id":{"type":"string","description":"Which starter template was last applied (informational, may be empty)."},"use_custom_homepage":{"type":"boolean","description":"Whether the B2B portal uses the configured section-based homepage."}}},"PortalExperienceSettingsResponse":{"type":"object","description":"Portal-safe presentation settings used by B2B/mobile/POS frontends.","required":["company_id","currency","timezone","language","date_format","time_format","allow_out_of_stock_orders"],"properties":{"allow_out_of_stock_orders":{"type":"boolean"},"company_id":{"type":"string"},"currency":{"type":"string"},"currency_locale":{"type":"string","nullable":true},"currency_symbol":{"type":"string","nullable":true},"date_format":{"type":"string"},"display_name":{"type":"string","nullable":true},"language":{"type":"string"},"logo_url":{"type":"string","nullable":true},"portal_name":{"type":"string","nullable":true},"primary_color":{"type":"string","nullable":true},"secondary_color":{"type":"string","nullable":true},"tax_rate":{"type":"number","format":"double","nullable":true},"time_format":{"type":"string"},"timezone":{"type":"string"}}},"PortalFrequentProductDto":{"type":"object","description":"A frequently ordered product in the portal catalog","required":["id","name","description","item_number","regular_price","wholesale_price","qty","order_count","last_ordered_at"],"properties":{"category_id":{"type":"string","nullable":true},"description":{"type":"string"},"id":{"type":"string"},"image_url":{"type":"string","description":"First product image URL (thumbnail 400px, or full if not available)","nullable":true},"item_number":{"type":"string"},"last_ordered_at":{"type":"string","description":"Last time this product was ordered by the account"},"name":{"type":"string"},"order_count":{"type":"integer","format":"int32","description":"Number of distinct orders that contain this product"},"qty":{"type":"integer","format":"int32"},"regular_price":{"type":"number","format":"double","description":"Regular (list) price as JSON number"},"wholesale_price":{"type":"number","format":"double","description":"Wholesale (B2B) price as JSON number"}}},"PortalHomeResponse":{"type":"object","description":"Builder-designed portal homepage (`GET /api/portal/home`).","required":["enabled","show_catalog_link","store_name","currency","sections"],"properties":{"currency":{"type":"string"},"enabled":{"type":"boolean","description":"False when the merchant has not switched the custom homepage on — the\nportal then keeps redirecting to the catalogue."},"sections":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontSectionResponse"},"description":"Denormalised sections, priced at wholesale for the signed-in account."},"show_catalog_link":{"type":"boolean"},"store_name":{"type":"string"}}},"PortalLoginRequest":{"type":"object","description":"Login request for portal users.\nThe `email` field accepts either an email address or a username.","required":["email","password","company_id"],"properties":{"company_id":{"type":"string"},"email":{"type":"string","description":"Email address or username"},"password":{"type":"string"}}},"PortalLoginResponse":{"type":"object","description":"Response for successful portal login","required":["message","token","user"],"properties":{"message":{"type":"string"},"token":{"type":"string"},"user":{"$ref":"#/components/schemas/PortalUserInfo"}}},"PortalOrderDetailDto":{"type":"object","description":"Order detail with line items — GET /api/portal/orders/{id}","required":["id","date","total","note","created_at","items"],"properties":{"created_at":{"type":"string"},"date":{"type":"string"},"delivered_at":{"type":"string","description":"When the order was delivered, if recorded. Additive field.","nullable":true},"delivery_status":{"type":"string","description":"Delivery status when fulfillment tracks it. Additive field.","nullable":true},"id":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/PortalOrderItemDto"}},"note":{"type":"string"},"order_number":{"type":"string","nullable":true},"status":{"type":"string","description":"Order lifecycle status (e.g. \"submitted\"). Additive field — declared\noptional in the schema so older client bindings stay compatible; the\nserver always sends it.","nullable":true},"total":{"type":"number","format":"double"}}},"PortalOrderDto":{"type":"object","description":"A single order in the orders list","required":["id","date","total","note","created_at"],"properties":{"created_at":{"type":"string"},"date":{"type":"string"},"id":{"type":"string"},"note":{"type":"string"},"order_number":{"type":"string","nullable":true},"status":{"type":"string","description":"Order lifecycle status (e.g. \"submitted\"). Additive field — declared\noptional in the schema so older client bindings stay compatible; the\nserver always sends it.","nullable":true},"total":{"type":"number","format":"double"}}},"PortalOrderItemDto":{"type":"object","description":"A line item within an order detail","required":["id","product_id","qty","price","total"],"properties":{"id":{"type":"string"},"price":{"type":"number","format":"double"},"product_id":{"type":"string"},"product_name":{"type":"string","nullable":true},"qty":{"type":"integer","format":"int32"},"total":{"type":"number","format":"double"}}},"PortalProductDto":{"type":"object","description":"A single product in the portal catalog","required":["id","name","description","item_number","regular_price","wholesale_price","qty"],"properties":{"category_id":{"type":"string","nullable":true},"description":{"type":"string"},"id":{"type":"string"},"image_url":{"type":"string","description":"First product image URL (thumbnail 400px, or full if not available)","nullable":true},"item_number":{"type":"string"},"name":{"type":"string"},"qty":{"type":"integer","format":"int32"},"regular_price":{"type":"number","format":"double","description":"Regular (list) price as JSON number"},"wholesale_price":{"type":"number","format":"double","description":"Wholesale (B2B) price as JSON number"}}},"PortalRequestPasswordResetRequest":{"type":"object","description":"Self-service password reset request — public endpoint, takes email + `company_id`","required":["email","company_id"],"properties":{"company_id":{"type":"string"},"email":{"type":"string"}}},"PortalResetPasswordConfirmRequest":{"type":"object","description":"Request body for portal user confirming their own password reset via token","required":["token","new_password"],"properties":{"new_password":{"type":"string"},"token":{"type":"string"}}},"PortalResponse":{"type":"object","description":"Response from POST /api/billing/portal","required":["portal_url"],"properties":{"portal_url":{"type":"string"}}},"PortalReturnDetailDto":{"type":"object","description":"Return request with line items — GET /api/portal/returns/{id}","required":["id","return_number","status","reason","customer_note","staff_note","created_at","updated_at","items"],"properties":{"created_at":{"type":"string"},"customer_note":{"type":"string"},"id":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/PortalReturnItemDto"}},"order_id":{"type":"string","nullable":true},"reason":{"type":"string"},"refund_amount":{"type":"number","format":"double","nullable":true},"return_number":{"type":"string"},"staff_note":{"type":"string"},"status":{"type":"string"},"updated_at":{"type":"string"}}},"PortalReturnDto":{"type":"object","description":"A return request header","required":["id","return_number","status","reason","customer_note","staff_note","created_at","updated_at"],"properties":{"created_at":{"type":"string"},"customer_note":{"type":"string"},"id":{"type":"string"},"order_id":{"type":"string","nullable":true},"reason":{"type":"string"},"refund_amount":{"type":"number","format":"double","nullable":true},"return_number":{"type":"string"},"staff_note":{"type":"string","description":"Staff-visible response note (set during staff review)"},"status":{"type":"string","description":"One of: requested, approved, rejected, received, refunded, cancelled"},"updated_at":{"type":"string"}}},"PortalReturnItemDto":{"type":"object","description":"A line of a return request","required":["id","product_id","qty","unit_price","reason"],"properties":{"id":{"type":"string"},"order_item_id":{"type":"string","nullable":true},"product_id":{"type":"string"},"product_name":{"type":"string","nullable":true},"qty":{"type":"integer","format":"int32"},"reason":{"type":"string"},"unit_price":{"type":"number","format":"double","description":"The unit price the customer paid on the original order line"}}},"PortalReturnLineRequest":{"type":"object","description":"One requested return line — must reference a line of the order","required":["order_item_id","qty"],"properties":{"order_item_id":{"type":"string","description":"`webapp_salesorderitem.id` of the order line being returned"},"qty":{"type":"integer","format":"int32","description":"Quantity to return; must be >= 1 and <= the ordered quantity"},"reason":{"type":"string","description":"Optional per-line reason (max 50 chars); falls back to the request reason","nullable":true}}},"PortalReturnsResponseDto":{"type":"object","description":"Paginated returns list — GET /api/portal/returns","required":["results","count"],"properties":{"count":{"type":"integer","format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/PortalReturnDto"}}}},"PortalSettingsResponse":{"type":"object","description":"Portal settings response (admin-facing)","required":["company_id","is_enabled","show_only_with_images","show_only_in_stock","allow_out_of_stock_orders"],"properties":{"allow_out_of_stock_orders":{"type":"boolean"},"catalog_display_settings":{"allOf":[{"$ref":"#/components/schemas/CatalogDisplaySettings"}],"nullable":true},"company_id":{"type":"string"},"created_at":{"type":"string","nullable":true},"display_name":{"type":"string","nullable":true},"id":{"type":"string","nullable":true},"is_enabled":{"type":"boolean"},"login_page_content":{"type":"string","nullable":true},"logo_url":{"type":"string","nullable":true},"portal_base_domain":{"type":"string","description":"The base domain for the portal (informational, shown when no `portal_name` is set)","nullable":true},"portal_name":{"type":"string","nullable":true},"portal_url":{"type":"string","description":"Computed portal URL: `https://{portal_name}.{PORTAL_BASE_DOMAIN}`","nullable":true},"primary_color":{"type":"string","nullable":true},"secondary_color":{"type":"string","nullable":true},"show_only_in_stock":{"type":"boolean"},"show_only_with_images":{"type":"boolean"},"updated_at":{"type":"string","nullable":true}}},"PortalUserInfo":{"type":"object","description":"Portal user info returned on login / me endpoints","required":["id","email","name","company_id","account_id","role"],"properties":{"account_id":{"type":"string"},"company_id":{"type":"string"},"email":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"role":{"type":"string"}}},"PortalUserResponse":{"type":"object","description":"Full portal user record (admin-facing)","required":["id","email","first_name","last_name","account_id","company_id","is_active","created_at"],"properties":{"account_id":{"type":"string"},"company_id":{"type":"string"},"created_at":{"type":"string"},"email":{"type":"string"},"first_name":{"type":"string"},"id":{"type":"string"},"is_active":{"type":"boolean"},"last_login":{"type":"string","nullable":true},"last_name":{"type":"string"},"username":{"type":"string","nullable":true}}},"PosAccountCreateRequest":{"type":"object","description":"What the till sends for a customer it created.","required":["localId","name"],"properties":{"address":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"localId":{"type":"string","description":"The id the till minted (a UUID string). Becomes the cloud id, so a\nreceipt pushed against it resolves without a mapping step."},"name":{"type":"string"},"phone":{"type":"string","nullable":true}}},"PosAccountCreateResponse":{"type":"object","description":"The stored customer, in the same shape the accounts pull delivers.","required":["id","name","accountNumber","existed"],"properties":{"accountNumber":{"type":"string"},"accountSeqNumber":{"type":"string","nullable":true},"existed":{"type":"boolean","description":"True when the id already existed (a retried push); nothing changed."},"id":{"type":"string"},"name":{"type":"string"}}},"PosCartItem":{"type":"object","required":["product_id","quantity"],"properties":{"note":{"type":"string","nullable":true},"product_id":{"type":"string"},"quantity":{"type":"integer","format":"int32"},"unit_price":{"type":"string","nullable":true}}},"PosCashSettings":{"type":"object","description":"Cash-handling policy for the till: the physical notes and coins in the\ndrawer, and what a cashier may do with them.\n\nEvery value here was a hardcoded EUR-shaped constant in the terminal\n(`app/lib/cash-drawer.ts`) or a terminal-local settings key with no admin\nUI at all, which meant rolling out ten tills meant hand-typing the\ndenomination ladder ten times at the counter — and typing a Dutch one in a\nshop that handles dollars.\n\nThe terminal keeps its local `denomination_config` / `cash_movement_config`\nkeys as an OVERRIDE, so a till already configured by hand does not change\nunder its operator when this group starts arriving.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level default only fires when the WHOLE group is missing).","properties":{"default_opening_float":{"type":"number","format":"double","description":"Opening float pre-filled at the start of a shift, in MAJOR units.\n`0` (the default) reproduces the till's behaviour of starting empty."},"denominations_minor":{"type":"array","items":{"type":"integer","format":"int64"},"description":"Drawer-count ladder in MINOR units (cents). Empty (the default) means\n\"use the till's built-in ladder\", which keeps existing terminals on\nexactly what they had.\n\nMinor units, not majors, because a ladder of `[0.01, 0.02]` in floats\nis a rounding argument waiting to happen at the one place in a POS\nwhere the number must be exact."},"lowest_note_minor":{"type":"integer","format":"int64","description":"At or above this MINOR amount a denomination is drawn as a note rather\nthan a coin. Cosmetic grouping on the count screen only. 500 (5 major\nunits) suits EUR/USD/GBP; JPY wants 100000, XCG/AWG want 1000."},"max_drawer_amount":{"type":"number","format":"double","description":"Warn the cashier once expected cash in the drawer exceeds this MAJOR\namount. `0` (the default) disables the check entirely."},"paid_in_reasons":{"type":"array","items":{"type":"string"},"description":"Reasons offered when cash is put IN the drawer. Empty (the default)\nmeans \"use the till's built-in list\", which is translated locally."},"paid_out_reasons":{"type":"array","items":{"type":"string"},"description":"Reasons offered when cash is taken OUT of the drawer. Empty (the\ndefault) means \"use the till's built-in list\"."},"pos_cash_actions":{"type":"boolean","description":"Show the Cash menu (open drawer / no sale, paid in, paid out, opening\nfloat, X-report) on the SALE screen. These actions already existed\nunder Settings → Cash, three screens away from a queue."},"quick_tender_denominations":{"type":"array","items":{"type":"number","format":"double"},"description":"Quick-tender buttons on the cash payment screen, in MAJOR units.\n\nEmpty (the default) means \"derive them from `denominations_minor`\" —\nthe drawer ladder and the tender ladder are the same physical notes, so\nconfiguring one should configure both. The till's own fallback ladder\n(5/10/20/50) is missing the 25 rung that AWG and the older ANG notes\nuse, which is exactly the kind of thing that must not be a constant."},"safe_drop_prompt":{"type":"boolean","description":"Offer a safe-drop paid-out when `max_drawer_amount` is exceeded."},"x_report_from_pos":{"type":"boolean","description":"Offer the X-report from that menu."}}},"PosCatalogueSettings":{"type":"object","description":"Point-of-sale catalogue behaviour for the till.\n\nCovers how a scanned or typed code is matched, what Enter does in the\nsearch box, search and scanner timing, and whether the server's stock\nfigure overwrites the till's own running count.\n\nEvery value here was a literal in the terminal (`WHERE barcode = ?`,\n`SCAN_DEBOUNCE_MS = 200`, a 50 ms burst guard) or simply absent (no Enter\nhandling in the search box, no stock ingest at all). A retail shop whose\nshelf labels carry the item code could not scan a single product, and\nthere was nothing an administrator could change about it. Company-wide;\ncode holds the defaults.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level default only fires when the WHOLE group is missing).","properties":{"allow_customer_creation_at_till":{"type":"boolean","description":"Let a cashier create a customer at the till (name, phone, e-mail) and\nlink the sale to it. The record is pushed to the cloud on the next sync\nthrough `POST /api/pos/accounts`, which needs only `pos.sell`."},"auto_add_on_scan":{"type":"boolean","description":"A scan that matches exactly one product adds it to the receipt at\nonce, honouring a quantity typed beforehand."},"clear_search_after_add":{"type":"boolean","description":"Empty the search box after a scan or Enter added a product, so the\nnext scan starts clean."},"cloud_receipt_history_days":{"type":"integer","format":"int32","description":"How many days of completed receipts a till pulls from the cloud on its\nfirst sync, so a cashier can look up, reprint and refund a sale rung\non another terminal. Incremental pulls are not windowed. `0` turns the\nreceipts pull off for the company. Default 90; max 3650.","minimum":0},"enter_adds_single_match":{"type":"boolean","description":"Enter in the search box adds the only remaining match. Off means Enter\nonly adds an exact code match."},"keypad_burst_guard_ms":{"type":"integer","format":"int32","description":"A digit followed by another key within this window (milliseconds) is\nscanner input, not a quantity for the next item.","minimum":0},"match_fields":{"type":"array","items":{"type":"string"},"description":"Identifiers a scanned or typed code is matched against, in priority\norder when one code matches two products. Allowed values:\n`\"barcode\"`, `\"sku\"` (the item number) and `\"code\"`."},"out_of_stock_behavior":{"type":"string","description":"`\"confirm\"` asks the cashier before adding a tracked product at zero\nstock; `\"allow\"` adds it with only the badge as a warning; `\"block\"`\nrefuses."},"scanner_max_key_gap_ms":{"type":"integer","format":"int32","description":"Longest pause between two characters that still counts as one scanner\nburst, in milliseconds. Raise it for a slow scanner, lower it if fast\ntypists trigger lookups.","minimum":0},"search_debounce_ms":{"type":"integer","format":"int32","description":"How long the till waits after the last keystroke before it searches\nthe catalogue, in milliseconds. 0 searches on every keystroke.","minimum":0},"sync_stock_from_cloud":{"type":"boolean","description":"Let the server's on-hand figure overwrite the till's running count on\nevery sync. Off keeps whatever the till counted itself."},"unknown_code_behavior":{"type":"string","description":"`\"search\"` puts an unknown code in the search box; `\"notify\"` shows a\nnot-found notice and leaves the search alone."}}},"PosListsSettings":{"type":"object","description":"How many rows the till's lists load.\n\nThese were five `TODO: restore to N` literals in the terminal that\nSHIPPED at 10 rows each, so the receipts list, receipt search, audit log,\nproduct history and gift-card list all stopped at ten entries in\nproduction. Admin-configurable per company; the defaults are the numbers\nthose TODOs were meant to be restored to.","properties":{"audit_log_rows":{"type":"integer","format":"int32","description":"Maximum rows loaded for the till audit log.","minimum":0},"gift_card_list_page_size":{"type":"integer","format":"int32","description":"Rows loaded per page in the gift-card list.","minimum":0},"product_history_rows":{"type":"integer","format":"int32","description":"Maximum rows loaded for product history.","minimum":0},"receipt_list_page_size":{"type":"integer","format":"int32","description":"Rows loaded per page in the receipt list.","minimum":0},"receipt_search_rows":{"type":"integer","format":"int32","description":"Maximum rows loaded for receipt search results.","minimum":0}}},"PosOrderRequest":{"type":"object","required":["items","payments"],"properties":{"account_id":{"type":"string","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/PosCartItem"}},"note":{"type":"string","nullable":true},"payments":{"type":"array","items":{"$ref":"#/components/schemas/PosPayment"}},"session_id":{"type":"string","nullable":true}}},"PosPayment":{"type":"object","required":["payment_method","amount"],"properties":{"amount":{"type":"string"},"payment_method":{"type":"string"}}},"PosPricingSettings":{"type":"object","description":"Point-of-sale pricing policy.\n\nBoth defaults reproduce the behaviour that existed before this group, so an\nexisting tenant sees no change until they opt in from the admin UI.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level default only fires when the WHOLE group is missing).","properties":{"accepted_tender_currencies":{"type":"array","items":{"type":"string"},"description":"Foreign currencies the till accepts as CASH alongside the store\ncurrency, e.g. `[\"USD\"]` for a Curaçao shop pricing in XCG.\n\nEmpty (the default) means store currency only, which is what the till\ndid before this existed. Rates come from\n`/api/tax-currency/currency-rates`; change is always given in the store\ncurrency, never in the tendered one."},"allow_custom_items":{"type":"boolean","description":"\"Open items\" — a line the cashier names and prices at the counter,\nfor something that is not in the catalogue."},"allow_price_override":{"type":"boolean","description":"A cashier may change a line's unit price at the till. The catalogue\nprice is kept on the receipt line either way, so a changed price is\nalways visible on the receipt and in the cloud."},"cash_rounding_increment":{"type":"number","format":"double","description":"Increment that CASH tenders are rounded to, e.g. `0.05` for the Dutch\nfive-cent rule. `0` disables rounding entirely, which is the default.\nCard and other electronic tenders are never rounded."},"currency_minor_units":{"type":"integer","format":"int32","description":"ISO 4217 minor-unit digits for the store currency: 2 for EUR/USD/XCG,\n0 for JPY/ISK/CLP, 3 for BHD/KWD/TND.\n\nDrives money rounding, display formatting and the cash numpad. Default 2\nreproduces the till's previous hardcoded behaviour. Clamped to 0..=3 by\nevery consumer, so a nonsense value cannot make prices unreadable.","minimum":0},"custom_item_max_amount":{"type":"number","format":"double","description":"Line total above which an open item needs a manager PIN. `0` = no cap."},"custom_item_requires_manager":{"type":"boolean","description":"Every open item needs a manager PIN."},"default_credit_limit":{"type":"number","format":"double","description":"Company-wide credit limit for charging a sale to a customer account.\n`0` (the default) means no limit is configured and On Account is\nrefused, which is exactly what every till did before this existed.\n\n`On Account` was a PERMANENTLY dead tender: the confirm screen fails\nclosed when no credit limit is set, and nothing anywhere ever populated\none — `webapp_account` has `store_credit` and `ar_balance` but no\ncredit-limit column at all, so the till's own `accounts.credit_limit`\nwas written by nothing. The message the customer saw (\"no credit limit\nhas been set for {account}\") was true and unfixable from the admin.\n\nA company-wide limit makes the tender usable today without a schema\nchange. A PER-CUSTOMER limit still wins where one exists, so the day\nthe cloud grows that column the till already prefers it."},"discount_manager_approval_percent":{"type":"number","format":"double","description":"Discounts ABOVE this percentage need a manager/owner PIN. `0` makes\nevery discount require approval; `100` disables the gate.\n\nThis was `const MANAGER_THRESHOLD_PCT = 20` in `DiscountModal.tsx` — a\nhardcoded policy no merchant could change without a redeploy, which is\nthe configurability rule the refund threshold in `pos_security` already\nfollows. The default MUST stay 20.0: anything else would silently move\nthe approval gate on every till that has never touched this setting."},"max_line_quantity":{"type":"number","format":"double","description":"Largest quantity the till will accept for one line.\n\nThe quantity multiplier on the POS screen was `prev * 10 + digit` with\nno cap, so ten keystrokes exceeded `i32::MAX`; the cloud then rejected\nthe line as an invalid quantity and the till marked the whole receipt\nPERMANENTLY unsyncable. A paid sale was stranded by a slipped finger."},"price_override_manager_approval_percent":{"type":"number","format":"double","description":"An override that LOWERS the price by more than this share of the\ncatalogue price needs a manager PIN. `0` puts every reduction behind\nthe gate; `100` disables it. Raising a price never needs one."},"price_override_reasons":{"type":"array","items":{"type":"string"},"description":"The merchant's own override reasons. Empty (the default) means the\ntill offers its built-in list (Damaged, Price match, Manager, Other)."},"prices_include_tax":{"type":"boolean","description":"When true, catalogue prices are gross (VAT already included) and the\ntill extracts tax out of the line rather than adding it on top.\n\nThis is the norm for EU retail and hospitality shelf pricing. The\ndefault is `false` because that is what the terminal did before this\nsetting existed; flipping it silently would change every price a\ncustomer is charged."},"quantity_decimals":{"type":"integer","format":"int32","description":"Decimal places shown for a line QUANTITY. 0 (the default) is right for\nunit-priced retail; a deli or butcher selling by weight wants 2 or 3.","minimum":0},"tax_label":{"type":"string","description":"What the merchant's country calls its consumption tax: `OB` in Curaçao,\n`Sales Tax` in the USA, `BTW` in the Netherlands, `VAT` in the UK,\n`BBO`/`BAZV` in Aruba.\n\nEmpty (the default) means \"use the generic word for tax in the till's\nlanguage\" — `Tax`/`Belasting`/`Impuesto`. It deliberately does NOT\ndefault to `VAT`: every till surface used to hardcode that word, so a\nCuraçao shop printed `VAT` on a receipt that legally levies OB. A\nper-rate name from `tax_rules.name` (delivered on the product sync\npayload) overrides this for lines that carry one."},"untracked_stock_sentinel":{"type":"number","format":"double","description":"Stock level at or above which a product is treated as NOT stock-tracked,\nso the out-of-stock confirm is skipped for it.\n\nReplaces a literal `product.qty < 999` on the POS screen, under which a\nproduct legitimately holding 1000+ units silently stopped raising the\nout-of-stock confirm."}}},"PosReceiptItem":{"type":"object","required":["product_id","product_name","quantity","unit_price","line_total"],"properties":{"line_total":{"type":"string"},"product_id":{"type":"string"},"product_name":{"type":"string"},"quantity":{"type":"integer","format":"int32"},"unit_price":{"type":"string"}}},"PosReceiptPayment":{"type":"object","required":["payment_method","amount"],"properties":{"amount":{"type":"string"},"payment_method":{"type":"string"}}},"PosReceiptResponse":{"type":"object","required":["order_id","date","items","subtotal","tax","total","payments","change_due","cashier_name"],"properties":{"cashier_name":{"type":"string"},"change_due":{"type":"string"},"date":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/PosReceiptItem"}},"order_id":{"type":"string"},"order_number":{"type":"string","nullable":true},"payments":{"type":"array","items":{"$ref":"#/components/schemas/PosReceiptPayment"}},"receipt_number":{"type":"string","nullable":true},"subtotal":{"type":"string"},"tax":{"type":"string"},"terminal_name":{"type":"string","nullable":true},"total":{"type":"string"}}},"PosReportsSettings":{"type":"object","description":"X/Z-report and business-day policy for the till.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level default only fires when the WHOLE group is missing).","properties":{"blind_count":{"type":"boolean","description":"Hide the expected cash figure until the drawer count is submitted.\nShowing it while the cashier counts defeats the control."},"business_day_start_hour":{"type":"integer","format":"int32","description":"Hour (0..=23, store timezone) at which a new business day starts. `0`\n(the default) is plain local midnight, which is what the till did\nbefore. A bar closing at 03:00 sets this to 4 so one night's takings\nland on one Z-report instead of being split across two, permanently.","minimum":0},"require_cash_count":{"type":"boolean","description":"Refuse to close the Z-report without a drawer count. Default false —\na close must never be blocked by something a merchant did not ask for."}}},"PosSecuritySettings":{"type":"object","description":"Point-of-sale staff-PIN policy.\n\nThe terminal reads this out of the synced `company_settings` blob\n(`frontend/apps/desktop/src-tauri/src/security/pin.rs`,\n`PinPolicy::from_company_settings`) and enforces it locally, because PIN entry\nhas to keep working with no network.\n\nWhy any of this is configurable: a four-digit PIN has 10,000 possible values,\nso an attacker holding a copy of the terminal's database can recover one by\nbrute force no matter how it is hashed. The salted Argon2 hash the till now\nstores raises the cost of that search and stops it from being precomputed, but\nthe only change that alters the arithmetic is a longer PIN — six digits is a\n100× larger search, eight is 10,000×. That is a trade against how fast a\ncashier can switch mid-rush, which is the merchant's call, not ours.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level default only fires when the WHOLE group is missing).","properties":{"approval_token_ttl_seconds":{"type":"integer","format":"int64","description":"How long a manager's step-up approval stays valid, in seconds.\n\nA manager PIN mints a single-use, action-scoped approval token that the\nterminal's Rust side requires before it will set a PIN, issue a gift\ncard, close the day, or move cash — so the renderer cannot approve\nitself. This is that token's lifetime: long enough for the manager to\nwalk away mid-dialog, short enough that a token cannot be banked."},"cash_movement_manager_approval_threshold":{"type":"number","format":"double","description":"Cash paid-in/paid-out at or above this amount (store currency) needs a\nmanager PIN. Paid-out is the standard cash-skim vector and had no gate\nand no threshold at all before this existed."},"gift_card_mask_mode":{"type":"string","description":"How gift card numbers render on a terminal: `\"last4\"` (default) shows\n`•••• 4821` and requires `pos.reveal_gift_card` to unmask one card;\n`\"full\"` restores the pre-masking behaviour for a merchant who wants it."},"idle_lock_seconds":{"type":"integer","format":"int64","description":"Lock the terminal to the cashier-PIN screen after this many seconds of\nno input. `0` disables the idle lock.\n\nDefaults to `0` so an upgrade never starts locking a till that was not\nasked to lock; a shop that leaves a terminal on the counter should set\nit (120–300 is the usual range)."},"max_pin_attempts":{"type":"integer","format":"int32","description":"Consecutive wrong PINs before the keypad locks. `0` disables the limiter.\n\nWithout a limiter the on-screen keypad is itself a viable attack on a\n4-digit PIN — nothing counted failures before this existed.","minimum":0},"min_pin_length":{"type":"integer","format":"int32","description":"Minimum staff PIN length. Clamped to 4..=12 by the terminal, so a value\noutside that range cannot lock a shop out of its own till.","minimum":0},"pin_lockout_seconds":{"type":"integer","format":"int64","description":"How long the keypad stays locked, in seconds. Survives an app restart, so\nclosing and reopening the till does not clear it."},"refund_manager_approval_threshold":{"type":"number","format":"double","description":"Refunds at or below this amount (store currency) clear without a\nmanager PIN; above it the cashier must get a manager/owner PIN first.\n\nThe terminal (`frontend/apps/desktop/src-tauri/src/security/pin.rs`'s\n`RefundPolicy::from_company_settings`) has read this exact key —\n`pos_security.refund_manager_approval_threshold` — since before this\nfield existed here, falling back to its own local default of 100.0\nbecause the backend had nowhere to store a tenant override. This is\nthat field, closing the loop: default here MUST stay 100.0 or a\ndeploy would silently change the approval threshold on every till\nthat had never touched this setting."},"reject_trivial_pins":{"type":"boolean","description":"Refuse PINs a colleague would guess standing at the terminal: repeated\ndigits (`0000`) and runs (`1234`, `4321`). This is a different threat from\nan attacker with the database file, and it is the more common one."},"require_pin_for_settings":{"type":"boolean","description":"Require a manager PIN to open the terminal's Settings screen, on top of\nthe `pos_terminals.manage` permission check.\n\nDefaults to `true`: Settings is where the server URL, the connection\nmode (including \"offline\", which stops every push) and every employee's\nPIN live. A merchant who runs a single-operator shop can turn it off."}}},"PosSessionResponse":{"type":"object","required":["id","company_id","cashier_id","opened_at","status","starting_cash","notes"],"properties":{"cashier_id":{"type":"integer","format":"int32"},"closed_at":{"type":"string","nullable":true},"company_id":{"type":"string"},"ending_cash":{"type":"string","nullable":true},"id":{"type":"string"},"notes":{"type":"string"},"opened_at":{"type":"string"},"starting_cash":{"type":"string"},"status":{"type":"string"},"terminal_id":{"type":"string","nullable":true}}},"PosShiftSettings":{"type":"object","description":"Point-of-sale day/shift policy.\n\nThe till has no day or shift ENTITY: \"open day\" is a `localStorage` flag,\nand `close_z_day` writes a `z_reports` row while deliberately touching no\nreceipt. Nothing therefore told the sale screen that a day had been closed,\nso a sale rung after the Z — at 18:05, after an 18:00 close — belonged to no\nZ report at all: the closed one was already immutable, and the next day's Z\ncovers from the next local midnight. The money simply left the books.\n\nThe safe default is to WARN rather than block, because refusing to sell is\nitself a way to lose a merchant money, and the pre-existing behaviour was to\nallow silently. A merchant who wants the harder guarantee turns the block on.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level default only fires when the WHOLE group is missing).","properties":{"block_sales_after_close":{"type":"boolean","description":"Refuse the sale outright instead of warning. Off by default: blocking a\nsale is a strictly bigger intervention than the defect it prevents."},"warn_on_sale_after_close":{"type":"boolean","description":"Warn the cashier when they ring a sale into a business day that has\nalready been Z-closed on this terminal."}}},"PosTerminalDetail":{"type":"object","required":["id","company_id","name","terminal_id","status","active","created_at","updated_at"],"properties":{"active":{"type":"boolean"},"app_version":{"type":"string","nullable":true},"company_id":{"type":"string"},"config":{"allOf":[{"$ref":"#/components/schemas/TerminalConfig"}],"nullable":true},"created_at":{"type":"string"},"id":{"type":"string"},"label":{"type":"string","nullable":true},"last_seen_at":{"type":"string","nullable":true},"last_sync_at":{"type":"string","nullable":true},"logged_in_user_id":{"type":"integer","format":"int32","nullable":true},"logged_in_user_name":{"type":"string","nullable":true},"name":{"type":"string"},"platform":{"type":"string","nullable":true},"platform_version":{"type":"string","nullable":true},"status":{"type":"string"},"terminal_id":{"type":"string"},"updated_at":{"type":"string"}}},"PosTerminalListItem":{"type":"object","required":["id","name","terminal_id","status","active","created_at","updated_at"],"properties":{"active":{"type":"boolean"},"app_version":{"type":"string","nullable":true},"created_at":{"type":"string"},"id":{"type":"string"},"label":{"type":"string","nullable":true},"last_seen_at":{"type":"string","nullable":true},"last_sync_at":{"type":"string","nullable":true},"logged_in_user_id":{"type":"integer","format":"int32","nullable":true},"logged_in_user_name":{"type":"string","nullable":true},"name":{"type":"string"},"platform":{"type":"string","nullable":true},"platform_version":{"type":"string","nullable":true},"status":{"type":"string"},"terminal_id":{"type":"string"},"updated_at":{"type":"string"}}},"PosTerminalResponse":{"type":"object","required":["message","terminal"],"properties":{"message":{"type":"string"},"terminal":{"$ref":"#/components/schemas/PosTerminalDetail"}}},"PosTerminalUiSettings":{"type":"object","description":"Point-of-sale terminal presentation — how big the till's UI is, and how\ndensely it packs.\n\nWhy this is a setting and not a constant: `minWidth`/`minHeight` in the\nterminal's Tauri config are LOGICAL pixels, so the viewport a maximised\nwindow gets is the panel's resolution divided by the Windows display\nscaling. A 1024x768 till at Windows' *default* 125% has an 819x614 viewport;\na 1366x768 till at 150% has 910x512. Those are common retail panels, and the\nonly lever that gives them their pixels back — short of telling a merchant\nto change a Windows display setting for one application — is the app's own\nscale. Conversely a 22\" back-office terminal is more comfortable at 115%.\nOne hardcoded size cannot serve both, and the right value depends on\nhardware the code cannot see.\n\nApplied on the till by writing `--ui-scale` to the document root, which\ndrives the root font-size and therefore every `rem` (Tailwind sizes\neverything in rem). See `frontend/apps/desktop/app/lib/ui-scale.ts` for the\nfull precedence rules: terminal-local override > per-terminal > this group >\nbuilt-in default.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level default only fires when the WHOLE group is missing).","properties":{"auto_update":{"type":"boolean","description":"Let the till check `https://cdn.vender.cloud/releases/desktop/latest.json`\nfor a newer build and offer it to the cashier.\n\nON by default: every till in the field today is updated by someone\nwalking to the counter with a USB stick, which is why installations run\nmonths-old builds carrying bugs that were fixed long ago. Turning this\noff is for a merchant with a change-freeze, not the normal case.\n\nThe flag governs the CHECK, not a silent install. Nothing is ever\ninstalled without a cashier pressing Install, and the terminal refuses\nto relaunch while a basket has lines on it or a table is bound — see\n`frontend/apps/desktop/app/hooks/use-app-update.ts`."},"density":{"type":"string","description":"`\"comfortable\"` (default) or `\"compact\"`. Compact trims vertical padding\non list rows only — never a control's tap target, which is `touch_mode`'s\njob — buying roughly one more row per screen on a short panel."},"hardware_discovery_timeout_ms":{"type":"integer","format":"int64","description":"Maximum wait for printer or serial-port discovery before showing recovery.","minimum":0},"product_default_view":{"type":"string","description":"Initial catalogue view on tills without an explicit local preference."},"product_grid_columns":{"type":"integer","format":"int32","description":"Preferred maximum product-grid columns; zero preserves responsive layout.","minimum":0},"start_maximized":{"type":"boolean","description":"Open the till maximised. Off by default because it would otherwise\noverride the window size a merchant has already chosen (the terminal\nremembers its last size in `vender-window-state`)."},"touch_mode":{"type":"boolean","description":"Enlarged tap targets and no hover-only affordances. Default `false`,\nmatching what a terminal did before this group existed."},"ui_scale_percent":{"type":"integer","format":"int32","description":"UI scale as a percentage; 100 is the design size. Clamped to 80..=150 by\nthe terminal, which also treats anything unparseable as 100 — a bad\nvalue here would make the till unreadable, and unlike a bad price the\nperson it affects cannot open the settings screen to correct it."}}},"PostCustomerReturnRequest":{"type":"object","required":["version"],"properties":{"version":{"type":"integer","format":"int32"}}},"PostGoodsReceiptRequest":{"type":"object","required":["version"],"properties":{"version":{"type":"integer","format":"int32"}}},"PreviewRequest":{"type":"object","description":"Body for `POST /api/query/{entity}/preview`.","required":["query"],"properties":{"limit":{"type":"integer","format":"int64","description":"Page size; clamped to the company's `preview_row_limit`.","nullable":true},"offset":{"type":"integer","format":"int64","description":"Row offset.","nullable":true},"query":{"type":"object","description":"The filter tree."},"sort_by":{"type":"string","description":"Field key to sort by. Must be registry-sortable; defaults to\n`created_at`.","nullable":true},"sort_dir":{"type":"string","description":"`asc` or `desc`; defaults to `desc`.","nullable":true}}},"PriceListResponse":{"type":"object","required":["id","company_id","name","currency","is_default","sort_order","entry_count","assignment_count","created_at","updated_at"],"properties":{"assignment_count":{"type":"integer","format":"int64"},"company_id":{"type":"string"},"created_at":{"type":"string"},"currency":{"type":"string"},"description":{"type":"string","nullable":true},"entry_count":{"type":"integer","format":"int64"},"id":{"type":"string"},"is_default":{"type":"boolean"},"name":{"type":"string"},"sort_order":{"type":"integer","format":"int32"},"updated_at":{"type":"string"}}},"PriceMargin":{"type":"object","description":"Margin at one of the product's list prices.","required":["price_type","price","margin"],"properties":{"margin":{"type":"string","description":"`price - average cost`."},"margin_percent":{"type":"string","description":"Margin as a percentage of price, `None` for a zero price.","nullable":true},"price":{"type":"string","description":"The list price."},"price_type":{"type":"string","description":"`regular_price`, `wholesale_price` or `msrp_price`."}}},"PrintJob":{"type":"object","required":["id","company_id","printer_profile_id","payload_type","payload","status","attempts","created_at"],"properties":{"attempts":{"type":"integer","format":"int32"},"claimed_by_terminal_id":{"type":"string","nullable":true},"company_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/DateTime"},"id":{"type":"string"},"last_error":{"type":"string","nullable":true},"open_order_id":{"type":"string","nullable":true},"payload":{"type":"string"},"payload_type":{"type":"string","description":"`escpos_base64` | html."},"printed_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true},"printer_profile_id":{"type":"string"},"receipt_id":{"type":"string","nullable":true},"status":{"type":"string","description":"queued | claimed | printed | failed."}}},"PrintJobResponse":{"type":"object","required":["id","printer_profile_id","payload_type","payload","status","attempts","created_at"],"properties":{"attempts":{"type":"integer","format":"int32"},"created_at":{"type":"string"},"id":{"type":"string"},"payload":{"type":"string"},"payload_type":{"type":"string"},"printer_profile_id":{"type":"string"},"status":{"type":"string"}}},"PrintProformaResponse":{"type":"object","description":"`POST /api/horeca/open-orders/{id}/print-proforma` response.","required":["print_jobs_created"],"properties":{"print_jobs_created":{"type":"integer","description":"How many `print_jobs` rows were enqueued. **Zero is a normal, and\nimportant, answer**: it means no printer profile matched, so nothing\nwill come out of any printer. The till surfaces this to the cashier\nrather than pretending the bill printed.","minimum":0}}},"PrinterProfile":{"type":"object","required":["id","company_id","name","printer_type","connection_type","is_default","created_at","updated_at"],"properties":{"company_id":{"type":"string"},"connection_config":{"nullable":true},"connection_type":{"type":"string"},"created_at":{"$ref":"#/components/schemas/DateTime"},"id":{"type":"string"},"is_default":{"type":"boolean"},"name":{"type":"string"},"printer_type":{"type":"string"},"updated_at":{"$ref":"#/components/schemas/DateTime"}}},"PrinterProfileWithRules":{"type":"object","description":"One element of the `GET /api/horeca/printer-profiles` response array.\nMirrors `handlers::list_printer_profiles`'s per-item\n`json!({\"profile\":.., \"routing_rules\":..})` exactly.","required":["profile","routing_rules"],"properties":{"profile":{"$ref":"#/components/schemas/crate.models.horeca.PrinterProfile"},"routing_rules":{"type":"array","items":{"$ref":"#/components/schemas/crate.models.horeca.PrinterRoutingRule"}}}},"PrinterRoutingRule":{"type":"object","required":["id","company_id","printer_profile_id","match_type","match_value","priority","created_at","updated_at"],"properties":{"company_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/DateTime"},"id":{"type":"string"},"match_type":{"type":"string"},"match_value":{"type":"string"},"printer_profile_id":{"type":"string"},"priority":{"type":"integer","format":"int32"},"updated_at":{"$ref":"#/components/schemas/DateTime"}}},"PrinterRoutingRuleInput":{"type":"object","required":["printer_profile_id","match_type","match_value"],"properties":{"match_type":{"type":"string"},"match_value":{"type":"string"},"printer_profile_id":{"type":"string","description":"Which profile this rule belongs to.\n\nDespite the name this has always carried the profile's **name**, because\nthe handler rebuilt every profile with a fresh id on each save and a name\nwas the only thing that survived. A real profile id is now accepted too,\nand is tried first; the name remains supported because the deployed admin\nsends it."},"priority":{"type":"integer","format":"int32","nullable":true}}},"ProductAttributeResponse":{"type":"object","description":"Product Attribute DTO","required":["name","code","value"],"properties":{"code":{"type":"string"},"name":{"type":"string"},"value":{"type":"string"}}},"ProductByItemNumber":{"type":"object","required":["id","item_number","name"],"properties":{"id":{"type":"string"},"item_number":{"type":"string"},"name":{"type":"string"}}},"ProductCollectionAssignmentResponse":{"type":"object","required":["id","collection_id","visibility_mode","created_at","updated_at"],"properties":{"account_id":{"type":"string","nullable":true},"collection_id":{"type":"string"},"created_at":{"type":"string"},"ends_at":{"type":"string","nullable":true},"group_id":{"type":"string","nullable":true},"id":{"type":"string"},"starts_at":{"type":"string","nullable":true},"updated_at":{"type":"string"},"visibility_mode":{"type":"string"}}},"ProductCollectionDetailResponse":{"type":"object","required":["id","name","is_active","product_count","assignment_count","items","assignments"],"properties":{"assignment_count":{"type":"integer","format":"int64"},"assignments":{"type":"array","items":{"$ref":"#/components/schemas/ProductCollectionAssignmentResponse"}},"description":{"type":"string","nullable":true},"ends_at":{"type":"string","nullable":true},"id":{"type":"string"},"is_active":{"type":"boolean"},"items":{"type":"array","items":{"$ref":"#/components/schemas/ProductCollectionItemResponse"}},"name":{"type":"string"},"product_count":{"type":"integer","format":"int64"},"starts_at":{"type":"string","nullable":true}}},"ProductCollectionItemResponse":{"type":"object","required":["id","collection_id","product_id","created_at"],"properties":{"collection_id":{"type":"string"},"created_at":{"type":"string"},"id":{"type":"string"},"product_id":{"type":"string"}}},"ProductCollectionResponse":{"type":"object","required":["id","company_id","name","is_active","product_count","assignment_count","created_at","updated_at"],"properties":{"assignment_count":{"type":"integer","format":"int64"},"company_id":{"type":"string"},"created_at":{"type":"string"},"description":{"type":"string","nullable":true},"ends_at":{"type":"string","nullable":true},"id":{"type":"string"},"is_active":{"type":"boolean"},"name":{"type":"string"},"product_count":{"type":"integer","format":"int64"},"starts_at":{"type":"string","nullable":true},"updated_at":{"type":"string"}}},"ProductCostSummaryResponse":{"type":"object","description":"A product's cost picture.","required":["product_id","locations","price_margins","sold_last_30_days","cogs_last_30_days"],"properties":{"average_unit_cost":{"type":"string","description":"Value-weighted average cost across those locations, `None` without stock value.","nullable":true},"cogs_last_30_days":{"type":"string","description":"Cost of goods of those units."},"last_received_at":{"type":"string","description":"When that receipt was posted (RFC 3339).","nullable":true},"last_received_unit_cost":{"type":"string","description":"Unit cost on the most recently posted goods receipt line.","nullable":true},"locations":{"type":"array","items":{"$ref":"#/components/schemas/ProductLocationCost"},"description":"Cost ledger rows by location (only locations the user can see)."},"price_margins":{"type":"array","items":{"$ref":"#/components/schemas/PriceMargin"},"description":"Margin at each list price against `average_unit_cost`."},"product_id":{"type":"string","description":"Product id."},"sold_last_30_days":{"type":"string","description":"Units sold in the last 30 days at a captured cost, and their cost of goods."}}},"ProductEnrichmentApprovalRequest":{"type":"object","required":["approved"],"properties":{"approved":{"type":"boolean"},"locked_fields":{"type":"array","items":{"type":"string"},"nullable":true},"override_json":{"nullable":true}}},"ProductEnrichmentApprovalResponse":{"type":"object","required":["product_id","status"],"properties":{"approved_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"product_id":{"type":"string"},"status":{"type":"string"}}},"ProductEnrichmentBulkRunRequest":{"type":"object","properties":{"category_id":{"type":"string","nullable":true},"force":{"type":"boolean"},"limit":{"type":"integer","format":"int64","nullable":true},"mode":{"type":"string","nullable":true},"product_ids":{"type":"array","items":{"type":"string"},"nullable":true},"search":{"type":"string","nullable":true},"status_filter":{"type":"array","items":{"type":"string"},"nullable":true},"target_languages":{"type":"array","items":{"type":"string"},"nullable":true}}},"ProductEnrichmentBulkRunResponse":{"type":"object","required":["queued","skipped","failed","run_ids"],"properties":{"batch_id":{"type":"string","description":"Batch grouping the queued jobs; None when nothing was queued.","nullable":true},"failed":{"type":"integer","format":"int64"},"queued":{"type":"integer","format":"int64"},"run_ids":{"type":"array","items":{"type":"string"}},"skipped":{"type":"integer","format":"int64"}}},"ProductEnrichmentDetailResponse":{"type":"object","required":["product_id","runs","projections"],"properties":{"current_attributes":{"type":"object","description":"Persisted product attributes for the review comparison, keyed by code.\nAbsent on older servers; an empty map means the product has no values.","additionalProperties":{"type":"string"},"nullable":true},"latest_run":{"allOf":[{"$ref":"#/components/schemas/ProductEnrichmentRunResponse"}],"nullable":true},"locked_fields":{"type":"array","items":{"type":"string"},"description":"Fields protected from subsequent automatic enrichment writes.","nullable":true},"product_id":{"type":"string"},"projections":{"type":"array","items":{"$ref":"#/components/schemas/ProductSearchProjectionResponse"}},"runs":{"type":"array","items":{"$ref":"#/components/schemas/ProductEnrichmentRunResponse"}},"state":{"allOf":[{"$ref":"#/components/schemas/EnrichmentSummaryResponse"}],"nullable":true}}},"ProductEnrichmentJobActionResponse":{"type":"object","description":"Result of a queue job action (retry / cancel).","required":["product_id","status"],"properties":{"job_id":{"type":"string","description":"The run affected by the action (the new run for retry, the cancelled\nrun for cancel).","nullable":true},"message":{"type":"string","nullable":true},"product_id":{"type":"string"},"status":{"type":"string","description":"Persisted run status after the action: \"queued\" for retry, \"failed\"\nfor cancel (the runs table has no 'cancelled' status)."}}},"ProductEnrichmentQueueItem":{"type":"object","description":"A single enrichment queue entry, backed by a `product_enrichment_runs` row.","required":["job_id","product_id","status","attempt_count","max_attempts","created_at"],"properties":{"attempt_count":{"type":"integer","format":"int32","description":"1 once the run has started executing, 0 while still waiting."},"created_at":{"$ref":"#/components/schemas/chrono.DateTime"},"error_message":{"type":"string","nullable":true},"job_id":{"type":"string","description":"Enrichment run id backing this queue entry."},"max_attempts":{"type":"integer","format":"int32","description":"Runs are single-attempt in the current enrichment pipeline."},"next_attempt_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"product_id":{"type":"string"},"product_name":{"type":"string","description":"Product name at query time; null if the product row is gone.","nullable":true},"status":{"type":"string","description":"Queue status: pending (run not started yet), processing, completed, failed."}}},"ProductEnrichmentQueueQuery":{"type":"object","description":"Query parameters for the enrichment queue endpoint.","properties":{"limit":{"type":"integer","format":"int64","description":"Max items to return (default 50, max 200).","nullable":true},"offset":{"type":"integer","format":"int64","description":"Items to skip for paging (default 0).","nullable":true},"status":{"type":"string","description":"Queue status filter: pending, claimed, processing, completed, failed, dead.","nullable":true}}},"ProductEnrichmentQueueResponse":{"type":"object","description":"Envelope for the enrichment queue endpoint.","required":["items","total_count"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ProductEnrichmentQueueItem"}},"total_count":{"type":"integer","format":"int64","description":"Total matching entries across all pages."}}},"ProductEnrichmentRunQueuedResponse":{"type":"object","required":["status"],"properties":{"message":{"type":"string","nullable":true},"run_id":{"type":"string","nullable":true},"status":{"type":"string"}}},"ProductEnrichmentRunRequest":{"type":"object","properties":{"force":{"type":"boolean"},"mode":{"type":"string","nullable":true},"target_languages":{"type":"array","items":{"type":"string"},"nullable":true}}},"ProductEnrichmentRunResponse":{"type":"object","required":["id","status","trigger_type","provider","model","pipeline_version","prompt_version","schema_version","input_hash","target_languages"],"properties":{"completed_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"confidence_json":{"nullable":true},"error_message":{"type":"string","nullable":true},"extracted_json":{"nullable":true},"id":{"type":"string"},"input_hash":{"type":"string"},"model":{"type":"string"},"normalized_json":{"nullable":true},"pipeline_version":{"type":"string"},"prompt_version":{"type":"string"},"provider":{"type":"string"},"schema_version":{"type":"string"},"started_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"status":{"type":"string"},"target_languages":{},"trigger_type":{"type":"string"},"warnings_json":{"nullable":true}}},"ProductFamily":{"type":"string","enum":["ordering","pos","operations"]},"ProductGroupActionResponse":{"type":"object","description":"Generic mutation acknowledgement for group actions.","required":["id","status"],"properties":{"id":{"type":"string"},"status":{"type":"string"}}},"ProductGroupAddMemberRequest":{"type":"object","description":"Add-member request body.","required":["product_id"],"properties":{"product_id":{"type":"string"}}},"ProductGroupItem":{"type":"object","description":"A proposed/approved/dismissed product group with its members.","required":["id","status","source","created_at","updated_at","members"],"properties":{"confidence":{"type":"number","format":"float","nullable":true},"created_at":{"$ref":"#/components/schemas/chrono.DateTime"},"id":{"type":"string"},"members":{"type":"array","items":{"$ref":"#/components/schemas/ProductGroupMemberItem"}},"product_type":{"type":"string","nullable":true},"source":{"type":"string"},"status":{"type":"string"},"title":{"type":"string","nullable":true},"updated_at":{"$ref":"#/components/schemas/chrono.DateTime"}}},"ProductGroupMemberItem":{"type":"object","description":"One member product inside a group, with the fields the review UI needs.","required":["product_id","name","role","added_by"],"properties":{"added_by":{"type":"string"},"color":{"type":"string","description":"`primary_color` EAV value, when present.","nullable":true},"image_thumb_url":{"type":"string","description":"400px thumbnail URL (or full image url) when the product has an image.","nullable":true},"name":{"type":"string"},"product_id":{"type":"string"},"product_type":{"type":"string","nullable":true},"role":{"type":"string"},"size":{"type":"string","description":"size EAV value, when present.","nullable":true}}},"ProductGroupMergeRequest":{"type":"object","description":"Merge request body: fold source group's members into target group.","required":["source_group_id","target_group_id"],"properties":{"source_group_id":{"type":"string"},"target_group_id":{"type":"string"}}},"ProductGroupProposeResponse":{"type":"object","description":"Result of enqueuing a group-proposal job.","required":["enqueued"],"properties":{"enqueued":{"type":"boolean","description":"True when a job was enqueued; false when one was already in flight or the\ntenant has no products to group."},"run_id":{"type":"string","description":"Run id of the enqueued job, when one was created.","nullable":true}}},"ProductGroupsQuery":{"type":"object","description":"Query for the groups review list.","properties":{"page":{"type":"integer","format":"int64","description":"1-based page number (default 1).","nullable":true},"page_size":{"type":"integer","format":"int64","description":"Items per page (default 20, max 100).","nullable":true},"status":{"type":"string","description":"Status filter: proposed | approved | dismissed. Omit for all.","nullable":true}}},"ProductGroupsResponse":{"type":"object","required":["items","total_count","page","page_size"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ProductGroupItem"}},"page":{"type":"integer","format":"int64"},"page_size":{"type":"integer","format":"int64"},"total_count":{"type":"integer","format":"int64"}}},"ProductIdentifierResponse":{"type":"object","description":"Product Identifier DTO","required":["type_","value","is_primary"],"properties":{"is_primary":{"type":"boolean"},"type_":{"type":"string"},"value":{"type":"string"}}},"ProductImageManifestEntry":{"type":"object","description":"One row of the manifest — see field docs for exactly what `bytes` and\n`available` mean in each request mode.","required":["id","product_id","updated_at","available"],"properties":{"available":{"type":"boolean","description":"Whether the object is expected to be retrievable.\n\nFor bulk/`since` listings this is always `true` — it reflects that the\nDB row is a live, non-deleted image record, NOT a live R2 check\n(checking every row against R2 would cost one GET per image across the\nwhole catalog, which is the exact load spike this endpoint exists to\navoid). For rows resolved through the bounded `ids` list, this\nreflects an actual R2 fetch: `false` means the object is confirmed\nmissing and the client should mark it permanently unavailable instead\nof retrying it forever."},"bytes":{"type":"integer","format":"int64","description":"Byte size of the served (original) image. Only populated when this row\nwent through the bounded `ids` live-check (see `available`) — a\nbulk/`since` listing never calls R2, so this is `null` there.","nullable":true},"id":{"type":"string"},"product_id":{"type":"string"},"updated_at":{"type":"string"}}},"ProductImageManifestRequest":{"type":"object","description":"Request body for [`get_product_image_manifest`].\n\n`since` and `ids` serve different jobs and are not meant to be combined:\n- Omit both for a company-wide, DB-only, paginated listing — safe for a\nfull-catalog offline prime, since it never calls R2.\n- Pass `since` to page through only what changed after a previous sync\n(compare against the newest `updated_at` seen last time).\n- Pass a small `ids` list (<= `IMAGE_MANIFEST_MAX_LIVE_CHECK_IDS`) when you\nneed a verified `available`/`bytes` for particular images — e.g.\nre-checking items a previous prime marked as failed before giving up on\nthem for good.","properties":{"cursor":{"type":"string","description":"Opaque pagination cursor from a previous response's `next_cursor`.","nullable":true},"ids":{"type":"array","items":{"type":"string"},"description":"Bound the response — and enable a live R2 availability/size check —\nto these specific image ids. Capped at `IMAGE_MANIFEST_MAX_LIVE_CHECK_IDS`.","nullable":true},"limit":{"type":"integer","format":"int64","description":"Page size, capped at `IMAGE_MANIFEST_MAX_PAGE_SIZE` (default: the cap).","nullable":true},"since":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true}}},"ProductImageManifestResponse":{"type":"object","required":["images"],"properties":{"images":{"type":"array","items":{"$ref":"#/components/schemas/ProductImageManifestEntry"}},"next_cursor":{"type":"string","description":"Present when more rows exist beyond this page — pass back as `cursor`\nto continue.","nullable":true}}},"ProductImageResponse":{"type":"object","description":"Product image response","required":["id","created_at","updated_at","image","image_url","product_id","sort_order","is_primary","kind"],"properties":{"created_at":{"$ref":"#/components/schemas/chrono.NaiveDateTime"},"id":{"type":"string"},"image":{"type":"string"},"image_thumb_200":{"type":"string","nullable":true},"image_thumb_200_url":{"type":"string","nullable":true},"image_thumb_400":{"type":"string","nullable":true},"image_thumb_400_url":{"type":"string","nullable":true},"image_url":{"type":"string"},"is_primary":{"type":"boolean","description":"The hero image for this product. At most one per product."},"kind":{"type":"string","description":"Provenance: `original` | `edited` | `ai_edited`."},"name":{"type":"string","nullable":true},"product_id":{"type":"string"},"sort_order":{"type":"integer","format":"int32","description":"Explicit gallery position. Without exposing this the ordering migration\nis invisible to clients, which defeats its purpose."},"source_document_id":{"type":"string","description":"The editor document that produced this image, when it is derived.\nThis is what makes \"re-open this edit\" possible instead of forcing the\nuser to rebuild a composite from scratch.","nullable":true},"updated_at":{"$ref":"#/components/schemas/chrono.NaiveDateTime"}}},"ProductImageUploadForm":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"Image file to upload."}}},"ProductInput":{"type":"object","description":"Product creation/update input","required":["name","code","item_number","msrp_price","qty","regular_price","wholesale_price"],"properties":{"category":{"allOf":[{"$ref":"#/components/schemas/CategoryPayload"}],"nullable":true},"code":{"type":"string"},"code_type":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"excluded":{"type":"boolean","description":"Hide the product from catalogues/POS. Absent means visible.","nullable":true},"expected_updated_at":{"allOf":[{"$ref":"#/components/schemas/chrono.NaiveDateTime"}],"nullable":true},"item_number":{"type":"string"},"msrp_price":{"$ref":"#/components/schemas/BigDecimal"},"name":{"type":"string"},"qty":{"type":"integer","format":"int32"},"regular_price":{"$ref":"#/components/schemas/BigDecimal"},"wholesale_price":{"$ref":"#/components/schemas/BigDecimal"}}},"ProductLocationCost":{"type":"object","description":"One location's cost ledger row for a product.","required":["location_id","quantity_on_hand","average_unit_cost","inventory_value"],"properties":{"average_unit_cost":{"type":"string","description":"Weighted-average unit cost."},"inventory_value":{"type":"string","description":"Inventory value."},"location_id":{"type":"string","description":"Location id."},"location_name":{"type":"string","description":"Location name.","nullable":true},"quantity_on_hand":{"type":"string","description":"Units in the cost ledger."},"variant_id":{"type":"string","description":"Variant, if the row is for one.","nullable":true}}},"ProductModifierGroup":{"type":"object","required":["id","product_id","modifier_group_id","company_id","sort_order","created_at"],"properties":{"company_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/DateTime"},"id":{"type":"string"},"modifier_group_id":{"type":"string"},"product_id":{"type":"string"},"sort_order":{"type":"integer","format":"int32"}}},"ProductSearchProjectionResponse":{"type":"object","required":["language_code","search_title","search_description","keywords","shopper_phrases","facet_json","normalized_attributes_json","updated_at"],"properties":{"embedding_json_legacy":{"nullable":true},"facet_json":{},"keywords":{},"language_code":{"type":"string"},"normalized_attributes_json":{},"search_description":{"type":"string"},"search_title":{"type":"string"},"semantic_description":{"type":"string","nullable":true},"shopper_phrases":{},"updated_at":{"$ref":"#/components/schemas/chrono.DateTime"}}},"ProductVariantResponse":{"type":"object","description":"Product Variant DTO","required":["id","sku","name","attributes"],"properties":{"attributes":{},"cost_price":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"id":{"type":"string"},"name":{"type":"string"},"price":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"sku":{"type":"string"},"stock":{"type":"integer","format":"int32","nullable":true}}},"ProductWithImagesResponse":{"type":"object","description":"Product with images response (Internal - for backend use)","required":["id","created_at","updated_at","name","description","code","code_type","company_id","item_number","msrp_price","qty","regular_price","wholesale_price","excluded","is_deleted","images"],"properties":{"attributes":{"type":"array","items":{"$ref":"#/components/schemas/ProductAttributeResponse"},"nullable":true},"category_id":{"type":"string","nullable":true},"category_name":{"type":"string","nullable":true},"code":{"type":"string"},"code_type":{"type":"string"},"company_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/chrono.NaiveDateTime"},"deleted_at":{"allOf":[{"$ref":"#/components/schemas/chrono.NaiveDateTime"}],"nullable":true},"description":{"type":"string"},"enrichment_status":{"type":"string","nullable":true},"enrichment_summary":{"allOf":[{"$ref":"#/components/schemas/EnrichmentSummaryResponse"}],"nullable":true},"excluded":{"type":"boolean"},"id":{"type":"string"},"identifiers":{"type":"array","items":{"$ref":"#/components/schemas/ProductIdentifierResponse"},"nullable":true},"images":{"type":"array","items":{"$ref":"#/components/schemas/ProductImageResponse"}},"is_deleted":{"type":"boolean"},"item_number":{"type":"string"},"msrp_price":{"$ref":"#/components/schemas/BigDecimal"},"name":{"type":"string"},"product_number":{"type":"string","description":"Company-scoped human-readable id (e.g. `P-00042`); null until backfilled.","nullable":true},"product_type":{"type":"string","description":"Canonical enrichment grouping key (e.g. `t-shirt`); null until enriched.","nullable":true},"qty":{"type":"integer","format":"int32"},"regular_price":{"$ref":"#/components/schemas/BigDecimal"},"stock_levels":{"type":"array","items":{"$ref":"#/components/schemas/StockLevelResponse"},"nullable":true},"updated_at":{"$ref":"#/components/schemas/chrono.NaiveDateTime"},"variants":{"type":"array","items":{"$ref":"#/components/schemas/ProductVariantResponse"},"nullable":true},"wholesale_price":{"$ref":"#/components/schemas/BigDecimal"}}},"PublicCatalogue":{"type":"object","description":"What the public viewer receives. No token, no email, no address hash.","required":["slug","kind","content"],"properties":{"content":{},"expires_at":{"type":"string","nullable":true},"kind":{"type":"string","description":"Which tool made this, so the viewer knows which renderer to use. Added\nafter the first release, so a client that ignores it still works."},"published_at":{"type":"string","nullable":true},"slug":{"type":"string"}}},"PublicCommercialService":{"type":"object","required":["terms","copy"],"properties":{"copy":{"$ref":"#/components/schemas/OfferCopy"},"terms":{"$ref":"#/components/schemas/CommercialServiceTerms"}}},"PublicContentItem":{"type":"object","required":["id","content_type","duration_seconds","order_index","schedule_enabled","schedule_days"],"properties":{"content":{"nullable":true},"content_type":{"type":"string"},"duration_seconds":{"type":"integer","format":"int32"},"id":{"$ref":"#/components/schemas/Uuid"},"image_url":{"type":"string","nullable":true},"order_index":{"type":"integer","format":"int32"},"schedule_days":{"type":"array","items":{"type":"integer","format":"int32"},"description":"ISO weekdays, 1 = Monday .. 7 = Sunday."},"schedule_enabled":{"type":"boolean","description":"Whether this slide only plays at certain times.\n\nThe schedule is sent to the screen rather than applied here, and that is\nthe whole point: `is_scheduled_now` compares against UTC, so a shop in\nany other zone would get the wrong slides. The clock that matters is the\none on the tablet standing in the shop. Added after the first release,\nso a viewer that ignores these fields behaves exactly as before."},"schedule_end_minute":{"type":"integer","format":"int32","nullable":true},"schedule_start_minute":{"type":"integer","format":"int32","description":"Minutes past midnight, local to the screen. `start > end` means the\nwindow runs past midnight and belongs to the day it started on.","nullable":true},"subtitle":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"video_url":{"type":"string","nullable":true}}},"PublicDisplayDataResponse":{"type":"object","required":["display","content","refresh_interval_seconds"],"properties":{"content":{"type":"array","items":{"$ref":"#/components/schemas/PublicContentItem"}},"display":{"$ref":"#/components/schemas/PublicDisplayResponse"},"refresh_interval_seconds":{"type":"integer","format":"int32"}}},"PublicDisplayResponse":{"type":"object","required":["id","name","display_type","mode","slide_duration_seconds","transition_type"],"properties":{"display_type":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"mode":{"type":"string"},"name":{"type":"string"},"slide_duration_seconds":{"type":"integer","format":"int32"},"theme":{"nullable":true},"transition_type":{"type":"string"}}},"PublicOffer":{"type":"object","required":["terms","publication","copy"],"properties":{"copy":{"$ref":"#/components/schemas/OfferCopy"},"publication":{"$ref":"#/components/schemas/OfferPublication"},"terms":{"$ref":"#/components/schemas/OfferTerms"}}},"PublicOrderStatusResponse":{"type":"object","description":"Deliberately minimal — an anonymous caller with just the `public_token`\n(design §3.4: \"nothing sensitive\") gets order-ready-ness, not line items,\nprices, or customer info.","required":["status","ready"],"properties":{"order_number":{"type":"string","nullable":true},"ready":{"type":"boolean"},"status":{"type":"string"},"table_tent_number":{"type":"string","description":"Horeca Ordering Suite wave B4 — echoes `open_orders.table_tent_number`\nwhen the order was placed with one (kiosk takeaway/pickup mainly).","nullable":true}}},"PublicPlan":{"type":"object","description":"One pricing card. A projection of `billing_plans` — never carries the\nentitlement map or Stripe ids.","required":["code","name","tagline","bullets","not_included","price_cents_monthly","included_seats","extra_seat_price_cents","included_rep_devices","extra_rep_device_price_cents","trial_days","is_highlighted","contact_sales","price_key_monthly","included_ai_credits","extra_ai_credits_price_cents"],"properties":{"annual_discount_percent":{"type":"integer","format":"int32","description":"`round(100 * (1 - annual / (monthly * 12)))`; `None` when annual is not\noffered or the monthly price is 0.","nullable":true},"badge":{"type":"string","description":"Localised badge, e.g. \"Most popular\". `None` when blank.","nullable":true},"bullets":{"type":"array","items":{"type":"string"},"description":"Localised feature bullets."},"code":{"type":"string"},"contact_sales":{"type":"boolean","description":"No self-serve price; show a contact CTA instead."},"description":{"type":"string","nullable":true},"extra_ai_credits_price_cents":{"type":"integer","format":"int32","description":"Price of a 1,000-credit AI top-up in cents; 0 = top-ups not sold."},"extra_rep_device_price_cents":{"type":"integer","format":"int32","description":"Per additional rep device, per month."},"extra_seat_price_cents":{"type":"integer","format":"int32","description":"Per additional seat, per month."},"included_ai_credits":{"type":"integer","format":"int32","description":"AI enrichment credits included per calendar month (managed mode). One\ncredit = one product enriched in one target language; a vision pass\ncosts one more. 0 = no managed allowance (e.g. contact-sales cards)."},"included_rep_devices":{"type":"integer","format":"int32"},"included_seats":{"type":"integer","format":"int32"},"is_highlighted":{"type":"boolean"},"name":{"type":"string"},"not_included":{"type":"array","items":{"type":"string"},"description":"Localised \"not included\" bullets (may be empty)."},"price_cents_annual":{"type":"integer","format":"int32","description":"TOTAL per year in cents when billed annually. `None` = not offered.","nullable":true},"price_cents_monthly":{"type":"integer","format":"int32"},"price_key_annual":{"type":"string","description":"Checkout price key for annual billing; `None` when annual is not offered.","nullable":true},"price_key_monthly":{"type":"string","description":"Checkout price key for monthly billing (`<code>_monthly`)."},"sku_limit":{"type":"integer","format":"int32","description":"`None` = unlimited.","nullable":true},"tagline":{"type":"string","description":"Localised one-liner under the plan name."},"trial_days":{"type":"integer","format":"int32"}}},"PublicPlansResponse":{"type":"object","required":["currency","plans"],"properties":{"currency":{"type":"string","description":"ISO currency code shared by every plan in the list."},"plans":{"type":"array","items":{"$ref":"#/components/schemas/PublicPlan"}}}},"PublicSalesReceiptItemResponse":{"type":"object","description":"Public sales receipt item response (Sanitized)","required":["product_name","product_code","qty","price","total"],"properties":{"price":{"$ref":"#/components/schemas/BigDecimal"},"product_code":{"type":"string"},"product_name":{"type":"string"},"qty":{"type":"integer","format":"int32"},"total":{"$ref":"#/components/schemas/BigDecimal"}}},"PublicSalesReceiptResponse":{"type":"object","description":"Public sales receipt response (Sanitized - for external export)","required":["receipt_number","date","subtotal","tax_amount","total_amount","payment_method","payment_amount"],"properties":{"account_name":{"type":"string","nullable":true},"change_amount":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"clerk_notes":{"type":"string","nullable":true},"date":{"$ref":"#/components/schemas/chrono.NaiveDateTime"},"note":{"type":"string","nullable":true},"payment_amount":{"$ref":"#/components/schemas/BigDecimal"},"payment_method":{"type":"string"},"receipt_number":{"type":"string"},"rounding_adjustment":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"sales_order_number":{"type":"string","nullable":true},"subtotal":{"$ref":"#/components/schemas/BigDecimal"},"tax_amount":{"$ref":"#/components/schemas/BigDecimal"},"terminal_id":{"type":"string","nullable":true},"total_amount":{"$ref":"#/components/schemas/BigDecimal"}}},"PublishLookbookRequest":{"type":"object","required":["published"],"properties":{"published":{"type":"boolean","description":"`true` publishes, `false` unpublishes. Unpublishing bumps `updated_at`\nso the sync can tombstone the lookbook off devices that already have it."}}},"PublishOfferRevision":{"type":"object","required":["publication","reason"],"properties":{"publication":{"$ref":"#/components/schemas/OfferPublication"},"reason":{"type":"string","description":"Operator evidence/reference for the commercial and product acceptance."}}},"PublishRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string"}}},"PurchaseOrderLineResponse":{"type":"object","required":["id","line_number","product_id","description","purchase_uom","units_per_purchase","ordered_qty","received_qty","rejected_qty","cancelled_qty","unit_cost","discount_amount","tax_amount","line_total"],"properties":{"cancelled_qty":{"type":"string"},"description":{"type":"string"},"discount_amount":{"type":"string"},"expected_date":{"type":"string","nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"line_number":{"type":"integer","format":"int32"},"line_total":{"type":"string"},"notes":{"type":"string","nullable":true},"ordered_qty":{"type":"string"},"product_id":{"type":"string"},"purchase_uom":{"type":"string"},"received_qty":{"type":"string"},"rejected_qty":{"type":"string"},"supplier_product_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"supplier_sku":{"type":"string","nullable":true},"tax_amount":{"type":"string"},"unit_cost":{"type":"string"},"units_per_purchase":{"type":"string"},"variant_id":{"type":"string","nullable":true}}},"PurchaseOrderListResponse":{"type":"object","required":["results","total","limit","offset"],"properties":{"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/PurchaseOrderResponse"}},"total":{"type":"integer","format":"int64"}}},"PurchaseOrderResponse":{"type":"object","required":["id","purchase_order_number","supplier_id","location_id","status","currency_code","order_date","subtotal","discount_total","tax_total","freight_total","total","created_at","updated_at","version","lines"],"properties":{"approval_notes":{"type":"string","nullable":true},"approved_at":{"type":"string","nullable":true},"created_at":{"type":"string"},"currency_code":{"type":"string"},"discount_total":{"type":"string"},"expected_date":{"type":"string","nullable":true},"freight_total":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/PurchaseOrderLineResponse"}},"location_id":{"type":"string"},"notes":{"type":"string","nullable":true},"order_date":{"type":"string"},"purchase_order_number":{"type":"string"},"sent_at":{"type":"string","nullable":true},"status":{"type":"string"},"subtotal":{"type":"string"},"supplier_id":{"$ref":"#/components/schemas/Uuid"},"supplier_reference":{"type":"string","nullable":true},"tax_total":{"type":"string"},"total":{"type":"string"},"updated_at":{"type":"string"},"version":{"type":"integer","format":"int32"}}},"PurchaseOrderTransitionRequest":{"type":"object","required":["version"],"properties":{"notes":{"type":"string","nullable":true},"version":{"type":"integer","format":"int32"}}},"PurchasingSettings":{"type":"object","description":"Company purchase-order approval policy.\n\nPer-company purchasing policy (E-3.0, G024 reconciliation): purchase-order\napproval gate and landed-cost allocation basis. Stored inside the company\nsettings JSON; absent config falls back to `Default` (approval off) so\nexisting tenants are unaffected.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`.","properties":{"aging_bucket_days":{"type":"array","items":{"type":"integer","format":"int32"},"description":"Upper bounds (days past due, ascending) of the accounts-payable aging\nbuckets; a final \"over\" bucket follows the last bound."},"allow_bill_before_receipt":{"type":"boolean","description":"Allow billing a purchase-order line before its goods are received: the\nbill is then matched against the ordered quantity instead."},"approval_required":{"type":"boolean","description":"Require approval before a submitted purchase order over\n`approval_threshold` is auto-approved."},"approval_threshold":{"type":"string","description":"Decimal string threshold (in company currency) above which a\nsubmitted purchase order requires manual approval."},"customer_return_reason_codes":{"type":"array","items":{"type":"string"},"description":"Allowed customer return line reason codes. These values are displayed\nas a select in the admin return workflow, while legacy API clients can\ncontinue to send their existing codes."},"landed_cost_allocation":{"type":"string","description":"How a landed-cost document's total is allocated across receipt lines.\nOnly `\"line_value\"` is implemented today."},"match_price_tolerance_percent":{"type":"string","description":"Supplier-bill 3-way match: how far (percent, decimal string) a billed\nunit cost may differ from the purchase-order unit cost before the line\nis a price variance that needs approval before posting."},"match_quantity_tolerance":{"type":"string","description":"Supplier-bill 3-way match: how many units (decimal string) a bill may\ncharge beyond what was received (or ordered, when billing before\nreceipt is allowed) before the line is a quantity variance."},"moq_policy":{"type":"string","description":"Supplier minimum-order quantity / order multiple on purchase orders:\n`\"off\"` ignores them, `\"warn\"` shows a warning in the order form,\n`\"block\"` refuses an order line below the minimum or off the multiple."},"price_variance_policy":{"type":"string","description":"What posting a bill does with its price variance:\n`\"variance_account\"` books it to the purchase-price-variance account\n(accounting outbox); `\"revalue_on_hand\"` adds the share that still sits\nin stock to the inventory value and books only the rest as variance."},"refund_methods":{"type":"array","items":{"type":"string"},"description":"Allowed refund methods for customer returns, configured per company."},"supplier_payment_methods":{"type":"array","items":{"type":"string"},"description":"Payment methods offered when recording a supplier payment."},"supplier_return_reason_codes":{"type":"array","items":{"type":"string"},"description":"Allowed supplier return line reason codes, configured per company."},"transfer_approval_required":{"type":"boolean","description":"Require approval before a stock transfer ships."}}},"PurchasingSpendResponse":{"type":"object","description":"The purchasing-spend report.","required":["group_by","results","total_spend","total","limit","offset"],"properties":{"group_by":{"$ref":"#/components/schemas/SpendGroupBy"},"limit":{"type":"integer","format":"int64","description":"Page size used."},"offset":{"type":"integer","format":"int64","description":"Offset used."},"results":{"type":"array","items":{"$ref":"#/components/schemas/PurchasingSpendRow"},"description":"One page of groups."},"total":{"type":"integer","format":"int64","description":"Groups across all pages."},"total_spend":{"type":"string","description":"Total received value across all groups."}}},"PurchasingSpendRow":{"type":"object","description":"One group of the purchasing-spend report.","required":["key","label","quantity","spend","receipts"],"properties":{"detail":{"type":"string","description":"Secondary label (supplier number / item number), if any.","nullable":true},"key":{"type":"string","description":"Supplier id, product id or `YYYY-MM`."},"label":{"type":"string","description":"Human label."},"quantity":{"type":"string","description":"Units accepted into stock."},"receipts":{"type":"integer","format":"int64","description":"Posted goods receipts involved."},"spend":{"type":"string","description":"Received value (accepted quantity × unit cost, before freight and tax)."}}},"PutContactConsentsRequest":{"type":"object","required":["consents"],"properties":{"consents":{"type":"array","items":{"$ref":"#/components/schemas/ContactConsentEntry"}}}},"QbposAccountBootstrapLink":{"type":"object","required":["external_id","entity_id"],"properties":{"entity_id":{"type":"string","description":"Existing live Vender account id selected by the reviewed bridge."},"external_id":{"type":"string","description":"QBPOS Customer `ListID`."}}},"QbposAccountBootstrapRequest":{"type":"object","required":["links"],"properties":{"links":{"type":"array","items":{"$ref":"#/components/schemas/QbposAccountBootstrapLink"}}}},"QbposAccountBootstrapResponse":{"type":"object","required":["linked"],"properties":{"linked":{"type":"integer","format":"int32","minimum":0}}},"QbposAccountFieldPolicies":{"type":"object","description":"Conflict ownership rules for customer/account fields imported from QBPOS.","properties":{"account_number":{"$ref":"#/components/schemas/QbposFieldOwnership"},"address":{"$ref":"#/components/schemas/QbposFieldOwnership"},"customer_type":{"$ref":"#/components/schemas/QbposFieldOwnership"},"email":{"$ref":"#/components/schemas/QbposFieldOwnership"},"excluded":{"$ref":"#/components/schemas/QbposFieldOwnership"},"name":{"$ref":"#/components/schemas/QbposFieldOwnership"},"phone":{"$ref":"#/components/schemas/QbposFieldOwnership"}}},"QbposAccountReconcileAction":{"type":"string","enum":["update_by_external_id","update_by_account_number","update_by_email","create","conflict","invalid"]},"QbposAccountReconcileRequest":{"type":"object","required":["rows"],"properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/QbposAccountReconcileRow"}}}},"QbposAccountReconcileResponse":{"type":"object","required":["total","would_create","would_update","conflicts","invalid","max_account_creates_per_run","batch_within_create_limit","rows"],"properties":{"batch_within_create_limit":{"type":"boolean"},"conflicts":{"type":"integer","format":"int32","minimum":0},"invalid":{"type":"integer","format":"int32","minimum":0},"max_account_creates_per_run":{"type":"integer","format":"int32","description":"Company setting. The agent applies this to the total across every batch.","minimum":0},"rows":{"type":"array","items":{"$ref":"#/components/schemas/QbposAccountReconcileRowResult"}},"total":{"type":"integer","format":"int32","minimum":0},"would_create":{"type":"integer","format":"int32","minimum":0},"would_update":{"type":"integer","format":"int32","minimum":0}}},"QbposAccountReconcileRow":{"type":"object","required":["external_id"],"properties":{"account_number":{"type":"string","description":"QBPOS `CustomerID`.","nullable":true},"address":{"type":"string","nullable":true},"customer_type":{"type":"string","nullable":true},"email":{"type":"string","description":"Used as a normalized (trimmed, lowercase) fallback identity.","nullable":true},"excluded":{"type":"boolean","nullable":true},"external_id":{"type":"string","description":"QBPOS Customer `ListID`."},"name":{"type":"string","description":"Required only when the projection would create an account.","nullable":true},"phone":{"type":"string","nullable":true}}},"QbposAccountReconcileRowResult":{"type":"object","required":["row","external_id","action","field_conflicts","would_block"],"properties":{"action":{"$ref":"#/components/schemas/QbposAccountReconcileAction"},"entity_id":{"type":"string","nullable":true},"external_id":{"type":"string"},"field_conflicts":{"type":"array","items":{"type":"string"}},"message":{"type":"string","nullable":true},"row":{"type":"integer","format":"int32","minimum":0},"would_block":{"type":"boolean"}}},"QbposAgentConfigResponse":{"type":"object","required":["quickbooks_pos","bulk_chunk_size","fetched_at"],"properties":{"bulk_chunk_size":{"type":"integer","format":"int32","minimum":0},"fetched_at":{"type":"string"},"quickbooks_pos":{"$ref":"#/components/schemas/QuickBooksPosSettings"}}},"QbposAgentStatusResponse":{"type":"object","required":["id","agent_version","last_heartbeat_at","entity_counts","created_at","updated_at"],"properties":{"agent_version":{"type":"string"},"created_at":{"type":"string"},"entity_counts":{},"id":{"type":"string"},"last_error":{"type":"string","nullable":true},"last_heartbeat_at":{"type":"string"},"last_run_at":{"type":"string","nullable":true},"recovery":{"allOf":[{"$ref":"#/components/schemas/QbposRecoveryStatus"}],"nullable":true},"updated_at":{"type":"string"}}},"QbposClaimCommandResponse":{"type":"object","properties":{"command":{"allOf":[{"$ref":"#/components/schemas/QbposCommandResponse"}],"nullable":true}}},"QbposClaimOperationsRequest":{"type":"object","properties":{"limit":{"type":"integer","format":"int32","description":"Maximum operations to claim; clamped to `write_back.max_writes_per_run`.","nullable":true,"minimum":0}}},"QbposClaimOperationsResponse":{"type":"object","required":["operations"],"properties":{"operations":{"type":"array","items":{"$ref":"#/components/schemas/QbposOperationResponse"}}}},"QbposCommandCompletionStatus":{"type":"string","enum":["completed","failed"]},"QbposCommandKind":{"type":"string","enum":["sync_now","dry_run","full_resync","pause","resume"]},"QbposCommandListResponse":{"type":"object","required":["commands"],"properties":{"commands":{"type":"array","items":{"$ref":"#/components/schemas/QbposCommandResponse"}}}},"QbposCommandResponse":{"type":"object","required":["id","command","status","requested_at"],"properties":{"claimed_at":{"type":"string","nullable":true},"command":{"type":"string"},"completed_at":{"type":"string","nullable":true},"error":{"type":"string","nullable":true},"id":{"type":"string"},"requested_at":{"type":"string"},"requested_by":{"type":"integer","format":"int32","nullable":true},"status":{"type":"string"}}},"QbposCompleteCommandRequest":{"type":"object","required":["status"],"properties":{"error":{"type":"string","nullable":true},"status":{"$ref":"#/components/schemas/QbposCommandCompletionStatus"}}},"QbposCompleteOperationRequest":{"type":"object","required":["status"],"properties":{"error":{"type":"string","nullable":true},"result":{"description":"Apply report: written values, new `external_id` for creates, observed\n`TimeModified`, `dry_run` marker, per-field conflict details on failure.","nullable":true},"status":{"$ref":"#/components/schemas/QbposOperationCompletionStatus"}}},"QbposCreateCommandRequest":{"type":"object","required":["command"],"properties":{"command":{"$ref":"#/components/schemas/QbposCommandKind"}}},"QbposCreateOperationsRequest":{"type":"object","required":["operations"],"properties":{"batch_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"operations":{"type":"array","items":{"$ref":"#/components/schemas/QbposOperationInput"}},"origin":{"allOf":[{"$ref":"#/components/schemas/QbposOperationOrigin"}],"nullable":true}}},"QbposCreateOperationsResponse":{"type":"object","required":["operations","batch_id"],"properties":{"batch_id":{"type":"string"},"operations":{"type":"array","items":{"$ref":"#/components/schemas/QbposOperationResponse"}}}},"QbposDeletionPolicy":{"type":"string","description":"What the QBPOS sync agent does when QuickBooks POS reports a record as\ndeleted (`ListDeletedQueryRq`). Serialized lowercase (\"ignore\" |\n\"deactivate\") in the settings JSON and over the API.","enum":["ignore","deactivate"]},"QbposExistingImagePolicy":{"type":"string","description":"First-sync behavior when Vender already has images but no QBPOS-managed slot.","enum":["preserve_existing","append"]},"QbposFieldConflict":{"type":"object","required":["id","entity_type","entity_id","external_id","field_name","updated_at"],"properties":{"conflict_detected_at":{"type":"string","nullable":true},"entity_id":{"type":"string"},"entity_type":{"type":"string"},"external_id":{"type":"string"},"field_name":{"type":"string"},"id":{"type":"string"},"local_value":{"type":"string","nullable":true},"pending_resolution":{"type":"string","nullable":true},"source_value":{"type":"string","nullable":true},"updated_at":{"type":"string"}}},"QbposFieldConflictListResponse":{"type":"object","required":["conflicts"],"properties":{"conflicts":{"type":"array","items":{"$ref":"#/components/schemas/QbposFieldConflict"}}}},"QbposFieldConflictResolution":{"type":"string","description":"What to do when a protected field changed independently in both systems.","enum":["keep_vender","source_wins","block_row"]},"QbposFieldConflictResolutionAction":{"type":"string","enum":["keep_vender","apply_source_next_sync"]},"QbposFieldOwnership":{"type":"string","description":"Ownership rule for one field on a record already linked to QBPOS.","enum":["source_wins","vender_wins","protect_manual"]},"QbposHeartbeatRequest":{"type":"object","required":["agent_version"],"properties":{"agent_version":{"type":"string","description":"Version string of the on-premise agent (e.g. \"1.0.3\")."},"entity_counts":{"description":"Per-entity sync counters from the last run, e.g.\n`{\"products\": 1204, \"categories\": 12}`. Free-form JSON object."},"last_error":{"type":"string","description":"Most recent agent-side error, or absent when the last run was clean.","nullable":true},"last_run_at":{"type":"string","description":"Completion time of the last successful sync run (RFC 3339). Absent when\nthe agent has not completed a run yet.","nullable":true},"log_entries":{"type":"array","items":{"$ref":"#/components/schemas/QbposRunLogEntry"}},"recovery":{"allOf":[{"$ref":"#/components/schemas/QbposRecoveryStatus"}],"nullable":true},"run_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"run_mode":{"allOf":[{"$ref":"#/components/schemas/QbposRunMode"}],"nullable":true},"run_started_at":{"type":"string","nullable":true},"run_status":{"allOf":[{"$ref":"#/components/schemas/QbposRunStatus"}],"nullable":true},"run_trigger":{"allOf":[{"$ref":"#/components/schemas/QbposRunTrigger"}],"nullable":true}}},"QbposImageSelectionMode":{"type":"string","description":"Which QBPOS pictures are mirrored to Vender for each product.","enum":["latest","all"]},"QbposMintAgentTokenRequest":{"type":"object","properties":{"expires_at":{"type":"string","description":"Optional ISO-8601 expiry. Absent = no expiry (rotation is by\nre-mint + revoke from internal-admin, not calendar expiry).","nullable":true},"label":{"type":"string","description":"Token label shown in the tenant's token list. Defaults to\n\"QBPOS Agent\" when absent.","nullable":true}}},"QbposOperationCompletionStatus":{"type":"string","enum":["applied","failed"]},"QbposOperationEntityType":{"type":"string","enum":["product","category","account","product_image"]},"QbposOperationInput":{"type":"object","required":["entity_type","operation"],"properties":{"entity_id":{"type":"string","description":"Vender entity id (uuid/string). Optional for pure-external creates.","nullable":true},"entity_type":{"$ref":"#/components/schemas/QbposOperationEntityType"},"external_id":{"type":"string","description":"QBPOS `ListID` when already linked. Optional for creates.","nullable":true},"id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"operation":{"$ref":"#/components/schemas/QbposOperationKind"},"payload":{"description":"Canonical field map to write, plus optional per-field\n`expected_source_hash` entries used for conflict detection at apply time."}}},"QbposOperationKind":{"type":"string","enum":["create","update","delete","upload_image"]},"QbposOperationListResponse":{"type":"object","required":["operations","total","status_counts"],"properties":{"operations":{"type":"array","items":{"$ref":"#/components/schemas/QbposOperationResponse"}},"status_counts":{"description":"Row counts per status for the current filter-free company scope."},"total":{"type":"integer","format":"int64"}}},"QbposOperationOrigin":{"type":"string","enum":["manual","bulk","suggestion","auto_sync","api"]},"QbposOperationResponse":{"type":"object","required":["id","entity_type","operation","payload","status","origin","attempts","result","created_at"],"properties":{"applied_at":{"type":"string","nullable":true},"attempts":{"type":"integer","format":"int32"},"batch_id":{"type":"string","nullable":true},"claimed_at":{"type":"string","nullable":true},"created_at":{"type":"string"},"entity_id":{"type":"string","nullable":true},"entity_type":{"type":"string"},"error":{"type":"string","nullable":true},"external_id":{"type":"string","nullable":true},"id":{"type":"string"},"operation":{"type":"string"},"origin":{"type":"string"},"payload":{},"queued_at":{"type":"string","nullable":true},"requested_by":{"type":"integer","format":"int32","nullable":true},"result":{},"reviewed_by":{"type":"integer","format":"int32","nullable":true},"status":{"type":"string"}}},"QbposPlatformConfig":{"type":"object","description":"Platform-managed QBPOS entitlement overrides for one company.\n\nPlatform-managed QBPOS override for a single tenant — written by operators\nvia internal-admin (/api/admin/qbpos), READ-ONLY for tenants. Stored inside\nthe company settings JSON alongside the tenant-editable `quickbooks_pos`\ngroup (mirrors `mcp_platform` / `enrichment_worker`). Absent = no override.","properties":{"write_back_suspended":{"type":"boolean","description":"Platform kill switch for outbound write-back. When true the effective\n`write_back.enabled` served to the agent is forced to false and the\noperations claim endpoint returns empty, regardless of what the\ncompany's own settings say. Default false (not suspended)."}}},"QbposPlatformTenantStatusResponse":{"type":"object","required":["company_id","heartbeat_stale_minutes","operation_status_counts","write_back","write_back_suspended","active_agent_tokens"],"properties":{"active_agent_tokens":{"type":"integer","format":"int64","description":"Active (not revoked/deleted) `kind='agent'` tokens for this tenant."},"agent":{"allOf":[{"$ref":"#/components/schemas/QbposAgentStatusResponse"}],"nullable":true},"company_id":{"type":"string"},"heartbeat_stale_minutes":{"type":"integer","format":"int32","description":"Company setting used by UIs to badge a stale heartbeat.","minimum":0},"operation_status_counts":{"description":"Write-operation row counts per status for this tenant, e.g.\n`{\"queued\": 3, \"applied\": 12}`."},"write_back":{"$ref":"#/components/schemas/QbposWriteBackSettings"},"write_back_suspended":{"type":"boolean","description":"Platform kill switch state. When true the agent is served\n`write_back.enabled=false` and claims return empty regardless of the\nstored config above."}}},"QbposPriceField":{"type":"string","description":"QBPOS price level mapped to the Vender product price.\n\nWhich QBPOS price field feeds Vender's product price. QBPOS carries\n`Price1`..`Price5` (Price1 is the regular selling price on a real POS 19\ncompany — verified against live `ItemInventoryRet` output), plus `MSRP` and\n`WebPrice`. Serialized lowercase (\"price1\"..\"price5\", \"msrp\", \"webprice\").","enum":["price1","price2","price3","price4","price5","msrp","webprice"]},"QbposProductCodeField":{"type":"string","description":"QBPOS field written to Vender's `code` field.","enum":["alu","item_number","none"]},"QbposProductFieldPolicies":{"type":"object","description":"Conflict ownership rules for product fields imported from QBPOS.","properties":{"category":{"$ref":"#/components/schemas/QbposFieldOwnership"},"code":{"$ref":"#/components/schemas/QbposFieldOwnership"},"description":{"$ref":"#/components/schemas/QbposFieldOwnership"},"excluded":{"$ref":"#/components/schemas/QbposFieldOwnership"},"item_number":{"$ref":"#/components/schemas/QbposFieldOwnership"},"name":{"$ref":"#/components/schemas/QbposFieldOwnership"},"price":{"$ref":"#/components/schemas/QbposFieldOwnership"},"quantity":{"$ref":"#/components/schemas/QbposFieldOwnership"}}},"QbposProductItemNumberField":{"type":"string","description":"QBPOS field used as Vender's stable `item_number` business key.","enum":["item_number","alu"]},"QbposProductReconcileAction":{"type":"string","enum":["update_by_external_id","update_by_item_number","create","conflict","invalid"]},"QbposProductReconcileRequest":{"type":"object","required":["rows"],"properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/QbposProductReconcileRow"}}}},"QbposProductReconcileResponse":{"type":"object","required":["total","would_create","would_update","conflicts","invalid","code_changes","max_product_creates_per_run","batch_within_create_limit","rows"],"properties":{"batch_within_create_limit":{"type":"boolean"},"code_changes":{"type":"integer","format":"int32","minimum":0},"conflicts":{"type":"integer","format":"int32","minimum":0},"invalid":{"type":"integer","format":"int32","minimum":0},"max_product_creates_per_run":{"type":"integer","format":"int32","description":"Company setting. The agent applies this to the total across every\nreconciliation batch, not independently to each batch.","minimum":0},"rows":{"type":"array","items":{"$ref":"#/components/schemas/QbposProductReconcileRowResult"}},"total":{"type":"integer","format":"int32","minimum":0},"would_create":{"type":"integer","format":"int32","minimum":0},"would_update":{"type":"integer","format":"int32","minimum":0}}},"QbposProductReconcileRow":{"type":"object","required":["external_id","item_number"],"properties":{"category":{"type":"string","nullable":true},"code":{"type":"string","description":"Value that the configured mapping will write to Vender `code`.","nullable":true},"description":{"type":"string","nullable":true},"excluded":{"type":"boolean","nullable":true},"external_id":{"type":"string","description":"QBPOS `ListID`. It remains a string because live values exceed signed\n64-bit ranges in some SDK responses."},"item_number":{"type":"string","description":"Value that the configured mapping will write to Vender `item_number`."},"msrp_price":{"allOf":[{"$ref":"#/components/schemas/bigdecimal.BigDecimal"}],"nullable":true},"name":{"type":"string","nullable":true},"qty":{"type":"integer","format":"int32","nullable":true},"regular_price":{"allOf":[{"$ref":"#/components/schemas/bigdecimal.BigDecimal"}],"nullable":true},"wholesale_price":{"allOf":[{"$ref":"#/components/schemas/bigdecimal.BigDecimal"}],"nullable":true}}},"QbposProductReconcileRowResult":{"type":"object","required":["row","external_id","item_number","action","code_will_change","field_conflicts","would_block"],"properties":{"action":{"$ref":"#/components/schemas/QbposProductReconcileAction"},"code_will_change":{"type":"boolean"},"entity_id":{"type":"string","nullable":true},"external_id":{"type":"string"},"field_conflicts":{"type":"array","items":{"type":"string"},"description":"Protected fields where both QuickBooks and Vender differ from the last\naccepted sync baseline."},"item_number":{"type":"string"},"message":{"type":"string","nullable":true},"row":{"type":"integer","format":"int32","description":"One-based row number inside this request batch.","minimum":0},"would_block":{"type":"boolean","description":"True when the configured conflict policy would reject this entire row."}}},"QbposRecoveryStatus":{"type":"object","required":["active","reason_code","attempt","next_retry_at","message"],"properties":{"active":{"type":"boolean"},"attempt":{"type":"integer","format":"int32","minimum":0},"message":{"type":"string"},"next_retry_at":{"type":"string"},"reason_code":{"type":"string"}}},"QbposResolveFieldConflictRequest":{"type":"object","required":["action"],"properties":{"action":{"$ref":"#/components/schemas/QbposFieldConflictResolutionAction"}}},"QbposResolveFieldConflictResponse":{"type":"object","required":["id","resolved"],"properties":{"id":{"type":"string"},"pending_resolution":{"type":"string","nullable":true},"resolved":{"type":"boolean"}}},"QbposRunDetailResponse":{"allOf":[{"$ref":"#/components/schemas/QbposRunSummary"},{"type":"object","required":["log_entries"],"properties":{"log_entries":{}}}]},"QbposRunListResponse":{"type":"object","required":["runs","total","limit","offset"],"properties":{"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"runs":{"type":"array","items":{"$ref":"#/components/schemas/QbposRunSummary"}},"total":{"type":"integer","format":"int64"}}},"QbposRunLogEntry":{"type":"object","required":["level","message"],"properties":{"entity":{"type":"string","nullable":true},"level":{"type":"string"},"message":{"type":"string"},"timestamp":{"type":"string","nullable":true}}},"QbposRunMode":{"type":"string","enum":["incremental","full","dry_run"]},"QbposRunStatus":{"type":"string","enum":["running","success","partial","failed","cancelled"]},"QbposRunSummary":{"type":"object","required":["id","agent_version","mode","trigger","status","started_at","entity_counts","error_count"],"properties":{"agent_version":{"type":"string"},"entity_counts":{},"error_count":{"type":"integer","format":"int32"},"finished_at":{"type":"string","nullable":true},"id":{"type":"string"},"mode":{"type":"string"},"started_at":{"type":"string"},"status":{"type":"string"},"trigger":{"type":"string"}}},"QbposRunTrigger":{"type":"string","enum":["schedule","tray","admin","installer","cli"]},"QbposStoreQuantityMode":{"type":"string","description":"Aggregation mode for QBPOS stock quantities across stores.\n\nHow the agent collapses QBPOS multi-store on-hand quantities\n(`OnHandStore01`..`OnHandStore12` in the SDK 4.0 item schema) into the one\nstock figure Vender carries per product. Serialized lowercase (\"total\" |\n\"`single_store`\").","enum":["total","single_store"]},"QbposWriteBackSettings":{"type":"object","description":"Outbound write-back (Vender → QBPOS) configuration. Kept as its own struct\nso future connectors can embed the same shape. Absent config = all off.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level `#[serde(default)]` only fires when the WHOLE group is\nmissing).","properties":{"auto_sync_enabled":{"type":"boolean","description":"Layer B: automatically turn Vender-side edits of linked entities into\nwrite operations. Requires `enabled`. Default false."},"enabled":{"type":"boolean","description":"Master gate. When false the agent constructs a read-only gateway, the\nclaim endpoint returns nothing, and no operation is applied. Default false."},"max_creates_per_run":{"type":"integer","format":"int32","description":"Maximum `create` operations applied per run. Fail-closed default 0,\nmirroring the inbound create caps.","minimum":0},"max_writes_per_run":{"type":"integer","format":"int32","description":"Maximum operations the agent may claim per run. Default 50.","minimum":0},"operation_claim_timeout_minutes":{"type":"integer","format":"int32","description":"Minutes before an operation left in `claimed` by a crashed agent is\nreturned to the queue. Default 60.","minimum":0},"protected_fields":{"type":"array","items":{"type":"string"},"description":"Fields that may never be written to QBPOS regardless of policy."},"require_approval":{"type":"boolean","description":"New operations start in `pending_approval` and require an admin to\napprove before they queue. Default true."},"verify_after_write":{"type":"boolean","description":"Read the entity back after a write and include the observed values in\nthe completion report before an operation counts as applied. Default true."},"write_customers":{"type":"boolean","description":"Allow customer (account) writes. Default false."},"write_departments":{"type":"boolean","description":"Allow department (category) writes. Default false."},"write_images":{"type":"boolean","description":"Allow item picture writes. Default false."},"write_products":{"type":"boolean","description":"Allow item (product) writes. Default false."}}},"QbposWriteBackSuspensionRequest":{"type":"object","required":["write_back_suspended"],"properties":{"write_back_suspended":{"type":"boolean"}}},"QbposWriteBackSuspensionResponse":{"type":"object","required":["company_id","write_back_suspended"],"properties":{"company_id":{"type":"string"},"write_back_suspended":{"type":"boolean"}}},"QrDisplayConfigDto":{"type":"object","description":"QR-relevant subset of `CompanySettings.horeca_ordering.qr` embedded in\nthe QR `TableMenuResponse` (Horeca Ordering Suite wave B4, item 4) — same\n\"kiosk/QR tokens can't read `/api/settings`\" reasoning as\n[`KioskDisplayConfigDto`].","required":["status_poll_seconds","require_name","mode"],"properties":{"mode":{"type":"string","description":"order | `menu_only`."},"require_name":{"type":"boolean"},"status_poll_seconds":{"type":"integer","format":"int32"}}},"QuarantineCode":{"type":"string","description":"Machine-readable reason an inbox row was quarantined. Stored in\n`mobile_order_inbox.error_code` and echoed in the per-order ack.","enum":["missing_account_id","account_not_found","missing_items_array","empty_order","malformed_line_item","invalid_quantity","invalid_products","price_validation_failed","stock_validation_failed","transaction_failed"]},"QuerySettings":{"type":"object","description":"Company dynamic query engine limits.\n\nPer-company query engine tuning (`shared::query`). Governs the abuse guards\nand preview sizing for the dynamic filter builder. Stored inside the company\nsettings JSON; absent config falls back to `Default` and never breaks.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\nfor the reason documented on [`CompanySettings`].","properties":{"enable_dsl_editor":{"type":"boolean","description":"Whether the free-text expression editor is offered alongside the visual\nbuilder. Default true."},"facet_value_limit":{"type":"integer","format":"int32","description":"Distinct values shown per facet before \"show more\". Default 20."},"max_aggregate_group_cardinality":{"type":"integer","format":"int32","description":"Maximum distinct group rows a `GROUP BY` aggregate query may return —\nthe `LIMIT` applied to the grouped result\n(`shared::query::aggregate::AggregateLimits`). Default 200, platform\nceiling 5000. This bounds the *response*; a `statement_timeout`\n(`query_timeout_ms`, above) is what actually protects against a truly\nhigh-cardinality underlying table, since Postgres generally computes\nevery group before a `LIMIT` can apply."},"max_aggregate_result_rows":{"type":"integer","format":"int32","description":"A second, independently configurable cap on aggregate rows returned to\nthe caller. Composes with `max_aggregate_group_cardinality` via the\nstricter of the two. Default 200, platform ceiling 5000."},"max_query_depth":{"type":"integer","format":"int32","description":"Maximum nesting depth of a query tree. Default 6."},"max_query_nodes":{"type":"integer","format":"int32","description":"Maximum number of nodes a single query tree may contain. Default 100."},"preview_row_limit":{"type":"integer","format":"int32","description":"Rows returned by the query preview endpoint. Default 50."},"query_timeout_ms":{"type":"integer","format":"int32","description":"Statement timeout applied to query execution, in milliseconds.\nDefault 5000."}}},"QuickBooksPosSettings":{"type":"object","description":"Per-company QuickBooks POS sync agent tuning (qbpos-sync plan Phase 0).\nStored inside the company settings JSON; absent config falls back to\n`Default` (sync disabled) and never breaks.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level `#[serde(default)]` only fires when the WHOLE group is\nmissing).","properties":{"account_field_policies":{"$ref":"#/components/schemas/QbposAccountFieldPolicies"},"allow_full_resync_from_tray":{"type":"boolean","description":"Allow full resync from the tray. Admin commands remain separately\npermission-gated."},"api_max_attempts":{"type":"integer","format":"int32","description":"Total API attempts per request, including the initial attempt.","minimum":0},"api_retry_base_delay_ms":{"type":"integer","format":"int32","description":"Initial retry delay; the agent applies exponential backoff.","minimum":0},"command_claim_timeout_minutes":{"type":"integer","format":"int32","description":"Minutes before a command left in `claimed` by a crashed/stopped agent\nis returned to the pending queue. Default 720 (12 hours).","minimum":0},"command_poll_seconds":{"type":"integer","format":"int32","description":"How often the agent checks for admin commands.","minimum":0},"deletion_policy":{"$ref":"#/components/schemas/QbposDeletionPolicy"},"enabled":{"type":"boolean","description":"Master switch for the on-premise sync agent. Default false."},"existing_image_policy":{"$ref":"#/components/schemas/QbposExistingImagePolicy"},"field_conflict_resolution":{"$ref":"#/components/schemas/QbposFieldConflictResolution"},"heartbeat_interval_seconds":{"type":"integer","format":"int32","description":"Seconds between liveness/progress heartbeats while a long sync is\nrunning. Default 30.","minimum":0},"heartbeat_stale_minutes":{"type":"integer","format":"int32","description":"Minutes without a heartbeat before the admin marks the agent stale.","minimum":0},"image_queries_per_run":{"type":"integer","format":"int32","description":"Maximum products whose pictures are queried per run. Changed products\nare prioritized; remaining capacity advances a durable catalogue sweep\nso pictures added later are eventually discovered. Default 500.","minimum":0},"image_query_batch_size":{"type":"integer","format":"int32","description":"Maximum individual picture queries packed into one QBPOS message set.\nOne preserves the legacy behavior; higher values reduce COM round trips.","minimum":0},"image_selection_mode":{"$ref":"#/components/schemas/QbposImageSelectionMode"},"image_session_request_limit":{"type":"integer","format":"int32","description":"Maximum individual picture requests before the agent renews its QBPOS\nsession. Picture queries remain single-request message sets.","minimum":0},"image_sync_max_bytes":{"type":"integer","format":"int64","description":"Max size (bytes) of a single item picture the agent uploads.\nDefault 10 `MiB` (`10_485_760`).","minimum":0},"max_account_creates_per_run":{"type":"integer","format":"int32","description":"Maximum accounts a reconciled run may create. Zero is fail-closed:\naccount updates/links are allowed, but creates require an explicit\nadmin decision after reviewing the dry-run projection.","minimum":0},"max_product_creates_per_run":{"type":"integer","format":"int32","description":"Maximum products a reconciled run may create. Zero is fail-closed:\nproduct updates are allowed, but creates require an explicit admin\ndecision after reviewing the dry-run projection.","minimum":0},"pause_minutes":{"type":"integer","format":"int32","description":"Duration of a pause command issued from tray or admin.","minimum":0},"price_field":{"$ref":"#/components/schemas/QbposPriceField"},"product_code_field":{"$ref":"#/components/schemas/QbposProductCodeField"},"product_field_policies":{"$ref":"#/components/schemas/QbposProductFieldPolicies"},"product_item_number_field":{"$ref":"#/components/schemas/QbposProductItemNumberField"},"query_page_size":{"type":"integer","format":"int32","description":"Rows requested per qbposXML iterator page (`<MaxReturned>`). MANDATORY —\nan unfiltered query is an out-of-memory crash on a large company.\nDefault 250.","minimum":0},"require_account_reconciliation":{"type":"boolean","description":"Require a successful full reconciliation before any account or other\nentity writes in the QBPOS run."},"require_product_reconciliation":{"type":"boolean","description":"Require a successful full reconciliation before product uploads."},"run_history_retention_days":{"type":"integer","format":"int32","description":"Server-side run history retention.","minimum":0},"run_log_max_entries":{"type":"integer","format":"int32","description":"Maximum structured log entries accepted for one run.","minimum":0},"store_number":{"type":"integer","format":"int32","description":"Which QBPOS store number (1..=12) supplies the quantity when\n`store_quantity_mode` is \"`single_store`\". Ignored in \"total\" mode.\nDefault 1.","minimum":0},"store_quantity_mode":{"$ref":"#/components/schemas/QbposStoreQuantityMode"},"sync_accounts":{"type":"boolean","description":"Sync QBPOS Customers → Vender accounts. Default true."},"sync_categories":{"type":"boolean","description":"Sync QBPOS Departments → Vender categories. Default true."},"sync_images":{"type":"boolean","description":"Sync QBPOS item pictures → Vender product images. Default true."},"sync_interval_minutes":{"type":"integer","format":"int32","description":"Minutes between agent sync runs. Default 15.","minimum":0},"sync_products":{"type":"boolean","description":"Sync QBPOS `ItemInventory` → Vender products. Default true."},"sync_retry_initial_seconds":{"type":"integer","format":"int32","description":"First delay after a failed/partial QBPOS sync. The long-lived agent\nretries forever and grows this delay exponentially. Default 30 seconds.","minimum":0},"sync_retry_max_seconds":{"type":"integer","format":"int32","description":"Maximum delay between automatic sync recovery attempts. The agent never\nexhausts retries; it remains at this cap until recovery. Default 900s.","minimum":0},"sync_retry_multiplier_percent":{"type":"integer","format":"int32","description":"Exponential multiplier as a percentage (200 = 2x). Default 200.","minimum":0},"tray_actions_enabled":{"type":"boolean","description":"Allow pause/resume/sync-now actions in the local tray."},"wholesale_price_field":{"$ref":"#/components/schemas/QbposPriceField"},"write_back":{"$ref":"#/components/schemas/QbposWriteBackSettings"}}},"QuoteDetailResponse":{"type":"object","description":"Quote detail with items (GET /api/quotes/{id}).","required":["id","account_id","company_id","status","total","created_at","updated_at","items"],"properties":{"account_id":{"type":"string"},"approved_at":{"type":"string","nullable":true},"approved_by":{"type":"integer","format":"int32","nullable":true},"company_id":{"type":"string"},"converted_sales_order_id":{"type":"string","nullable":true},"created_at":{"type":"string"},"id":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/QuoteItemResponse"}},"note":{"type":"string","nullable":true},"status":{"type":"string"},"total":{"type":"string"},"updated_at":{"type":"string"}}},"QuoteItemInput":{"type":"object","description":"Quote item input (from \"Save as Quote\"). `price` is optional and ignored for\npricing — the effective price is resolved from product wholesale pricing at\nconvert time (single source of truth in the `sales_orders` service).","required":["product_id","qty"],"properties":{"price":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"product_id":{"type":"string"},"qty":{"type":"integer","format":"int32"}}},"QuoteItemKind":{"type":"string","enum":["base","extra_rep","extra_office_user","extra_till"]},"QuoteItemResponse":{"type":"object","description":"Quote item row in responses.","required":["id","product_id","qty","price","total"],"properties":{"id":{"type":"string"},"price":{"type":"string"},"product_id":{"type":"string"},"qty":{"type":"integer","format":"int32"},"total":{"type":"string"}}},"QuoteLine":{"type":"object","required":["offer_code","offer_revision","kind","quantity","unit_amount_cents","total_amount_cents"],"properties":{"kind":{"$ref":"#/components/schemas/QuoteItemKind"},"offer_code":{"type":"string"},"offer_revision":{"type":"integer","format":"int32","minimum":0},"quantity":{"type":"integer","format":"int32","minimum":0},"total_amount_cents":{"type":"integer","format":"int64"},"unit_amount_cents":{"type":"integer","format":"int64"}}},"QuoteListItem":{"type":"object","description":"Quote list row (GET /api/quotes).","required":["id","account_id","status","total","created_at"],"properties":{"account_id":{"type":"string"},"created_at":{"type":"string"},"id":{"type":"string"},"note":{"type":"string","nullable":true},"status":{"type":"string"},"total":{"type":"string"}}},"QuoteOpenOrderInput":{"type":"object","description":"The optional body of a quote request: the same bill decision\n[`SettleOpenOrderInput`] carries, minus everything that moves money.\n\nEvery field is optional and an absent body is the pre-split behaviour, so\nthe existing `GET .../quote` and every deployed client keep working\nunchanged.","properties":{"apply_service_charge":{"type":"boolean","description":"Whether the merchant's configured service charge is on this bill.\n`true` while none is configured is a 422, exactly as at settle.","nullable":true},"item_ids":{"type":"array","items":{"type":"string"},"description":"Price only these lines (split by items / by seat). Every id must be an\nunsettled, non-voided line of this order — the same rule settle\nenforces, so a quote can never promise a part settle would refuse.","nullable":true},"split":{"allOf":[{"$ref":"#/components/schemas/SettleSplitInput"}],"nullable":true},"tip_amount":{"type":"string","description":"A proposed gratuity, validated against `billing.max_tip_percent` for\nTHIS part — so the till learns a tip is too large before the guest is\nasked to pay it, rather than at the charge.","nullable":true}}},"RbacCatalogResponse":{"type":"object","required":["assignable_roles","role_presets","permission_groups","api_token_permission_groups"],"properties":{"api_token_permission_groups":{"type":"array","items":{"$ref":"#/components/schemas/PermissionCatalogGroup"}},"assignable_roles":{"type":"array","items":{"$ref":"#/components/schemas/UserRole"}},"permission_groups":{"type":"array","items":{"$ref":"#/components/schemas/PermissionCatalogGroup"}},"role_presets":{"type":"array","items":{"$ref":"#/components/schemas/RolePermissionPreset"}}}},"ReadyOrderResponse":{"type":"object","required":["id","order_number","customer_name","customer_id","agent_name","total_items","picking_status","picking_priority","created_at","total"],"properties":{"agent_name":{"type":"string"},"created_at":{"type":"string"},"customer_id":{"type":"string"},"customer_name":{"type":"string"},"id":{"type":"string"},"order_number":{"type":"string"},"picking_priority":{"type":"string"},"picking_status":{"type":"string"},"picking_zone":{"type":"string","nullable":true},"total":{"type":"string"},"total_items":{"type":"integer","format":"int64"}}},"ReadyOrdersListResponse":{"type":"object","required":["orders","count"],"properties":{"count":{"type":"integer","minimum":0},"orders":{"type":"array","items":{"$ref":"#/components/schemas/ReadyOrderResponse"}},"page_size":{"type":"integer","format":"int64","nullable":true},"total_count":{"type":"integer","format":"int64","description":"Total matching orders; count remains the current batch length for compatibility.","nullable":true}}},"ReceiptAlignment":{"type":"string","description":"Alignment of the receipt header.","enum":["left","center","right"]},"ReceiptDestination":{"type":"string","description":"Receipt output selected by company policy or an installation override.","enum":["configured_thermal","system_dialog"]},"ReceiptEmailRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string","description":"Where to send the receipt."}}},"ReceiptEmailResponse":{"type":"object","required":["receiptId","email","status"],"properties":{"email":{"type":"string"},"receiptId":{"type":"string"},"status":{"type":"string","description":"Always \"queued\": the send runs in the background."}}},"ReceiptGraphicsMode":{"type":"string","description":"Printer graphics protocol or explicit text-only output.","enum":["graphics","legacy_raster","text_only"]},"ReceiptLayout":{"type":"object","description":"Company receipt appearance and output defaults.\n\n`None` at `ReceiptSettings.layout` preserves the legacy layout. Saving this\ngroup enables the designer; omitted fields in older groups use defaults.","properties":{"codepage_index":{"type":"integer","format":"int32","description":"Printer-specific ESC t index; required by the renderer for single-byte encodings.","default":null,"nullable":true,"minimum":0},"graphics_buffer_rows":{"type":"integer","format":"int32","description":"Rows per graphics chunk sent to the printer; must be positive.","default":128,"minimum":1},"graphics_mode":{"allOf":[{"$ref":"#/components/schemas/ReceiptGraphicsMode"}],"default":"graphics"},"header_alignment":{"allOf":[{"$ref":"#/components/schemas/ReceiptAlignment"}],"default":"center"},"logo_data_uri":{"type":"string","description":"Optional inline PNG, JPEG or `WebP` asset encoded as base64.","default":null,"nullable":true},"logo_decode_timeout_ms":{"type":"integer","format":"int32","description":"Maximum logo decode time in milliseconds; must be positive.","default":5000,"minimum":1},"logo_max_height_dots":{"type":"integer","format":"int32","description":"Maximum rendered logo height in printer dots; must be positive.","default":192,"minimum":1},"logo_max_source_pixels":{"type":"integer","format":"int32","description":"Maximum decoded source image pixel count; must be positive.","default":16000000,"minimum":1},"logo_threshold":{"type":"integer","format":"int32","description":"Luminance threshold used to convert logo pixels to monochrome.","default":128,"minimum":0},"logo_width_percent":{"type":"integer","format":"int32","description":"Logo width as a percentage of printable width, from 1 to 100.","default":60,"maximum":100,"minimum":1},"preset":{"allOf":[{"$ref":"#/components/schemas/ReceiptPreset"}],"default":"classic"},"printable_width_dots":{"type":"integer","format":"int32","description":"None uses the physical width derived from the selected 58/80 mm roll.","default":null,"nullable":true,"minimum":1},"qr_caption":{"type":"string","description":"Optional text caption accompanying the QR code.","default":null,"nullable":true},"qr_correction":{"allOf":[{"$ref":"#/components/schemas/ReceiptQrCorrection"}],"default":"M"},"qr_enabled":{"type":"boolean","description":"Encodes only the receipt number, never customer data or an implicit external URL.","default":false},"qr_module_size":{"type":"integer","format":"int32","description":"QR module size in printer dots; must be positive.","default":4,"minimum":1},"section_order":{"type":"array","items":{"$ref":"#/components/schemas/ReceiptSection"},"description":"Permutation containing every receipt section exactly once.","default":["header","metadata","items","totals","payments","footer"]},"separator":{"allOf":[{"$ref":"#/components/schemas/ReceiptSeparator"}],"default":"dashed"},"show_cashier":{"type":"boolean","description":"Include available cashier details.","default":false},"show_customer":{"type":"boolean","description":"Include available customer details.","default":true},"show_logo":{"type":"boolean","description":"Whether the configured logo is included.","default":false},"show_sku":{"type":"boolean","description":"Include item stock-keeping identifiers.","default":false},"show_terminal":{"type":"boolean","description":"Include available terminal details.","default":false},"text_encoding":{"allOf":[{"$ref":"#/components/schemas/ReceiptTextEncoding"}],"default":"utf8"},"text_size":{"allOf":[{"$ref":"#/components/schemas/ReceiptTextSize"}],"default":"normal"},"unsupported_character":{"allOf":[{"$ref":"#/components/schemas/ReceiptUnsupportedCharacter"}],"default":"error"}}},"ReceiptPreset":{"type":"string","description":"Spacing preset used by receipt renderers.","enum":["classic","compact","spacious"]},"ReceiptPrintMode":{"type":"string","description":"Merchant policy for producing a customer receipt after settlement.","enum":["automatic","ask","manual"]},"ReceiptQrCorrection":{"type":"string","description":"QR error-correction level encoded in the receipt image.","enum":["L","M","Q","H"]},"ReceiptSection":{"type":"string","description":"A section included exactly once in the configured receipt order.","enum":["header","metadata","items","totals","payments","footer"]},"ReceiptSeparator":{"type":"string","description":"Style of separators between receipt sections.","enum":["dashed","solid","none"]},"ReceiptSettings":{"type":"object","description":"What goes on the printed receipt, and how its number is formed.\n\nThe receipt is the legal document of the sale. Two things here are\ncompliance items rather than preferences: `tax_id_line` (a registration\nnumber is required on a receipt in NL, CW, AW and most of the EU, and the\ntill had nowhere to put one) and the numbering fields — the terminal used\nto derive the number's date prefix from `Utc::now()`, so west of UTC the\nprefix rolled to tomorrow and the counter reset to 0001 mid-evening, mid-\nshift, while the Z-report's business day was still on the old date.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\n(the group-level default only fires when the WHOLE group is missing).","properties":{"copies":{"type":"integer","format":"int32","description":"Copies per print request; does not repeat settlement or cash-drawer actions.","maximum":255,"minimum":1},"copy_roles":{"type":"array","items":{"type":"string"},"description":"Optional labels by copy index, separate from duplicate/reprint markers."},"destination":{"allOf":[{"$ref":"#/components/schemas/super.receipt_policy.ReceiptDestination"}],"nullable":true},"email_receipts_enabled":{"type":"boolean","description":"Tills may e-mail a receipt to a customer (`POST /api/pos/receipts/{id}/email`).\nThe send runs as a background job through the company's mail transport."},"footer_lines":{"type":"array","items":{"type":"string"},"description":"Extra lines printed after the footer text."},"header_lines":{"type":"array","items":{"type":"string"},"description":"Extra lines printed under the store name/address, before the sale."},"layout":{"allOf":[{"$ref":"#/components/schemas/super.receipt_layout.ReceiptLayout"}],"nullable":true},"legal_lines":{"type":"array","items":{"type":"string"},"description":"Any other legally-required boilerplate, printed verbatim after the tax\nid (return policy, licence number, small-business exemption notice)."},"number_format":{"type":"string","description":"Receipt-number template. `{date}` is the LOCAL business date as\n`YYYYMMDD`, `{prefix}` the per-terminal prefix, `{seq}` the counter\n(zero-padded to 4). Default `\"{date}-{seq}\"` reproduces exactly the\nnumbers the till produced before, so existing sequences continue."},"number_scope":{"type":"string","description":"`daily` resets the counter each business day (the historical\nbehaviour, and the default); `continuous` never resets, which is what\njurisdictions requiring an unbroken sequence ask for."},"print_mode":{"allOf":[{"$ref":"#/components/schemas/ReceiptPrintMode"}],"nullable":true},"tax_id_line":{"type":"string","description":"Tax registration number line, printed verbatim, e.g.\n`BTW: NL001234567B01` or `Crib: 1234567890`. Empty = not printed."},"terminal_overrides":{"type":"object","description":"Per-installation exceptions, scoped to this company. Absent fields inherit.","additionalProperties":{"$ref":"#/components/schemas/super.receipt_policy.ReceiptTerminalOverride"}}}},"ReceiptTerminalOverride":{"type":"object","description":"Optional policy values for one installation within the owning company.","properties":{"copies":{"type":"integer","format":"int32","description":"Copies per request; absent values inherit the company setting.","default":null,"nullable":true,"maximum":255,"minimum":1},"copy_roles":{"type":"array","items":{"type":"string"},"description":"None inherits company copy roles; an empty list explicitly clears them.","default":null,"nullable":true},"destination":{"allOf":[{"$ref":"#/components/schemas/ReceiptDestination"}],"default":null,"nullable":true},"layout":{"allOf":[{"$ref":"#/components/schemas/ReceiptLayout"}],"default":null,"nullable":true},"print_mode":{"allOf":[{"$ref":"#/components/schemas/ReceiptPrintMode"}],"default":null,"nullable":true},"serial_baud_rate":{"type":"integer","format":"int32","description":"Serial receipt-printer speed for this installation; absent uses 9600.","default":null,"nullable":true,"minimum":1}}},"ReceiptTextEncoding":{"type":"string","description":"Encoding used for text sent to the receipt printer.","enum":["utf8","cp437","cp858","windows1252"]},"ReceiptTextSize":{"type":"string","description":"Receipt text size preference.","enum":["normal","large"]},"ReceiptUnsupportedCharacter":{"type":"string","description":"Handling of text that the selected encoding cannot represent.","enum":["error","replace"]},"ReceivedNotBilledResponse":{"type":"object","required":["currency_code","rows","total_rows","total_value","limit","offset"],"properties":{"currency_code":{"type":"string"},"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"rows":{"type":"array","items":{"$ref":"#/components/schemas/ReceivedNotBilledRow"}},"total_rows":{"type":"integer","format":"int64"},"total_value":{"type":"string","description":"Value of everything received and not billed (all rows, not just this page)."}}},"ReceivedNotBilledRow":{"type":"object","required":["purchase_order_id","purchase_order_number","supplier_id","supplier_name","purchase_order_line_id","line_number","product_id","description","received_qty","billed_qty","unbilled_qty","unit_cost","unbilled_value"],"properties":{"billed_qty":{"type":"string"},"description":{"type":"string"},"line_number":{"type":"integer","format":"int32"},"product_id":{"type":"string"},"purchase_order_id":{"$ref":"#/components/schemas/Uuid"},"purchase_order_line_id":{"$ref":"#/components/schemas/Uuid"},"purchase_order_number":{"type":"string"},"received_qty":{"type":"string"},"supplier_id":{"$ref":"#/components/schemas/Uuid"},"supplier_name":{"type":"string"},"unbilled_qty":{"type":"string"},"unbilled_value":{"type":"string"},"unit_cost":{"type":"string","description":"Net purchase-order unit cost."}}},"ReceivingSettings":{"type":"object","description":"Company goods-receiving discrepancy policy.\n\nPer-company goods-receiving discrepancy policy (E-3.0, G024\nreconciliation). Stored inside the company settings JSON; absent config\nfalls back to `Default` (zero tolerance, no forced reason) so existing\ntenants are unaffected.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`.","properties":{"over_delivery_tolerance_percent":{"type":"string","description":"How far (percent of the ordered quantity, decimal string) a receipt\nmay exceed what is still open on a purchase-order line. `\"0\"` refuses\nany over-delivery; an accepted over-delivery is always a discrepancy,\nso it needs approval when `require_approval_outside_tolerance` is on."},"quantity_variance_tolerance":{"type":"string","description":"Decimal string quantity variance (accepted vs. ordered) tolerated\nbefore a receipt line is flagged as a discrepancy."},"require_approval_outside_tolerance":{"type":"boolean","description":"Require manager approval before posting a receipt with an unresolved\ndiscrepancy."},"require_discrepancy_reason":{"type":"boolean","description":"Require a free-text reason on any discrepant receipt line."},"value_variance_tolerance":{"type":"string","description":"Decimal string unit-cost variance (received vs. PO price) tolerated\nbefore a receipt line is flagged as a discrepancy."}}},"ReconcileInventoryCostRequest":{"type":"object","description":"Align the cost ledger's quantity with real stock at one location.\n\nBoth expected quantities come from the valuation row the user is looking\nat; a mismatch means something moved since, and the request is refused.","required":["location_id","product_id","expected_stock_quantity","expected_cost_quantity"],"properties":{"expected_cost_quantity":{"type":"string","description":"Cost-ledger quantity the user saw (decimal string); must still match."},"expected_stock_quantity":{"type":"string","description":"Stock quantity the user saw (decimal string); must still match."},"location_id":{"type":"string","description":"Location whose ledger row is reconciled."},"notes":{"type":"string","description":"Free-text reason, kept in the audit log.","nullable":true},"product_id":{"type":"string","description":"Product to reconcile."},"variant_id":{"type":"string","description":"Variant, `None` for a plain product.","nullable":true}}},"ReconcileInventoryCostResponse":{"type":"object","description":"Result of a cost-ledger reconciliation.","required":["operation_id","location_id","product_id","cost_quantity_before","cost_quantity_after","adjusted_quantity","unit_cost","value_change","inventory_value"],"properties":{"adjusted_quantity":{"type":"string","description":"Signed: positive when the ledger was below stock."},"cost_quantity_after":{"type":"string","description":"Ledger quantity after the adjustment (equals stock)."},"cost_quantity_before":{"type":"string","description":"Ledger quantity before the adjustment."},"inventory_value":{"type":"string","description":"Inventory value after the adjustment."},"location_id":{"type":"string","description":"Location reconciled."},"operation_id":{"$ref":"#/components/schemas/Uuid"},"product_id":{"type":"string","description":"Product reconciled."},"unit_cost":{"type":"string","description":"Average unit cost the difference was posted at."},"value_change":{"type":"string","description":"Signed change in inventory value."},"variant_id":{"type":"string","description":"Variant reconciled, if any.","nullable":true}}},"RecordPaymentRequest":{"type":"object","description":"Body of `POST /fulfillment/documents/{id}/payments` (E-5.3, ADR-006).\n`amount` must be strictly positive, `payment_method` non-blank, and the\ndocument must be an ISSUED invoice — enforced in\n`payment_service::record_payment`, never trusted from the client.","required":["amount","payment_method"],"properties":{"amount":{"$ref":"#/components/schemas/BigDecimal"},"idempotency_key":{"type":"string","description":"Optional client-supplied idempotency key. A replayed request with the\nsame key against the same invoice returns the ORIGINAL payment\ninstead of recording a second one.","nullable":true},"payment_method":{"type":"string"},"reference":{"type":"string","nullable":true}}},"RecordPickRequest":{"type":"object","required":["sales_order_item_id","product_id","picked_quantity"],"properties":{"barcode":{"type":"string","nullable":true},"location":{"type":"string","nullable":true},"picked_quantity":{"type":"integer","format":"int32"},"product_id":{"type":"string"},"sales_order_item_id":{"type":"string"}}},"RecordPickResponse":{"type":"object","required":["message","pick_id","sales_order_item_id","picked_quantity"],"properties":{"location":{"type":"string","nullable":true},"message":{"type":"string"},"pick_id":{"type":"string"},"picked_quantity":{"type":"integer","format":"int32"},"sales_order_item_id":{"type":"string"}}},"RecoveryCodesResponse":{"type":"object","required":["message","recovery_codes"],"properties":{"message":{"type":"string"},"recovery_codes":{"type":"array","items":{"type":"string"}}}},"RecurringPrice":{"type":"object","required":["monthly_cents"],"properties":{"annual_cents":{"type":"integer","format":"int64","description":"Actual annual invoice amount, never a monthly equivalent.","nullable":true},"monthly_cents":{"type":"integer","format":"int64"}}},"RefundSalesOrderRequest":{"type":"object","description":"Body of `POST /api/sales-orders/{id}/refund` (storefront orders only).","properties":{"amount":{"type":"string","description":"Amount to refund (decimal string). Omit for the full remaining amount.","nullable":true},"reason":{"type":"string","description":"Free-text reason kept in the audit log.","nullable":true}}},"RefundSalesOrderResponse":{"type":"object","description":"Result of a refund: the order's new payment state.","required":["order_id","payment_status","refund_total","refunded_now"],"properties":{"order_id":{"type":"string"},"payment_status":{"type":"string","description":"\"refunded\" | \"partially_refunded\""},"refund_total":{"type":"string","description":"Total refunded so far (decimal string)."},"refunded_now":{"type":"string","description":"Amount refunded by this call (decimal string)."},"stripe_refund_id":{"type":"string","description":"Stripe refund id when the money went back through Stripe.","nullable":true}}},"RegenerateRecoveryCodesRequest":{"type":"object","description":"Replace the recovery-code set (e.g. the printout was lost).","properties":{"code":{"type":"string","nullable":true},"recovery_code":{"type":"string","nullable":true}}},"RegisterPosTerminalRequest":{"type":"object","required":["terminal_id","name"],"properties":{"app_version":{"type":"string","nullable":true},"label":{"type":"string","nullable":true},"name":{"type":"string"},"platform":{"type":"string","nullable":true},"platform_version":{"type":"string","nullable":true},"terminal_id":{"type":"string"}}},"RejectOrderInput":{"type":"object","properties":{"reason":{"type":"string","nullable":true}}},"RenameKioskDeviceInput":{"type":"object","description":"`PATCH /api/horeca/kiosk-devices/{id}` body (Horeca Ordering Suite wave\nB4, item 7) — rename only, staff-gated the same way `revoke_kiosk_device`\nis.","required":["name"],"properties":{"name":{"type":"string"}}},"ReorderMarketingImagesRequest":{"type":"object","required":["image_ids"],"properties":{"image_ids":{"type":"array","items":{"$ref":"#/components/schemas/Uuid"}}}},"ReplaceLicenceAllocation":{"type":"object","required":["expected_revision","agreement_id","assignments","reason"],"properties":{"agreement_id":{"$ref":"#/components/schemas/uuid.Uuid"},"assignments":{"type":"array","items":{"$ref":"#/components/schemas/LicenceAssignment"}},"expected_revision":{"type":"integer","format":"int32"},"reason":{"type":"string"}}},"ReplaceLookbookItemsRequest":{"type":"object","description":"Replace a lookbook's items wholesale, in array order.\n\nThe array index becomes `position`, so the client never computes positions\nand they can never end up sparse or duplicated. A full replace rather than a\ndiff: the builder always holds the complete ordered list, and reconciling\nadds/removes/moves separately would be more code and more failure modes.","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/LookbookItemInput"}}}},"ReplayRunRequest":{"type":"object","description":"`POST /api/pipelines/runs/{runId}/replay` body. `dry_run` defaults to\n`true`, same posture as a manual run — replay for real only on explicit\nopt-in.","properties":{"dry_run":{"type":"boolean","nullable":true}}},"ReplyThreadRequest":{"type":"object","required":["body_html"],"properties":{"body_html":{"type":"string"},"body_text":{"type":"string","nullable":true}}},"ReportFilters":{"type":"object","properties":{"category_id":{"type":"string","nullable":true},"end_date":{"type":"string","description":"Inclusive range end. `YYYY-MM-DD` is interpreted as end of that day in\nthe company timezone; an RFC 3339 instant is an exclusive upper bound.","nullable":true},"limit":{"type":"integer","format":"int64","description":"Maximum number of rows returned for the report's largest table.","nullable":true},"offset":{"type":"integer","format":"int64","description":"Number of rows to skip for the report's largest table.","nullable":true},"start_date":{"type":"string","description":"Inclusive range start. `YYYY-MM-DD` (interpreted as start of that day\nin the company timezone) or an RFC 3339 instant.","nullable":true}}},"ReportingSettings":{"type":"object","description":"Company reporting and customer segmentation thresholds.\n\nPer-company reporting thresholds (customer segmentation + tiering used by\n`/api/reports/customer-analytics`). Previously hardcoded in\n`features/reports/handlers.rs` (R0-10); code holds the DEFAULT, the stored\nsetting overrides it, absent config falls back to defaults and never\nbreaks.\n\nRevenue thresholds are in the company currency's major unit — the backend\nnever formats or attaches a currency symbol; the frontend formats.\n\nNOTE: every field added here MUST carry its own `#[serde(default = ...)]`\nfor the reason documented on [`CompanySettings`].","properties":{"segment_at_risk_days":{"type":"integer","format":"int32","description":"Days since last order after which a customer counts as \"at risk\".\nDefault 180."},"segment_frequent_min_orders":{"type":"integer","format":"int32","description":"Minimum order count for the \"frequent/loyal\" segment. Default 10."},"segment_frequent_min_revenue":{"type":"integer","format":"int64","description":"Minimum total revenue for the \"frequent/loyal\" segment. Default 5000."},"segment_occasional_min_orders":{"type":"integer","format":"int32","description":"Minimum order count for the \"occasional/regular\" segment. Default 5."},"segment_vip_min_orders":{"type":"integer","format":"int32","description":"Minimum order count for the \"VIP\" customer segment. Default 20."},"segment_vip_min_revenue":{"type":"integer","format":"int64","description":"Minimum total revenue for the \"VIP\" customer segment. Default 10000."},"tier_bronze_min":{"type":"integer","format":"int64","description":"Total-spend floor for the Bronze customer tier. Default 5000."},"tier_gold_min":{"type":"integer","format":"int64","description":"Total-spend floor for the Gold customer tier. Default 20000."},"tier_platinum_min":{"type":"integer","format":"int64","description":"Total-spend floor for the Platinum customer tier. Default 50000."},"tier_silver_min":{"type":"integer","format":"int64","description":"Total-spend floor for the Silver customer tier. Default 10000."}}},"RequeueResponse":{"type":"object","required":["requeued"],"properties":{"requeued":{"type":"boolean"}}},"ResendVerificationRequest":{"type":"object","description":"Request body for resending verification email","required":["email"],"properties":{"email":{"type":"string"}}},"ResetPasswordRequest":{"type":"object","required":["user_id","new_password"],"properties":{"new_password":{"type":"string"},"user_id":{"type":"integer","format":"int32"}}},"ResetPasswordResponse":{"type":"object","required":["message","email_sent"],"properties":{"email_sent":{"type":"boolean"},"message":{"type":"string"}}},"ResetPortalPasswordRequest":{"type":"object","description":"Reset password request (admin-only, manual)","required":["new_password"],"properties":{"new_password":{"type":"string"}}},"ResolveExternalReferenceQuery":{"type":"object","required":["entity_type","source_system","external_id"],"properties":{"entity_type":{"type":"string"},"external_id":{"type":"string"},"source_system":{"type":"string"}}},"ResolveExternalReferenceResponse":{"type":"object","properties":{"entity_id":{"type":"string","description":"Local entity id, or null when no mapping exists.","nullable":true}}},"ResolveInboxRequest":{"type":"object","description":"Request body for `POST /mobile-order-inbox/{id}/resolve`: the admin-edited\norder payload to process in place of the stored raw payload (which is kept\nuntouched for forensics).","required":["payload"],"properties":{"payload":{"description":"The edited order payload (same shape as a pushed order)."}}},"ResolvePriceRequest":{"type":"object","required":["product_ids"],"properties":{"account_id":{"type":"string","nullable":true},"product_ids":{"type":"array","items":{"type":"string"}},"quantity":{"type":"integer","format":"int32","nullable":true}}},"ResolvePriceResponse":{"type":"object","required":["prices"],"properties":{"prices":{"type":"array","items":{"$ref":"#/components/schemas/ResolvedPrice"}}}},"ResolvedAccount":{"type":"object","description":"A customer/account resolved from an order payload's account id.","required":["id","name"],"properties":{"account_number":{"type":"string","nullable":true},"customer_type":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"id":{"type":"string"},"name":{"type":"string"},"phone":{"type":"string","nullable":true}}},"ResolvedFeatureRow":{"type":"object","required":["feature_code","enabled","source","limits"],"properties":{"enabled":{"type":"boolean"},"feature_code":{"type":"string"},"limits":{"type":"object"},"source":{"type":"string","description":"`override` | `plan` | `tier_default` — WHERE this value came from.\nWithout provenance, \"enabled\" is unanswerable at support time."}}},"ResolvedInboxOrder":{"type":"object","description":"A fully resolved, human-readable view of an inbox order's raw payload.","required":["company_id","items","item_count","total_qty","total_amount","has_short_stock"],"properties":{"account":{"allOf":[{"$ref":"#/components/schemas/ResolvedAccount"}],"nullable":true},"company_id":{"type":"string"},"company_name":{"type":"string","nullable":true},"has_short_stock":{"type":"boolean","description":"True when any line exceeds available stock."},"item_count":{"type":"integer","format":"int64"},"items":{"type":"array","items":{"$ref":"#/components/schemas/ResolvedLineItem"}},"note":{"type":"string","nullable":true},"order_date":{"type":"string","nullable":true},"parse_error":{"type":"string","description":"Present when the payload could not be parsed as an order (still displayed\nraw). Enrichment never fails the request — this explains a thin result.","nullable":true},"sales_rep":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"total_amount":{"type":"string","description":"Order total (sum of line totals), as a decimal string."},"total_qty":{"type":"integer","format":"int64"}}},"ResolvedLineItem":{"type":"object","description":"One order line resolved to product name + stock context.","required":["product_id","name","qty","unit_price","line_total","short_stock"],"properties":{"available_qty":{"type":"integer","format":"int32","description":"Available stock for the product, if the product was found.","nullable":true},"code":{"type":"string","nullable":true},"item_number":{"type":"string","nullable":true},"line_total":{"type":"string","description":"qty × `unit_price`, as a decimal string."},"name":{"type":"string","description":"Resolved product name, or the raw id if the product was not found."},"product_id":{"type":"string"},"qty":{"type":"integer","format":"int32"},"short_stock":{"type":"boolean","description":"True when the ordered qty exceeds available stock (drives stock negative)."},"unit_price":{"type":"string","description":"Unit price from the order payload, as a decimal string."}}},"ResolvedPrice":{"type":"object","required":["product_id","original_price","resolved_price","price_type","rule_description"],"properties":{"original_price":{"$ref":"#/components/schemas/BigDecimal"},"price_type":{"type":"string"},"product_id":{"type":"string"},"resolved_price":{"$ref":"#/components/schemas/BigDecimal"},"rule_description":{"type":"string"}}},"RetentionMetrics":{"type":"object","required":["returning_customers","new_customers","retention_rate","churn_rate","avg_days_between_orders"],"properties":{"avg_days_between_orders":{"type":"number","format":"double"},"churn_rate":{"type":"number","format":"double"},"new_customers":{"type":"integer","format":"int64"},"retention_rate":{"type":"number","format":"double"},"returning_customers":{"type":"integer","format":"int64"}}},"RevokeAccessTokenResponse":{"type":"object","required":["message","access_token"],"properties":{"access_token":{"$ref":"#/components/schemas/AccessTokenListItem"},"message":{"type":"string"}}},"RolePermissionPreset":{"type":"object","required":["role","permissions"],"properties":{"permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"}},"role":{"$ref":"#/components/schemas/UserRole"}}},"RotateAccessTokenRequest":{"type":"object","properties":{"expires_at":{"type":"string","nullable":true}}},"RunPipelineRequest":{"type":"object","description":"`POST /api/pipelines/{id}/run` body. `dry_run` defaults to `true` — a\nmanual run only executes for real when the caller explicitly opts in.","properties":{"dry_run":{"type":"boolean","nullable":true},"trigger_payload":{"description":"Optional JSON the run's `{{ trigger.* }}` expressions resolve against\n— inject sample or real-record data to test the graph with. Omitted =\nempty trigger context, as before.","nullable":true}}},"SalesChannel":{"type":"string","description":"A way the company sells. Answers the onboarding wizard's \"how do you sell?\"\nquestion and drives which apps appear in the admin sidebar and which setup\nsteps appear in the checklist.\n\nAn enum rather than free strings so a typo cannot silently hide a channel:\nan unknown value fails deserialization of the group, which\n`deserialize_null_default` then turns into the default (all channels\nvisible) rather than a half-configured sidebar.","enum":["pos","b2b","storefront","mobile_picking"]},"SalesOrderDetailComponent":{"type":"object","required":["id","fulfilled_product_id","quantity"],"properties":{"fulfilled_product_id":{"type":"string"},"id":{"type":"string"},"product_name_snapshot":{"type":"string","nullable":true},"quantity":{"type":"integer","format":"int32"},"sku_snapshot":{"type":"string","nullable":true}}},"SalesOrderDetailEvent":{"type":"object","required":["id","event_type","created_at"],"properties":{"created_at":{"type":"string"},"event_type":{"type":"string"},"id":{"type":"string"},"note":{"type":"string","nullable":true}}},"SalesOrderDetailImage":{"type":"object","required":["id","image_url"],"properties":{"id":{"type":"string"},"image_thumb_200_url":{"type":"string","nullable":true},"image_url":{"type":"string"},"name":{"type":"string","nullable":true}},"additionalProperties":{}},"SalesOrderDetailItem":{"type":"object","required":["id","qty","price","total","product_id","product_name","product_code","product_images"],"properties":{"id":{"type":"string"},"price":{"type":"string"},"product_code":{"type":"string"},"product_description":{"type":"string","nullable":true},"product_id":{"type":"string"},"product_images":{"type":"array","items":{"$ref":"#/components/schemas/SalesOrderDetailImage"}},"product_name":{"type":"string"},"qty":{"type":"integer","format":"int32"},"total":{"type":"string"},"variant_label":{"type":"string","description":"Product variant sold on this line (online store), e.g. \"M / Blue\".","nullable":true}},"additionalProperties":{}},"SalesOrderDetailResolution":{"type":"object","required":["id","sales_order_line_id","resolution_type","ordered_quantity","picked_quantity","remaining_quantity","approval_status"],"properties":{"approval_status":{"type":"string"},"components":{"type":"array","items":{"$ref":"#/components/schemas/SalesOrderDetailComponent"},"nullable":true},"id":{"type":"string"},"note":{"type":"string","nullable":true},"ordered_quantity":{"type":"integer","format":"int32"},"picked_quantity":{"type":"integer","format":"int32"},"reason_code":{"type":"string","nullable":true},"remaining_quantity":{"type":"integer","format":"int32"},"resolution_type":{"type":"string"},"sales_order_line_id":{"type":"string"}}},"SalesOrderDetailResponse":{"type":"object","description":"Typed detail contract. Unknown legacy/additive fields round-trip unchanged.","required":["id","date","total","note","customer_name","agent_name","items","items_count"],"properties":{"account_name":{"type":"string","description":"The linked customer account (storefront orders attach to a default\naccount; the buyer is in `customer_*`).","nullable":true},"agent_email":{"type":"string","nullable":true},"agent_name":{"type":"string"},"customer_address":{"type":"string","nullable":true},"customer_email":{"type":"string","nullable":true},"customer_name":{"type":"string"},"customer_order_url":{"type":"string","description":"Absolute link to the shopper's order-status page (storefront orders).","nullable":true},"customer_phone":{"type":"string","nullable":true},"date":{"type":"string"},"delivered_at":{"type":"string","nullable":true},"delivery_carrier":{"type":"string","nullable":true},"delivery_method":{"type":"string","nullable":true},"delivery_notes":{"type":"string","nullable":true},"delivery_status":{"type":"string","nullable":true},"delivery_tracking_code":{"type":"string","nullable":true},"discount_code":{"type":"string","nullable":true},"discount_total":{"type":"string","description":"Amount the discount took off the total (decimal string).","nullable":true},"estimated_delivery":{"type":"string","nullable":true},"id":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/SalesOrderDetailItem"}},"items_count":{"type":"integer","minimum":0},"items_subtotal":{"type":"string","description":"Sum of the line totals (decimal string).","nullable":true},"line_resolutions":{"type":"array","items":{"$ref":"#/components/schemas/SalesOrderDetailResolution"},"nullable":true},"note":{"type":"string"},"order_number":{"type":"string","nullable":true},"payment_status":{"type":"string","description":"\"\" | \"manual\" | \"pending\" | \"paid\" (storefront orders).","nullable":true},"prices_include_tax":{"type":"boolean","description":"Whether `tax_total` is already inside `total` (store setting at read time).","nullable":true},"proof_of_delivery_url":{"type":"string","nullable":true},"public_token":{"type":"string","description":"Public order-status token (storefront orders) for the \"view as customer\" link.","nullable":true},"refund_total":{"type":"string","description":"Amount refunded so far on a storefront order (decimal string).","nullable":true},"shipping_address":{"description":"The buyer's shipping address as entered at checkout.","nullable":true},"shipping_fee":{"type":"string","description":"Shipping fee charged (decimal string; total = items + shipping − discount).","nullable":true},"source":{"type":"string","description":"Origin channel (\"storefront\", \"pos\", …; empty for legacy rows).","nullable":true},"status":{"type":"string","nullable":true},"tax_total":{"type":"string","description":"Tax recorded on a storefront order (decimal string; included in the\ntotal for tax-inclusive stores, added on top otherwise).","nullable":true},"timeline":{"type":"array","items":{"$ref":"#/components/schemas/SalesOrderDetailEvent"},"nullable":true},"total":{"type":"string"}},"additionalProperties":{}},"SalesOrderEmailSettings":{"type":"object","description":"Sales-order email content and attachment preferences.","properties":{"export_format":{"type":"string","description":"Attachment format: \"`EMAIL_ONLY`\" | \"EXCEL\" | \"PDF\" | \"`EXCEL_PDF`\""},"paper_size":{"type":"string","description":"Paper size for print layout: \"A4\" or \"LETTER\""},"show_barcode":{"type":"boolean","description":"Show Barcode / Code column in the attachment"},"show_item_number":{"type":"boolean","description":"Show Item Number column in the attachment"},"show_photos":{"type":"boolean","description":"Embed product thumbnail images in the attachment rows"},"show_product_id":{"type":"boolean","description":"Show internal product UUID column in the attachment"}}},"SalesOrderItemInput":{"type":"object","description":"Sales order item input for creation/updates","required":["product_id","qty","price"],"properties":{"id":{"type":"string","nullable":true},"price":{"$ref":"#/components/schemas/BigDecimal"},"product_id":{"type":"string"},"qty":{"type":"integer","format":"int32"}}},"SalesOrderItemWithProductResponse":{"type":"object","description":"Sales order item with product response","required":["id","created_at","updated_at","qty","product_id","total","sales_order_id","price","is_deleted"],"properties":{"created_at":{"$ref":"#/components/schemas/NaiveDateTime"},"deleted_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"id":{"type":"string"},"is_deleted":{"type":"boolean"},"price":{"$ref":"#/components/schemas/BigDecimal"},"product_code":{"type":"string","nullable":true},"product_description":{"type":"string","nullable":true},"product_id":{"type":"string"},"product_images":{"type":"array","items":{"$ref":"#/components/schemas/ProductImageResponse"},"nullable":true},"product_name":{"type":"string","nullable":true},"qty":{"type":"integer","format":"int32"},"sales_order_id":{"type":"string"},"total":{"$ref":"#/components/schemas/BigDecimal"},"updated_at":{"$ref":"#/components/schemas/NaiveDateTime"}}},"SalesOrderQuery":{"type":"object","description":"Sales order query parameters","properties":{"account_id":{"type":"string","description":"Filter by account/customer ID","nullable":true},"date_from":{"type":"string","description":"Filter orders from this date (YYYY-MM-DD format)","nullable":true},"date_to":{"type":"string","description":"Filter orders until this date (YYYY-MM-DD format)","nullable":true},"include_deleted":{"type":"boolean","description":"Include soft-deleted orders (admin recovery view). Requires the\n`sales_orders.manage` permission; defaults to false."},"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"product_id":{"type":"string","description":"Filter by product ID (orders containing this product)","nullable":true},"search":{"type":"string","description":"Search by order ID or customer name","nullable":true},"source":{"type":"string","description":"Origin channel filter: \"storefront\" (online store), \"\" (staff),\n… — matches `webapp_salesorder.source` exactly.","nullable":true},"status":{"type":"string","description":"Filter by order status (draft, submitted, processing, completed, cancelled)","nullable":true}}},"SalesPeriod":{"type":"object","required":["period","order_count","revenue"],"properties":{"order_count":{"type":"integer","format":"int64"},"period":{"type":"string"},"revenue":{"$ref":"#/components/schemas/BigDecimal"}}},"SalesReceiptDetailResponse":{"type":"object","description":"Sales receipt detail response","required":["id","date","total","note","account_id"],"properties":{"account_id":{"type":"string"},"change_amount":{"type":"string","nullable":true},"clerk_notes":{"type":"string","nullable":true},"date":{"type":"string"},"id":{"type":"string"},"location_id":{"type":"string","nullable":true},"note":{"type":"string"},"payment_amount":{"type":"string","nullable":true},"payment_method":{"type":"string","nullable":true},"receipt_number":{"type":"string","nullable":true},"rounding_adjustment":{"type":"string","description":"Signed cash-rounding difference applied when this sale was tendered\n(negative when the merchant forwent the difference by rounding down).\n\nPurely additive: nothing else on this response changes meaning or\nvalue. `total` is the sum of the receipt's line totals (the sync\nhandler derives it that way and ignores the till's own `totalAmount`),\nso it is the pre-rounding figure; `payment_amount` is what the drawer\nactually took, and the two differ by exactly this adjustment on a\nrounded cash sale. Neither is recomputed here — this field only names\nthe gap, so an accountant can book cash rounding on its own line\ninstead of leaving it mixed into sales, as several EU jurisdictions\nrequire.\n\n`None` on receipts created before terminals recorded it, and on any\nsale rung by a till binary that does not send it — \"not recorded\",\nnot \"rounded by zero\".","nullable":true},"sales_order_number":{"type":"string","nullable":true},"terminal_id":{"type":"string","nullable":true},"total":{"type":"string"}}},"SalesReceiptExportParams":{"type":"object","properties":{"end_date":{"allOf":[{"$ref":"#/components/schemas/chrono.NaiveDateTime"}],"nullable":true},"start_date":{"allOf":[{"$ref":"#/components/schemas/chrono.NaiveDateTime"}],"nullable":true}}},"SalesReceiptInput":{"type":"object","required":["receipt_items","account_id","payment_method","payment_amount"],"properties":{"account_id":{"type":"string"},"change_amount":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"clerk_notes":{"type":"string","nullable":true},"date":{"type":"string","nullable":true},"gift_card_id":{"allOf":[{"$ref":"#/components/schemas/uuid.Uuid"}],"nullable":true},"location_id":{"type":"string","description":"Store location where this sale occurred. Required for multi-location stock tracking.","nullable":true},"note":{"type":"string","nullable":true},"payment_amount":{"$ref":"#/components/schemas/BigDecimal"},"payment_method":{"type":"string"},"receipt_items":{"type":"array","items":{"$ref":"#/components/schemas/SalesReceiptItemInput"}},"sales_order_id":{"type":"string","nullable":true},"sales_order_number":{"type":"string","nullable":true},"subtotal":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"tax_amount":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"terminal_id":{"type":"string","nullable":true},"total_amount":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true}}},"SalesReceiptItemInput":{"type":"object","required":["qty","price","total","product_id"],"properties":{"modifiers":{"description":"Optional modifier selections captured at order time.\nEach entry: {`group_id`, `group_name`, `option_id`, `option_name`, `price_adjustment`}","nullable":true},"price":{"$ref":"#/components/schemas/BigDecimal"},"product_id":{"type":"string"},"qty":{"type":"integer","format":"int32"},"total":{"$ref":"#/components/schemas/BigDecimal"}}},"SalesReceiptItemResponse":{"type":"object","description":"Sales receipt item response for detail view","required":["id","product_id","qty","price","total","product_images"],"properties":{"id":{"type":"string"},"price":{"type":"string"},"product_id":{"type":"string"},"product_images":{"type":"array","items":{"$ref":"#/components/schemas/ProductImageResponse"}},"product_name":{"type":"string","nullable":true},"qty":{"type":"integer","format":"int32"},"total":{"type":"string"}}},"SalesReceiptQuery":{"type":"object","properties":{"end_date":{"type":"string","nullable":true},"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"product_id":{"type":"string","nullable":true},"search":{"type":"string","nullable":true},"start_date":{"type":"string","nullable":true}}},"SalesReport":{"type":"object","required":["total_orders","total_revenue","avg_order_value","top_customers","sales_by_period","top_customers_total"],"properties":{"avg_order_value":{"$ref":"#/components/schemas/BigDecimal"},"sales_by_period":{"type":"array","items":{"$ref":"#/components/schemas/SalesPeriod"}},"top_customers":{"type":"array","items":{"$ref":"#/components/schemas/CustomerSales"}},"top_customers_total":{"type":"integer","format":"int64"},"total_orders":{"type":"integer","format":"int64"},"total_revenue":{"$ref":"#/components/schemas/BigDecimal"}}},"SampleDataRemovalResponse":{"type":"object","description":"Result of removing the sample catalogue.","required":["products_removed","products_kept","categories_removed","images_removed"],"properties":{"categories_removed":{"type":"integer","description":"Sample categories soft-deleted. A category with any surviving product is\nkept.","minimum":0},"images_removed":{"type":"integer","description":"Image objects removed from storage. `0` when storage is not configured.","minimum":0},"products_kept":{"type":"integer","description":"Sample products left in place because the tenant has used or edited\nthem — they are now real data.","minimum":0},"products_removed":{"type":"integer","description":"Sample products soft-deleted.","minimum":0}}},"SampleDataResponse":{"type":"object","description":"Result of loading the sample catalogue into the caller's own company.","required":["products_created","categories_created","images_copied"],"properties":{"categories_created":{"type":"integer","description":"Categories inserted (categories whose code already existed are reused,\nnot counted).","minimum":0},"images_copied":{"type":"integer","description":"Product images copied from the demo image library. `0` when R2 is not\nconfigured — the catalogue is still created.","minimum":0},"products_created":{"type":"integer","description":"Products inserted into the caller's company.","minimum":0}}},"SampleDataSummaryResponse":{"type":"object","description":"Tagged sample data currently live in a company.","required":["present","product_count","has_other_products"],"properties":{"has_other_products":{"type":"boolean","description":"Whether the company also holds live products that are not sample data —\nthe moment the demo catalogue becomes clutter worth offering to clear."},"pack":{"type":"string","description":"Which pack was loaded.","nullable":true},"present":{"type":"boolean","description":"Whether any tagged sample product is still live."},"product_count":{"type":"integer","description":"How many tagged products are still live.","minimum":0}}},"SamplePackOption":{"type":"object","description":"A sample pack the wizard may offer.","required":["id","product_count"],"properties":{"id":{"type":"string","description":"Vertical id, e.g. `casacesar`."},"product_count":{"type":"integer","description":"How many products the pack holds.","minimum":0}}},"SaveContentRequest":{"type":"object","required":["content"],"properties":{"content":{"description":"The catalogue itself: products and branding, rendered by the viewer."}}},"SaveOnboardingProfileRequest":{"type":"object","description":"Body for `POST /api/onboarding/profile` — the wizard's answers.","properties":{"catalogue_source":{"type":"string","description":"How they chose to fill the catalogue: `upload` | `quickbooks_pos` |\n`sample` | `later`. Recorded for follow-up routing; loading the sample\ncatalogue is still a separate explicit call.","nullable":true},"channels":{"type":"array","items":{"type":"string"},"description":"How the company sells: `pos` | `b2b` | `storefront` | `mobile_picking`."},"vertical":{"type":"string","description":"Which vertical they picked, when the question was shown.","nullable":true}}},"SavedListDetailResponse":{"type":"object","required":["list","items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SavedListItemResponse"}},"list":{"$ref":"#/components/schemas/SavedListResponse"}}},"SavedListItemResponse":{"type":"object","required":["id","list_id","product_id","quantity","sort_order"],"properties":{"id":{"type":"string"},"list_id":{"type":"string"},"product_id":{"type":"string"},"product_name":{"type":"string","nullable":true},"quantity":{"type":"integer","format":"int32"},"sort_order":{"type":"integer","format":"int32"}}},"SavedListResponse":{"type":"object","required":["id","account_id","name","item_count","created_at","updated_at"],"properties":{"account_id":{"type":"string"},"created_at":{"type":"string"},"id":{"type":"string"},"item_count":{"type":"integer","format":"int64"},"name":{"type":"string"},"updated_at":{"type":"string"}}},"SearchCategory":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}},"SearchChoice":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}},"SearchChoices":{"type":"object","required":["categories","products"],"properties":{"categories":{"type":"array","items":{"$ref":"#/components/schemas/SearchChoice"}},"products":{"type":"array","items":{"$ref":"#/components/schemas/SearchChoice"}}}},"SearchEvent":{"type":"object","required":["query"],"properties":{"product_id":{"type":"string","nullable":true},"query":{"type":"string"}}},"SearchProduct":{"type":"object","required":["id","sku","name","description","category","image","url","in_stock"],"properties":{"category":{"type":"string"},"category_id":{"type":"string","nullable":true},"description":{"type":"string"},"id":{"type":"string"},"image":{"type":"string"},"in_stock":{"type":"boolean"},"name":{"type":"string"},"price":{"type":"string","nullable":true},"sku":{"type":"string"},"url":{"type":"string"}}},"SearchStatistic":{"type":"object","required":["query","searches","clicks"],"properties":{"clicks":{"type":"integer","format":"int64"},"query":{"type":"string"},"searches":{"type":"integer","format":"int64"}}},"SectionStyle":{"type":"object","description":"Presentation knobs shared by every section (Shopify's \"section settings\"\npanel). Every field has a default so pre-builder sections, which stored no\n`style` at all, read back as the default look.","properties":{"background":{"type":"string","description":"One of: default | surface | primary | accent | custom."},"background_color":{"type":"string","description":"Hex colour, only read when `background == \"custom\"`."},"full_width":{"type":"boolean","description":"Break out of the page width (edge-to-edge backgrounds)."},"hidden":{"type":"boolean","description":"Hidden sections stay in the layout (and in the builder) but are not\nserved to shoppers — the builder's eye toggle."},"padding":{"type":"string","description":"One of: none | small | normal | large."},"text_align":{"type":"string","description":"One of: left | center | right."},"text_color":{"type":"string","description":"Hex colour override for the section's text. Empty = inherit."}}},"SecurityPolicySettings":{"type":"object","description":"Company login-lockout and MFA policy.\n\nPer-company security policy: login-lockout thresholds plus the MFA posture.\nValues fall back to the built-in defaults (which match the historical\nhardcoded constants in `features::auth::types`) when a tenant has never\nconfigured them, so existing companies keep the same behavior — and in\nparticular a company that has never touched these settings gets\n`MfaMode::Optional`, i.e. MFA changes nothing until somebody asks for it.","properties":{"allow_mfa_disable":{"type":"boolean","description":"Whether a user may turn their own MFA back off. Default true.\n\nIndependent of `mfa_mode` on purpose: a company can leave MFA optional\nbut still say \"once you've turned it on, only an admin can turn it off\",\nwhich is what stops an attacker who steals a live session from quietly\nremoving the second factor. Note that a user whose enrolment is *required*\nby `mfa_mode` can never self-disable regardless of this flag — that is\nenforced in `features::mfa::policy`."},"lockout_duration_minutes":{"type":"integer","format":"int64","description":"Length of the lockout window / lockout duration, in minutes. Default 15."},"max_failed_login_attempts":{"type":"integer","format":"int32","description":"Failed login attempts within the lockout window before the account is\ntemporarily locked. Default 5."},"mfa_mode":{"$ref":"#/components/schemas/MfaMode"},"require_mfa_for_sensitive_actions":{"type":"boolean","description":"Whether sensitive actions demand a recent re-authentication (step-up)\non top of a valid session. Default false."},"step_up_freshness_minutes":{"type":"integer","format":"int32","description":"How long a step-up re-authentication stays valid, in minutes. Default 15."}}},"SegmentDefinitionType":{"type":"string","description":"Mirrors the DB CHECK on `crm_segments.definition_type`.","enum":["static","dynamic"]},"SegmentPreviewMember":{"type":"object","required":["contact_id","first_name","last_name"],"properties":{"contact_id":{"$ref":"#/components/schemas/Uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"primary_email":{"type":"string","nullable":true}}},"SegmentPreviewResponse":{"type":"object","required":["segment_id","member_count","members"],"properties":{"member_count":{"type":"integer","format":"int64"},"members":{"type":"array","items":{"$ref":"#/components/schemas/SegmentPreviewMember"},"description":"First N members (contact id + name + email)."},"segment_id":{"$ref":"#/components/schemas/Uuid"}}},"SegmentRequest":{"type":"object","required":["name","definition_type"],"properties":{"definition_type":{"$ref":"#/components/schemas/SegmentDefinitionType"},"filter_json":{"description":"`ConditionTree` (typed filter, never freeform) — required when dynamic.","nullable":true},"name":{"type":"string"}}},"SegmentResponse":{"type":"object","required":["id","name","definition_type","is_active","member_count","created_at","updated_at"],"properties":{"created_at":{"type":"string"},"definition_type":{"type":"string"},"filter_json":{"nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"is_active":{"type":"boolean"},"member_count":{"type":"integer","format":"int64"},"name":{"type":"string"},"updated_at":{"type":"string"}}},"SendCampaignRequest":{"type":"object","required":["confirm_suppression_check_id"],"properties":{"confirm_suppression_check_id":{"$ref":"#/components/schemas/Uuid"}}},"SendCommandRequest":{"type":"object","required":["command_type"],"properties":{"command_type":{"type":"string"},"payload":{"nullable":true}}},"SendCourseInput":{"type":"object","required":["course"],"properties":{"course":{"type":"string"}}},"SendCourseResponse":{"type":"object","description":"`POST /api/horeca/open-orders/{id}/send-course` response. Mirrors\n`handlers::send_course`'s `json!({\"course\":.., \"items_sent\":..,\n\"items\":.., \"print_jobs_created\":..})` exactly (NOT a `{success,\nitems_sent}` pair — that shape does not match what the handler has\nalways actually emitted; see the handler doc for the field-by-field\ncitation).","required":["course","items_sent","items","print_jobs_created"],"properties":{"course":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/crate.models.horeca.OpenOrderItem"}},"items_sent":{"type":"integer","minimum":0},"print_jobs_created":{"type":"integer","minimum":0}}},"SendResetLinkResponse":{"type":"object","description":"Response returned when a reset link is generated (admin-only)","required":["reset_url","message"],"properties":{"message":{"type":"string"},"reset_url":{"type":"string"}}},"SendTestEmailRequest":{"type":"object","required":["to"],"properties":{"to":{"type":"string"}}},"SendTestEmailResponse":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}},"SetAgreementStatus":{"type":"object","required":["status","reason"],"properties":{"reason":{"type":"string"},"status":{"$ref":"#/components/schemas/AgreementStatus"}}},"SetContractOverrideRequest":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean","description":"`true` grants above the plan, `false` revokes below it. Both directions\nare needed — an override that could only add cannot model a revocation."},"limits":{"type":"object","nullable":true},"reason":{"type":"string","description":"Why this tenant deviates from their plan. Strongly encouraged: an\nunexplained override becomes permanent mystery state that nobody dares\nremove.","nullable":true}}},"SetIntegrationCredentialsRequest":{"type":"object","description":"Credentials are write-only: sealed with AES-256-GCM on PUT and only opened\nserver-side by the sync executor. No endpoint ever returns them.","required":["access_token"],"properties":{"access_token":{"type":"string"},"expires_at":{"type":"string","nullable":true},"extra":{"description":"Adapter-specific extras (e.g. Exact \"division\", QBO \"`realm_id`\",\nmock \"prefix\")."},"refresh_token":{"type":"string","nullable":true}}},"SetPlanFeatureRequest":{"type":"object","properties":{"enabled":{"type":"boolean","description":"`false` EXCLUDES the feature explicitly, which is different from omitting\nit: omitted falls back to the tier default, excluded does not.","nullable":true},"limits":{"type":"object","description":"Per-feature caps, e.g. `{\"max_users\": 10}`.","nullable":true}}},"SettleOpenOrderInput":{"type":"object","properties":{"apply_service_charge":{"type":"boolean","description":"Whether to add the merchant's configured service charge to this bill.\n\nThe client sends a **decision**, never an amount: the percentage is\napplied server-side to the server's own re-priced basket, so a till\ncannot invent a charge the merchant never configured, and the figure\non the pro-forma cannot drift a cent from the figure charged (the\nquote endpoint runs this same code, so what the cashier is shown and\nwhat is taken come from one place).\n\nAbsent or `false` = no service charge, which is today's behaviour.\n`true` while `billing.service_charge_percent` is unset or zero is a\nhard 422, not a silent no-op.","nullable":true},"approved_by_employee_id":{"type":"string","description":"The employee who authorised a step-up action on this settle — today\nonly waiving the service charge. Recorded on the audit row.\n\nOptional and purely additive; the terminal's own signed-in identity is\nalready on every audit row, and this is the *third* party (the manager\nwho walked over and entered a PIN) that the old shape had no room for.","nullable":true},"approved_by_name":{"type":"string","nullable":true},"expected_updated_at":{"type":"string","description":"Optimistic-concurrency guard: the `updated_at` (RFC3339) the client's\ncopy of the order was last read at, from whatever GET populated its\nUI (`get_open_order` / the KDS/POS-inbox feeds). `open_orders` has no\ndedicated version column, so this reuses the column that already\nchanges on every write to the row (item add/edit/void, course sent,\naccept/reject all bump it) — additive and optional: omitting it\nskips the check entirely, so older callers keep working unchanged.\nOn a mismatch the call fails with [`SettleError::OrderChanged`] (409)\ninstead of silently settling a total the operator's screen doesn't\nmatch, which the desktop settle UI can't otherwise fully guard\nagainst (a re-priced-from-DB total is still wrong if it re-prices a\n*stale* set of lines).","nullable":true},"item_ids":{"type":"array","items":{"type":"string"},"description":"Settle only these lines (split by items, and — since the till writes\n`seat_number` now — split by seat, which is the same operation over a\nfiltered set).\n\nEvery id must be an unsettled, non-voided line of this order. The\nlines are claimed with a single guarded `UPDATE ... WHERE\nsettled_receipt_id IS NULL` whose affected-row count must equal the\nrequested count, so two tills racing for the same line cannot both\ncharge for it: one wins, the other aborts.","nullable":true},"settlement_key":{"type":"string","description":"Client-generated key that makes THIS settle retry-safe (H8 §3).\n\nThe pre-split idempotency guard was \"one receipt per order\", enforced\nby `open_orders.receipt_id IS NULL` on the final UPDATE. A split bill\nmeans N receipts for one order, so that guard cannot survive — it is\nreplaced by a unique index on `(open_order_id, settlement_key)`. A\nretried request carrying the same key gets the FIRST receipt back\ninstead of charging the guest twice.\n\n**Required for any partial settle** (`item_ids` or `split` present),\nbecause a part that claims no items has no other race guard. Optional\nfor a plain full settle, which is still protected by claim-by-UPDATE\nover the order's unsettled lines — that is what keeps every existing\ncaller working unchanged.","nullable":true},"split":{"allOf":[{"$ref":"#/components/schemas/SettleSplitInput"}],"nullable":true},"tenders":{"type":"array","items":{"$ref":"#/components/schemas/SettleTenderInput"}},"tip_amount":{"type":"string","description":"Gratuity, as an absolute decimal string in the merchant's currency\n(H8 §2). Additive and optional: absent means \"no tip step was shown\",\nwhich is every existing caller, and produces a byte-identical total to\nbefore this field existed.\n\n`Some(\"0.00\")` is meaningfully different — it records that the till\noffered a tip and the guest declined, which a payroll report needs to\ndistinguish from \"never asked\".\n\nRejected if negative, unparseable, or above\n`horeca_ordering.billing.max_tip_percent` of the goods total.","nullable":true}}},"SettleOpenOrderResponse":{"type":"object","required":["receipt_id","open_order_id","total","subtotal","tax_amount","tax_lines","paid","change_due","rounding_adjustment","already_settled","tip_amount","service_charge_amount","order_fully_settled","remaining_total","remaining_total_with_service_charge"],"properties":{"already_settled":{"type":"boolean","description":"`true` when this call found an already-settled order and returned\nthe existing receipt instead of creating a new one (design §3.3\npoint 5 idempotency; also the path taken when a concurrent settle\ncall wins the race — see module docs)."},"change_due":{"type":"string"},"open_order_id":{"type":"string"},"order_fully_settled":{"type":"boolean","description":"`true` when no unsettled sellable line is left on the order, i.e. this\ncall closed the tab. `false` after a partial settle: the order stays\nopen with `payment_state = 'pending'` and the floor plan keeps showing\nthe table."},"paid":{"type":"string","description":"Sum of `input.tenders`, echoed back alongside `total` so the client\ncan render both sides of the tender without recomputing the sum\nitself."},"receipt_id":{"type":"string"},"receipt_lines":{"type":"array","items":{"$ref":"#/components/schemas/SettleReceiptLineResponse"},"description":"Persisted goods on THIS receipt, not the whole table. Equal shares\nhave no goods rows. Optional for compatibility with older servers.","nullable":true},"receipt_number":{"type":"string","nullable":true},"receipt_prior_payments":{"type":"array","items":{"$ref":"#/components/schemas/SettleReceiptPriorPaymentResponse"},"description":"Earlier equal-share receipts when this receipt pays the final whole-bill\nbalance. Goods rows then describe shared items, not another full charge.\nOptional for older servers; an empty list means no balance context.","nullable":true},"remaining_total":{"type":"string","description":"What is still owed on this tab after this call, as a decimal string.\n`\"0.00\"` when `order_fully_settled`. This is the figure the till puts\non the \"remaining\" line of a split bill.\n\nGOODS ONLY. On a tab with a service charge configured this is NOT what\nthe next guest hands over — see `remaining_total_with_service_charge`,\nwhich is."},"remaining_total_with_service_charge":{"type":"string","description":"What is still owed INCLUDING the service charge that would be taken on\nthe remaining lines — the figure a cashier is actually going to\ncollect, and the one to put on a \"still to pay\" banner.\n\nEqual to `remaining_total` when no service charge is configured, which\nis every tenant that has not switched one on. Additive: a client that\nhas never heard of this field keeps reading `remaining_total` and\nbehaves exactly as before."},"rounding_adjustment":{"type":"string","description":"See module docs — informational drift from cash-rounding, not an\nenforced adjustment to `total`."},"service_charge_amount":{"type":"string","description":"Service charge actually taken, in the same \"menu price\" basis the\npro-forma showed. `\"0.00\"` when none was applied."},"service_charge_label":{"type":"string","description":"The label that was printed for it, snapshotted from settings.","nullable":true},"settlement_key":{"type":"string","description":"The key this settlement is recorded under — echoed back so a client\nthat let the server generate one can retry with it (H8 §3).","nullable":true},"subtotal":{"type":"string"},"tax_amount":{"type":"string"},"tax_lines":{"type":"array","items":{"$ref":"#/components/schemas/SettleTaxLineResponse"},"description":"Per-rate breakdown for printing (design's EU-compliance requirement:\na single blended VAT line is not legal on a mixed-rate basket)."},"tip_amount":{"type":"string","description":"Gratuity actually recorded on this receipt (H8 §2). `\"0.00\"` when the\ncaller sent none — the response is a flat money shape, unlike the\nnullable column, because a client rendering a receipt has no use for\nthe \"never asked\" / \"declined\" distinction a report needs."},"total":{"type":"string","description":"Authoritative, server-computed total actually charged — always\nre-priced from the DB (see module docs), never echoed from the\nclient's request. This is what the client should display as \"what\nwas charged\", not whatever total its own (possibly stale) local\ncart/order state computed."}}},"SettleReceiptLineResponse":{"type":"object","required":["product_id","name","sku","quantity","unit_price","total","modifiers"],"properties":{"modifiers":{"type":"array","items":{"$ref":"#/components/schemas/SettleReceiptModifierResponse"}},"name":{"type":"string"},"product_id":{"type":"string"},"quantity":{"type":"integer","format":"int32"},"sku":{"type":"string"},"total":{"type":"string"},"unit_price":{"type":"string"}}},"SettleReceiptModifierResponse":{"type":"object","required":["name","price_delta"],"properties":{"name":{"type":"string"},"price_delta":{"type":"string"}}},"SettleReceiptPriorPaymentResponse":{"type":"object","required":["receipt_id","total"],"properties":{"receipt_id":{"type":"string"},"receipt_number":{"type":"string","nullable":true},"total":{"type":"string","description":"Actual earlier receipt total, including any gratuity and cash rounding."}}},"SettleSplitInput":{"type":"object","description":"\"Split this bill N ways, and this call is part `part_index` of it.\"\n\nEqual parts is genuinely different from splitting by items: it does not\npartition the basket, so it cannot be faked by handing each part a subset\nof lines — the arithmetic would not land on the same total. It is one\norder-level amount divided N ways, with the remainder cents assigned\ndeterministically, and the LAST part reconciles against what the earlier\nparts actually paid so the sum is exact to the cent even if the tab\nchanged in between.","required":["parts","part_index"],"properties":{"part_index":{"type":"integer","format":"int32","description":"Which part this call pays, zero-based.\n\n**Advisory since 2026-09-04.** The server DERIVES the outstanding part\nfrom how many equal-part settlements the tab already carries\n(`resolve_equal_part`) and uses that instead. It is still validated —\nan out-of-range value is a broken client and is refused before any\nwrite — but a well-formed value that disagrees with the tab's actual\nprogress is ignored rather than obeyed.\n\nWhy: the desktop till sends `0` on every tap, by design (\"always pay\nthe FIRST outstanding part\"). With the client's value obeyed, a 2-way\nsplit paid twice as part 0 meant nobody ever reached `parts - 1`, so no\nline was claimed, the order never completed, and the table could be\ncharged again. Deriving it is the only version of this that is safe\nagainst a client that cannot know the answer.\n\nThe part that lands on `parts - 1` claims the order's remaining lines,\ncloses the tab, and charges whatever is left rather than a computed\nshare."},"parts":{"type":"integer","format":"int32","description":"How many ways the bill is being split. 2..=50."}}},"SettleTaxLineResponse":{"type":"object","description":"Per-rate net/tax/gross breakdown, mirroring the desktop's\n`TaxBreakdownLine` (`frontend/apps/desktop/app/types/index.ts`) exactly —\n`rate_percent` here, NOT the fraction `pos_receipt_tax_lines.tax_rate`\nstores, so the desktop printer (`ReceiptPrintData.taxLines`) can consume\nthis response directly with no unit conversion.","required":["rate_percent","net","tax","gross"],"properties":{"gross":{"type":"string"},"net":{"type":"string"},"rate_percent":{"type":"string"},"tax":{"type":"string"}}},"SettleTenderInput":{"type":"object","required":["method","amount"],"properties":{"amount":{"type":"string"},"amount_tendered":{"type":"string","description":"What the customer physically handed over, if different from\n`amount` (e.g. a cash tender where change is due). Defaults to\n`amount` when absent.","nullable":true},"card_last4":{"type":"string","nullable":true},"change_given":{"type":"string","nullable":true},"gift_card_id":{"type":"string","nullable":true},"method":{"type":"string","description":"cash | card | `gift_card` | `on_account` — matched case-sensitively\nagainst the literal `\"cash\"` only for cash-rounding purposes and\n`\"gift_card\"` for redemption purposes; not otherwise validated\nagainst an enum, mirroring `pos_receipt_tenders.method`'s own\nfree-form `VARCHAR(50)`."},"reference":{"type":"string","nullable":true}}},"ShipmentActionResponse":{"type":"object","description":"Shared response shape for all three delivery-action endpoints.","required":["order_id","shipment_id","shipment_status","delivery_status","order_status"],"properties":{"assigned_to":{"type":"string","nullable":true},"delivered_at":{"type":"string","nullable":true},"delivery_status":{"type":"string","description":"The UI-facing field on the order (`pending` | `assigned` |\n`out_for_delivery` | `delivered`) — what\n`SalesOrderDetailPage.tsx` actually branches its buttons on."},"notes":{"type":"string","nullable":true},"order_id":{"type":"string"},"order_status":{"type":"string"},"pod_url":{"type":"string","nullable":true},"shipment_id":{"type":"string"},"shipment_status":{"type":"string","description":"Shipment's own internal status (`packing` | `shipped` |\n`out_for_delivery` | `delivered` | `failed`)."}}},"SignupRequest":{"type":"object","description":"User signup request","required":["username","email","first_name","last_name","password","company_name","turnstile_token"],"properties":{"commercial_interest":{"allOf":[{"$ref":"#/components/schemas/crate.features.billing.interest.CommercialInterest"}],"nullable":true},"company_name":{"type":"string"},"email":{"type":"string"},"first_name":{"type":"string"},"last_name":{"type":"string"},"password":{"type":"string"},"turnstile_token":{"type":"string"},"username":{"type":"string"},"utm_campaign":{"type":"string","nullable":true},"utm_medium":{"type":"string","nullable":true},"utm_source":{"type":"string","nullable":true}}},"SignupResponse":{"type":"object","description":"Signup response","required":["message","user_id","username","email"],"properties":{"email":{"type":"string"},"message":{"type":"string"},"user_id":{"type":"integer","format":"int32"},"username":{"type":"string"}}},"SimilarProductItem":{"type":"object","description":"One semantically-similar product (pgvector cosine neighbour).","required":["product_id","name","score"],"properties":{"name":{"type":"string"},"product_id":{"type":"string"},"product_type":{"type":"string","nullable":true},"score":{"type":"number","format":"double","description":"Cosine similarity in [0,1]; 1 = identical."}}},"SimilarProductsQuery":{"type":"object","description":"Query for the semantic-similarity endpoint.","properties":{"limit":{"type":"integer","format":"int64","description":"Max neighbours to return (1-100, default 10).","nullable":true}}},"SimilarProductsResponse":{"type":"object","required":["items","embeddings_enabled"],"properties":{"embeddings_enabled":{"type":"boolean","description":"False when embeddings are disabled for the tenant (items will be empty)."},"items":{"type":"array","items":{"$ref":"#/components/schemas/SimilarProductItem"}}}},"SimpleMessage":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}},"SkuDisplayMode":{"type":"string","enum":["none","code","item_number","both"]},"SlaPolicyRequest":{"type":"object","required":["name","priority","first_response_minutes","resolution_minutes"],"properties":{"first_response_minutes":{"type":"integer","format":"int32","description":"Flat minutes, no business-hours calendar in v1."},"is_default":{"type":"boolean"},"name":{"type":"string"},"priority":{"$ref":"#/components/schemas/CasePriority"},"resolution_minutes":{"type":"integer","format":"int32"}}},"SlaPolicyResponse":{"type":"object","required":["id","name","priority","first_response_minutes","resolution_minutes","is_default","created_at","updated_at"],"properties":{"created_at":{"type":"string"},"first_response_minutes":{"type":"integer","format":"int32"},"id":{"$ref":"#/components/schemas/Uuid"},"is_default":{"type":"boolean"},"name":{"type":"string"},"priority":{"type":"string"},"resolution_minutes":{"type":"integer","format":"int32"},"updated_at":{"type":"string"}}},"SlideshowSlide":{"type":"object","description":"One slide of a `slideshow` section.","properties":{"cta_link":{"type":"string","description":"Destination of the call-to-action link."},"cta_text":{"type":"string","description":"Label of the call-to-action link."},"id":{"type":"string","description":"Stable identifier of this configured entry."},"image_url":{"type":"string","description":"URL of the image displayed with this content."},"subtitle":{"type":"string","description":"Supporting text displayed below the heading."},"title":{"type":"string","description":"Display heading for this content."}}},"SortDirection":{"type":"string","enum":["asc","desc"]},"SpeakerSound":{"type":"string","enum":["music_box","cash_register","subscription_ding"]},"SpendGroupBy":{"type":"string","description":"How the purchasing-spend report is grouped.","enum":["supplier","product","month"]},"StaffRosterEntry":{"type":"object","description":"One row of the POS staff roster.\n\nDeliberately NOT `UserListResponse` minus a few fields — a separate type, so\nthat adding a field to the admin's user list cannot leak it to every till by\naccident. `username`, `date_joined` and `last_login` are absent on purpose:\na terminal maps name, email, role and active flag and needs nothing more.","required":["id","first_name","last_name","email","role","is_active","is_staff","is_superuser","permissions"],"properties":{"display_name":{"type":"string","description":"Human-readable identity, including username-only accounts.","nullable":true},"email":{"type":"string","description":"Kept because the terminal falls back to the email as a display name when\na user has neither first nor last name set."},"first_name":{"type":"string"},"id":{"type":"integer","format":"int32"},"is_active":{"type":"boolean","description":"Inactive users ARE included, unlike in the admin list's usual use — a\nterminal has to learn that someone was deactivated in order to revoke\ntheir local PIN. Dropping them is what let a departed employee's local\nrow keep `active = 1` and its `pin_hash` indefinitely."},"is_staff":{"type":"boolean","description":"Legacy flags, still sent so a terminal older than the `role` mapping\nkeeps resolving roles the way it always did."},"is_superuser":{"type":"boolean"},"last_name":{"type":"string"},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"},"description":"The user's EFFECTIVE permissions (role defaults, or their custom\noverride), which is what the terminal derives its local role from."},"role":{"$ref":"#/components/schemas/UserRole"}}},"StaffRosterEnvelope":{"type":"object","required":["results","count"],"properties":{"count":{"type":"integer","minimum":0},"results":{"type":"array","items":{"$ref":"#/components/schemas/StaffRosterEntry"}}}},"StartPickingRequest":{"type":"object","properties":{"notes":{"type":"string","nullable":true}}},"StepUpRequest":{"type":"object","description":"Re-authenticate to unlock sensitive actions.","required":["password"],"properties":{"code":{"type":"string","description":"TOTP code — required when the user has MFA enabled.","nullable":true},"password":{"type":"string","description":"The account password. Always required — step-up is about proving the\nperson at the keyboard is still the account holder, and a stolen session\ncookie alone must not be enough."},"recovery_code":{"type":"string","description":"Recovery code — alternative to TOTP.","nullable":true}}},"StepUpResponse":{"type":"object","required":["message","success","expires_at"],"properties":{"expires_at":{"type":"string","description":"When the granted window lapses, ISO-8601."},"message":{"type":"string"},"success":{"type":"boolean"}}},"StockLevelResponse":{"type":"object","description":"Stock Level DTO","required":["quantity","reserved"],"properties":{"location_id":{"type":"string","nullable":true},"quantity":{"type":"integer","format":"int32"},"reserved":{"type":"integer","format":"int32"}}},"StockMovementListResponse":{"type":"object","required":["items","total"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/StockMovementRow"}},"total":{"type":"integer","format":"int64","description":"Total rows matching the filter, for the pager."}}},"StockMovementRow":{"type":"object","required":["id","created_at","product_id","location_id","quantity_change","reason","actor_id"],"properties":{"actor_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/DateTime"},"id":{"type":"integer","format":"int64"},"location_id":{"type":"string"},"new_quantity":{"type":"integer","format":"int32","nullable":true},"product_id":{"type":"string"},"quantity_change":{"type":"integer","format":"int32"},"reason":{"type":"string"},"reference_id":{"type":"string","nullable":true},"unit_cost":{"type":"string","nullable":true},"value_change":{"type":"string","nullable":true}}},"StockTransferListResponse":{"type":"object","required":["results","total","limit","offset"],"properties":{"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/StockTransferResponse"}},"total":{"type":"integer","format":"int64"}}},"StockTransferResponse":{"type":"object","required":["id","transfer_number","from_location_id","to_location_id","product_id","quantity","status","created_at","updated_at","version"],"properties":{"approved_at":{"type":"string","nullable":true},"cancel_reason":{"type":"string","nullable":true},"cancelled_at":{"type":"string","nullable":true},"completed_at":{"type":"string","nullable":true},"created_at":{"type":"string"},"from_location_id":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"notes":{"type":"string","nullable":true},"product_id":{"type":"string"},"quantity":{"type":"integer","format":"int32"},"shipped_at":{"type":"string","nullable":true},"status":{"type":"string"},"to_location_id":{"type":"string"},"transfer_number":{"type":"string"},"transferred_unit_cost":{"type":"string","nullable":true},"transferred_value":{"type":"string","nullable":true},"updated_at":{"type":"string"},"variant_id":{"type":"string","nullable":true},"version":{"type":"integer","format":"int32"}}},"StockTransferTransitionRequest":{"type":"object","required":["version"],"properties":{"reason":{"type":"string","nullable":true},"version":{"type":"integer","format":"int32"}}},"StorefrontAnalytics":{"type":"object","description":"Third-party analytics ids injected by the store app (empty = not loaded).","properties":{"ga4_measurement_id":{"type":"string","description":"Google Analytics 4 measurement id, e.g. \"G-XXXXXXX\"."},"meta_pixel_id":{"type":"string","description":"Meta (Facebook) pixel id — digits only."}}},"StorefrontAnnouncement":{"type":"object","required":["enabled","text"],"properties":{"enabled":{"type":"boolean"},"text":{"type":"string"}}},"StorefrontCatalogSettings":{"type":"object","description":"Product listing and stock availability policy for the storefront.","properties":{"allow_out_of_stock_orders":{"type":"boolean","description":"Whether checkout may accept products without available stock."},"default_sort":{"type":"string","description":"One of: `name_asc` | `name_desc` | `price_asc` | `price_desc` | newest."},"products_per_page":{"type":"integer","format":"int32","description":"Number of products displayed on each listing page."},"show_out_of_stock":{"type":"boolean","description":"Whether to include products without stock in listings."}}},"StorefrontCategorySummary":{"type":"object","required":["id","name","product_count"],"properties":{"id":{"type":"string"},"image_url":{"type":"string","nullable":true},"name":{"type":"string"},"parent_id":{"type":"string","nullable":true},"product_count":{"type":"integer","format":"int64"}}},"StorefrontCheckoutCustomer":{"type":"object","required":["name","email"],"properties":{"email":{"type":"string"},"name":{"type":"string"},"phone":{"type":"string","nullable":true}}},"StorefrontCheckoutItem":{"type":"object","required":["product_id","quantity"],"properties":{"product_id":{"type":"string"},"quantity":{"type":"integer","format":"int32"},"variant_id":{"type":"string","description":"Chosen variant of `product_id` (must belong to it); omit for a plain\nproduct.","nullable":true}}},"StorefrontCheckoutPublic":{"type":"object","description":"Checkout options the store app needs to render the form. Mirrors\n`StorefrontCheckoutSettings` minus anything staff-only (there is none, but\nthe public type keeps the contract independent of the settings shape).","required":["allow_order_note","require_phone","terms_text","terms_required","thank_you_text","allow_discount_codes"],"properties":{"allow_discount_codes":{"type":"boolean","description":"True when the tenant has at least one enabled discount code AND allows\ncodes at checkout — the store only shows the code field then."},"allow_order_note":{"type":"boolean"},"require_phone":{"type":"boolean"},"terms_required":{"type":"boolean"},"terms_text":{"type":"string"},"thank_you_text":{"type":"string"}}},"StorefrontCheckoutRequest":{"type":"object","required":["items","customer","shipping_address","delivery","payment_method"],"properties":{"accepted_terms":{"type":"boolean","description":"Echo of the terms checkbox when `checkout.terms_required`.","nullable":true},"customer":{"$ref":"#/components/schemas/StorefrontCheckoutCustomer"},"delivery":{"$ref":"#/components/schemas/StorefrontDeliveryRequest"},"discount_code":{"type":"string","description":"Optional discount code (see `StorefrontSettings::discounts`). Unknown,\nexpired or ineligible codes are rejected with 400, never ignored.","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontCheckoutItem"}},"note":{"type":"string","nullable":true},"payment_method":{"type":"string","description":"\"manual\" | \"stripe\""},"shipping_address":{"$ref":"#/components/schemas/StorefrontShippingAddressRequest"}}},"StorefrontCheckoutResponse":{"type":"object","required":["order_token","payment"],"properties":{"order_number":{"type":"string","nullable":true},"order_token":{"type":"string"},"payment":{"$ref":"#/components/schemas/StorefrontPaymentResult"}}},"StorefrontCheckoutSettings":{"type":"object","description":"Checkout template settings (what the checkout form asks for and says).","properties":{"allow_discount_codes":{"type":"boolean","description":"Let shoppers enter a discount code (see `StorefrontSettings::discounts`)."},"allow_order_note":{"type":"boolean","description":"Whether customers can add a note to their order."},"prices_include_tax":{"type":"boolean","description":"Store prices already include tax (VAT/OB): the company `tax_rate` is\nshown as an \"includes …\" line and never changes the total. `false` =\ntax is added on top at checkout (sales-tax style)."},"require_phone":{"type":"boolean","description":"Whether checkout requires a contact telephone number."},"show_tax_line":{"type":"boolean","description":"Show the tax line in the cart, checkout, order page and emails at all\n(only meaningful when the company has a `tax_rate`)."},"terms_required":{"type":"boolean","description":"Whether checkout requires acceptance of the store terms."},"terms_text":{"type":"string","description":"Rich text shown next to the terms checkbox (empty = no checkbox)."},"thank_you_text":{"type":"string","description":"Rich text on the order-status page after a successful checkout."}}},"StorefrontCollectionPageSettings":{"type":"object","description":"Collection / catalogue listing template settings.","properties":{"banner_image_url":{"type":"string","description":"URL of the collection banner image."},"columns":{"type":"integer","format":"int32","description":"Products per row on desktop (2..=5)."},"intro_text":{"type":"string","description":"Introductory copy displayed above the product listing."},"show_filters":{"type":"boolean","description":"Whether to display collection filtering controls."},"show_in_stock_filter":{"type":"boolean","description":"Whether to offer a filter for products in stock."},"show_search":{"type":"boolean","description":"Whether to display collection search."},"show_sort":{"type":"boolean","description":"Whether to display product sorting controls."}}},"StorefrontConfigResponse":{"type":"object","required":["store_name","theme","sections","currency","payment_methods","shipping_methods","pickup_enabled","pickup_label","seo","announcement","navigation","footer","product_page","collection_page","checkout","pages","policies","contact","analytics","cookie_banner","store_url","products_per_page","default_sort"],"properties":{"analytics":{"$ref":"#/components/schemas/StorefrontAnalytics"},"announcement":{"$ref":"#/components/schemas/StorefrontAnnouncement"},"checkout":{"$ref":"#/components/schemas/StorefrontCheckoutPublic"},"collection_page":{"$ref":"#/components/schemas/StorefrontCollectionPageSettings"},"contact":{"$ref":"#/components/schemas/StorefrontContactInfo"},"cookie_banner":{"$ref":"#/components/schemas/StorefrontCookieBanner"},"currency":{"type":"string"},"default_sort":{"type":"string","description":"Default listing sort (`catalog.default_sort`)."},"footer":{"$ref":"#/components/schemas/StorefrontFooter"},"free_shipping_threshold":{"type":"string","nullable":true},"navigation":{"$ref":"#/components/schemas/StorefrontNavigation"},"pages":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontPageSummary"},"description":"Published custom pages, in navigation order."},"payment_methods":{"$ref":"#/components/schemas/StorefrontPaymentMethodsPublic"},"pickup_enabled":{"type":"boolean"},"pickup_label":{"type":"string"},"policies":{"$ref":"#/components/schemas/StorefrontPolicies"},"product_page":{"$ref":"#/components/schemas/StorefrontProductPageSettings"},"products_per_page":{"type":"integer","format":"int32","description":"Products per page for the catalogue listing (`catalog.products_per_page`)."},"sections":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontSectionResponse"}},"seo":{"$ref":"#/components/schemas/StorefrontSeoSettings"},"shipping_methods":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontShippingMethodPublic"}},"store_name":{"type":"string"},"store_url":{"type":"string","description":"Canonical public URL of this store (from `STORE_PUBLIC_URL_TEMPLATE`)."},"theme":{"$ref":"#/components/schemas/StorefrontTheme"}}},"StorefrontConfirmPaymentRequest":{"type":"object","required":["session_id"],"properties":{"session_id":{"type":"string"}}},"StorefrontContactInfo":{"type":"object","description":"Physical store / contact details reused by the `store_info` section, the\nfooter and the contact page.","properties":{"address_line1":{"type":"string","description":"First line of the business address."},"address_line2":{"type":"string","description":"Additional business address details."},"city":{"type":"string","description":"City of the business address."},"country":{"type":"string","description":"Country of the business address."},"email":{"type":"string","description":"Business contact email address."},"hours":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontOpeningHours"},"description":"Opening-hours entries displayed in their configured order."},"phone":{"type":"string","description":"Business contact telephone number."},"postal_code":{"type":"string","description":"Postal code of the business address."}}},"StorefrontContactRequest":{"type":"object","required":["name","email","message"],"properties":{"email":{"type":"string"},"message":{"type":"string"},"name":{"type":"string"},"phone":{"type":"string","nullable":true},"website":{"type":"string","description":"Honeypot — see `StorefrontNewsletterRequest::website`.","nullable":true}}},"StorefrontCookieBanner":{"type":"object","description":"Cookie / consent banner. Analytics scripts only load after acceptance.","properties":{"accept_label":{"type":"string","description":"Label for accepting optional cookies."},"decline_label":{"type":"string","description":"Label for declining optional cookies."},"enabled":{"type":"boolean","description":"Whether the storefront displays its cookie preference banner."},"link":{"type":"string","description":"Link to the privacy policy (`/policies/privacy` by default when empty\nand a privacy policy exists)."},"text":{"type":"string","description":"Message displayed in the cookie preference banner."}}},"StorefrontDeliveryRequest":{"type":"object","required":["method"],"properties":{"method":{"type":"string","description":"\"shipping\" | \"pickup\""},"shipping_method_id":{"type":"string","nullable":true}}},"StorefrontDiscountCode":{"type":"object","description":"A checkout discount code. Codes are matched case-insensitively; the\nstorefront checkout applies at most one code per order, server-side.","properties":{"code":{"type":"string","description":"Code entered by the customer at checkout."},"enabled":{"type":"boolean","description":"Whether this discount code may be used."},"ends_at":{"type":"string","description":"Optional end of the discount validity window, as an RFC 3339 timestamp.","nullable":true},"id":{"type":"string","description":"Stable identifier of the discount configuration."},"kind":{"type":"string","description":"One of: percentage | fixed | `free_shipping`."},"min_subtotal":{"type":"string","description":"Decimal string; `None` = no minimum.","nullable":true},"starts_at":{"type":"string","description":"RFC 3339 window; `None` = open-ended.","nullable":true},"usage_limit":{"type":"integer","format":"int32","description":"Maximum redemptions across all orders; `None` = unlimited.","nullable":true},"value":{"type":"string","description":"Decimal string: percent for `percentage`, amount for `fixed`, ignored\nfor `free_shipping`."}}},"StorefrontFooter":{"type":"object","description":"Content and visibility preferences for the storefront footer.","properties":{"copyright_text":{"type":"string","description":"Empty = \"© {year} {store name}\"."},"newsletter_title":{"type":"string","description":"Heading displayed above the footer newsletter signup."},"show_language_switcher":{"type":"boolean","description":"Whether to display the footer language selector."},"show_newsletter":{"type":"boolean","description":"Whether the footer displays the newsletter signup."},"show_payment_icons":{"type":"boolean","description":"Whether to display payment method icons."},"show_powered_by":{"type":"boolean","description":"Whether to display the platform attribution."},"social":{"$ref":"#/components/schemas/StorefrontSocialLinks"},"text":{"type":"string","description":"Short blurb under the store name (rich text / markdown)."}}},"StorefrontFormResponse":{"type":"object","required":["ok","message"],"properties":{"message":{"type":"string"},"ok":{"type":"boolean"}}},"StorefrontLayout":{"type":"object","description":"Ordered homepage sections plus everything else that shapes the site:\ncustom pages, menus, footer and the product / collection templates.","properties":{"collection_page":{"$ref":"#/components/schemas/StorefrontCollectionPageSettings"},"footer":{"$ref":"#/components/schemas/StorefrontFooter"},"navigation":{"$ref":"#/components/schemas/StorefrontNavigation"},"pages":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontPage"},"description":"Additional storefront content pages."},"product_page":{"$ref":"#/components/schemas/StorefrontProductPageSettings"},"sections":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontSection"},"description":"Homepage sections displayed in their configured order."}}},"StorefrontMenuChild":{"type":"object","description":"A nested menu entry (second level only — no deeper nesting).","properties":{"id":{"type":"string","description":"Stable identifier of this configured entry."},"label":{"type":"string","description":"Text displayed for this navigation link."},"link":{"type":"string","description":"Store-relative path (`/products`, `/categories/{id}`, `/pages/about`)\nor an absolute http(s) URL."}}},"StorefrontMenuItem":{"type":"object","description":"A top-level menu entry in the header or footer navigation.","properties":{"children":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontMenuChild"},"description":"Nested navigation links displayed below this menu item."},"id":{"type":"string","description":"Stable identifier of this configured entry."},"label":{"type":"string","description":"Text displayed for this navigation link."},"link":{"type":"string","description":"Destination opened when this entry is selected."}}},"StorefrontNavigation":{"type":"object","description":"Header and footer menus. Empty header = the store's built-in default\n(Home, Shop, top-level categories).","properties":{"footer":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontMenuItem"},"description":"Navigation items displayed in the storefront footer."},"header":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontMenuItem"},"description":"Navigation items displayed in the storefront header."}}},"StorefrontNewsletterRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string"},"name":{"type":"string","nullable":true},"website":{"type":"string","description":"Honeypot — must be empty. Bots that fill every field are dropped\nsilently with a 200 so they cannot tell.","nullable":true}}},"StorefrontOpeningHours":{"type":"object","description":"One opening-hours row (\"Monday\", \"09:00 – 18:00\").","properties":{"day":{"type":"string","description":"Day label for this opening-hours entry."},"hours":{"type":"string","description":"Opening-hours text displayed for this day."},"id":{"type":"string","description":"Stable identifier of the opening-hours entry."}}},"StorefrontOrderLineDto":{"type":"object","required":["product_id","name","qty","price","total"],"properties":{"name":{"type":"string"},"price":{"type":"string"},"product_id":{"type":"string"},"qty":{"type":"integer","format":"int32"},"total":{"type":"string"},"variant_label":{"type":"string","description":"Variant sold on this line (\"M / Blue\"), when the product has variants.","nullable":true}}},"StorefrontOrderStatusResponse":{"type":"object","required":["status","payment_status","lines","subtotal","shipping","total","created_at"],"properties":{"carrier":{"type":"string","nullable":true},"created_at":{"type":"string"},"customer_name":{"type":"string","nullable":true},"delivery_method":{"type":"string","description":"\"shipping\" | \"pickup\" as chosen at checkout.","nullable":true},"delivery_status":{"type":"string","description":"\"pending\" | \"assigned\" | \"out_for_delivery\" | \"delivered\" (or null).","nullable":true},"discount":{"type":"string","description":"Discount applied at checkout (\"0\" when none)."},"discount_code":{"type":"string"},"estimated_delivery":{"type":"string","description":"\"YYYY-MM-DD\" when the merchant set one.","nullable":true},"lines":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontOrderLineDto"}},"order_number":{"type":"string","nullable":true},"payment_status":{"type":"string","description":"\"\" | \"manual\" | \"pending\" | \"paid\""},"prices_include_tax":{"type":"boolean"},"refund_total":{"type":"string","description":"Amount refunded so far (\"0\" when none); `payment_status` becomes\n\"partially_refunded\" / \"refunded\"."},"shipping":{"type":"string"},"shipping_address":{"description":"The address entered at checkout (line1, line2, city, postal_code,\ncountry) so the buyer can double-check it.","nullable":true},"status":{"type":"string"},"subtotal":{"type":"string"},"tax":{"type":"string","description":"Tax amount recorded on the order (\"0\" when none)."},"tax_rate":{"type":"number","format":"double","nullable":true},"total":{"type":"string"},"tracking_code":{"type":"string","nullable":true}}},"StorefrontPage":{"type":"object","description":"A custom storefront page (`/pages/{handle}`) built from sections — About,\nContact, FAQ, Size guide, …","properties":{"handle":{"type":"string","description":"URL handle: lowercase alphanumeric + hyphens, unique per store."},"id":{"type":"string","description":"Stable identifier of this configured entry."},"published":{"type":"boolean","description":"Unpublished pages 404 on the store but stay editable in the builder."},"sections":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontSection"},"description":"Page sections displayed in their configured order."},"seo_description":{"type":"string","description":"Page description override for search metadata."},"seo_title":{"type":"string","description":"Page title override for search metadata."},"title":{"type":"string","description":"Display heading for this content."}}},"StorefrontPageResponse":{"type":"object","description":"A custom page with its sections denormalised, ready to render.","required":["handle","title","seo_title","seo_description","sections"],"properties":{"handle":{"type":"string"},"sections":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontSectionResponse"}},"seo_description":{"type":"string"},"seo_title":{"type":"string"},"title":{"type":"string"}}},"StorefrontPageSummary":{"type":"object","description":"A custom page as listed in the navigation (content comes from\n`GET /api/storefront/{slug}/pages/{handle}`).","required":["handle","title"],"properties":{"handle":{"type":"string"},"title":{"type":"string"}}},"StorefrontPaymentMethodsPublic":{"type":"object","required":["manual_enabled","manual_label","stripe_enabled","stripe_publishable_key"],"properties":{"manual_enabled":{"type":"boolean"},"manual_label":{"type":"string"},"stripe_enabled":{"type":"boolean"},"stripe_publishable_key":{"type":"string","description":"Publishable key only — the secret key never leaves the server."}}},"StorefrontPaymentResult":{"oneOf":[{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["none"]}}},{"type":"object","required":["url","type"],"properties":{"type":{"type":"string","enum":["stripe_redirect"]},"url":{"type":"string"}}}],"discriminator":{"propertyName":"type"}},"StorefrontPaymentSettings":{"type":"object","description":"Checkout payment methods and integration credentials.","properties":{"manual_enabled":{"type":"boolean","description":"Whether to offer the manual payment option at checkout."},"manual_label":{"type":"string","description":"Customer-facing label for the manual payment option."},"stripe_enabled":{"type":"boolean","description":"Whether the storefront Stripe payment integration is enabled."},"stripe_publishable_key":{"type":"string","description":"Public Stripe key used by the checkout integration."},"stripe_secret_key":{"type":"string","description":"WRITE-ONLY — `CompanySettings::mask_sensitive_data` replaces this with\nthe `***configured***` sentinel before settings ever leave the server.\nThe update path preserves the stored value when the client echoes the\nsentinel back unchanged, or sends blank."},"stripe_webhook_secret":{"type":"string","description":"WRITE-ONLY like the secret key. Signing secret of the Stripe webhook\nendpoint `POST /api/storefront/{slug}/stripe/webhook` (`whsec_…`);\nempty = webhook disabled, payment is confirmed on the return URL only."}}},"StorefrontPolicies":{"type":"object","description":"Legal pages served at `/policies/{kind}` (rich text / markdown; empty =\npage hidden and not linked from the footer).","properties":{"privacy":{"type":"string","description":"Privacy policy content displayed by the storefront."},"refund":{"type":"string","description":"Refund policy content displayed by the storefront."},"shipping":{"type":"string","description":"Shipping policy content displayed by the storefront."},"terms":{"type":"string","description":"Terms and conditions content displayed by the storefront."}}},"StorefrontProductDetail":{"type":"object","required":["id","name","description","price","images","in_stock"],"properties":{"category_id":{"type":"string","nullable":true},"description":{"type":"string"},"id":{"type":"string"},"images":{"type":"array","items":{"type":"string"}},"in_stock":{"type":"boolean"},"name":{"type":"string"},"price":{"type":"string"},"related":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontProductSummary"},"description":"Products from the same category, for the \"You may also like\" row.\nEmpty when `product_page.show_related` is off or nothing else sells."},"sku":{"type":"string","description":"Merchant SKU / item number (shown when `product_page.show_sku`)."},"variants":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontVariantDto"},"description":"Purchasable variants (size / colour …). Empty = the product itself is\nthe only option. Stock is tracked on the product, not per variant."}}},"StorefrontProductPageSettings":{"type":"object","description":"Product detail page template settings.","properties":{"image_layout":{"type":"string","description":"One of: carousel | grid | stacked."},"related_title":{"type":"string","description":"Heading above the related products."},"show_related":{"type":"boolean","description":"Whether to display related products."},"show_share":{"type":"boolean","description":"Whether to display product sharing controls."},"show_sku":{"type":"boolean","description":"Whether to display the product SKU."},"show_stock":{"type":"boolean","description":"Whether to display stock information."},"sticky_add_to_cart":{"type":"boolean","description":"Whether the add-to-cart control stays visible while scrolling."},"trust_text":{"type":"string","description":"Rich text shown under every product (shipping promise, returns)."}}},"StorefrontProductSummary":{"type":"object","required":["id","name","description","price","in_stock"],"properties":{"category_id":{"type":"string","nullable":true},"description":{"type":"string"},"id":{"type":"string"},"image_url":{"type":"string","nullable":true},"in_stock":{"type":"boolean"},"name":{"type":"string"},"price":{"type":"string","description":"Decimal string, e.g. \"19.99\"."}}},"StorefrontProductsResponse":{"type":"object","required":["results","count","page","page_size"],"properties":{"count":{"type":"integer","format":"int64"},"page":{"type":"integer","format":"int32"},"page_size":{"type":"integer","format":"int32"},"results":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontProductSummary"}}}},"StorefrontQuoteRequest":{"type":"object","description":"Preview of totals for a cart + delivery choice + optional discount code,\ncomputed server-side with the same rules as checkout.","required":["items","delivery"],"properties":{"delivery":{"$ref":"#/components/schemas/StorefrontDeliveryRequest"},"discount_code":{"type":"string","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontCheckoutItem"}}}},"StorefrontQuoteResponse":{"type":"object","required":["subtotal","shipping","discount","total","discount_code","discount_error"],"properties":{"discount":{"type":"string"},"discount_code":{"type":"string","description":"Echo of the applied code (normalised) or empty."},"discount_error":{"type":"string","description":"Human-readable reason when the code was rejected (empty = applied or\nno code given)."},"prices_include_tax":{"type":"boolean"},"shipping":{"type":"string"},"subtotal":{"type":"string"},"tax":{"type":"string","description":"Tax amount (\"0\" when the company has no tax rate or the store hides\nit). Included in `total` when `prices_include_tax`, added otherwise."},"tax_rate":{"type":"number","format":"double","description":"Company tax rate in percent, when one applies.","nullable":true},"total":{"type":"string"}}},"StorefrontSection":{"oneOf":[{"type":"object","description":"Large image banner with headings and calls to action.","required":["id","type"],"properties":{"cta_link":{"type":"string","description":"Destination of the primary call-to-action link."},"cta_text":{"type":"string","description":"Label for the primary call-to-action link."},"height":{"type":"string","description":"One of: small | medium | large | full."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"image_url":{"type":"string","description":"URL of the image displayed by this section."},"overlay_opacity":{"type":"integer","format":"int32","description":"Darken the image behind the copy (0-80, percent)."},"secondary_cta_link":{"type":"string","description":"Destination of the secondary call-to-action link."},"secondary_cta_text":{"type":"string","description":"Optional second call to action (Shopify's image banner has two)."},"style":{"$ref":"#/components/schemas/SectionStyle"},"subtitle":{"type":"string","description":"Supporting text displayed below the section heading."},"title":{"type":"string","description":"Heading displayed for this section."},"type":{"type":"string","enum":["hero"]}}},{"type":"object","description":"Grid of explicitly selected products.","required":["id","type"],"properties":{"columns":{"type":"integer","format":"int32","description":"Products per row on desktop (2..=5)."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"product_ids":{"type":"array","items":{"$ref":"#/components/schemas/Uuid"},"description":"Identifiers of the products selected for this section."},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string","description":"Heading displayed for this section."},"type":{"type":"string","enum":["featured_products"]}}},{"type":"object","description":"Grid of product categories.","required":["id","type"],"properties":{"category_ids":{"type":"array","items":{"$ref":"#/components/schemas/Uuid"},"description":"Empty = all top-level categories."},"columns":{"type":"integer","format":"int32","description":"Number of columns in the category grid."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string","description":"Heading displayed for this section."},"type":{"type":"string","enum":["category_grid"]}}},{"type":"object","description":"Formatted text content.","required":["id","type"],"properties":{"content":{"type":"string","description":"Plain rich text / markdown — rendered without raw HTML injection."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"style":{"$ref":"#/components/schemas/SectionStyle"},"type":{"type":"string","enum":["rich_text"]}}},{"type":"object","description":"Linked promotional image.","required":["id","type"],"properties":{"alt":{"type":"string","description":"Alternative text describing the banner image."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"image_url":{"type":"string","description":"URL of the image displayed by this section."},"link":{"type":"string","description":"Destination opened when the banner is selected."},"style":{"$ref":"#/components/schemas/SectionStyle"},"type":{"type":"string","enum":["banner"]}}},{"type":"object","description":"Tenant-authored HTML/CSS/JS with `{{ variable }}` substitution, for\nlayouts the structured sections above cannot express.\n\nSECURITY — this is the only section that accepts raw tenant markup, and\nit is handled by three separate defences (see\n`crate::shared::storefront_template`):\n1. `html` is rendered through the template engine, which ALWAYS\nHTML-escapes interpolated values and has no raw-output escape hatch,\nso a hostile product name cannot inject markup.\n2. The rendered result is then passed through an allowlist sanitizer\nthat drops `<script>`, `<form>`/`<input>` (credential phishing),\n`<iframe>`, `on*=` handlers and `javascript:` URLs.\n3. `js` is NEVER inlined into the store document. It is delivered to a\n`sandbox=\"allow-scripts\"` iframe (deliberately WITHOUT\n`allow-same-origin`), which the browser gives an opaque origin — so\nit cannot read the session cookie, the parent DOM, or localStorage,\nand can only talk to the page over a narrow postMessage bridge.\n\nBoth `html` and `css` are stored raw and sanitized on read, so tightening\nthe sanitizer later retroactively protects pages already saved.","required":["id","type"],"properties":{"css":{"type":"string","description":"Tenant-authored styles for the custom section."},"html":{"type":"string","description":"Tenant-authored markup passed to the storefront template renderer."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"js":{"type":"string","description":"Tenant-authored script content for the custom section sandbox."},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string","description":"Heading displayed for this section."},"type":{"type":"string","enum":["custom_html"]}}},{"type":"object","description":"Image beside copy — the \"image with text\" block every theme ships.","required":["id","type"],"properties":{"cta_link":{"type":"string","description":"Destination of the primary call-to-action link."},"cta_text":{"type":"string","description":"Label for the primary call-to-action link."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"image_position":{"type":"string","description":"One of: left | right."},"image_url":{"type":"string","description":"URL of the image displayed by this section."},"style":{"$ref":"#/components/schemas/SectionStyle"},"text":{"type":"string","description":"Rich text / markdown."},"title":{"type":"string","description":"Heading displayed for this section."},"type":{"type":"string","enum":["image_with_text"]}}},{"type":"object","description":"Two to four columns of icon/title/text — value propositions, USPs.","required":["id","type"],"properties":{"columns":{"type":"array","items":{"$ref":"#/components/schemas/MulticolumnItem"},"description":"Content entries displayed as columns in their configured order."},"columns_per_row":{"type":"integer","format":"int32","description":"2..=4 columns on desktop."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"style":{"$ref":"#/components/schemas/SectionStyle"},"subtitle":{"type":"string","description":"Supporting text displayed below the section heading."},"title":{"type":"string","description":"Heading displayed for this section."},"type":{"type":"string","enum":["multicolumn"]}}},{"type":"object","description":"Sequence of image slides with optional automatic progression.","required":["id","type"],"properties":{"autoplay_seconds":{"type":"integer","format":"int32","description":"0 = no autoplay, otherwise seconds per slide (2..=30)."},"height":{"type":"string","description":"One of: small | medium | large | full."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"slides":{"type":"array","items":{"$ref":"#/components/schemas/SlideshowSlide"},"description":"Slides displayed in their configured order."},"style":{"$ref":"#/components/schemas/SectionStyle"},"type":{"type":"string","enum":["slideshow"]}}},{"type":"object","description":"Accordion of question/answer pairs (FAQ, shipping info, size guide).","required":["id","type"],"properties":{"first_open":{"type":"boolean","description":"Open the first item by default."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"items":{"type":"array","items":{"$ref":"#/components/schemas/CollapsibleItem"},"description":"Question-and-answer entries displayed in their configured order."},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string","description":"Heading displayed for this section."},"type":{"type":"string","enum":["collapsible_content"]}}},{"type":"object","description":"Email sign-up. Posts to `/api/storefront/{slug}/newsletter`, which\nupserts a CRM contact with marketing consent.","required":["id","type"],"properties":{"button_text":{"type":"string","description":"Label of the form submission button."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"style":{"$ref":"#/components/schemas/SectionStyle"},"success_text":{"type":"string","description":"Configured message for a successful form submission."},"text":{"type":"string","description":"Supporting copy displayed by this section."},"title":{"type":"string","description":"Heading displayed for this section."},"type":{"type":"string","enum":["newsletter"]}}},{"type":"object","description":"Contact form. Posts to `/api/storefront/{slug}/contact`, which opens a\nCRM case for the tenant.","required":["id","type"],"properties":{"button_text":{"type":"string","description":"Label of the form submission button."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"show_phone":{"type":"boolean","description":"Whether the contact form includes a telephone field."},"style":{"$ref":"#/components/schemas/SectionStyle"},"success_text":{"type":"string","description":"Configured message for a successful form submission."},"text":{"type":"string","description":"Supporting copy displayed by this section."},"title":{"type":"string","description":"Heading displayed for this section."},"type":{"type":"string","enum":["contact_form"]}}},{"type":"object","description":"Collection of customer testimonials.","required":["id","type"],"properties":{"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"items":{"type":"array","items":{"$ref":"#/components/schemas/TestimonialItem"},"description":"Testimonials displayed in their configured order."},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string","description":"Heading displayed for this section."},"type":{"type":"string","enum":["testimonials"]}}},{"type":"object","description":"Embedded video. `video_url` accepts a `YouTube` or Vimeo page/embed URL\nor a direct `.mp4`/`.webm` file; anything else renders nothing.","required":["id","type"],"properties":{"autoplay":{"type":"boolean","description":"Muted autoplay for file videos; YouTube/Vimeo get the autoplay flag."},"caption":{"type":"string","description":"Supporting caption for the video."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string","description":"Heading displayed for this section."},"type":{"type":"string","enum":["video"]},"video_url":{"type":"string","description":"URL of the video to embed or play."}}},{"type":"object","description":"Row of brand / partner / \"as seen in\" logos.","required":["id","type"],"properties":{"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"logos":{"type":"array","items":{"$ref":"#/components/schemas/LogoItem"},"description":"Logo entries displayed in their configured order."},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string","description":"Heading displayed for this section."},"type":{"type":"string","enum":["logo_list"]}}},{"type":"object","description":"Countdown to a moment (sale end, launch). `ends_at` is RFC 3339.","required":["id","type"],"properties":{"cta_link":{"type":"string","description":"Destination of the primary call-to-action link."},"cta_text":{"type":"string","description":"Label for the primary call-to-action link."},"ends_at":{"type":"string","description":"Countdown deadline encoded as an RFC 3339 timestamp."},"expired_text":{"type":"string","description":"Shown instead of the timer once `ends_at` has passed."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"style":{"$ref":"#/components/schemas/SectionStyle"},"text":{"type":"string","description":"Supporting copy displayed by this section."},"title":{"type":"string","description":"Heading displayed for this section."},"type":{"type":"string","enum":["countdown"]}}},{"type":"object","description":"Automatic product listing — no hand-picking. Empty category = whole\ncatalogue. This is what starter templates use so they work on any\ncatalogue without configuration.","required":["id","type"],"properties":{"category_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"columns":{"type":"integer","format":"int32","description":"Products per row on desktop (2..=5)."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"limit":{"type":"integer","format":"int32","description":"1..=48 products."},"show_view_all":{"type":"boolean","description":"Whether to display the link to the complete product listing."},"sort":{"type":"string","description":"One of: newest | `name_asc` | `name_desc` | `price_asc` | `price_desc`."},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string","description":"Heading displayed for this section."},"type":{"type":"string","enum":["product_grid"]}}},{"type":"object","description":"One product, large, with its add-to-cart control.","required":["id","type"],"properties":{"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"product_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"style":{"$ref":"#/components/schemas/SectionStyle"},"text":{"type":"string","description":"Optional copy shown beside the product instead of its description."},"title":{"type":"string","description":"Optional heading above the product (empty = none)."},"type":{"type":"string","enum":["featured_product"]}}},{"type":"object","description":"Address, opening hours and contact details from\n`StorefrontSettings::contact`, optionally with an embedded Google map.","required":["id","type"],"properties":{"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"map_embed_url":{"type":"string","description":"Only `https://www.google.com/maps/embed…` URLs are served; anything\nelse is dropped on read (see `service::safe_map_embed_url`)."},"show_address":{"type":"boolean","description":"Whether to display the business address."},"show_contact":{"type":"boolean","description":"Whether to display the business contact details."},"show_hours":{"type":"boolean","description":"Whether to display the business opening hours."},"style":{"$ref":"#/components/schemas/SectionStyle"},"text":{"type":"string","description":"Supporting copy displayed by this section."},"title":{"type":"string","description":"Heading displayed for this section."},"type":{"type":"string","enum":["store_info"]}}},{"type":"object","description":"Vertical whitespace / divider.","required":["id","type"],"properties":{"height":{"type":"string","description":"One of: small | medium | large."},"id":{"type":"string","description":"Stable section identifier retained when sections are reordered."},"show_line":{"type":"boolean","description":"Whether the spacer includes a visible divider line."},"style":{"$ref":"#/components/schemas/SectionStyle"},"type":{"type":"string","enum":["spacer"]}}}],"description":"One ordered section of a storefront page.\n\nThe `id` is stable across reorders (used for drag-to-reorder in the admin builder and React keys in the store\napp). Every variant carries a `style` (see `SectionStyle`), defaulted so\npre-builder blobs keep deserialising.\n\nAdding a variant is additive: only this backend ever writes the tag, so an\nold store bundle can only receive tags it was shipped with once the store\napp is deployed alongside the backend (see `docs/STOREFRONT.md`).","discriminator":{"propertyName":"type"}},"StorefrontSectionResponse":{"oneOf":[{"type":"object","required":["id","style","title","subtitle","image_url","cta_text","cta_link","secondary_cta_text","secondary_cta_link","height","overlay_opacity","type"],"properties":{"cta_link":{"type":"string"},"cta_text":{"type":"string"},"height":{"type":"string"},"id":{"type":"string"},"image_url":{"type":"string"},"overlay_opacity":{"type":"integer","format":"int32"},"secondary_cta_link":{"type":"string"},"secondary_cta_text":{"type":"string"},"style":{"$ref":"#/components/schemas/SectionStyle"},"subtitle":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["hero"]}}},{"type":"object","required":["id","style","title","products","columns","type"],"properties":{"columns":{"type":"integer","format":"int32"},"id":{"type":"string"},"products":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontProductSummary"}},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string"},"type":{"type":"string","enum":["featured_products"]}}},{"type":"object","required":["id","style","title","categories","columns","type"],"properties":{"categories":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontCategorySummary"}},"columns":{"type":"integer","format":"int32"},"id":{"type":"string"},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string"},"type":{"type":"string","enum":["category_grid"]}}},{"type":"object","required":["id","style","content","type"],"properties":{"content":{"type":"string"},"id":{"type":"string"},"style":{"$ref":"#/components/schemas/SectionStyle"},"type":{"type":"string","enum":["rich_text"]}}},{"type":"object","required":["id","style","image_url","link","alt","type"],"properties":{"alt":{"type":"string"},"id":{"type":"string"},"image_url":{"type":"string"},"link":{"type":"string"},"style":{"$ref":"#/components/schemas/SectionStyle"},"type":{"type":"string","enum":["banner"]}}},{"type":"object","description":"Tenant-authored section. See `StorefrontSection::CustomHtml` for the\nfull security rationale.","required":["id","style","title","html","css","js","variables","type"],"properties":{"css":{"type":"string","description":"Sanitized. Safe to inline in a `<style>` block."},"html":{"type":"string","description":"Template-rendered AND sanitizer-filtered. Safe to inject directly."},"id":{"type":"string"},"js":{"type":"string","description":"RAW tenant JavaScript — deliberately NOT sanitized, because\nsanitizing JS is not a solvable problem. The client MUST run this\nonly inside a `sandbox=\"allow-scripts\"` iframe with NO\n`allow-same-origin`. Never inline it into the store document."},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string"},"type":{"type":"string","enum":["custom_html"]},"variables":{"description":"The variable context used to render `html`, echoed so the sandboxed\nscript can read the same values without needing API access."}}},{"type":"object","required":["id","style","title","text","image_url","image_position","cta_text","cta_link","type"],"properties":{"cta_link":{"type":"string"},"cta_text":{"type":"string"},"id":{"type":"string"},"image_position":{"type":"string"},"image_url":{"type":"string"},"style":{"$ref":"#/components/schemas/SectionStyle"},"text":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["image_with_text"]}}},{"type":"object","required":["id","style","title","subtitle","columns","columns_per_row","type"],"properties":{"columns":{"type":"array","items":{"$ref":"#/components/schemas/MulticolumnItem"}},"columns_per_row":{"type":"integer","format":"int32"},"id":{"type":"string"},"style":{"$ref":"#/components/schemas/SectionStyle"},"subtitle":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["multicolumn"]}}},{"type":"object","required":["id","style","slides","autoplay_seconds","height","type"],"properties":{"autoplay_seconds":{"type":"integer","format":"int32"},"height":{"type":"string"},"id":{"type":"string"},"slides":{"type":"array","items":{"$ref":"#/components/schemas/SlideshowSlide"}},"style":{"$ref":"#/components/schemas/SectionStyle"},"type":{"type":"string","enum":["slideshow"]}}},{"type":"object","required":["id","style","title","items","first_open","type"],"properties":{"first_open":{"type":"boolean"},"id":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/CollapsibleItem"}},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string"},"type":{"type":"string","enum":["collapsible_content"]}}},{"type":"object","required":["id","style","title","text","button_text","success_text","type"],"properties":{"button_text":{"type":"string"},"id":{"type":"string"},"style":{"$ref":"#/components/schemas/SectionStyle"},"success_text":{"type":"string"},"text":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["newsletter"]}}},{"type":"object","required":["id","style","title","text","button_text","success_text","show_phone","type"],"properties":{"button_text":{"type":"string"},"id":{"type":"string"},"show_phone":{"type":"boolean"},"style":{"$ref":"#/components/schemas/SectionStyle"},"success_text":{"type":"string"},"text":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["contact_form"]}}},{"type":"object","required":["id","style","title","items","type"],"properties":{"id":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/TestimonialItem"}},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string"},"type":{"type":"string","enum":["testimonials"]}}},{"type":"object","required":["id","style","title","embed","caption","autoplay","type"],"properties":{"autoplay":{"type":"boolean"},"caption":{"type":"string"},"embed":{"$ref":"#/components/schemas/StorefrontVideoEmbed"},"id":{"type":"string"},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string"},"type":{"type":"string","enum":["video"]}}},{"type":"object","required":["id","style","title","logos","type"],"properties":{"id":{"type":"string"},"logos":{"type":"array","items":{"$ref":"#/components/schemas/LogoItem"}},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string"},"type":{"type":"string","enum":["logo_list"]}}},{"type":"object","required":["id","style","title","text","ends_at","cta_text","cta_link","expired_text","type"],"properties":{"cta_link":{"type":"string"},"cta_text":{"type":"string"},"ends_at":{"type":"string"},"expired_text":{"type":"string"},"id":{"type":"string"},"style":{"$ref":"#/components/schemas/SectionStyle"},"text":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["countdown"]}}},{"type":"object","required":["id","style","title","products","columns","view_all_link","type"],"properties":{"columns":{"type":"integer","format":"int32"},"id":{"type":"string"},"products":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontProductSummary"}},"style":{"$ref":"#/components/schemas/SectionStyle"},"title":{"type":"string"},"type":{"type":"string","enum":["product_grid"]},"view_all_link":{"type":"string","description":"Store-relative link for \"View all\" (`/products` or\n`/categories/{id}`); empty when the section hides the link."}}},{"type":"object","required":["id","style","title","text","type"],"properties":{"id":{"type":"string"},"product":{"allOf":[{"$ref":"#/components/schemas/StorefrontProductDetail"}],"nullable":true},"style":{"$ref":"#/components/schemas/SectionStyle"},"text":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["featured_product"]}}},{"type":"object","required":["id","style","title","text","show_address","show_hours","show_contact","contact","map_embed_url","type"],"properties":{"contact":{"$ref":"#/components/schemas/StorefrontContactInfo"},"id":{"type":"string"},"map_embed_url":{"type":"string","description":"Validated Google Maps embed URL or empty."},"show_address":{"type":"boolean"},"show_contact":{"type":"boolean"},"show_hours":{"type":"boolean"},"style":{"$ref":"#/components/schemas/SectionStyle"},"text":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["store_info"]}}},{"type":"object","required":["id","style","height","show_line","type"],"properties":{"height":{"type":"string"},"id":{"type":"string"},"show_line":{"type":"boolean"},"style":{"$ref":"#/components/schemas/SectionStyle"},"type":{"type":"string","enum":["spacer"]}}}],"discriminator":{"propertyName":"type"}},"StorefrontSeoSettings":{"type":"object","description":"Default search metadata for storefront pages.","properties":{"meta_description":{"type":"string","description":"Default storefront description used for search metadata."},"meta_title":{"type":"string","description":"Default storefront title used for search metadata."}}},"StorefrontSettings":{"type":"object","description":"Company configuration for the public storefront.","properties":{"analytics":{"$ref":"#/components/schemas/StorefrontAnalytics"},"catalog":{"$ref":"#/components/schemas/StorefrontCatalogSettings"},"checkout":{"$ref":"#/components/schemas/StorefrontCheckoutSettings"},"contact":{"$ref":"#/components/schemas/StorefrontContactInfo"},"cookie_banner":{"$ref":"#/components/schemas/StorefrontCookieBanner"},"discounts":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontDiscountCode"},"description":"Configured checkout discount codes."},"enabled":{"type":"boolean","description":"Whether the company storefront is enabled."},"layout":{"$ref":"#/components/schemas/StorefrontLayout"},"notifications":{"$ref":"#/components/schemas/StorefrontNotificationSettings"},"payments":{"$ref":"#/components/schemas/StorefrontPaymentSettings"},"policies":{"$ref":"#/components/schemas/StorefrontPolicies"},"seo":{"$ref":"#/components/schemas/StorefrontSeoSettings"},"shipping":{"$ref":"#/components/schemas/StorefrontShippingSettings"},"slug":{"type":"string","description":"Unique-ish store slug (subdomain / path segment). Validated non-empty\nwhen enabled; reserved words are rejected (see\n`settings::services::RESERVED_STOREFRONT_SLUGS`)."},"store_name":{"type":"string","description":"Public name displayed for the storefront."},"theme":{"$ref":"#/components/schemas/StorefrontTheme"}}},"StorefrontShippingAddressRequest":{"type":"object","required":["line1","city","postal_code","country"],"properties":{"city":{"type":"string"},"country":{"type":"string"},"line1":{"type":"string"},"line2":{"type":"string","nullable":true},"postal_code":{"type":"string"}}},"StorefrontShippingMethod":{"type":"object","description":"A single shipping option offered at checkout.","properties":{"enabled":{"type":"boolean","description":"Whether to offer this shipping option at checkout."},"id":{"type":"string","description":"Stable identifier of the shipping option."},"name":{"type":"string","description":"Customer-facing name of the shipping option."},"price":{"type":"string","description":"Decimal string, e.g. \"5.00\"."}}},"StorefrontShippingMethodPublic":{"type":"object","required":["id","name","price"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"price":{"type":"string","description":"Decimal string, e.g. \"5.00\"."}}},"StorefrontShippingSettings":{"type":"object","description":"Shipping options, pickup and free-shipping preferences.","properties":{"free_shipping_threshold":{"type":"string","description":"Decimal string. `None` = no free-shipping threshold.","nullable":true},"methods":{"type":"array","items":{"$ref":"#/components/schemas/StorefrontShippingMethod"},"description":"Configured shipping options offered at checkout."},"pickup_enabled":{"type":"boolean","description":"Whether to offer pickup at checkout."},"pickup_label":{"type":"string","description":"Customer-facing label for the pickup option."}}},"StorefrontSocialLinks":{"type":"object","description":"Social profile URLs shown in the footer (empty = hidden).","properties":{"facebook":{"type":"string","description":"Link to the company Facebook presence."},"instagram":{"type":"string","description":"Link to the company Instagram presence."},"linkedin":{"type":"string","description":"Link to the company `LinkedIn` presence."},"tiktok":{"type":"string","description":"Link to the company `TikTok` presence."},"whatsapp":{"type":"string","description":"Link to the company `WhatsApp` presence."},"x":{"type":"string","description":"Link to the company X presence."},"youtube":{"type":"string","description":"Link to the company `YouTube` presence."}}},"StorefrontTheme":{"type":"object","description":"Storefront visual theme, applied by the store app as CSS custom properties\nat boot.\n\nThe original nine fields (colours, body font, logo, favicon, announcement)\nare the v1 contract; everything after `announcement_enabled` was added by\nthe webshop builder (2026-09) and carries its own serde default so a blob\nstored before then keeps deserialising unchanged. Enumerated fields are\nplain strings validated in `settings::services` (see\n`STOREFRONT_THEME_CHOICES`), not Rust enums, so an unknown value stored by\na newer backend degrades to \"ignored\" on the store instead of resetting\nevery company setting.","properties":{"accent_color":{"type":"string","description":"Accent colour used by the theme."},"announcement_enabled":{"type":"boolean","description":"Whether to display the announcement bar."},"announcement_text":{"type":"string","description":"Text displayed in the announcement bar."},"background_color":{"type":"string","description":"Default page background colour."},"border_color":{"type":"string","description":"Colour used for theme borders and dividers."},"border_radius":{"type":"string","description":"One of: none | small | medium | large | pill."},"button_style":{"type":"string","description":"One of: solid | outline | soft."},"button_text_color":{"type":"string","description":"Text colour on primary buttons."},"cart_type":{"type":"string","description":"One of: drawer | page."},"favicon_url":{"type":"string","description":"URL of the storefront browser icon."},"font_family":{"type":"string","description":"Font family preference for storefront text."},"header_layout":{"type":"string","description":"One of: `logo_left` | `logo_center`."},"header_sticky":{"type":"boolean","description":"Whether the header remains visible while scrolling."},"heading_font_family":{"type":"string","description":"Heading font. Empty = same as `font_family`."},"image_ratio":{"type":"string","description":"One of: square | portrait | landscape."},"logo_url":{"type":"string","description":"URL of the storefront logo."},"page_width":{"type":"string","description":"One of: narrow | normal | wide."},"primary_color":{"type":"string","description":"Primary theme colour."},"product_card_style":{"type":"string","description":"One of: standard | minimal | bold."},"secondary_text_color":{"type":"string","description":"Muted text (captions, helper copy)."},"section_spacing":{"type":"string","description":"One of: compact | normal | relaxed."},"surface_color":{"type":"string","description":"Card / surface background (product cards, drawers, footer)."},"template_id":{"type":"string","description":"Which starter template was last applied (informational, may be empty)."},"text_color":{"type":"string","description":"Default foreground text colour."}}},"StorefrontVariantDto":{"type":"object","description":"One product variant as the shopper sees it.","required":["id","name","sku","attributes","price"],"properties":{"attributes":{"description":"Free-form attribute map, e.g. `{\"size\": \"M\", \"colour\": \"Blue\"}`."},"id":{"type":"string"},"name":{"type":"string","description":"e.g. \"M / Blue\""},"price":{"type":"string","description":"Variant price (its override, else the product price), decimal string."},"sku":{"type":"string"}}},"StorefrontVideoEmbed":{"type":"object","description":"Normalised embed for a `video` section.","required":["kind","url"],"properties":{"kind":{"type":"string","description":"One of: youtube | vimeo | file | none."},"url":{"type":"string","description":"Embed/iframe src for youtube/vimeo, the media URL for file, empty for none."}}},"SubscriptionListResponse":{"type":"object","required":["items","total","page","per_page","total_pages"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SubscriptionSummary"}},"page":{"type":"integer","format":"int64"},"per_page":{"type":"integer","format":"int64"},"total":{"type":"integer","format":"int64"},"total_pages":{"type":"integer","format":"int64"}}},"SubscriptionSummary":{"type":"object","required":["id","company_id","plan_name","status","cancel_at_period_end","created_at"],"properties":{"cancel_at_period_end":{"type":"boolean"},"cancelled_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"company_id":{"type":"string"},"company_name":{"type":"string","nullable":true},"created_at":{"$ref":"#/components/schemas/chrono.DateTime"},"current_period_end":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"id":{"type":"string"},"plan_name":{"type":"string"},"status":{"type":"string"},"stripe_customer_id":{"type":"string","nullable":true},"trial_ends_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true}}},"SupplierBillActionRequest":{"type":"object","description":"Optimistic-lock version plus an optional reason (required to void).","required":["version"],"properties":{"reason":{"type":"string","nullable":true},"version":{"type":"integer","format":"int32"}}},"SupplierBillLineResponse":{"type":"object","required":["id","line_number","description","quantity","unit_cost","tax_amount","line_total","price_variance","quantity_variance","revalued_amount","match_status"],"properties":{"description":{"type":"string"},"expected_unit_cost":{"type":"string","description":"Net purchase-order unit cost the bill is matched against.","nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"line_number":{"type":"integer","format":"int32"},"line_total":{"type":"string"},"match_status":{"type":"string","description":"`no_po`, `matched`, `price_variance`, `quantity_variance` or\n`price_and_quantity_variance`."},"previously_billed_qty":{"type":"string","description":"Quantity already billed on other posted bills for the same order line.","nullable":true},"price_variance":{"type":"string","description":"`(unit_cost - expected_unit_cost) × quantity`; signed."},"product_id":{"type":"string","nullable":true},"purchase_order_line_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"quantity":{"type":"string"},"quantity_variance":{"type":"string","description":"Units billed beyond what may be billed."},"received_qty":{"type":"string","description":"Quantity received on the purchase-order line.","nullable":true},"revalued_amount":{"type":"string","description":"Share of the price variance added to inventory value at posting."},"tax_amount":{"type":"string"},"unit_cost":{"type":"string"},"variant_id":{"type":"string","nullable":true}}},"SupplierBillListResponse":{"type":"object","required":["results","total","limit","offset"],"properties":{"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/SupplierBillResponse"}},"total":{"type":"integer","format":"int64"}}},"SupplierBillResponse":{"type":"object","required":["id","bill_number","supplier_id","supplier_name","supplier_invoice_number","status","match_status","bill_date","due_date","currency_code","subtotal","tax_total","freight_total","total","amount_paid","amount_credited","balance_due","price_variance_total","revalued_total","created_at","updated_at","version","lines","settlements"],"properties":{"amount_credited":{"type":"string"},"amount_paid":{"type":"string"},"approved_at":{"type":"string","nullable":true},"balance_due":{"type":"string"},"bill_date":{"type":"string"},"bill_number":{"type":"string"},"created_at":{"type":"string"},"currency_code":{"type":"string"},"due_date":{"type":"string"},"freight_total":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/SupplierBillLineResponse"}},"match_status":{"type":"string","description":"`not_applicable`, `matched`, `variance` or `approved`. For a draft this\nis recomputed from the current orders and receipts on every read."},"notes":{"type":"string","nullable":true},"posted_at":{"type":"string","nullable":true},"price_variance_total":{"type":"string"},"purchase_order_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"purchase_order_number":{"type":"string","nullable":true},"revalued_total":{"type":"string"},"settlements":{"type":"array","items":{"$ref":"#/components/schemas/SupplierBillSettlementResponse"}},"status":{"type":"string","description":"`draft`, `posted`, `partially_paid`, `paid` or `void`."},"subtotal":{"type":"string"},"supplier_id":{"$ref":"#/components/schemas/Uuid"},"supplier_invoice_number":{"type":"string"},"supplier_name":{"type":"string"},"tax_total":{"type":"string"},"total":{"type":"string"},"updated_at":{"type":"string"},"version":{"type":"integer","format":"int32"},"void_reason":{"type":"string","nullable":true},"voided_at":{"type":"string","nullable":true}}},"SupplierBillSettlementResponse":{"type":"object","description":"A payment or vendor credit settled against a bill.","required":["kind","document_id","document_number","amount","date"],"properties":{"amount":{"type":"string"},"date":{"type":"string"},"document_id":{"$ref":"#/components/schemas/Uuid"},"document_number":{"type":"string"},"kind":{"type":"string","description":"`payment` or `credit`."}}},"SupplierListResponse":{"type":"object","required":["results","total","limit","offset"],"properties":{"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/SupplierResponse"}},"total":{"type":"integer","format":"int64"}}},"SupplierPaymentAllocationRequest":{"type":"object","required":["supplier_bill_id","amount"],"properties":{"amount":{"type":"string"},"supplier_bill_id":{"$ref":"#/components/schemas/Uuid"}}},"SupplierPaymentAllocationResponse":{"type":"object","required":["supplier_bill_id","bill_number","supplier_invoice_number","amount","created_at"],"properties":{"amount":{"type":"string"},"bill_number":{"type":"string"},"created_at":{"type":"string"},"supplier_bill_id":{"$ref":"#/components/schemas/Uuid"},"supplier_invoice_number":{"type":"string"}}},"SupplierPaymentListResponse":{"type":"object","required":["results","total","limit","offset"],"properties":{"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/SupplierPaymentResponse"}},"total":{"type":"integer","format":"int64"}}},"SupplierPaymentResponse":{"type":"object","required":["id","payment_number","supplier_id","supplier_name","status","payment_date","method","currency_code","amount","unapplied_amount","created_at","version","allocations"],"properties":{"allocations":{"type":"array","items":{"$ref":"#/components/schemas/SupplierPaymentAllocationResponse"}},"amount":{"type":"string"},"created_at":{"type":"string"},"currency_code":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"method":{"type":"string"},"notes":{"type":"string","nullable":true},"payment_date":{"type":"string"},"payment_number":{"type":"string"},"reference":{"type":"string","nullable":true},"status":{"type":"string","description":"`posted` or `void`."},"supplier_id":{"$ref":"#/components/schemas/Uuid"},"supplier_name":{"type":"string"},"unapplied_amount":{"type":"string"},"version":{"type":"integer","format":"int32"},"void_reason":{"type":"string","nullable":true},"voided_at":{"type":"string","nullable":true}}},"SupplierProductCostHistoryResponse":{"type":"object","required":["id","cost_kind","unit_cost","currency_code","purchase_uom","units_per_purchase","source_type","effective_at"],"properties":{"cost_kind":{"type":"string"},"currency_code":{"type":"string"},"effective_at":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"purchase_uom":{"type":"string"},"recorded_by":{"type":"integer","format":"int32","nullable":true},"source_id":{"type":"string","nullable":true},"source_type":{"type":"string"},"unit_cost":{"type":"string"},"units_per_purchase":{"type":"string"}}},"SupplierProductListResponse":{"type":"object","required":["results","total","limit","offset"],"properties":{"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/SupplierProductResponse"}},"total":{"type":"integer","format":"int64"}}},"SupplierProductResponse":{"type":"object","required":["id","supplier_id","product_id","product_name","purchase_uom","units_per_purchase","is_preferred","is_active","version","created_at","updated_at","cost_history"],"properties":{"cost_history":{"type":"array","items":{"$ref":"#/components/schemas/SupplierProductCostHistoryResponse"}},"created_at":{"type":"string"},"effective_from":{"type":"string","nullable":true},"effective_to":{"type":"string","nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"is_active":{"type":"boolean"},"is_preferred":{"type":"boolean"},"last_unit_cost":{"type":"string","nullable":true},"lead_time_days":{"type":"integer","format":"int32","nullable":true},"minimum_order_qty":{"type":"string","nullable":true},"order_multiple":{"type":"string","nullable":true},"product_id":{"type":"string"},"product_name":{"type":"string"},"purchase_uom":{"type":"string"},"quoted_unit_cost":{"type":"string","nullable":true},"supplier_barcode":{"type":"string","nullable":true},"supplier_description":{"type":"string","nullable":true},"supplier_id":{"$ref":"#/components/schemas/Uuid"},"supplier_sku":{"type":"string","nullable":true},"units_per_purchase":{"type":"string"},"updated_at":{"type":"string"},"variant_id":{"type":"string","nullable":true},"variant_name":{"type":"string","nullable":true},"version":{"type":"integer","format":"int32"}}},"SupplierProductTransitionRequest":{"type":"object","required":["version"],"properties":{"version":{"type":"integer","format":"int32"}}},"SupplierResponse":{"type":"object","required":["id","company_id","supplier_number","name","status","currency_code","payment_terms_days","contacts","accounting_mapping","created_at","updated_at"],"properties":{"account_number":{"type":"string","nullable":true},"accounting_mapping":{},"billing_address":{"nullable":true},"company_id":{"type":"string"},"contacts":{},"created_at":{"type":"string"},"currency_code":{"type":"string"},"email":{"type":"string","nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"lead_time_days":{"type":"integer","format":"int32","nullable":true},"legal_name":{"type":"string","nullable":true},"name":{"type":"string"},"notes":{"type":"string","nullable":true},"payment_terms_days":{"type":"integer","format":"int32"},"phone":{"type":"string","nullable":true},"shipping_address":{"nullable":true},"status":{"type":"string"},"supplier_number":{"type":"string"},"tax_identifier":{"type":"string","nullable":true},"updated_at":{"type":"string"}}},"SupplierReturnLineResponse":{"type":"object","required":["id","line_number","product_id","quantity","unit_cost","line_total","reason_code"],"properties":{"goods_receipt_line_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"id":{"$ref":"#/components/schemas/Uuid"},"line_number":{"type":"integer","format":"int32"},"line_total":{"type":"string"},"notes":{"type":"string","nullable":true},"product_id":{"type":"string"},"quantity":{"type":"string"},"reason_code":{"type":"string"},"unit_cost":{"type":"string"},"variant_id":{"type":"string","nullable":true}}},"SupplierReturnListResponse":{"type":"object","required":["results","total","limit","offset"],"properties":{"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/SupplierReturnResponse"}},"total":{"type":"integer","format":"int64"}}},"SupplierReturnResponse":{"type":"object","required":["id","supplier_return_number","supplier_id","location_id","status","expected_credit","created_at","updated_at","version","lines"],"properties":{"approved_at":{"type":"string","nullable":true},"closed_at":{"type":"string","nullable":true},"created_at":{"type":"string"},"credited_amount":{"type":"string","nullable":true},"credited_at":{"type":"string","nullable":true},"expected_credit":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/SupplierReturnLineResponse"}},"location_id":{"type":"string"},"notes":{"type":"string","nullable":true},"shipped_at":{"type":"string","nullable":true},"status":{"type":"string"},"supplier_id":{"$ref":"#/components/schemas/Uuid"},"supplier_reference":{"type":"string","nullable":true},"supplier_return_number":{"type":"string"},"updated_at":{"type":"string"},"version":{"type":"integer","format":"int32"}}},"SupplierReturnTransitionRequest":{"type":"object","required":["version"],"properties":{"credited_amount":{"type":"string","nullable":true},"version":{"type":"integer","format":"int32"}}},"SuppressionCheckResponse":{"type":"object","required":["check_id","segment_member_count","suppressed_count","sendable_count","computed_at"],"properties":{"check_id":{"$ref":"#/components/schemas/Uuid"},"computed_at":{"type":"string"},"segment_member_count":{"type":"integer","format":"int64"},"sendable_count":{"type":"integer","format":"int64"},"suppressed_count":{"type":"integer","format":"int64"}}},"SuppressionListResponse":{"type":"object","required":["items","total_count","page","page_size"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SuppressionResponse"}},"page":{"type":"integer","format":"int64"},"page_size":{"type":"integer","format":"int64"},"total_count":{"type":"integer","format":"int64"}}},"SuppressionResponse":{"type":"object","required":["id","email","reason","suppressed_at"],"properties":{"email":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"reason":{"type":"string"},"suppressed_at":{"type":"string"}}},"SyncAccount":{"type":"object","description":"Sync representation of an Account (Customer)","required":["server_created_at","server_updated_at","id","name","company"],"properties":{"address":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"company":{"type":"string"},"country":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"id":{"type":"string"},"name":{"type":"string"},"phone":{"type":"string","nullable":true},"server_created_at":{"type":"string"},"server_updated_at":{"type":"string"},"state":{"type":"string","nullable":true},"zip_code":{"type":"string","nullable":true}}},"SyncCategory":{"type":"object","description":"Sync representation of a Category","required":["server_created_at","server_updated_at","id","name","company"],"properties":{"company":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"parent_id":{"type":"string","nullable":true},"server_created_at":{"type":"string"},"server_updated_at":{"type":"string"}}},"SyncChanges":{"type":"object","description":"Collection of sync changes for different entity types","required":["products","categories","accounts","sales_orders","sales_order_items","product_images"],"properties":{"accounts":{"type":"array","items":{"$ref":"#/components/schemas/SyncAccount"},"description":"Account (customer) changes"},"categories":{"type":"array","items":{"$ref":"#/components/schemas/SyncCategory"},"description":"Category changes"},"gift_cards":{"type":"array","items":{"$ref":"#/components/schemas/SyncGiftCard"},"description":"Gift card changes"},"product_images":{"type":"array","items":{"$ref":"#/components/schemas/SyncProductImage"},"description":"Product image changes"},"products":{"type":"array","items":{"$ref":"#/components/schemas/SyncProduct"},"description":"Product changes"},"sales_order_items":{"type":"array","items":{"$ref":"#/components/schemas/SyncSalesOrderItem"},"description":"Sales order item changes"},"sales_orders":{"type":"array","items":{"$ref":"#/components/schemas/SyncSalesOrder"},"description":"Sales order changes"}}},"SyncGiftCard":{"type":"object","description":"Sync representation of a Gift Card","required":["id","company_id","card_number","initial_balance","current_balance","active","server_created_at","server_updated_at"],"properties":{"active":{"type":"boolean"},"card_number":{"type":"string"},"company_id":{"type":"string"},"current_balance":{"type":"string"},"expires_at":{"type":"string","nullable":true},"id":{"type":"string"},"initial_balance":{"type":"string"},"notes":{"type":"string","nullable":true},"server_created_at":{"type":"string"},"server_updated_at":{"type":"string"},"terminal_id":{"type":"string","nullable":true}}},"SyncGiftCardPushEntry":{"type":"object","description":"One locally-issued card. camelCase because the desktop till serialises\ncamelCase keys (see module docs).","required":["localId","cardNumber","initialBalance","currentBalance"],"properties":{"active":{"type":"boolean","description":"Defaults to true. A card the till auto-deactivated because its LOCAL\nbalance hit zero is still stored active — the redemption-ledger replay\nis what deactivates it in the cloud (an inactive cloud card would refuse\nthat very replay). Only an operator deactivation of a card that still\nheld balance is stored inactive.","nullable":true},"cardNumber":{"type":"string"},"currentBalance":{"type":"string","description":"Decimal string. Informational: validated (0 ≤ current ≤ initial) but the\ncloud row is created at issuance state — see module docs."},"expiresAt":{"type":"string","description":"RFC3339. Absent = never expires.","nullable":true},"initialBalance":{"type":"string","description":"Decimal string, e.g. \"25.00\"."},"issuedAt":{"type":"string","description":"RFC3339. Absent/unparseable falls back to now.","nullable":true},"localId":{"type":"string","description":"The till's own card UUID — the idempotency key AND the cloud row id, so\nthe next pull round-trips onto the same local row."},"terminalId":{"type":"string","nullable":true}}},"SyncGiftCardPushResult":{"type":"object","description":"Per-entry outcome. `snake_case` on purpose: the desktop till parses these\nresults with the same struct it uses for the ledger endpoints\n(`PushLedgerResponse` in sync/service.rs), which expects `snake_case`.","required":["local_id","status"],"properties":{"balance_after":{"type":"string","description":"The cloud card's current balance, for terminal reconciliation. On\n`duplicate` this is the EXISTING row's balance (redemptions included),\nnot the pushed value.","nullable":true},"error":{"type":"string","nullable":true},"local_id":{"type":"string"},"status":{"type":"string","description":"\"applied\" | \"duplicate\" | \"rejected\""}}},"SyncOrderAck":{"type":"object","description":"One entry of the `order_sync_results` array in the sync-push response.","required":["client_order_id","status"],"properties":{"client_order_id":{"type":"string","description":"Client-generated order id the ack refers to."},"error":{"type":"string","description":"Human-readable quarantine message (quarantined only).","nullable":true},"error_code":{"allOf":[{"$ref":"#/components/schemas/QuarantineCode"}],"nullable":true},"inbox_id":{"type":"string","description":"Inbox row id (durability receipt).","nullable":true},"sales_order_id":{"type":"string","description":"Server order id (set for created/duplicate outcomes when known).","nullable":true},"status":{"$ref":"#/components/schemas/SyncOrderAckStatus"}}},"SyncOrderAckStatus":{"type":"string","description":"Per-order acknowledgement status returned in `order_sync_results` (§B7).\n\n`quarantined` is only emitted to clients that signal capability via\n`supports_quarantine: true` in the push request body; legacy clients get the\norder reported in the top-level `errors` array instead (their behavior is\nunchanged — the order is still durably held server-side).","enum":["created","duplicate_replayed","duplicate","quarantined"]},"SyncProduct":{"type":"object","description":"Sync representation of a Product","required":["server_created_at","server_updated_at","id","item_number","code","code_type","description","name","msrp_price","regular_price","wholesale_price","company","qty","category_id"],"properties":{"category_id":{"type":"string"},"code":{"type":"string"},"code_type":{"type":"string"},"company":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"identifiers":{"type":"array","items":{"$ref":"#/components/schemas/ProductIdentifierResponse"},"nullable":true},"image_name":{"type":"string","nullable":true},"image_url":{"type":"string","nullable":true},"item_number":{"type":"string"},"msrp_price":{"type":"string"},"name":{"type":"string"},"qty":{"type":"integer","format":"int32"},"regular_price":{"type":"string"},"server_created_at":{"type":"string"},"server_updated_at":{"type":"string"},"stock_levels":{"type":"array","items":{"$ref":"#/components/schemas/StockLevelResponse"},"nullable":true},"wholesale_price":{"type":"string"}}},"SyncProductImage":{"type":"object","description":"Sync representation of a Product Image","required":["id","product_id","image","is_primary"],"properties":{"id":{"type":"string"},"image":{"type":"string"},"is_primary":{"type":"boolean"},"product_id":{"type":"string"}}},"SyncPullResponse":{"type":"object","description":"Sync pull response containing all changes since last sync","required":["timestamp","created","updated","deleted"],"properties":{"created":{"$ref":"#/components/schemas/SyncChanges"},"deleted":{"$ref":"#/components/schemas/SyncChanges"},"timestamp":{"type":"integer","format":"int64","description":"Timestamp of this sync response (milliseconds since epoch)"},"updated":{"$ref":"#/components/schemas/SyncChanges"}}},"SyncPushGiftCardsRequest":{"type":"object","required":["cards"],"properties":{"cards":{"type":"array","items":{"$ref":"#/components/schemas/SyncGiftCardPushEntry"}}}},"SyncPushGiftCardsResponse":{"type":"object","required":["results","applied","duplicates","rejected"],"properties":{"applied":{"type":"integer","minimum":0},"duplicates":{"type":"integer","minimum":0},"rejected":{"type":"integer","minimum":0},"results":{"type":"array","items":{"$ref":"#/components/schemas/SyncGiftCardPushResult"}}}},"SyncSalesOrder":{"type":"object","description":"Sync representation of a Sales Order","required":["id","date","company","agent","account_id","customer_name","total"],"properties":{"account_id":{"type":"string"},"agent":{"type":"string"},"company":{"type":"string"},"customer_name":{"type":"string","description":"Customer name - CRITICAL for mobile app sync"},"date":{"type":"string"},"id":{"type":"string"},"note":{"type":"string","nullable":true},"total":{"type":"string"}}},"SyncSalesOrderItem":{"type":"object","description":"Sync representation of a Sales Order Item","required":["id","sales_order_id","product_id","quantity","unit_price","total_price"],"properties":{"id":{"type":"string"},"product_id":{"type":"string"},"quantity":{"type":"integer","format":"int32"},"sales_order_id":{"type":"string"},"total_price":{"type":"string"},"unit_price":{"type":"string"}}},"SyncZReport":{"type":"object","description":"Sync representation of a Z-Report","required":["id","company_id","terminal_id","report_date","shift_start_time","shift_end_time","opening_cash","gross_sales","net_sales","total_discount","total_tax","total_refunds","receipt_count","refund_count","expected_cash","cash_variance","status","server_created_at"],"properties":{"cash_variance":{"type":"string"},"cashier_name":{"type":"string","nullable":true},"company_id":{"type":"string"},"counted_cash":{"type":"string","nullable":true},"expected_cash":{"type":"string"},"gross_sales":{"type":"string"},"id":{"type":"string"},"location_id":{"type":"string","nullable":true},"net_sales":{"type":"string"},"notes":{"type":"string","nullable":true},"opening_cash":{"type":"string"},"payment_breakdown":{"nullable":true},"receipt_count":{"type":"integer","format":"int32"},"refund_count":{"type":"integer","format":"int32"},"report_date":{"type":"string"},"server_created_at":{"type":"string"},"shift_end_time":{"type":"string"},"shift_start_time":{"type":"string"},"status":{"type":"string"},"terminal_id":{"type":"string"},"total_discount":{"type":"string"},"total_refunds":{"type":"string"},"total_tax":{"type":"string"}}},"SystemHealthResponse":{"type":"object","required":["status","timestamp","version","checks"],"properties":{"checks":{"type":"array","items":{"$ref":"#/components/schemas/HealthCheckItem"}},"status":{"type":"string","description":"Overall rollup: \"healthy\" while the database is reachable, else \"unhealthy\"."},"timestamp":{"type":"string"},"version":{"$ref":"#/components/schemas/HealthVersion"}}},"TableContextDto":{"type":"object","required":["floor_plan_object_id"],"properties":{"floor_plan_object_id":{"type":"string"},"table_label":{"type":"string","nullable":true}}},"TableMenuResponse":{"type":"object","required":["table","menu","qr_config"],"properties":{"menu":{"$ref":"#/components/schemas/MenuResponse"},"qr_config":{"$ref":"#/components/schemas/QrDisplayConfigDto"},"table":{"$ref":"#/components/schemas/TableContextDto"}}},"TableOrderSummary":{"type":"object","description":"The per-table order digest the till's floor plan renders on a tile.","required":["open_order_id","order_status","item_count","running_total","bill_requested"],"properties":{"bill_requested":{"type":"boolean","description":"True when the guest has asked for the bill. Reads BOTH the new\n`bill_requested_at` column and the legacy `status = 'BILL_REQUESTED'`\nliteral, so orders written by the pre-workstream-E code still show the\nflag."},"bill_requested_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"item_count":{"type":"integer","format":"int64","description":"Non-voided line count."},"open_order_id":{"type":"string"},"order_status":{"type":"string","description":"Raw stored status — may be new vocabulary or a legacy literal. Clients\nshould read it tolerantly."},"placed_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"running_total":{"type":"string","description":"Sum of `qty * unit_price` over non-voided lines, as a decimal string.\n\nThis is an INDICATIVE running total for the floor screen, not an\ninvoice: it carries no tax breakdown, no discounts and no service\ncharge, and `settle` re-prices every line server-side from\n`webapp_product.regular_price` before any money moves. Never present it\nto a guest as the amount due."}}},"TableQrToken":{"type":"object","required":["id","company_id","floor_plan_object_id","token","active","created_at"],"properties":{"active":{"type":"boolean"},"company_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/DateTime"},"floor_plan_object_id":{"type":"string"},"id":{"type":"string"},"revoked_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true},"token":{"type":"string"}}},"TableQrTokenResponse":{"type":"object","required":["id","floor_plan_object_id","token","active","created_at","order_path"],"properties":{"active":{"type":"boolean"},"created_at":{"type":"string"},"floor_plan_object_id":{"type":"string"},"id":{"type":"string"},"order_path":{"type":"string","description":"RELATIVE path only (e.g. `/t/{token}`), matching `apps/store`'s\nplanned `/t/{table_token}` route (design §4.3) — no base-domain\nconfig for the storefront/QR frontend exists on the backend to\ncompose a full URL from (unlike `PortalConfig` for the customer\nportal), so the admin QR codes page (design §7 item 2 — not built\nthis wave) is expected to prepend its own known frontend origin."},"token":{"type":"string"}}},"TableSession":{"type":"object","required":["id","company_id","floor_plan_object_id","opened_at","status","created_at","updated_at"],"properties":{"closed_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true},"company_id":{"type":"string"},"created_at":{"$ref":"#/components/schemas/DateTime"},"floor_plan_object_id":{"type":"string"},"id":{"type":"string"},"note":{"type":"string","nullable":true},"opened_at":{"$ref":"#/components/schemas/DateTime"},"opened_by_user_id":{"type":"integer","format":"int32","nullable":true},"party_size":{"type":"integer","format":"int32","nullable":true},"status":{"type":"string"},"updated_at":{"$ref":"#/components/schemas/DateTime"}}},"Tenant":{"type":"object","required":["id","name","is_deleted","created_at","updated_at"],"properties":{"created_at":{"$ref":"#/components/schemas/chrono.NaiveDateTime"},"id":{"type":"string"},"is_deleted":{"type":"boolean"},"name":{"type":"string"},"updated_at":{"$ref":"#/components/schemas/chrono.NaiveDateTime"}}},"TenantListResponse":{"type":"object","required":["items","total","page","per_page","total_pages"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Tenant"}},"page":{"type":"integer","format":"int64"},"per_page":{"type":"integer","format":"int64"},"total":{"type":"integer","format":"int64"},"total_pages":{"type":"integer","format":"int64"}}},"TerminalConfig":{"type":"object","required":["id","allow_discounts","auto_print_receipt","created_at","updated_at"],"properties":{"allow_discounts":{"type":"boolean"},"auto_print_receipt":{"type":"boolean"},"created_at":{"type":"string"},"currency":{"type":"string","nullable":true},"id":{"type":"string"},"kds_enabled":{"type":"boolean","description":"Run THIS terminal as a Kitchen Display (POS-ship task H12).\n\n`null`/`false` mean \"not a kitchen screen\". A restaurant runs one till\nin the pass as a KDS and the rest as ordering terminals, and the\nexisting `terminal_mode` (retail/horeca/both) is a local desktop\nsetting that the admin app cannot see, so an owner had no way to\nprovision a kitchen screen remotely.\n\nIt is a gate on top of the horeca gates, never instead of them: the\nKitchen screen also needs `horeca_ordering.enabled` for the company,\na non-retail terminal mode, and `horeca.serve` on the signed-in user.","nullable":true},"max_discount_percent":{"type":"string","nullable":true},"receipt_footer":{"type":"string","nullable":true},"receipt_number_prefix":{"type":"string","description":"Prefix stamped on this terminal's receipt numbers, e.g. \"T2\".\n\nWithout it two tills in one shop both emit `20260903-0001`, so the\nreceipt number a customer quotes on the phone is not a key at all. The\nterminal sanitises it to at most eight uppercase alphanumerics — it\nends up on printed paper and in a settings-key fragment.","nullable":true},"receipt_printer_type":{"type":"string","nullable":true},"store_address":{"type":"string","nullable":true},"store_name":{"type":"string","nullable":true},"tax_rate":{"type":"string","nullable":true},"touch_mode":{"type":"boolean","nullable":true},"ui_density":{"type":"string","nullable":true},"ui_scale_percent":{"type":"integer","format":"int32","description":"Per-terminal presentation overrides. `null` means \"inherit the company\ndefault\" (`CompanySettings::pos_terminal_ui`).\n\nThese exist because the same estate genuinely needs different values: a\n15\" 1024x768 till at Windows' default 125% scaling has an 819x614\nLOGICAL viewport and wants 90%, while a 22\" back-office terminal in the\nsame shop is easier to read at 115%.","nullable":true},"updated_at":{"type":"string"}}},"TerminalSaleItem":{"type":"object","required":["id","date","total","agent_id","items_count"],"properties":{"agent_id":{"type":"integer","format":"int32"},"agent_name":{"type":"string","nullable":true},"customer_name":{"type":"string","nullable":true},"date":{"type":"string"},"id":{"type":"string"},"items_count":{"type":"integer","format":"int64"},"payment_method":{"type":"string","nullable":true},"total":{"type":"string"}}},"TerminalSalesHistory":{"type":"object","required":["sales","total"],"properties":{"sales":{"type":"array","items":{"$ref":"#/components/schemas/TerminalSaleItem"}},"total":{"type":"integer","format":"int64"}}},"TestimonialItem":{"type":"object","description":"One quote of a `testimonials` section.","properties":{"author":{"type":"string","description":"Name attributed to the testimonial."},"id":{"type":"string","description":"Stable identifier of this configured entry."},"image_url":{"type":"string","description":"URL of the image displayed with this content."},"quote":{"type":"string","description":"Text of the testimonial."},"rating":{"type":"integer","format":"int32","description":"0 = no stars, otherwise 1..=5."},"role":{"type":"string","description":"Role or affiliation displayed with the testimonial author."}}},"TillSettings":{"type":"object","description":"Operational settings safe to disclose to a selling terminal.","required":["company_name","currency","timezone","language","date_format","time_format","pos_pricing","pos_catalogue","pos_lists","pos_security","pos_shifts","pos_cash","receipt_settings","pos_reports","pos_terminal_ui","horeca_ordering","offline_images","diagnostics","live_sync"],"properties":{"address":{"type":"string","description":"Store address printed when inherited by a receipt layout.","nullable":true},"company_name":{"type":"string","description":"Registered company display name, used when no receipt name overrides it."},"currency":{"type":"string","description":"ISO currency code used for catalogue and receipt amounts."},"currency_locale":{"type":"string","description":"Optional locale for currency formatting.","nullable":true},"currency_symbol":{"type":"string","description":"Optional merchant-configured currency symbol.","nullable":true},"date_format":{"type":"string","description":"Preferred date display format."},"diagnostics":{"$ref":"#/components/schemas/DiagnosticsSettings"},"horeca_ordering":{"$ref":"#/components/schemas/HorecaOrderingSettings"},"language":{"type":"string","description":"Company interface language."},"live_sync":{"$ref":"#/components/schemas/super.live_sync.LiveSyncSettings"},"logo_url":{"type":"string","description":"Store logo inherited by receipt layouts.","nullable":true},"offline_images":{"$ref":"#/components/schemas/OfflineImagesSettings"},"phone":{"type":"string","description":"Store contact phone for receipts.","nullable":true},"pos_cash":{"$ref":"#/components/schemas/PosCashSettings"},"pos_catalogue":{"$ref":"#/components/schemas/PosCatalogueSettings"},"pos_lists":{"$ref":"#/components/schemas/PosListsSettings"},"pos_pricing":{"$ref":"#/components/schemas/PosPricingSettings"},"pos_reports":{"$ref":"#/components/schemas/PosReportsSettings"},"pos_security":{"$ref":"#/components/schemas/PosSecuritySettings"},"pos_shifts":{"$ref":"#/components/schemas/PosShiftSettings"},"pos_terminal_ui":{"$ref":"#/components/schemas/PosTerminalUiSettings"},"receipt_settings":{"$ref":"#/components/schemas/ReceiptSettings"},"tax_rate":{"type":"number","format":"double","description":"Default tax percentage for products without a specific tax rate.","nullable":true},"time_format":{"type":"string","description":"Preferred time display format."},"timezone":{"type":"string","description":"Company timezone for display and business dates."}}},"TopCustomer":{"type":"object","required":["customer_name","total_spent","order_count","avg_order_value","customer_tier"],"properties":{"avg_order_value":{"$ref":"#/components/schemas/BigDecimal"},"customer_name":{"type":"string"},"customer_tier":{"type":"string"},"first_order_date":{"allOf":[{"$ref":"#/components/schemas/NaiveDateTime"}],"nullable":true},"last_order_date":{"allOf":[{"$ref":"#/components/schemas/NaiveDateTime"}],"nullable":true},"order_count":{"type":"integer","format":"int64"},"total_spent":{"$ref":"#/components/schemas/BigDecimal"}}},"TotpSetupResponse":{"type":"object","description":"The shared secret, shown once, at enrolment time.","required":["secret","provisioning_uri","qr_code_data"],"properties":{"provisioning_uri":{"type":"string","description":"`otpauth://` URI — hand this to an authenticator app directly."},"qr_code_data":{"type":"string","description":"The exact string to encode into a QR image. Identical to\n`provisioning_uri`: the QR is rendered client-side (the admin app uses\n`qrcode`) rather than shipping a PNG from Rust, so this field carries the\n*data*, not an image."},"secret":{"type":"string","description":"Base32 secret for manual entry, for users who cannot scan a QR code."}}},"TotpSetupVerifyRequest":{"type":"object","description":"Prove the authenticator works, completing enrolment for a signed-in user.","required":["code"],"properties":{"code":{"type":"string","description":"The 6-digit code currently shown by the authenticator app."}}},"TotpSetupVerifyResponse":{"type":"object","description":"Enrolment result. Recovery codes appear here and nowhere else, ever.","required":["message","recovery_codes"],"properties":{"message":{"type":"string"},"recovery_codes":{"type":"array","items":{"type":"string"},"description":"Single-use break-glass codes, plaintext, shown exactly once."},"token":{"type":"string","description":"Present only when enrolment completed via a `setup_token` (forced\nenrolment): the JWT the user would have received at login. The auth\ncookie is set on the same response.","nullable":true},"user":{"description":"The authenticated user payload, same shape as the login response, present\nunder the same condition as `token`.","nullable":true}}},"TriggerEventResponse":{"type":"object","description":"One event type a pipeline may declare as its trigger, with the JSON Schema\nof the payload `{{ trigger.* }}` expressions resolve against. Serves the\ntrigger dropdown (previously a hardcoded, drift-prone list in the UI) and\nthe payload-editor / autocomplete tooling.","required":["event_type","entity_type","title","description","payload_schema"],"properties":{"description":{"type":"string"},"entity_type":{"type":"string"},"event_type":{"type":"string"},"payload_schema":{},"title":{"type":"string"}}},"UpdateBillingFeatureRequest":{"type":"object","description":"Operator-editable fields only.\n\n`code` and `tier` are absent on purpose: `code` is identity, and `tier` is\nstructural — it drives entitlement resolution defaults, and the boot seeder\noverwrites it from the enum. Accepting a `tier` here would let an operator\nset a value that silently reverts on the next deploy, which is worse than\nnot offering it.","properties":{"currency":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"is_active":{"type":"boolean","nullable":true},"name":{"type":"string","nullable":true},"price_cents":{"type":"integer","format":"int32","nullable":true},"sort_order":{"type":"integer","format":"int32","nullable":true}}},"UpdateCampaignRequest":{"type":"object","properties":{"body_template_ref":{"type":"string","nullable":true},"name":{"type":"string","nullable":true},"scheduled_at":{"type":"string","nullable":true},"subject":{"type":"string","nullable":true}}},"UpdateCaseRequest":{"type":"object","description":"PATCH-shaped. Setting `status` to `resolved` stamps `resolved_at`;\nre-opening clears it. Priority edits do NOT re-snapshot SLA deadlines.","properties":{"assigned_to_user_id":{"type":"integer","format":"int32","nullable":true},"contact_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"organization_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"priority":{"allOf":[{"$ref":"#/components/schemas/CasePriority"}],"nullable":true},"status":{"type":"string","description":"new | open | pending | resolved | closed.","nullable":true},"subject":{"type":"string","nullable":true}}},"UpdateCommercialCheckoutPolicy":{"type":"object","required":["expected_revision","policy","reason"],"properties":{"expected_revision":{"type":"integer","format":"int32","minimum":0},"policy":{"$ref":"#/components/schemas/CommercialCheckoutPolicy"},"reason":{"type":"string"}}},"UpdateCompanySettingsRequest":{"type":"object","description":"Partial company-settings update; omitted or null fields retain their stored values.","properties":{"address":{"type":"string","description":"Business address shown in company information.","nullable":true},"ai_automation":{"allOf":[{"$ref":"#/components/schemas/AiAutomationSettings"}],"nullable":true},"billing":{"allOf":[{"$ref":"#/components/schemas/BillingSettings"}],"nullable":true},"business_hours":{"type":"string","description":"Business opening hours as entered by an administrator.","nullable":true},"catalog_search":{"allOf":[{"$ref":"#/components/schemas/crate.features.catalog_search.dto.CatalogSearchSettings"}],"nullable":true},"currency":{"type":"string","description":"Company currency code used for monetary values.","nullable":true},"currency_locale":{"type":"string","description":"Optional locale override for formatting monetary values.","nullable":true},"currency_symbol":{"type":"string","description":"Optional symbol override for displaying monetary values.","nullable":true},"date_format":{"type":"string","description":"Company preference for formatting dates.","nullable":true},"delivery_workflow":{"allOf":[{"$ref":"#/components/schemas/DeliveryWorkflowSettings"}],"nullable":true},"diagnostics":{"allOf":[{"$ref":"#/components/schemas/DiagnosticsSettings"}],"nullable":true},"digital_display":{"allOf":[{"$ref":"#/components/schemas/DigitalDisplaySettings"}],"nullable":true},"email_notifications":{"type":"boolean","description":"Whether company email notifications are enabled.","nullable":true},"fulfillment_notifications":{"allOf":[{"$ref":"#/components/schemas/FulfillmentNotificationSettings"}],"nullable":true},"horeca_ordering":{"allOf":[{"$ref":"#/components/schemas/HorecaOrderingSettings"}],"nullable":true},"image_editor":{"allOf":[{"$ref":"#/components/schemas/ImageEditorSettings"}],"nullable":true},"image_naming_patterns":{"type":"array","items":{"$ref":"#/components/schemas/ImageNamingPattern"},"description":"Saved filename matching patterns for image imports.","nullable":true},"import_settings":{"allOf":[{"$ref":"#/components/schemas/ImportSettings"}],"nullable":true},"import_templates":{"allOf":[{"$ref":"#/components/schemas/ImportTemplates"}],"nullable":true},"inventory_costing":{"allOf":[{"$ref":"#/components/schemas/InventoryCostingSettings"}],"nullable":true},"inventory_counts":{"allOf":[{"$ref":"#/components/schemas/InventoryCountSettings"}],"nullable":true},"inventory_reservation":{"allOf":[{"$ref":"#/components/schemas/InventoryReservationSettings"}],"nullable":true},"invoice_settings":{"allOf":[{"$ref":"#/components/schemas/InvoiceSettings"}],"nullable":true},"language":{"type":"string","description":"Preferred language for the company interface.","nullable":true},"live_sync":{"allOf":[{"$ref":"#/components/schemas/super.live_sync.LiveSyncSettings"}],"nullable":true},"logo_url":{"type":"string","description":"URL of the company logo.","nullable":true},"low_stock_alerts":{"type":"boolean","description":"Whether low-stock notifications are enabled for the company.","nullable":true},"low_stock_threshold":{"type":"integer","format":"int32","description":"Stock quantity threshold used for low-stock notifications.","nullable":true},"mcp":{"allOf":[{"$ref":"#/components/schemas/McpSettings"}],"nullable":true},"navigation":{"allOf":[{"$ref":"#/components/schemas/NavigationSettings"}],"nullable":true},"notification_emails":{"type":"string","description":"Company notification recipients; normalized by the settings repository.","nullable":true},"offline_images":{"allOf":[{"$ref":"#/components/schemas/OfflineImagesSettings"}],"nullable":true},"onboarding":{"allOf":[{"$ref":"#/components/schemas/OnboardingSettings"}],"nullable":true},"order_rules":{"allOf":[{"$ref":"#/components/schemas/OrderRulesSettings"}],"nullable":true},"order_sync":{"allOf":[{"$ref":"#/components/schemas/OrderSyncSettings"}],"nullable":true},"phone":{"type":"string","description":"Business contact telephone number.","nullable":true},"pipeline_limits":{"allOf":[{"$ref":"#/components/schemas/PipelineLimitsSettings"}],"nullable":true},"portal_design":{"allOf":[{"$ref":"#/components/schemas/PortalDesignSettings"}],"nullable":true},"pos_cash":{"allOf":[{"$ref":"#/components/schemas/PosCashSettings"}],"nullable":true},"pos_catalogue":{"allOf":[{"$ref":"#/components/schemas/PosCatalogueSettings"}],"nullable":true},"pos_lists":{"allOf":[{"$ref":"#/components/schemas/PosListsSettings"}],"nullable":true},"pos_pricing":{"allOf":[{"$ref":"#/components/schemas/PosPricingSettings"}],"nullable":true},"pos_reports":{"allOf":[{"$ref":"#/components/schemas/PosReportsSettings"}],"nullable":true},"pos_security":{"allOf":[{"$ref":"#/components/schemas/PosSecuritySettings"}],"nullable":true},"pos_shifts":{"allOf":[{"$ref":"#/components/schemas/PosShiftSettings"}],"nullable":true},"pos_terminal_ui":{"allOf":[{"$ref":"#/components/schemas/PosTerminalUiSettings"}],"nullable":true},"purchasing":{"allOf":[{"$ref":"#/components/schemas/PurchasingSettings"}],"nullable":true},"query":{"allOf":[{"$ref":"#/components/schemas/QuerySettings"}],"nullable":true},"quickbooks_pos":{"allOf":[{"$ref":"#/components/schemas/QuickBooksPosSettings"}],"nullable":true},"receipt_settings":{"allOf":[{"$ref":"#/components/schemas/ReceiptSettings"}],"nullable":true},"receiving":{"allOf":[{"$ref":"#/components/schemas/ReceivingSettings"}],"nullable":true},"reporting":{"allOf":[{"$ref":"#/components/schemas/ReportingSettings"}],"nullable":true},"sales_order_email":{"allOf":[{"$ref":"#/components/schemas/SalesOrderEmailSettings"}],"nullable":true},"security_policy":{"allOf":[{"$ref":"#/components/schemas/SecurityPolicySettings"}],"nullable":true},"sms_notifications":{"type":"boolean","description":"Whether company SMS notifications are enabled.","nullable":true},"storefront":{"allOf":[{"$ref":"#/components/schemas/StorefrontSettings"}],"nullable":true},"tax_rate":{"type":"number","format":"double","description":"Company default tax rate.","nullable":true},"time_format":{"type":"string","description":"Company preference for formatting times.","nullable":true},"timezone":{"type":"string","description":"Company timezone used when interpreting and displaying local times.","nullable":true}}},"UpdateContactRequest":{"type":"object","description":"PATCH-shaped: unset = no change. Clearing a nullable field is done with\nan explicit JSON `null` via the double-Option pattern.","properties":{"first_name":{"type":"string","nullable":true},"job_title":{"type":"string","nullable":true},"last_name":{"type":"string","nullable":true},"lifecycle_stage_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"organization_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"owner_user_id":{"type":"integer","format":"int32","nullable":true},"primary_email":{"type":"string","nullable":true},"primary_phone":{"type":"string","nullable":true}}},"UpdateContractRequest":{"type":"object","properties":{"auto_renew":{"type":"boolean","nullable":true},"billing_interval":{"type":"string","nullable":true},"currency":{"type":"string","nullable":true},"ends_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"notes":{"type":"string","nullable":true},"payment_method":{"type":"string","nullable":true},"plan_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"price_cents":{"type":"integer","format":"int32","nullable":true},"starts_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"status":{"type":"string","nullable":true}}},"UpdateCustomerGroupRequest":{"type":"object","properties":{"default_min_order_qty":{"type":"integer","format":"int32","nullable":true},"description":{"type":"string","nullable":true},"name":{"type":"string","nullable":true}}},"UpdateDataset":{"type":"object","properties":{"data_file":{"type":"string","nullable":true},"data_set_format":{"type":"string","nullable":true},"enrichment_config":{"nullable":true},"enrichment_status":{"type":"string","nullable":true},"processing_metadata":{"nullable":true},"size":{"type":"integer","format":"int32","nullable":true},"source_type":{"type":"string","nullable":true},"version":{"type":"integer","format":"int32","nullable":true}}},"UpdateDealPipelineRequest":{"type":"object","description":"PATCH-shaped. When `stages` is present it upserts by `code`: existing\ncodes are updated, new codes inserted; removing a code that still has\ndeals in its stage is a 409 (data-model §3's migration-step rule).","properties":{"is_default":{"type":"boolean","nullable":true},"name":{"type":"string","nullable":true},"stages":{"type":"array","items":{"$ref":"#/components/schemas/DealPipelineStageInput"},"nullable":true}}},"UpdateDealRequest":{"type":"object","description":"PATCH-shaped; stage changes go through POST /{id}/move-stage, never here.","properties":{"amount":{"type":"string","nullable":true},"currency":{"type":"string","nullable":true},"expected_close_date":{"type":"string","nullable":true},"lost_reason_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"name":{"type":"string","nullable":true},"organization_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"owner_user_id":{"type":"integer","format":"int32","nullable":true},"primary_contact_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true}}},"UpdateDiscountRuleRequest":{"type":"object","properties":{"applies_to":{"type":"string","nullable":true},"discount_type":{"type":"string","nullable":true},"ends_at":{"type":"string","nullable":true},"is_stackable":{"type":"boolean","nullable":true},"min_order_qty":{"type":"integer","format":"int32","nullable":true},"min_order_value":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"name":{"type":"string","nullable":true},"priority":{"type":"integer","format":"int32","nullable":true},"starts_at":{"type":"string","nullable":true},"target_id":{"type":"string","nullable":true},"value":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true}}},"UpdateFieldDefinitionRequest":{"type":"object","description":"PATCH-shaped; `entity_type`, `code` and `field_type` are immutable.","properties":{"is_active":{"type":"boolean","nullable":true},"is_required":{"type":"boolean","nullable":true},"label":{"type":"string","nullable":true},"options":{"type":"array","items":{"$ref":"#/components/schemas/CrmPicklistOption"},"nullable":true},"sort_order":{"type":"integer","format":"int32","nullable":true}}},"UpdateFlagRequest":{"type":"object","properties":{"description":{"type":"string","nullable":true},"enabled":{"type":"boolean","nullable":true}}},"UpdateFloorPlanInput":{"type":"object","properties":{"background_image_url":{"type":"string","nullable":true},"height_px":{"type":"integer","format":"int32","nullable":true},"name":{"type":"string","nullable":true},"objects":{"type":"array","items":{"$ref":"#/components/schemas/CreateFloorPlanObjectInput"},"nullable":true},"room_label":{"type":"string","nullable":true},"width_px":{"type":"integer","format":"int32","nullable":true}}},"UpdateFulfillmentPolicy":{"type":"object","description":"Double-Option idiom: None = unchanged, Some(v) = set.","properties":{"allow_add_off_order_products":{"type":"boolean","nullable":true},"allow_barcode_scanning":{"type":"boolean","nullable":true},"allow_manual_picking":{"type":"boolean","nullable":true},"allow_mixed_fulfillment":{"type":"boolean","nullable":true},"allow_negative_stock":{"type":"boolean","nullable":true},"allow_over_picking":{"type":"boolean","nullable":true},"allow_partial_picking":{"type":"boolean","nullable":true},"allow_product_substitution":{"type":"boolean","nullable":true},"allow_remove_lines":{"type":"boolean","nullable":true},"camera_scanning_optional":{"type":"boolean","nullable":true},"default_picking_view":{"type":"string","nullable":true},"max_over_pick_percent":{"type":"number","format":"double","description":"Nullable column, so this one really is double-Option: field absent =\nunchanged, `null` = clear the cap, number = set it. `serde(default)`\nkeeps absent-field requests (old admin builds) deserializing to\n`None` (skip) instead of failing.","nullable":true},"notify_backoffice_on_exception":{"type":"boolean","nullable":true},"notify_sales_rep_on_exception":{"type":"boolean","nullable":true},"remaining_quantity_behavior":{"type":"string","nullable":true},"require_approval_for_mixed_fulfillment":{"type":"boolean","nullable":true},"require_approval_for_substitution":{"type":"boolean","nullable":true},"require_note_for_added_product":{"type":"boolean","nullable":true},"require_note_for_removed_line":{"type":"boolean","nullable":true},"require_note_for_shortage":{"type":"boolean","nullable":true},"require_reason_for_shortage":{"type":"boolean","nullable":true},"updated_at":{"allOf":[{"$ref":"#/components/schemas/DateTime"}],"nullable":true}}},"UpdateHelpArticleRequest":{"type":"object","properties":{"app_target":{"type":"string","nullable":true},"category":{"type":"string","nullable":true},"content":{"type":"string","nullable":true},"is_published":{"type":"boolean","nullable":true},"sort_order":{"type":"integer","format":"int32","nullable":true},"title":{"type":"string","nullable":true}}},"UpdateImageDocumentRequest":{"type":"object","description":"Request body for `PUT /api/image-documents/{id}`.\n\n`product_id` is deliberately absent: a document's product association is\nfixed at creation. `version` and `updated_at` are also absent — the\nhandler owns both and bumps them on every save, so a client can never\nset, skip, or rewind the version counter.","required":["scene_json","source_refs"],"properties":{"name":{"type":"string","nullable":true},"output_image_id":{"type":"string","nullable":true},"scene_json":{"type":"object"},"source_refs":{"type":"object"}}},"UpdateInternalUserRequest":{"type":"object","properties":{"company_id":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"first_name":{"type":"string","nullable":true},"is_active":{"type":"boolean","nullable":true},"is_platform_admin":{"type":"boolean","description":"Grant/revoke platform (internal-admin) access.","nullable":true},"last_name":{"type":"string","nullable":true},"role":{"allOf":[{"$ref":"#/components/schemas/UserRole"}],"nullable":true},"username":{"type":"string","nullable":true}}},"UpdateInventoryCountLineRequest":{"type":"object","required":["counted_quantity","expected_version"],"properties":{"counted_quantity":{"type":"string"},"expected_version":{"type":"integer","format":"int64"}}},"UpdateLifecycleStageRequest":{"type":"object","properties":{"is_active":{"type":"boolean","nullable":true},"label":{"type":"string","nullable":true},"sort_order":{"type":"integer","format":"int32","nullable":true}}},"UpdateLineResolutionApprovalRequest":{"type":"object","required":["approval_status"],"properties":{"approval_status":{"type":"string"},"note":{"type":"string","nullable":true}}},"UpdateListItemRequest":{"type":"object","properties":{"quantity":{"type":"integer","format":"int32","nullable":true},"sort_order":{"type":"integer","format":"int32","nullable":true}}},"UpdateLookbookRequest":{"type":"object","description":"Patch request. Every field is optional and absent means \"leave alone\".\n\nNOTE: this shape cannot express \"clear this field back to null\" — an absent\nkey and an explicit null both arrive as `None`. That is a deliberate\nlimitation for now; clearing a subtitle or cover image is done by sending an\nempty string, which the handler normalises. If a future field needs true\nthree-state semantics, switch that field to `Option<Option<T>>` with\n`#[serde(default, deserialize_with = \"...\")]` rather than reinterpreting the\nexisting ones, which would be a breaking change for any client already\nsending nulls.","properties":{"cover_image_url":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"ends_at":{"type":"string","nullable":true},"is_active":{"type":"boolean","nullable":true},"name":{"type":"string","nullable":true},"position":{"type":"integer","format":"int32","nullable":true},"starts_at":{"type":"string","nullable":true},"subtitle":{"type":"string","nullable":true}}},"UpdateMailboxRequest":{"type":"object","properties":{"display_name":{"type":"string","nullable":true},"shared":{"type":"boolean","nullable":true},"sync_direction":{"type":"string","nullable":true},"sync_interval_minutes":{"type":"integer","format":"int32","nullable":true}}},"UpdateMarketingImageRequest":{"type":"object","properties":{"image_url":{"type":"string","nullable":true},"is_active":{"type":"boolean","nullable":true},"order_index":{"type":"integer","format":"int32","nullable":true},"title":{"type":"string","nullable":true}}},"UpdateModifierGroupInput":{"type":"object","properties":{"is_required":{"type":"boolean","nullable":true},"max_selections":{"type":"integer","format":"int32","nullable":true},"min_selections":{"type":"integer","format":"int32","nullable":true},"name":{"type":"string","nullable":true},"selection_type":{"type":"string","nullable":true}}},"UpdateNumberingConfigRequest":{"type":"object","properties":{"pad_width":{"type":"integer","format":"int32","nullable":true},"prefix":{"type":"string","nullable":true},"start_at":{"type":"integer","format":"int64","nullable":true}}},"UpdateOnboardingStepRequest":{"type":"object","required":["step"],"properties":{"completed":{"type":"boolean","nullable":true},"skipped":{"type":"boolean","nullable":true},"step":{"type":"string"}}},"UpdateOrderItemInput":{"type":"object","properties":{"course":{"type":"string","nullable":true},"modifiers_snapshot":{"nullable":true},"note":{"type":"string","nullable":true},"qty":{"type":"integer","format":"int32","nullable":true},"seat_number":{"type":"integer","format":"int32","description":"Seat / cover this line belongs to. Additive and optional.","nullable":true},"unit_price":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true}}},"UpdateOrganizationRequest":{"type":"object","properties":{"domain":{"type":"string","nullable":true},"industry_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"lifecycle_stage_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"name":{"type":"string","nullable":true},"owner_user_id":{"type":"integer","format":"int32","nullable":true},"size_range":{"type":"string","nullable":true}}},"UpdatePermissionsRequest":{"type":"object","properties":{"permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"},"nullable":true}}},"UpdatePickingNotesRequest":{"type":"object","properties":{"notes":{"type":"string","nullable":true}}},"UpdatePipelineRequest":{"type":"object","description":"Metadata update (name/description/trigger) plus pause/resume via\n`enabled`. Publishing a version happens via `POST /activate` (which also\nre-enables); `enabled: true` here is rejected while the pipeline has no\nactive version, since such a pipeline can never fire.","properties":{"description":{"type":"string","nullable":true},"enabled":{"type":"boolean","description":"`false` pauses the pipeline (event fan-out skips it); `true` resumes\nit. Omitted = unchanged.","nullable":true},"name":{"type":"string","nullable":true},"trigger_event_type":{"type":"string","description":"One of `models::domain_event::domain_event_type::ALL`; validated\nserver-side, a 400 on anything else.","nullable":true}}},"UpdatePlanRequest":{"type":"object","properties":{"billing_interval":{"type":"string","nullable":true},"contact_sales":{"type":"boolean","nullable":true},"currency":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"extra_ai_credits_price_cents":{"type":"integer","format":"int32","description":"Price of a 1,000-credit top-up in cents. Absent = keep.","nullable":true},"extra_rep_device_price_cents":{"type":"integer","format":"int32","nullable":true},"extra_seat_price_cents":{"type":"integer","format":"int32","nullable":true},"features":{"type":"object","nullable":true},"included_ai_credits":{"type":"integer","format":"int32","description":"AI credits included per calendar month. Absent = keep.","nullable":true},"included_rep_devices":{"type":"integer","format":"int32","nullable":true},"included_seats":{"type":"integer","format":"int32","nullable":true},"is_active":{"type":"boolean","nullable":true},"is_highlighted":{"type":"boolean","nullable":true},"is_public":{"type":"boolean","nullable":true},"is_trial_default":{"type":"boolean","nullable":true},"marketing":{"type":"object","description":"Per-locale display copy; must be a JSON object. Absent = keep.","nullable":true},"name":{"type":"string","nullable":true},"price_cents":{"type":"integer","format":"int32","nullable":true},"price_cents_annual":{"type":"integer","format":"int32","description":"TOTAL per YEAR in cents. Absent = keep; explicit `null` = CLEAR (annual\nno longer offered) — the same absent/null distinction as `sku_limit`.","nullable":true},"sku_limit":{"type":"integer","format":"int32","description":"Absent = keep; explicit `null` = CLEAR (unlimited).","nullable":true},"sort_order":{"type":"integer","format":"int32","nullable":true},"stripe_price_id_annual":{"type":"string","description":"Annual Stripe Price ID. Absent = keep; empty string = clear.","nullable":true},"stripe_price_id_monthly":{"type":"string","description":"Monthly Stripe Price ID. Absent = keep the stored value; empty string =\nCLEAR it (back to the env fallback). Clearing must be possible, which is\nwhy this does not follow the `.or(existing)` pattern of `description`.","nullable":true},"trial_days":{"type":"integer","format":"int32","nullable":true}}},"UpdatePortalCartRequest":{"type":"object","description":"Full-replace cart update — PUT /api/portal/cart","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PortalCartLineRequest"},"description":"The complete desired cart; an empty array clears the cart"},"note":{"type":"string","description":"Replaces the cart note when present; absent leaves it unchanged","nullable":true}}},"UpdatePortalUserRequest":{"type":"object","description":"Update portal user request (admin-only)","properties":{"first_name":{"type":"string","nullable":true},"is_active":{"type":"boolean","nullable":true},"last_name":{"type":"string","nullable":true}}},"UpdatePosTerminalRequest":{"type":"object","properties":{"active":{"type":"boolean","nullable":true},"label":{"type":"string","nullable":true},"name":{"type":"string","nullable":true}}},"UpdatePriceListRequest":{"type":"object","properties":{"currency":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"is_default":{"type":"boolean","nullable":true},"name":{"type":"string","nullable":true},"sort_order":{"type":"integer","format":"int32","nullable":true}}},"UpdatePrinterProfileInput":{"type":"object","properties":{"connection_config":{"nullable":true},"connection_type":{"type":"string","nullable":true},"is_default":{"type":"boolean","nullable":true},"name":{"type":"string","nullable":true},"printer_type":{"type":"string","nullable":true}}},"UpdateProductCollectionRequest":{"type":"object","properties":{"description":{"type":"string","nullable":true},"ends_at":{"type":"string","nullable":true},"is_active":{"type":"boolean","nullable":true},"name":{"type":"string","nullable":true},"starts_at":{"type":"string","nullable":true}}},"UpdateProductVariantRequest":{"type":"object","description":"Body of `PATCH /api/products/{id}/variants/{variant_id}` (all optional;\nsend `\"price\": \"\"` to clear the override).","properties":{"attributes":{"nullable":true},"cost_price":{"type":"string","nullable":true},"name":{"type":"string","nullable":true},"price":{"type":"string","nullable":true},"sku":{"type":"string","nullable":true}}},"UpdateProfileRequest":{"type":"object","properties":{"email":{"type":"string","nullable":true},"first_name":{"type":"string","nullable":true},"last_name":{"type":"string","nullable":true}}},"UpdateSalesOrderRequest":{"type":"object","description":"Update sales order request","properties":{"account_id":{"type":"string","nullable":true},"agent_id":{"type":"integer","format":"int32","nullable":true},"date":{"type":"string","nullable":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/SalesOrderItemInput"},"nullable":true},"note":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"total":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true}}},"UpdateSalesReceipt":{"type":"object","properties":{"account_id":{"type":"string","nullable":true},"change_amount":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"clerk_notes":{"type":"string","nullable":true},"gift_card_id":{"allOf":[{"$ref":"#/components/schemas/uuid.Uuid"}],"nullable":true},"location_id":{"type":"string","nullable":true},"note":{"type":"string","nullable":true},"payment_amount":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"payment_method":{"type":"string","nullable":true},"sales_order_id":{"type":"string","nullable":true},"sales_order_number":{"type":"string","nullable":true},"terminal_id":{"type":"string","nullable":true}}},"UpdateSavedListRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}},"UpdateSegmentRequest":{"type":"object","properties":{"filter_json":{"nullable":true},"is_active":{"type":"boolean","nullable":true},"name":{"type":"string","nullable":true}}},"UpdateSlaPolicyRequest":{"type":"object","properties":{"first_response_minutes":{"type":"integer","format":"int32","nullable":true},"is_default":{"type":"boolean","nullable":true},"name":{"type":"string","nullable":true},"resolution_minutes":{"type":"integer","format":"int32","nullable":true}}},"UpdateSupplierProductRequest":{"type":"object","required":["version","purchase_uom","units_per_purchase","is_preferred"],"properties":{"effective_from":{"type":"string","nullable":true},"effective_to":{"type":"string","nullable":true},"is_preferred":{"type":"boolean"},"lead_time_days":{"type":"integer","format":"int32","nullable":true},"minimum_order_qty":{"type":"string","nullable":true},"order_multiple":{"type":"string","nullable":true},"purchase_uom":{"type":"string"},"quoted_unit_cost":{"type":"string","nullable":true},"supplier_barcode":{"type":"string","nullable":true},"supplier_description":{"type":"string","nullable":true},"supplier_sku":{"type":"string","nullable":true},"units_per_purchase":{"type":"string"},"version":{"type":"integer","format":"int32"}}},"UpdateSupplierRequest":{"type":"object","properties":{"account_number":{"type":"string","nullable":true},"accounting_mapping":{"nullable":true},"billing_address":{"nullable":true},"contacts":{"nullable":true},"currency_code":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"lead_time_days":{"type":"integer","format":"int32","nullable":true},"legal_name":{"type":"string","nullable":true},"name":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"payment_terms_days":{"type":"integer","format":"int32","nullable":true},"phone":{"type":"string","nullable":true},"shipping_address":{"nullable":true},"status":{"type":"string","nullable":true},"supplier_number":{"type":"string","nullable":true},"tax_identifier":{"type":"string","nullable":true}}},"UpdateTenantRequest":{"type":"object","properties":{"name":{"type":"string","nullable":true}}},"UpdateTerminalConfigRequest":{"type":"object","properties":{"allow_discounts":{"type":"boolean","nullable":true},"auto_print_receipt":{"type":"boolean","nullable":true},"clear_kds_enabled_override":{"type":"boolean","description":"Clear `kds_enabled` back to unset. Needed for the same reason\n`clear_touch_mode_override` is: a bare `Option<bool>` cannot express\n\"unset\", because `None` already means \"don't change\".","nullable":true},"clear_touch_mode_override":{"type":"boolean","description":"Clear `touch_mode` back to inheriting the company default. Needed\nbecause a bare `Option<bool>` cannot express \"unset\" — `None` already\nmeans \"don't change\".","nullable":true},"currency":{"type":"string","nullable":true},"kds_enabled":{"type":"boolean","description":"Run this terminal as a Kitchen Display, or omit to leave it unchanged.","nullable":true},"max_discount_percent":{"type":"string","nullable":true},"receipt_footer":{"type":"string","nullable":true},"receipt_number_prefix":{"type":"string","description":"Prefix stamped on this terminal's receipt numbers, e.g. \"T2\".\n\nWithout it two tills in one shop both emit `20260903-0001`, so the\nreceipt number a customer quotes on the phone is not a key at all. The\nterminal sanitises it to at most eight uppercase alphanumerics — it\nends up on printed paper and in a settings-key fragment.","nullable":true},"receipt_printer_type":{"type":"string","nullable":true},"store_address":{"type":"string","nullable":true},"store_name":{"type":"string","nullable":true},"tax_rate":{"type":"string","nullable":true},"touch_mode":{"type":"boolean","nullable":true},"ui_density":{"type":"string","description":"`\"comfortable\"`, `\"compact\"`, or `\"\"` to clear the override.","nullable":true},"ui_scale_percent":{"type":"integer","format":"int32","description":"UI scale for THIS terminal, 80..=150, or omit to leave it unchanged.\nSend `0` to clear the override and fall back to the company default —\n`null` is indistinguishable from \"absent\" once this struct is\ndeserialised, and a nested `Option<Option<_>>` in a request body is a\nworse contract for the admin client than a documented sentinel.","nullable":true}}},"UpdateThreadStatusRequest":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"open | pending | closed"}}},"UpdateUserAccessRequest":{"type":"object","description":"Combined access update: role + active flag + per-permission override,\napplied atomically by `PUT /api/users/{id}/access`.\n\n`custom_permissions` and `clear_custom_permissions` are deliberately two\nfields so the API can distinguish three states the old\n`PUT /api/users/{id}/permissions` endpoint conflated:\n- field omitted → leave the stored override untouched\n- `custom_permissions: []` → explicit EMPTY override (no permissions)\n- `clear_custom_permissions: true` → drop the override (NULL → role defaults)","properties":{"clear_custom_permissions":{"type":"boolean","description":"Clear the stored override so the user falls back to role defaults.\nMutually exclusive with `custom_permissions`.","nullable":true},"custom_permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"},"description":"Set a per-user permission override. An empty list revokes every\npermission. Omitted = override unchanged.","nullable":true},"is_active":{"type":"boolean","description":"Enable/disable the account. Omitted = unchanged.","nullable":true},"role":{"allOf":[{"$ref":"#/components/schemas/UserRole"}],"nullable":true}}},"UpdateUserPermissionsResponse":{"type":"object","required":["message","user_id"],"properties":{"custom_permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"},"nullable":true},"message":{"type":"string"},"user_id":{"type":"integer","format":"int32"}}},"UpdateUserRequest":{"type":"object","properties":{"email":{"type":"string","nullable":true},"first_name":{"type":"string","nullable":true},"is_active":{"type":"boolean","nullable":true},"last_name":{"type":"string","nullable":true},"role":{"allOf":[{"$ref":"#/components/schemas/UserRole"}],"nullable":true}}},"UpsertExternalReferenceRequest":{"type":"object","required":["entity_type","entity_id","source_system","external_id"],"properties":{"entity_id":{"type":"string"},"entity_type":{"type":"string"},"external_id":{"type":"string"},"source_system":{"type":"string"}}},"UpsertModifierOptionsInput":{"type":"object","properties":{"options":{"type":"array","items":{"$ref":"#/components/schemas/CreateModifierOptionInput"}}}},"UpsertPortalSettingsRequest":{"type":"object","description":"Upsert portal settings (admin-only)","properties":{"allow_out_of_stock_orders":{"type":"boolean","description":"Allow ordering products whose stock quantity is zero or negative","nullable":true},"catalog_display_settings":{"allOf":[{"$ref":"#/components/schemas/CatalogDisplaySettings"}],"nullable":true},"display_name":{"type":"string","description":"Human-readable name shown on the login page","nullable":true},"is_enabled":{"type":"boolean","description":"Whether the portal is accessible to customers","nullable":true},"login_page_content":{"type":"string","description":"Extra text/markdown shown on the login page","nullable":true},"logo_url":{"type":"string","description":"Logo URL (set via the logo upload endpoint)","nullable":true},"portal_name":{"type":"string","description":"Subdomain slug (globally unique, DNS label format)","nullable":true},"primary_color":{"type":"string","description":"Primary brand color (hex, e.g. #3B82F6)","nullable":true},"secondary_color":{"type":"string","description":"Secondary brand color (hex)","nullable":true},"show_only_in_stock":{"type":"boolean","description":"Show only products with strictly positive stock quantity","nullable":true},"show_only_with_images":{"type":"boolean","description":"Show only products that have at least one image","nullable":true}}},"UpsertPrinterProfilesInput":{"type":"object","properties":{"profiles":{"type":"array","items":{"$ref":"#/components/schemas/CreatePrinterProfileInput"}},"routing_rules":{"type":"array","items":{"$ref":"#/components/schemas/PrinterRoutingRuleInput"}}}},"UpsertPrinterProfilesResponse":{"type":"object","description":"`PUT /api/horeca/printer-profiles` 200 body.\n\nThe handler has always answered `{\"success\": true}`; it simply had no\n`body = ...` in its utoipa annotation, so the generated TS client typed\nthis response as `unknown` — the last untyped horeca 200 (audit 05 §1.2\nrow 25). Declaring the existing shape is additive: the wire payload does\nnot change.","required":["success"],"properties":{"success":{"type":"boolean"}}},"UserClaims":{"type":"object","required":["id","email","name","company_id","role","permissions"],"properties":{"company_id":{"type":"string"},"email":{"type":"string"},"id":{"type":"integer","format":"int32"},"location_ids":{"type":"array","items":{"type":"string"},"description":"IDs of store locations this user is assigned to.\nEmpty vec = admin (unrestricted) or unassigned."},"name":{"type":"string"},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"}},"role":{"$ref":"#/components/schemas/UserRole"},"session_id":{"allOf":[{"$ref":"#/components/schemas/uuid.Uuid"}],"nullable":true}}},"UserListEnvelope":{"type":"object","required":["results","count"],"properties":{"count":{"type":"integer","minimum":0},"results":{"type":"array","items":{"$ref":"#/components/schemas/UserListResponse"}}}},"UserListResponse":{"type":"object","required":["id","username","email","first_name","last_name","role","is_active","is_staff","is_superuser","date_joined","has_custom_permissions"],"properties":{"date_joined":{"type":"string"},"email":{"type":"string"},"first_name":{"type":"string"},"has_custom_permissions":{"type":"boolean"},"id":{"type":"integer","format":"int32"},"is_active":{"type":"boolean"},"is_staff":{"type":"boolean"},"is_superuser":{"type":"boolean"},"last_login":{"type":"string","nullable":true},"last_name":{"type":"string"},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"},"nullable":true},"role":{"$ref":"#/components/schemas/UserRole"},"username":{"type":"string"}}},"UserMutationResponse":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}},"UserProfileResponse":{"type":"object","required":["id","username","email","first_name","last_name","company_id","role","permissions"],"properties":{"company_id":{"type":"string"},"company_name":{"type":"string","nullable":true},"email":{"type":"string"},"first_name":{"type":"string"},"id":{"type":"integer","format":"int32"},"last_name":{"type":"string"},"permissions":{"type":"array","items":{"$ref":"#/components/schemas/Permission"}},"role":{"$ref":"#/components/schemas/UserRole"},"username":{"type":"string"}}},"UserRole":{"type":"string","enum":["admin","staff","manager","employee","viewer","integration"]},"Uuid":{"type":"string","description":"UUID string","example":"550e8400-e29b-41d4-a716-446655440000"},"ValidateRequest":{"type":"object","description":"Body for `POST /api/query/{entity}/validate`.","required":["query"],"properties":{"query":{"type":"object","description":"The filter tree to validate and normalise."}}},"ValidateResponse":{"type":"object","description":"Response for `POST /api/query/{entity}/validate`.","required":["ok","errors"],"properties":{"errors":{"type":"array","items":{"$ref":"#/components/schemas/ValidationError"},"description":"Errors, empty when `ok` is true."},"normalized":{"type":"object","description":"The normalised AST, present when valid."},"ok":{"type":"boolean","description":"Whether the query is valid."}}},"ValidationError":{"type":"object","description":"A single validation error, shaped for inline display on the frontend.","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable machine code, mapped to `query.errors.*` on the frontend."},"field":{"type":"string","description":"Field key the error relates to, if any.","nullable":true},"message":{"type":"string","description":"English fallback message."}}},"VendorCreditAllocationResponse":{"type":"object","required":["supplier_bill_id","bill_number","amount","created_at"],"properties":{"amount":{"type":"string"},"bill_number":{"type":"string"},"created_at":{"type":"string"},"supplier_bill_id":{"$ref":"#/components/schemas/Uuid"}}},"VendorCreditListResponse":{"type":"object","required":["results","total","limit","offset"],"properties":{"limit":{"type":"integer","format":"int64"},"offset":{"type":"integer","format":"int64"},"results":{"type":"array","items":{"$ref":"#/components/schemas/VendorCreditResponse"}},"total":{"type":"integer","format":"int64"}}},"VendorCreditResponse":{"type":"object","required":["id","credit_number","supplier_id","supplier_name","status","credit_date","currency_code","amount","amount_applied","remaining","created_at","version","allocations"],"properties":{"allocations":{"type":"array","items":{"$ref":"#/components/schemas/VendorCreditAllocationResponse"}},"amount":{"type":"string"},"amount_applied":{"type":"string"},"created_at":{"type":"string"},"credit_date":{"type":"string"},"credit_number":{"type":"string"},"currency_code":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"notes":{"type":"string","nullable":true},"remaining":{"type":"string"},"status":{"type":"string","description":"`open`, `applied` or `void`."},"supplier_id":{"$ref":"#/components/schemas/Uuid"},"supplier_name":{"type":"string"},"supplier_reference":{"type":"string","nullable":true},"supplier_return_id":{"allOf":[{"$ref":"#/components/schemas/Uuid"}],"nullable":true},"supplier_return_number":{"type":"string","nullable":true},"version":{"type":"integer","format":"int32"},"void_reason":{"type":"string","nullable":true},"voided_at":{"type":"string","nullable":true}}},"VerifyEmailQuery":{"type":"object","description":"Query parameters for email verification","required":["token"],"properties":{"token":{"type":"string"}}},"VersionInfo":{"type":"object","description":"Version information for the API","required":["version","environment"],"properties":{"branch":{"type":"string","description":"Git branch (if available)","nullable":true},"build_time":{"type":"string","description":"Build timestamp (if available)","nullable":true},"commit":{"type":"string","description":"Git commit hash (if available)","nullable":true},"environment":{"type":"string","description":"Environment (dev, staging, production)"},"version":{"type":"string","description":"API version (e.g., \"1.0.0\")"}}},"VoidDocumentRequest":{"type":"object","required":["version","reason"],"properties":{"reason":{"type":"string"},"version":{"type":"integer","format":"int32"}}},"VoidOrderItemInput":{"type":"object","description":"Body for `POST /api/horeca/open-orders/{id}/items/{item_id}/void`\n(POS-ship workstream E; audit 05 finding B3).\n\nBefore this endpoint the ONLY way to remove a line was\n`DELETE …/items/{item_id}`, an unconditional hard delete with no reason,\nno sent-guard and no audit-log entry — so a fired, cooked dish could be\nremoved from a tab leaving no record that it ever existed. The\n`voided_reason` / `voided_by_user_id` columns had shipped but no handler\never wrote them.","required":["reason"],"properties":{"approved_by_employee_id":{"type":"string","description":"The employee who authorised the void at the terminal's approval\nkeypad, when one was required (POS-ship workstream E / H8 step 4).\n\nThis is a THIRD identity, distinct from the two the audit row already\ncarries: `user_claims` is the account the terminal is signed in as\n(in practice a shared admin account — see audit 02-rbac.md §A7), and\nthe active cashier is whoever is working the till. Neither is the\nmanager who walked over and entered a PIN, and without recording that\nmanager a void is not defensible to a merchant investigating a\nshortage.\n\nBoth fields are optional and additive: a client that does not send\nthem behaves exactly as before, and a void that needed no approval\nlegitimately has none.","nullable":true},"approved_by_name":{"type":"string","nullable":true},"reason":{"type":"string","description":"Why the line is being voided. Required and non-empty: an unexplained\nvoid on a fired item is exactly the audit hole this endpoint closes."}}},"VoidPaymentRequest":{"type":"object","description":"Body of `POST /fulfillment/payments/{id}/void`.","properties":{"reason":{"type":"string","nullable":true}}},"WebappAccount":{"type":"object","required":["id","created_at","updated_at","name","company_id","customer_type","account_number","excluded","is_deleted","store_credit","ar_balance"],"properties":{"account_number":{"type":"string"},"account_seq_number":{"type":"string","description":"Company-scoped human-readable sequential id (e.g. `AC-00042`). Distinct\nfrom the legacy globally-unique user-entered `account_number`. Nullable:\ngenerated on create, backfilled for existing rows.","nullable":true},"address":{"type":"string","nullable":true},"ar_balance":{"type":"string","description":"What the customer owes on account. Positive = they owe us."},"catalog_rule":{"type":"string","nullable":true},"company_id":{"type":"string"},"created_at":{"type":"string"},"customer_type":{"type":"string"},"deleted_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"email":{"type":"string","nullable":true},"excluded":{"type":"boolean"},"id":{"type":"string"},"is_deleted":{"type":"boolean"},"merged_into_id":{"type":"string","nullable":true},"min_order_qty":{"type":"integer","format":"int32","nullable":true},"name":{"type":"string"},"phone":{"type":"string","nullable":true},"preferred_currency":{"type":"string","nullable":true},"store_credit":{"type":"string","description":"Prepaid balance the customer can spend at the till. Maintained\ntransactionally alongside `pos_account_ledger` entries; that ledger is\nthe audit trail and this is the running total."},"updated_at":{"type":"string"}}},"WebappCategory":{"type":"object","required":["id","created_at","updated_at","name","company_id","excluded","level","lft","rght","tree_id","is_deleted"],"properties":{"code":{"type":"string","nullable":true},"company_id":{"type":"string"},"created_at":{"type":"string"},"deleted_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"excluded":{"type":"boolean"},"id":{"type":"string"},"is_deleted":{"type":"boolean"},"level":{"type":"integer","format":"int32"},"lft":{"type":"integer","format":"int32"},"merged_into_id":{"type":"string","nullable":true},"name":{"type":"string"},"parent_id":{"type":"string","nullable":true},"rght":{"type":"integer","format":"int32"},"tree_id":{"type":"integer","format":"int32"},"updated_at":{"type":"string"}}},"WebappCompany":{"type":"object","required":["id","created_at","updated_at","name","notification_emails","exclude_categories","exclude_products","min_qty","is_deleted"],"properties":{"created_at":{"$ref":"#/components/schemas/chrono.DateTime"},"deleted_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"exclude_categories":{"type":"string"},"exclude_products":{"type":"string"},"id":{"type":"string"},"is_deleted":{"type":"boolean"},"logo":{"type":"string","nullable":true},"min_qty":{"type":"integer","format":"int32"},"name":{"type":"string"},"notification_emails":{"type":"string"},"settings":{"nullable":true},"updated_at":{"$ref":"#/components/schemas/chrono.DateTime"}}},"WebappProduct":{"type":"object","required":["id","created_at","updated_at","name","description","code","code_type","company_id","item_number","msrp_price","qty","regular_price","wholesale_price","excluded","is_deleted"],"properties":{"category_id":{"type":"string","nullable":true},"code":{"type":"string"},"code_type":{"type":"string"},"company_id":{"type":"string"},"created_at":{"type":"string"},"deleted_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"description":{"type":"string"},"excluded":{"type":"boolean"},"id":{"type":"string"},"is_deleted":{"type":"boolean"},"item_number":{"type":"string"},"min_order_qty":{"type":"integer","format":"int32","nullable":true},"msrp_price":{"$ref":"#/components/schemas/BigDecimal"},"name":{"type":"string"},"product_number":{"type":"string","description":"Company-scoped human-readable sequential id (e.g. `P-00042`). Nullable:\ngenerated on create, backfilled for existing rows.","nullable":true},"product_type":{"type":"string","description":"Canonical enrichment grouping key (e.g. `t-shirt`, `running shoe`).\nNormalized on approve (see `normalize_product_type`); NULL until the\nproduct is enriched/backfilled. Travels over sync to mobile/POS/B2B.","nullable":true},"qty":{"type":"integer","format":"int32"},"regular_price":{"$ref":"#/components/schemas/BigDecimal"},"reserved_qty":{"type":"integer","format":"int32","description":"Units committed to open sales orders (see `stock_reservations` ledger).\nAvailable-to-sell = qty - `reserved_qty`."},"tax_rule_id":{"allOf":[{"$ref":"#/components/schemas/uuid.Uuid"}],"nullable":true},"updated_at":{"type":"string"},"wholesale_price":{"$ref":"#/components/schemas/BigDecimal"}}},"WebappProductImage":{"type":"object","required":["id","created_at","updated_at","image","product_id","is_deleted","sort_order","is_primary","kind"],"properties":{"created_at":{"type":"string"},"deleted_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"id":{"type":"string"},"image":{"type":"string"},"image_thumb_200":{"type":"string","nullable":true},"image_thumb_400":{"type":"string","nullable":true},"is_deleted":{"type":"boolean"},"is_primary":{"type":"boolean","description":"The hero image. At most one per product, enforced by a partial unique\nindex (`idx_webapp_productimage_one_primary`)."},"kind":{"type":"string","description":"Provenance: `original` | `edited` | `ai_edited`. Derived images are\nexcluded from the AI vision colour pass so a composite carrying text or\nbadges can never poison colour enrichment."},"name":{"type":"string","nullable":true},"product_id":{"type":"string"},"sort_order":{"type":"integer","format":"int32","description":"Explicit gallery order within a product. Backfilled from the previous\nimplicit `created_at ASC` ordering, so behaviour is unchanged on deploy."},"source_document_id":{"type":"string","description":"Links a derived image back to the `image_documents` row that produced\nit, so an edit can be re-opened instead of redone from scratch.","nullable":true},"updated_at":{"type":"string"}}},"WebappSalesOrder":{"type":"object","required":["id","created_at","updated_at","agent_id","account_id","total","company_id","date","note","is_deleted","status","source","payment_status"],"properties":{"account_id":{"type":"string"},"agent_id":{"type":"integer","format":"int32"},"company_id":{"type":"string"},"created_at":{"type":"string"},"customer_email":{"type":"string","nullable":true},"customer_name":{"type":"string","nullable":true},"customer_phone":{"type":"string","nullable":true},"date":{"$ref":"#/components/schemas/NaiveDateTime"},"deleted_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"delivered_at":{"allOf":[{"$ref":"#/components/schemas/NaiveDateTime"}],"nullable":true},"delivery_carrier":{"type":"string","nullable":true},"delivery_method":{"type":"string","nullable":true},"delivery_status":{"type":"string","nullable":true},"delivery_tracking_code":{"type":"string","nullable":true},"discount_code":{"type":"string","description":"Storefront discount code redeemed on this order (webshop builder).","nullable":true},"discount_total":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"estimated_delivery":{"allOf":[{"$ref":"#/components/schemas/chrono.NaiveDate"}],"nullable":true},"id":{"type":"string"},"is_deleted":{"type":"boolean"},"note":{"type":"string"},"order_number":{"type":"string","nullable":true},"payment_status":{"type":"string"},"picking_priority":{"type":"string","nullable":true},"picking_status":{"type":"string","nullable":true},"picking_zone":{"type":"string","nullable":true},"public_token":{"type":"string","nullable":true},"refund_total":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"shipping_address":{"type":"object","nullable":true},"source":{"type":"string"},"status":{"type":"string"},"status_raw":{"type":"string","description":"Legacy-passthrough column (E-2.2 order state machine) — see the\n`2026-08-03-120000-0000_add_sales_order_status_raw` migration.","nullable":true},"stripe_payment_intent_id":{"type":"string","description":"Stripe PaymentIntent behind a paid storefront order (webhook / confirm).","nullable":true},"stripe_session_id":{"type":"string","nullable":true},"tax_total":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"total":{"$ref":"#/components/schemas/BigDecimal"},"updated_at":{"type":"string"}}},"WebappSalesOrderItem":{"type":"object","required":["id","created_at","updated_at","qty","product_id","total","sales_order_id","price","is_deleted"],"properties":{"created_at":{"type":"string"},"deleted_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"discount_snapshot":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"id":{"type":"string"},"is_deleted":{"type":"boolean"},"price":{"$ref":"#/components/schemas/BigDecimal"},"price_source":{"type":"string","nullable":true},"product_id":{"type":"string"},"qty":{"type":"integer","format":"int32"},"sales_order_id":{"type":"string"},"tax_rate_snapshot":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"total":{"$ref":"#/components/schemas/BigDecimal"},"unit_price_snapshot":{"allOf":[{"$ref":"#/components/schemas/BigDecimal"}],"nullable":true},"updated_at":{"type":"string"},"variant_id":{"type":"string","description":"Product variant sold on this line (storefront), if any.","nullable":true},"variant_label":{"type":"string","description":"Human label of that variant at order time (e.g. \"Size M / Blue\").","nullable":true}}},"WebappUserProfile":{"type":"object","required":["id","created_at","updated_at","company_id","user_id","is_deleted","role"],"properties":{"company_id":{"type":"string"},"created_at":{"type":"string"},"custom_permissions":{"description":"Custom permissions that override role-based defaults (null = use role defaults)","nullable":true},"deleted_at":{"allOf":[{"$ref":"#/components/schemas/chrono.DateTime"}],"nullable":true},"id":{"type":"string"},"is_deleted":{"type":"boolean"},"role":{"type":"string"},"updated_at":{"type":"string"},"user_id":{"type":"integer","format":"int32"}}},"WebhookEventTypeResponse":{"type":"object","description":"One subscribable event type, served to the admin UI so the endpoint form\ncan offer a real picker instead of free text.","required":["event","label","description"],"properties":{"description":{"type":"string"},"event":{"type":"string","description":"Wire value stored in `events` (e.g. `order.created`)."},"label":{"type":"string"}}},"WebhookTokenResponse":{"type":"object","description":"`POST /api/pipelines/{id}/webhook-token` response — the ONLY place the\ntoken is ever returned. Shown once in the UI; rotate to get a new one.","required":["token","path"],"properties":{"path":{"type":"string","description":"The path an external system POSTs to (relative to the API host)."},"token":{"type":"string"}}},"WorkZoneSettings":{"type":"object","description":"The short always-open list above the sidebar's section accordion — the\nhandful of destinations a given tenant actually touches every day.\n\nEmpty `default_paths` (the default) means \"derive it\": the admin frontend\nintersects its registry's `tier: \"primary\"` set with this company's\n`onboarding.enabled_channels`, so a horeca tenant leads with POS and\nZ-Reports while a wholesaler leads with Quotes and Customers — with nothing\nto configure. Setting `default_paths` overrides that derivation outright.\n\n`default_paths` is `Vec<String>` and deliberately NOT a typed route enum. A\nroute renamed or removed in a later frontend release must degrade to being\nignored, never fail deserialization — see the warning above\n`CompanySettings`: one rejected field silently resets EVERY setting for the\ntenant. The frontend already drops paths it cannot resolve.","properties":{"default_paths":{"type":"array","items":{"type":"string"},"description":"Explicit destination paths, in display order. Empty = derive from\nchannels."},"max_items":{"type":"integer","format":"int32","description":"How many rows the zone may hold, counting the user's own pinned\nfavourites."}}},"bigdecimal.BigDecimal":{"type":"string","description":"Arbitrary precision decimal number represented as string","example":"123.45"},"chrono.DateTime":{"type":"string","description":"Date-time with timezone in ISO 8601 format","example":"2024-01-15T10:30:00Z"},"chrono.NaiveDate":{"type":"string","description":"Calendar date (no time) in ISO 8601 format","example":"2024-01-15"},"chrono.NaiveDateTime":{"type":"string","description":"Date-time without timezone in ISO 8601 format","example":"2024-01-15T10:30:00"},"uuid.Uuid":{"type":"string","description":"UUID string","example":"550e8400-e29b-41d4-a716-446655440000"}}},"security":[{"bearer_auth":["bearer"]}],"tags":[{"name":"Authentication","description":"User authentication and authorization endpoints"},{"name":"Products","description":"Product management and inventory endpoints"},{"name":"Accounts","description":"Customer account management endpoints"},{"name":"Categories","description":"Product category management endpoints"},{"name":"Sales Orders","description":"Sales order management and tracking endpoints"},{"name":"POS Terminals","description":"Desktop POS terminal management and configuration endpoints"},{"name":"POS Checkout","description":"Point-of-sale session management and order creation"},{"name":"Dashboard","description":"Dashboard statistics and metrics endpoints"},{"name":"Reports","description":"Business intelligence and reporting endpoints"},{"name":"Sync","description":"Mobile offline sync endpoints"},{"name":"Users","description":"User management and profile endpoints"},{"name":"Settings","description":"Company settings and configuration endpoints"},{"name":"Health","description":"System health and monitoring endpoints"},{"name":"Customer Portal","description":"B2B self-serve portal endpoints (use portal JWT)"},{"name":"Portal Users","description":"Admin management of B2B portal user accounts"},{"name":"Portal Settings","description":"Admin management of B2B portal configuration"},{"name":"Internal Admin","description":"SaaS operator control panel for platform management"},{"name":"backups","description":"Company data backup management endpoints"},{"name":"ExternalReferences","description":"Source-system identity mapping for imports and integrations"},{"name":"image_documents","description":"Image editor document CRUD (editable fabric.js scenes)"},{"name":"Billing","description":"Tenant-facing Stripe subscription management endpoints"},{"name":"procurement","description":"Suppliers, purchase orders, goods receipts, and opening inventory cost"},{"name":"customer-returns","description":"Customer returns against fulfilled sales orders"},{"name":"supplier-returns","description":"Returns shipped back to suppliers"},{"name":"stock-transfers","description":"Inter-location stock transfers"},{"name":"inventory-counts","description":"Controlled physical-inventory count sessions"}]}