openapi: 3.0.3 info: title: Copenhagen Truck Wash API description: | This API provides access to the Copenhagen Truck Wash system, managing orders, bookings, departments, products, customers, and various integrations including e-conomic, Stripe, XLVask, and more. ## Authentication Most endpoints require authentication using a Bearer token obtained from the `/auth/login` or `/auth/employee/login` endpoints. ## Permissions Many endpoints require specific permissions that are assigned to user groups/roles. ## Subusers and customer targeting When authenticated as a subuser, most customer-scoped endpoints require an explicit target customer context. Provide the header `X-Customer-Number: ` to target a specific customer. If omitted, the API attempts to infer the customer from the authenticated user context when possible. Classic user sessions ignore this header. version: 1.0.0 contact: name: Copenhagen Truck Wash email: support@truckwash.dk servers: - url: https://api.truckwash.dk description: Production server (.dk) - url: https://api.truckwash.io description: Production server (.io) - url: http://localhost/api description: Local development server security: - BearerAuth: [] tags: - name: Authentication description: User and employee authentication endpoints - name: Users description: User management and customer operations - name: Search description: System-wide search endpoints - name: Orders description: Order creation, management, and retrieval - name: Order Items description: Managing items within orders - name: Bookings description: Booking management for wash services - name: Departments description: Department and location management - name: Products description: Product catalog and pricing - name: Categories description: Product category management - name: Invoices description: Invoice generation and management - name: Payments description: Payment processing and collection - name: Vehicles description: Vehicle registration and management - name: Notifications description: System notifications and alerts - name: Statistics description: Business analytics and reporting - name: Modules description: Third-party integrations and modules - name: Attachments description: File upload and attachment management - name: Forms description: Form submissions and management - name: Worker description: System worker status and maintenance - name: Plate Scans description: License plate scanning operations - name: Config description: Module configuration management - name: Branding description: Branding options management - name: Roles description: Role and permission management - name: Self-Serve description: Self-serve lane operations and questions - name: Goals description: Department goals management - name: Subusers description: Subuser registration and setup - name: Bird description: Voice Calls via Bird paths: # Bird Voice Calls /bird/voice/calls: post: tags: - Bird summary: Create/place a voice call via Bird operationId: birdCreateVoiceCall parameters: - in: query name: workspaceId schema: type: string required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string required: false description: Bird Channel identifier (falls back to module configuration if omitted) requestBody: required: true content: application/json: schema: type: object additionalProperties: true properties: to: type: string description: E.164 phone number of the callee example: "+4511122233" from: type: string description: E.164 phone number of the caller (sender) example: "+4599988877" responses: '200': description: Call created content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallSingleResponse' } get: tags: - Bird summary: List voice calls operationId: birdListVoiceCalls parameters: - in: query name: workspaceId schema: type: string required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string required: false description: Bird Channel identifier (falls back to module configuration if omitted) - in: query name: page schema: type: integer required: false responses: '200': description: A list of calls content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallListResponse' } /bird/voice/calls/{id}: get: tags: - Bird summary: Get a voice call by ID operationId: birdGetVoiceCall parameters: - in: query name: workspaceId schema: type: string required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string required: false description: Bird Channel identifier (falls back to module configuration if omitted) - in: path name: id required: true schema: type: string responses: '200': description: Call details content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallSingleResponse' } /bird/voice/calls/{id}/hangup: post: tags: - Bird summary: Hang up a voice call by ID operationId: birdHangupVoiceCall parameters: - in: query name: workspaceId schema: type: string required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string required: false description: Bird Channel identifier (falls back to module configuration if omitted) - in: path name: id required: true schema: type: string responses: '200': description: Hangup requested content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallSingleResponse' } /bird/voice/calls/{id}/say: post: tags: - Bird summary: Say a message on an active voice call and hang up afterwards operationId: birdSayOnVoiceCall parameters: - in: query name: workspaceId schema: type: string required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string required: false description: Bird Channel identifier (falls back to module configuration if omitted) - in: path name: id required: true schema: type: string description: Call identifier requestBody: required: true content: application/json: schema: type: object required: - text properties: text: type: string description: The text message to play via TTS example: "The gate will open shortly." locale: type: string description: The locale to use for the TTS voice (e.g. en-US) example: "en-US" voice: type: string description: The voice identifier to use example: "male" loop: type: integer description: Number of times to loop the message example: 1 timeout: type: integer description: Timeout in seconds for the TTS action example: 1 hangup: type: boolean description: Whether to hang up the call after the message finishes playing (defaults to true) example: true responses: '200': description: TTS action requested content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallSingleResponse' } /bird/voice/calls/test-outbound: post: tags: - Bird summary: Place a test outbound call and hang up when accepted operationId: birdTestOutboundVoiceCall description: Calls +45 42 33 11 28 and hangs up when the call reaches accepted/ongoing state. parameters: - in: query name: workspaceId schema: type: string required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string required: false description: Bird Channel identifier (falls back to module configuration if omitted) requestBody: required: false content: application/json: schema: type: object additionalProperties: true properties: from: type: string description: Caller E.164 number to use for the test call example: "+4599988877" pollIntervalSeconds: type: integer minimum: 1 description: Poll interval while waiting for accepted status example: 2 maxPollSeconds: type: integer minimum: 5 description: Max time to wait before timing out example: 30 hangupCause: type: string description: Optional hangup cause passed through to Bird responses: '200': description: Test call created and either hung up or timed out content: application/json: schema: { $ref: '#/components/schemas/BirdTestOutboundCallResponse' } # Bird Numbers /bird/numbers: get: tags: - Bird summary: List your numbers operationId: birdListNumbers parameters: - in: query name: workspaceId required: false schema: type: string description: Bird Workspace identifier (optional if configured) - in: query name: page required: false schema: type: integer - in: query name: limit required: false schema: type: integer responses: '200': description: A list of numbers content: application/json: schema: $ref: '#/components/schemas/BirdNumberListResponse' /bird/numbers/{id}: get: tags: - Bird summary: Get a number by ID operationId: birdGetNumber parameters: - in: query name: workspaceId required: false schema: type: string description: Bird Workspace identifier (optional if configured) - in: path name: id required: true schema: type: string responses: '200': description: Number details content: application/json: schema: $ref: '#/components/schemas/BirdNumberSingleResponse' delete: tags: - Bird summary: Delete/release a number by ID operationId: birdDeleteNumber parameters: - in: query name: workspaceId required: false schema: type: string description: Bird Workspace identifier (optional if configured) - in: path name: id required: true schema: type: string responses: '200': description: Number deletion/release accepted content: application/json: schema: type: object additionalProperties: true # Bird Voice Flash Calling /bird/voice/flash-calls: post: tags: - Bird summary: Create/place a flash call via Bird operationId: birdCreateFlashCall parameters: - in: query name: workspaceId schema: type: string required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string required: false description: Bird Channel identifier (falls back to module configuration if omitted) requestBody: required: true content: application/json: schema: type: object additionalProperties: true responses: '200': description: Flash call created content: application/json: schema: type: object additionalProperties: true get: tags: - Bird summary: List flash calls operationId: birdListFlashCalls parameters: - in: query name: workspaceId schema: type: string required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string required: false description: Bird Channel identifier (falls back to module configuration if omitted) - in: query name: page schema: type: integer required: false responses: '200': description: A list of flash calls content: application/json: schema: type: object additionalProperties: true /bird/voice/flash-calls/{id}: get: tags: - Bird summary: Get a flash call by ID operationId: birdGetFlashCall parameters: - in: query name: workspaceId schema: type: string required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string required: false description: Bird Channel identifier (falls back to module configuration if omitted) - in: path name: id required: true schema: type: string responses: '200': description: Flash call details content: application/json: schema: type: object additionalProperties: true post: tags: - Bird summary: Complete/end a flash call by ID description: Posts a completion/update payload to the flash call resource to finalize verification. operationId: birdEndFlashCall parameters: - in: query name: workspaceId schema: type: string required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string required: false description: Bird Channel identifier (falls back to module configuration if omitted) - in: path name: id required: true schema: type: string requestBody: required: false content: application/json: schema: type: object additionalProperties: true responses: '200': description: Flash call completed content: application/json: schema: type: object additionalProperties: true /bird/voice/flash-calls/end: post: tags: - Bird summary: Complete/end a flash call using from/to numbers description: Ends an ongoing flash call by specifying the originating and destination numbers. operationId: birdEndFlashCallByNumbers parameters: - in: query name: workspaceId schema: type: string required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string required: false description: Bird Channel identifier (falls back to module configuration if omitted) requestBody: required: true content: application/json: schema: type: object additionalProperties: true properties: from: type: string description: E.164 formatted caller number example: "+4599988877" to: type: string description: E.164 formatted callee number example: "+4511122233" responses: '200': description: Flash call completed (by numbers) content: application/json: schema: type: object additionalProperties: true # Subusers (public registration + setup) /subusers: get: tags: - Subusers summary: List subusers visible to the authenticated user description: | Returns a paginated list of subusers (drivers) that have enabled grants tied to the authenticated user's customer number. Only subusers with at least one enabled, non-deleted grant for the caller's customer are returned. operationId: listSubusers parameters: - name: page in: query required: false schema: type: integer minimum: 1 - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 1000 - name: search in: query required: false schema: type: string - name: include_non_enabled in: query required: false description: Include subusers that only have non-enabled grants (default false) schema: type: boolean responses: '200': description: List of visible subusers content: application/json: schema: type: array items: type: object properties: id: type: integer username: type: string nullable: true name: type: string nullable: true email: type: string format: email nullable: true phone_country_code: type: integer nullable: true phone: type: integer nullable: true created_at: type: string format: date-time nullable: true updated_at: type: string format: date-time nullable: true suspended_at: type: string format: date-time nullable: true two_factor_enabled: type: boolean description: Indicates if 2FA is enabled for this account permissions: type: array description: Aggregated permission keys granted for the caller's customer items: type: string '401': { $ref: '#/components/responses/Unauthorized' } '500': { $ref: '#/components/responses/InternalServerError' } /subusers/me: get: tags: - Subusers summary: Get current subuser profile description: | Returns the authenticated subuser (driver) profile and their enabled grants grouped by `billing_customer_number`. Notes: - This endpoint is available only to authenticated subuser sessions. - It does not require the `X-Customer-Number` header; all enabled, non-deleted grants for the subuser are included in the response. operationId: getCurrentSubuser responses: '200': description: Current subuser details content: application/json: schema: $ref: '#/components/schemas/SubuserSelf' '401': { $ref: '#/components/responses/Unauthorized' } '500': { $ref: '#/components/responses/InternalServerError' } post: tags: - Subusers summary: Create a subuser registration description: | Creates a subuser (driver) account using a company's CVR and a phone number. Validates the CVR via e-conomic, ensures the phone number is not already in use, and if SMS is enabled sends a setup link by SMS for the user to complete registration. operationId: createSubuser security: [] requestBody: required: true content: application/json: schema: type: object required: - cvr - phone_country_code - phone properties: cvr: type: integer description: Danish CVR (8 digits) example: 12345678 phone_country_code: type: integer description: Phone country code (1–3 digits) example: 45 phone: type: integer description: Phone number (4–15 digits, no leading +) example: 12345678 responses: '200': description: Subuser created (or pending setup) and company identified content: application/json: schema: type: object properties: cvr: type: integer example: 12345678 customer_number: type: integer description: Matched e-conomic customer number example: 1000 '400': { $ref: '#/components/responses/BadRequest' } '404': { $ref: '#/components/responses/NotFound' } '500': { $ref: '#/components/responses/InternalServerError' } /subusers/{id}: get: tags: - Subusers summary: Get a subuser by ID (visible by grant) description: | Returns the subuser if the authenticated user has at least one enabled, non-deleted grant for their customer number to this subuser. Otherwise returns 404. operationId: getSubuser parameters: - name: id in: path required: true schema: type: integer responses: '200': description: Subuser details content: application/json: schema: type: object properties: id: type: integer username: type: string nullable: true name: type: string nullable: true email: type: string format: email nullable: true phone_country_code: type: integer nullable: true phone: type: integer nullable: true created_at: type: string format: date-time nullable: true updated_at: type: string format: date-time nullable: true suspended_at: type: string format: date-time nullable: true permissions: type: array description: Aggregated permission keys granted for the caller's customer items: type: string '401': { $ref: '#/components/responses/Unauthorized' } '404': { $ref: '#/components/responses/NotFound' } '500': { $ref: '#/components/responses/InternalServerError' } /subusers/setup: get: tags: - Subusers summary: Validate setup token description: Validates a subuser setup token generated during registration. operationId: validateSubuserSetupToken security: [] parameters: - name: token in: query required: true schema: type: string description: One-time setup token received via SMS responses: '200': description: Token is valid content: application/json: schema: type: object properties: message: type: string example: Token is valid subuser_id: type: integer example: 42 '400': { $ref: '#/components/responses/BadRequest' } '500': { $ref: '#/components/responses/InternalServerError' } post: tags: - Subusers summary: Complete subuser setup description: | Completes subuser setup by setting a password and basic profile fields. Accepts optional `username` and `email`. operationId: completeSubuserSetup security: [] requestBody: required: true content: application/json: schema: type: object required: - token - password - name properties: token: type: string description: One-time setup token password: type: string format: password minLength: 8 description: Must include at least one uppercase letter, one lowercase letter, and one number pattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).+$' name: type: string minLength: 3 maxLength: 255 username: type: string minLength: 3 maxLength: 255 email: type: string format: email minLength: 3 maxLength: 255 responses: '200': description: Setup completed content: application/json: schema: type: object properties: message: type: string example: Password set successfully '400': { $ref: '#/components/responses/BadRequest' } '500': { $ref: '#/components/responses/InternalServerError' } /subusers/auth/password: post: tags: - Subusers summary: Authenticate subuser with password description: | Authenticates a subuser (driver) using a password together with one of the supported identifiers: `phone_country_code` + `phone`, `subuser_id`, or `username`. On success, returns a newly generated session token for the subuser. operationId: subuserPasswordAuth security: [] requestBody: required: true content: application/json: schema: oneOf: - type: object required: [phone_country_code, phone, password] properties: phone_country_code: type: integer description: Phone country code (1–3 digits) minimum: 1 maximum: 999 example: 45 phone: type: integer description: Phone number (4–15 digits, no leading +) minimum: 1000 maximum: 999999999999999 example: 12345678 password: type: string format: password minLength: 8 maxLength: 255 - type: object required: [subuser_id, password] properties: subuser_id: type: integer description: Subuser ID example: 42 password: type: string format: password minLength: 8 maxLength: 255 - type: object required: [username, password] properties: username: type: string minLength: 3 maxLength: 255 example: jdoe password: type: string format: password minLength: 8 maxLength: 255 examples: withPhone: summary: Authenticate with phone value: phone_country_code: 45 phone: 12345678 password: MySecureP@ssw0rd withSubuserId: summary: Authenticate with subuser_id value: subuser_id: 42 password: MySecureP@ssw0rd withUsername: summary: Authenticate with username value: username: jdoe password: MySecureP@ssw0rd responses: '200': description: Authentication successful content: application/json: schema: oneOf: - type: object required: [session] properties: session: type: string description: Newly generated subuser session token example: "2f7a8c0e-9b1d-4c6a-91a9-1a2b3c4d5e6f" - type: object required: [2fa_required, 2fa_token] properties: 2fa_required: type: boolean example: true 2fa_token: type: string description: Temporary 2FA verification token example: "557a3e7b1a2b..." '400': { $ref: '#/components/responses/BadRequest' } '404': { $ref: '#/components/responses/NotFound' } '500': { $ref: '#/components/responses/InternalServerError' } /subusers/grants: get: tags: - Subusers summary: List subuser grants description: Returns subuser grant records filtered by `customer_number` and/or `subuser_id`. operationId: listSubuserGrants security: - BearerAuth: [] parameters: - name: customer_number in: query required: false schema: type: integer description: e-conomic customer number to filter by - name: subuser_id in: query required: false schema: type: integer description: Subuser ID to filter by responses: '200': description: Grants fetched content: application/json: schema: type: object properties: grants: type: array items: $ref: '#/components/schemas/SubuserGrant' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '500': { $ref: '#/components/responses/InternalServerError' } post: tags: - Subusers summary: Create subuser grant operationId: createSubuserGrant security: - BearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SubuserGrantCreateRequest' examples: default: value: customer_number: 1000 subuser_id: 42 enabled: true note: "Grant for bookings access" permissions: ["BOOKINGS_LIST", "BOOKINGS_ADD", "BOOKINGS_EDIT"] responses: '200': description: Grant created content: application/json: schema: type: object properties: grant: $ref: '#/components/schemas/SubuserGrant' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '500': { $ref: '#/components/responses/InternalServerError' } /subusers/grants/{id}: patch: tags: - Subusers summary: Update subuser grant operationId: updateSubuserGrant security: - BearerAuth: [] parameters: - in: path name: id required: true schema: type: integer requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SubuserGrantUpdateRequest' examples: enableOnly: value: enabled: true updatePermissions: value: permissions: ["VEHICLES_LIST", "SELFSERVE_ADD"] responses: '200': description: Grant updated content: application/json: schema: type: object properties: grant: $ref: '#/components/schemas/SubuserGrant' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '404': { $ref: '#/components/responses/NotFound' } '500': { $ref: '#/components/responses/InternalServerError' } delete: tags: - Subusers summary: Delete subuser grant operationId: deleteSubuserGrant security: - BearerAuth: [] parameters: - in: path name: id required: true schema: type: integer responses: '200': description: Grant deleted content: application/json: schema: type: object properties: message: type: string example: Grant deleted '401': { $ref: '#/components/responses/Unauthorized' } '404': { $ref: '#/components/responses/NotFound' } '500': { $ref: '#/components/responses/InternalServerError' } /subusers/permission-nodes: get: tags: - Subusers summary: List available subuser permission nodes description: Returns grouped permission nodes available for subuser grants. operationId: listSubuserPermissionNodes security: - BearerAuth: [] responses: '200': description: Permission nodes fetched content: application/json: schema: type: object properties: permission_nodes: type: array items: $ref: '#/components/schemas/PermissionNodeGroup' '401': { $ref: '#/components/responses/Unauthorized' } '500': { $ref: '#/components/responses/InternalServerError' } # Authentication Endpoints /auth/login: post: tags: - Authentication summary: Customer login description: Authenticate a customer using customer number and password operationId: customerLogin security: [] requestBody: required: true content: application/json: schema: type: object required: - customer_number - password - g_recaptcha_response properties: customer_number: type: integer description: Customer's e-conomic customer number example: 12345 password: type: string format: password description: Customer password minLength: 1 g_recaptcha_response: type: string description: reCAPTCHA verification token responses: '200': description: Login successful content: application/json: schema: oneOf: - type: object required: [token] properties: token: type: string description: Bearer authentication token example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." - type: object required: [2fa_required, 2fa_token] properties: 2fa_required: type: boolean example: true 2fa_token: type: string description: Temporary 2FA verification token example: "557a3e7b1a2b..." '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /auth/employee/login: post: tags: - Authentication summary: Employee login description: Authenticate an employee using user ID and password operationId: employeeLogin security: [] requestBody: required: true content: application/json: schema: type: object required: - user_id - password - g_recaptcha_response properties: user_id: type: integer description: Employee user ID example: 1 password: type: string format: password description: Employee password g_recaptcha_response: type: string description: reCAPTCHA verification token responses: '200': description: Login successful content: application/json: schema: oneOf: - type: object required: [token] properties: token: type: string description: Bearer authentication token - type: object required: [2fa_required, 2fa_token] properties: 2fa_required: type: boolean example: true 2fa_token: type: string description: Temporary 2FA verification token '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /auth/passkey/challenge: post: tags: - Authentication summary: Initiate passkey authentication challenge description: Generates a WebAuthn PublicKeyCredentialRequestOptions payload. If customer_number is provided, allowCredentials will be populated with existing passkeys for that account. Otherwise, a challenge is issued for discoverable credentials. operationId: passkeyChallenge security: [] requestBody: required: true content: application/json: schema: type: object required: - g_recaptcha_response properties: customer_number: type: integer description: Optional customer's e-conomic customer number example: 12345 g_recaptcha_response: type: string description: reCAPTCHA verification token responses: '200': description: Challenge generated content: application/json: schema: type: object properties: challenge_token: type: string description: Temporary token binding the challenge to the login attempt publicKey: type: object properties: challenge: type: string description: Base64URL-encoded challenge rpId: type: string description: Relying party ID (truckwash.io or localhost) example: truckwash.io timeout: type: integer description: Timeout in milliseconds userVerification: type: string enum: [required, preferred, discouraged] allowCredentials: type: array items: type: object properties: type: type: string example: public-key id: type: string description: Base64URL-encoded credential ID transports: type: array items: type: string '400': $ref: '#/components/responses/BadRequest' /auth/passkey/verify: post: tags: - Authentication summary: Verify passkey authentication and start session description: Verifies the WebAuthn assertion and challenge token. Returns a session token on success. operationId: passkeyVerify security: [] requestBody: required: true content: application/json: schema: type: object required: - challenge_token - credential - g_recaptcha_response properties: challenge_token: type: string description: The token returned by the challenge endpoint g_recaptcha_response: type: string description: reCAPTCHA verification token credential: type: object description: The WebAuthn PublicKeyCredential object (assertion) required: - id - rawId - type - response properties: id: type: string description: The credential ID (base64url) rawId: type: string description: The raw credential ID (base64url) type: type: string example: public-key clientExtensionResults: type: object response: type: object required: - clientDataJSON - authenticatorData - signature properties: clientDataJSON: type: string description: Base64URL-encoded client data authenticatorData: type: string description: Base64URL-encoded authenticator data signature: type: string description: Base64URL-encoded signature userHandle: type: string nullable: true description: Base64URL-encoded user handle responses: '200': description: Verification successful, session started content: application/json: schema: oneOf: - type: object required: [token] properties: token: type: string description: Bearer token for customer - type: object required: [session] properties: session: type: string description: Session token for subuser '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /auth/logout: get: tags: - Authentication summary: Logout description: Invalidate the current authentication token operationId: logout responses: '200': description: Logout successful content: application/json: schema: type: object properties: message: type: string example: "Logged out" '401': $ref: '#/components/responses/Unauthorized' /auth/session: get: tags: - Authentication summary: Get current session description: Retrieve information about the current authenticated user session operationId: getSession responses: '200': description: Session information retrieved successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/User' - type: object properties: two_factor_enabled: type: boolean description: Indicates if 2FA is enabled for this account '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /auth/2fa/setup: post: tags: - Authentication summary: Generate 2FA secret description: Generate a new TOTP secret for the authenticated user/subuser operationId: setup2fa requestBody: required: false content: application/json: schema: {} responses: '200': description: 2FA secret generated successfully content: application/json: schema: type: object properties: secret: type: string description: The base32 encoded TOTP secret qr_code_url: type: string description: An otpauth URL for generating a QR code '401': $ref: '#/components/responses/Unauthorized' /auth/2fa/enable: post: tags: - Authentication summary: Enable 2FA description: Verify a code and enable 2FA for the authenticated user/subuser operationId: enable2fa requestBody: required: true content: application/json: schema: type: object required: [code] properties: code: type: string description: The 6-digit TOTP code responses: '200': description: 2FA enabled successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /auth/2fa/disable: post: tags: - Authentication summary: Disable 2FA description: Verify a code and disable 2FA for the authenticated user/subuser operationId: disable2fa requestBody: required: true content: application/json: schema: type: object required: [code] properties: code: type: string description: The 6-digit TOTP code responses: '200': description: 2FA disabled successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /auth/2fa/verify: post: tags: - Authentication summary: Verify 2FA code during login description: Complete the login process by verifying the 2FA code operationId: verify2fa requestBody: required: true content: application/json: schema: type: object required: [2fa_token, code] properties: 2fa_token: type: string description: The temporary 2FA verification token code: type: string description: The 6-digit TOTP code responses: '200': description: Login successful content: application/json: schema: oneOf: - type: object required: [token] properties: token: type: string description: Bearer authentication token (for users/employees) - type: object required: [session] properties: session: type: string description: Session token (for subusers) '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /auth/reCAPTCHA/public: get: tags: - Authentication summary: Get reCAPTCHA configuration description: Retrieve public reCAPTCHA configuration for login forms operationId: getRecaptchaConfig security: [] responses: '200': description: reCAPTCHA configuration retrieved successfully content: application/json: schema: type: object properties: rate_limit: type: object properties: enabled: type: boolean limit: type: integer remaining: type: integer reset: type: integer warning: type: string nullable: true recaptcha: type: object /auth/register/cvr: post: tags: - Authentication summary: Register new customer by CVR description: Register a new customer account using Danish CVR number operationId: registerCustomerByCvr security: [] requestBody: required: true content: application/json: schema: type: object required: - cvr - companyPhone - invoiceEmail - contactEmail - contactPhone - contactName - g_recaptcha_response properties: cvr: type: string description: Danish CVR number minLength: 8 maxLength: 20 example: "44794780" companyPhone: type: integer description: Company phone number minimum: 10000000 maximum: 9999999999 example: 21754690 invoiceEmail: type: string format: email description: Email for invoices minLength: 5 maxLength: 255 example: "invoice@company.dk" contactEmail: type: string format: email description: Contact email minLength: 5 maxLength: 255 example: "contact@company.dk" contactPhone: type: integer description: Contact phone number minimum: 10000000 maximum: 9999999999 example: 21754690 contactName: type: string description: Contact person name example: "Mikkel" g_recaptcha_response: type: string description: reCAPTCHA verification token responses: '201': description: Customer registered successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' /auth/password-reset/request: post: tags: - Authentication summary: Request a customer password reset email description: Send an email with a password reset token to the customer's email address operationId: requestPasswordReset security: [] requestBody: required: true content: application/json: schema: type: object required: - customer_number - g_recaptcha_response properties: customer_number: type: integer description: The customer number example: 123456 g_recaptcha_response: type: string description: reCAPTCHA verification token responses: '200': description: Request processed content: application/json: schema: type: object properties: message: type: string '400': $ref: '#/components/responses/BadRequest' /auth/password-reset/validate: get: tags: - Authentication summary: Validate a customer password reset key description: Check if a password reset token is valid and hasn't expired operationId: validatePasswordResetToken security: [] parameters: - name: token in: query required: true schema: type: string description: The password reset token responses: '200': description: Token is valid content: application/json: schema: type: object properties: valid: type: boolean customer_id: type: integer '404': description: Invalid or expired token content: application/json: schema: $ref: '#/components/schemas/Error' /auth/password-reset/set: post: tags: - Authentication summary: Set a customer password using a reset key description: Update the customer password using a valid reset token operationId: setPasswordUsingResetToken security: [] requestBody: required: true content: application/json: schema: type: object required: - token - password - g_recaptcha_response properties: token: type: string description: The password reset token password: type: string description: The new password g_recaptcha_response: type: string description: reCAPTCHA verification token responses: '200': description: Password updated successfully content: application/json: schema: type: object properties: message: type: string '400': $ref: '#/components/responses/BadRequest' '404': description: Invalid or expired token content: application/json: schema: $ref: '#/components/schemas/Error' /su/intimidate: post: tags: - Authentication summary: Intimidate a user description: Create an authentication token for another user (Superuser only) operationId: suIntimidate requestBody: required: true content: application/json: schema: type: object required: [user_id] properties: user_id: {type: integer} responses: '200': description: Success content: application/json: schema: type: object properties: token: {type: string} # User Endpoints /users: get: tags: - Users summary: List users description: Retrieve a paginated list of users operationId: listUsers parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' responses: '200': description: Users retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/User' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Users summary: Create new user description: Create a new user account operationId: createUser requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UserCreate' responses: '201': description: User created successfully content: application/json: schema: $ref: '#/components/schemas/User' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' put: tags: - Users summary: Update user description: Update an existing user operationId: updateUser requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UserUpdate' responses: '200': description: User updated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /users/customer: get: tags: - Users summary: Get customer details description: Get details about a specific customer operationId: getCustomer parameters: - name: customer_number in: query schema: type: integer responses: '200': description: Customer retrieved successfully content: application/json: schema: $ref: '#/components/schemas/User' '404': $ref: '#/components/responses/NotFound' /superuser/user: get: tags: - Users summary: Get user by ID (superuser) description: Get detailed user information by user ID operationId: getSuperuserUser parameters: - name: user_id in: query schema: type: integer responses: '200': description: User retrieved successfully content: application/json: schema: $ref: '#/components/schemas/User' '404': $ref: '#/components/responses/NotFound' /admin/customer/code: get: tags: - Users summary: Get customer code operationId: getCustomerCode parameters: - name: customer_number in: query schema: {type: integer} - name: user_id in: query schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} post: tags: - Users summary: Add customer code operationId: addCustomerCode requestBody: required: true content: application/json: schema: type: object properties: customer_number: {type: integer} user_id: {type: integer} code: {type: string} responses: '200': description: Success content: application/json: schema: {} /customer/department/default: get: tags: - Users summary: Get customer default department operationId: getCustomerDefaultDepartment parameters: - name: customer_number in: query schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} post: tags: - Users summary: Add customer default department operationId: addCustomerDefaultDepartment requestBody: required: true content: application/json: schema: type: object required: [department] properties: customer_number: {type: integer} department: {type: integer} responses: '200': description: Success content: application/json: schema: {} delete: tags: - Users summary: Delete customer default department operationId: deleteCustomerDefaultDepartment parameters: - name: customer_number in: query schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} /customer/pricing/fixed: get: tags: - Users summary: Get customer fixed pricing operationId: getCustomerFixedPricing parameters: - name: customer_number in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} post: tags: - Users summary: Add customer fixed pricing operationId: addCustomerFixedPricing requestBody: required: true content: application/json: schema: type: object required: [customer_number, price, description] properties: customer_number: {type: integer} price: {type: integer} description: {type: string} responses: '200': description: Success content: application/json: schema: {} delete: tags: - Users summary: Delete customer fixed pricing operationId: deleteCustomerFixedPricing parameters: - name: customer_number in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} /account/notifications: put: tags: - Users summary: Update user notification settings operationId: updateUserNotifications requestBody: required: true content: application/json: schema: type: object properties: wash_certificate_email: {type: string} sms_notifications_enabled: {type: boolean} email_notifications_enabled: {type: boolean} responses: '200': description: Success content: application/json: schema: {} /user/permissions: get: tags: - Users summary: Get user permissions operationId: getUserPermissions responses: '200': description: Success content: application/json: schema: {} /customers: get: tags: - Users summary: List customers operationId: listCustomers parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' - name: barred in: query required: false description: Optional e-conomic barred customer filter. schema: type: string enum: ['true', 'false', 'barred', 'active', '1', '0'] responses: '200': description: Success content: application/json: schema: {} /superuser/user/discounts: get: tags: - Users summary: Get user discounts operationId: getUserDiscounts parameters: - name: user_id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} post: tags: - Users summary: Set user discount operationId: setUserDiscount requestBody: required: true content: application/json: schema: type: object required: [discount, object_id, is_category] properties: user_id: {type: integer} discount: {type: integer} object_id: {type: string} is_category: {type: boolean} responses: '200': description: Success content: application/json: schema: {} /superuser/user/keys: get: tags: - Users summary: Get user keys operationId: getUserKeys parameters: - name: user_id in: query required: true schema: {type: integer} - name: key in: query schema: {type: string} responses: '200': description: Success content: application/json: schema: {} post: tags: - Users summary: Set user key operationId: setUserKey requestBody: required: true content: application/json: schema: type: object required: [key, value] properties: user_id: {type: integer} key: {type: string} value: {type: string} responses: '200': description: Success content: application/json: schema: {} /superuser/user/password: post: tags: - Users summary: Set user password operationId: setUserPassword requestBody: required: true content: application/json: schema: type: object required: [password] properties: user_id: {type: integer} password: {type: string} responses: '200': description: Success content: application/json: schema: {} /admin/customer/getUserId: get: tags: - Users summary: Get user ID from customer number description: Convert e-conomic customer number to internal user ID operationId: getUserIdFromCustomerNumber parameters: - name: customer_number in: query required: true schema: type: integer responses: '200': description: User ID retrieved successfully content: application/json: schema: type: object properties: user_id: type: integer /admin/customer/name: get: tags: - Users summary: Get customer name description: Get the full name of a customer operationId: getCustomerName parameters: - name: user_id in: query schema: type: integer responses: '200': description: Customer name retrieved successfully content: application/json: schema: type: object properties: name: type: string # Orders Endpoints /orders: get: tags: - Orders summary: List orders description: Retrieve a paginated list of orders operationId: listOrders parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' - name: show_wash_subscription in: query schema: type: string enum: [true, false] responses: '200': description: Orders retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Order' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' post: tags: - Orders summary: Create new order description: Create a new wash order operationId: createOrder requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OrderCreate' responses: '201': description: Order created successfully content: application/json: schema: $ref: '#/components/schemas/Order' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' delete: tags: - Orders summary: Delete order description: Delete an existing order operationId: deleteOrder parameters: - name: id in: query required: true schema: type: integer responses: '200': description: Order deleted successfully content: application/json: schema: {} '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' put: tags: - Orders summary: Update order (alias) description: Update an existing order operationId: updateOrders requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OrderUpdate' responses: '200': description: Order updated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' /order: get: tags: - Orders summary: Get order details description: Get detailed information about a specific order operationId: getOrder parameters: - name: id in: query required: true schema: type: integer responses: '200': description: Order retrieved successfully content: application/json: schema: $ref: '#/components/schemas/Order' '404': $ref: '#/components/responses/NotFound' put: tags: - Orders summary: Update order description: Update an existing order operationId: updateOrder requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OrderUpdate' responses: '200': description: Order updated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /user/orders: get: tags: - Orders summary: Get current user's orders description: Retrieve orders for the authenticated user operationId: getUserOrders parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: Orders retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Order' /user/order: get: tags: - Orders summary: Get user's specific order description: Get details of a specific order for the authenticated user operationId: getUserOrder parameters: - name: id in: query required: true schema: type: integer responses: '200': description: Order retrieved successfully content: application/json: schema: $ref: '#/components/schemas/Order' /orders/mark_as_completed: post: tags: - Orders summary: Mark order as completed description: Mark an order as completed operationId: markOrderCompleted requestBody: required: true content: application/json: schema: type: object required: - id properties: id: type: integer responses: '200': description: Order marked as completed successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' /order/wash-certificate: post: tags: - Orders summary: Generate wash certificate description: Generate a wash certificate for an order operationId: generateWashCertificate requestBody: required: true content: application/json: schema: type: object properties: order_id: type: integer responses: '200': description: Wash certificate generated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' # Order Items Endpoints /order/items: get: tags: - Order Items summary: List order items description: Get all items for a specific order operationId: listOrderItems parameters: - name: order_id in: query required: true schema: type: integer responses: '200': description: Order items retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/OrderItem' post: tags: - Order Items summary: Add item to order description: Add a new item to an existing order operationId: addOrderItem requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OrderItemCreate' responses: '201': description: Order item added successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' put: tags: - Order Items summary: Update order item description: Update an existing order item operationId: updateOrderItem requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OrderItemUpdate' responses: '200': description: Order item updated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' delete: tags: - Order Items summary: Delete order item description: Remove an item from an order operationId: deleteOrderItem parameters: - name: id in: query required: true schema: type: integer responses: '200': description: Order item deleted successfully content: application/json: schema: {} '404': $ref: '#/components/responses/NotFound' # Departments Endpoints /departments: get: tags: - Departments summary: List departments description: Retrieve a list of all visible departments operationId: listDepartments parameters: - name: id in: query schema: type: integer description: Filter by specific department ID - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' responses: '200': description: Departments retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Department' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Departments summary: Create department description: Create a new department operationId: createDepartment requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentCreate' responses: '201': description: Department created successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' put: tags: - Departments summary: Update department description: Update an existing department operationId: updateDepartment requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentUpdate' responses: '200': description: Department updated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' /departments/categories: get: tags: - Departments summary: Get department categories description: Get product categories available in a department operationId: getDepartmentCategories parameters: - name: department_id in: query required: true schema: type: integer responses: '200': description: Department categories retrieved successfully content: application/json: schema: {} post: tags: - Departments summary: Add category to department description: Associate a product category with a department operationId: addDepartmentCategory requestBody: required: true content: application/json: schema: type: object properties: department_id: type: integer category_id: type: integer responses: '201': description: Category added to department successfully content: application/json: schema: {} delete: tags: - Departments summary: Remove category from department operationId: removeDepartmentCategory requestBody: required: false content: application/json: schema: {} responses: '200': description: Success content: application/json: schema: {} /departments/self-serve/enabled: get: tags: - Departments summary: Get department self-serve status description: Check if self-serve is enabled for a specific department operationId: getDepartmentSelfServeEnabled parameters: - name: id in: query required: true description: Department ID schema: type: integer responses: '200': description: Successfully retrieved status content: application/json: schema: type: object properties: enabled: type: boolean '404': $ref: '#/components/responses/NotFound' put: tags: - Departments summary: Update department self-serve status description: Enable or disable self-serve for a specific department operationId: updateDepartmentSelfServeEnabled parameters: - name: id in: query required: true description: Department ID schema: type: integer - name: enabled in: query required: true description: Enabled status (true/false) schema: type: string enum: ['true', 'false'] responses: '200': description: Status updated successfully content: application/json: schema: type: object properties: message: type: string '404': $ref: '#/components/responses/NotFound' /departments/order/recommended: get: tags: - Departments summary: Get recommended order for department operationId: getDepartmentRecommendedOrder parameters: - name: department_id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} /department/lanes: get: tags: - Departments summary: List department lanes description: Retrieve a list of all department lanes operationId: listDepartmentLanes parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' responses: '200': description: Department lanes retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/DepartmentLane' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Departments summary: Create department lane description: Create a new department lane operationId: createDepartmentLane requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentLaneCreate' responses: '201': description: Department lane created successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' put: tags: - Departments summary: Update department lane description: Update an existing department lane operationId: updateDepartmentLane requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentLaneUpdate' responses: '200': description: Department lane updated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /department/gates: get: tags: - Departments summary: List department gates description: Retrieve department gates, optionally filtered by id operationId: listDepartmentGates parameters: - name: id in: query required: false schema: type: integer minimum: 1 - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' responses: '200': description: Department gates retrieved successfully content: application/json: schema: oneOf: - $ref: '#/components/schemas/DepartmentGate' - type: array items: $ref: '#/components/schemas/DepartmentGate' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Departments summary: Create department gate operationId: createDepartmentGate requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentGateCreate' responses: '201': description: Department gate created successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentGate' put: tags: - Departments summary: Update department gate operationId: updateDepartmentGate requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentGateUpdate' responses: '200': description: Department gate updated successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentGate' '404': $ref: '#/components/responses/NotFound' delete: tags: - Departments summary: Delete department gate operationId: deleteDepartmentGate parameters: - name: id in: query required: true schema: type: integer minimum: 1 responses: '200': description: Department gate deleted content: application/json: schema: {} /department/relays: get: tags: - Departments summary: List department relays description: Retrieve department relays, optionally filtered by id operationId: listDepartmentRelays parameters: - name: id in: query required: false schema: type: integer minimum: 1 - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' responses: '200': description: Department relays retrieved successfully content: application/json: schema: oneOf: - $ref: '#/components/schemas/DepartmentRelay' - type: array items: $ref: '#/components/schemas/DepartmentRelay' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Departments summary: Create department relay operationId: createDepartmentRelay requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentRelayCreate' responses: '201': description: Department relay created successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentRelay' put: tags: - Departments summary: Update department relay operationId: updateDepartmentRelay requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentRelayUpdate' responses: '200': description: Department relay updated successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentRelay' '404': $ref: '#/components/responses/NotFound' delete: tags: - Departments summary: Delete department relay operationId: deleteDepartmentRelay parameters: - name: id in: query required: true schema: type: integer minimum: 1 responses: '200': description: Department relay deleted content: application/json: schema: {} /department/lanes/dynamic-image: get: tags: - Departments summary: Generate dynamic image for a department lane description: | Returns a composed machine UI image for the specified department lane. You can optionally highlight button indices, set the current step indicator, and toggle only-current-step mode. operationId: getDepartmentLaneDynamicImage parameters: - name: department in: query required: true description: Department ID schema: type: integer minimum: 1 - name: lane in: query required: true description: Lane ID schema: type: integer minimum: 1 - name: buttons in: query required: false description: Highlighted button IDs (0-indexed). Accepts CSV, JSON array, or repeated query params. schema: oneOf: - type: string - type: array items: type: integer - name: current_step in: query required: false description: Current step indicator (non-negative integer) schema: type: integer minimum: 0 - name: only_current_step in: query required: false description: If true, only draw the current step highlight schema: type: boolean - name: vehicle_type in: query required: false description: Vehicle type selection override (nullable non-negative integer) schema: type: integer minimum: 0 responses: '200': description: Dynamic image rendered successfully content: image/png: schema: type: string format: binary '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /guest/validation/customer-number: post: tags: - Users summary: Validate customer number description: Check if a customer number is valid and exists operationId: validateCustomerNumber security: [] requestBody: required: true content: application/json: schema: type: object required: - customer_number properties: customer_number: type: integer responses: '200': description: Customer number validation successful content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' /guest/departments: get: tags: - Departments summary: List public departments description: Get list of departments without authentication operationId: listGuestDepartments security: [] parameters: - name: include_lanes in: query description: Whether to include lane status and self-serve information schema: type: boolean responses: '200': description: Departments retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/DepartmentGuest' /department/selfserve/questions: get: tags: - Self-Serve summary: List self-serve questions description: Retrieve a list of self-serve questions for a department, lane, or product. operationId: listSelfserveQuestions parameters: - name: id in: query description: Filter by question ID schema: type: integer - name: department in: query description: Filter by department ID schema: type: integer - name: lane in: query description: Filter by lane ID schema: type: integer - name: product in: query description: Filter by product ID schema: type: integer - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' - $ref: '#/components/parameters/FiltersParam' responses: '200': description: Successfully retrieved questions content: application/json: schema: type: array items: $ref: '#/components/schemas/DepartmentSelfserveQuestion' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' post: tags: - Self-Serve summary: Add self-serve question description: Add a new self-serve question. operationId: addSelfserveQuestion requestBody: required: true content: application/json: schema: type: object required: - department - lane - product - question - description properties: department: type: integer lane: type: integer product: type: integer question: type: string description: type: string condition_id: type: integer nullable: true order_priority: type: integer default: 0 responses: '200': description: Successfully added question content: application/json: schema: $ref: '#/components/schemas/DepartmentSelfserveQuestion' '400': $ref: '#/components/responses/BadRequest' '500': $ref: '#/components/responses/InternalServerError' put: tags: - Self-Serve summary: Update self-serve question description: Update an existing self-serve question. operationId: updateSelfserveQuestion parameters: - name: id in: query required: true description: Question ID schema: type: integer requestBody: content: application/json: schema: type: object properties: department: type: integer lane: type: integer product: type: integer question: type: string description: type: string condition_id: type: integer nullable: true order_priority: type: integer responses: '200': description: Successfully updated question content: application/json: schema: $ref: '#/components/schemas/DepartmentSelfserveQuestion' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' delete: tags: - Self-Serve summary: Delete self-serve question description: Delete a self-serve question by ID. operationId: deleteSelfserveQuestion parameters: - name: id in: query required: true description: Question ID schema: type: integer responses: '200': description: Successfully deleted question content: application/json: schema: type: string example: Question deleted '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /department/selfserve/conditions: get: tags: - Self-Serve summary: List self-serve conditions description: Retrieve a list of self-serve conditions for a department, lane, or product. operationId: listSelfserveConditions parameters: - name: id in: query description: Filter by condition ID schema: type: integer - name: department in: query description: Filter by department ID schema: type: integer - name: lane in: query description: Filter by lane ID schema: type: integer - name: product in: query description: Filter by product ID schema: type: integer - name: condition_id in: query description: Filter by condition ID schema: type: integer - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' - $ref: '#/components/parameters/FiltersParam' responses: '200': description: Successfully retrieved conditions content: application/json: schema: type: array items: $ref: '#/components/schemas/DepartmentSelfserveCondition' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' post: tags: - Self-Serve summary: Add self-serve condition description: Add a new self-serve condition. operationId: addSelfserveCondition requestBody: required: true content: application/json: schema: type: object required: - department - lane - product - name - description properties: department: type: integer lane: type: integer product: type: integer condition_id: type: integer nullable: true name: type: string description: type: string responses: '200': description: Successfully added condition content: application/json: schema: $ref: '#/components/schemas/DepartmentSelfserveCondition' '400': $ref: '#/components/responses/BadRequest' '500': $ref: '#/components/responses/InternalServerError' put: tags: - Self-Serve summary: Update self-serve condition description: Update an existing self-serve condition. operationId: updateSelfserveCondition parameters: - name: id in: query required: true description: Condition ID schema: type: integer requestBody: content: application/json: schema: type: object properties: department: type: integer lane: type: integer product: type: integer condition_id: type: integer nullable: true name: type: string description: type: string responses: '200': description: Successfully updated condition content: application/json: schema: $ref: '#/components/schemas/DepartmentSelfserveCondition' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' delete: tags: - Self-Serve summary: Delete self-serve condition description: Delete a self-serve condition. operationId: deleteSelfserveCondition parameters: - name: id in: query required: true description: Condition ID schema: type: integer responses: '200': description: Successfully deleted condition content: application/json: schema: type: string example: Condition deleted '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /department/selfserve/condition/rules: get: tags: - Self-Serve summary: List self-serve condition rules description: Retrieve a list of self-serve condition rules. operationId: listSelfserveConditionRules parameters: - name: id in: query description: Filter by rule ID schema: type: integer - name: condition_id in: query description: Filter by condition ID schema: type: integer - name: type in: query description: Filter by rule type schema: type: string - name: object_type in: query description: Filter by object type schema: type: string - name: object_id in: query description: Filter by object ID schema: type: integer - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' - $ref: '#/components/parameters/FiltersParam' responses: '200': description: Successfully retrieved condition rules content: application/json: schema: type: array items: $ref: '#/components/schemas/DepartmentSelfserveConditionRule' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' post: tags: - Self-Serve summary: Add self-serve condition rule description: Add a new self-serve condition rule. operationId: addSelfserveConditionRule requestBody: required: true content: application/json: schema: type: object required: - condition_id - type - object_type - object_id - name - description properties: condition_id: type: integer type: type: string object_type: type: string object_id: type: integer name: type: string description: type: string responses: '200': description: Successfully added condition rule content: application/json: schema: $ref: '#/components/schemas/DepartmentSelfserveConditionRule' '400': $ref: '#/components/responses/BadRequest' '500': $ref: '#/components/responses/InternalServerError' put: tags: - Self-Serve summary: Update self-serve condition rule description: Update an existing self-serve condition rule. operationId: updateSelfserveConditionRule parameters: - name: id in: query required: true description: Rule ID schema: type: integer requestBody: content: application/json: schema: type: object properties: condition_id: type: integer type: type: string object_type: type: string object_id: type: integer name: type: string description: type: string responses: '200': description: Successfully updated condition rule content: application/json: schema: $ref: '#/components/schemas/DepartmentSelfserveConditionRule' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' delete: tags: - Self-Serve summary: Delete self-serve condition rule description: Delete a self-serve condition rule. operationId: deleteSelfserveConditionRule parameters: - name: id in: query required: true description: Rule ID schema: type: integer responses: '200': description: Successfully deleted condition rule content: application/json: schema: type: string example: Rule deleted '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /department/selfserve/vehicle/conditions: get: tags: - Self-Serve summary: List vehicle conditions description: Retrieve a list of vehicle conditions for a department, lane, reg, or question. Customers will only see their own vehicle conditions. operationId: listSelfserveVehicleConditions parameters: - name: id in: query description: Filter by condition ID schema: type: integer - name: department in: query description: Filter by department ID schema: type: integer - name: lane in: query description: Filter by lane ID schema: type: integer - name: reg in: query description: Filter by vehicle registration number schema: type: string - name: question in: query description: Filter by question ID schema: type: integer - name: customer_id in: query description: Filter by customer ID schema: type: integer - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' - $ref: '#/components/parameters/FiltersParam' responses: '200': description: Successfully retrieved vehicle conditions content: application/json: schema: type: array items: $ref: '#/components/schemas/DepartmentSelfserveVehicleCondition' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' post: tags: - Self-Serve summary: Add vehicle condition description: Add a new vehicle condition (answer to a question). Customers can only add conditions for their own vehicles. operationId: addSelfserveVehicleCondition requestBody: required: true content: application/json: schema: type: object required: - department - lane - reg - question properties: department: type: integer lane: type: integer reg: type: string question: type: integer value: type: boolean customer_id: type: integer nullable: true responses: '200': description: Successfully added vehicle condition content: application/json: schema: $ref: '#/components/schemas/DepartmentSelfserveVehicleCondition' '400': $ref: '#/components/responses/BadRequest' '500': $ref: '#/components/responses/InternalServerError' put: tags: - Self-Serve summary: Update vehicle condition description: Update an existing vehicle condition. Customers can only update conditions for their own vehicles. operationId: updateSelfserveVehicleCondition parameters: - name: id in: query required: true description: Condition ID schema: type: integer requestBody: content: application/json: schema: type: object properties: department: type: integer lane: type: integer reg: type: string question: type: integer value: type: boolean customer_id: type: integer nullable: true responses: '200': description: Successfully updated vehicle condition content: application/json: schema: $ref: '#/components/schemas/DepartmentSelfserveVehicleCondition' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' delete: tags: - Self-Serve summary: Delete vehicle condition description: Delete a vehicle condition. Customers can only delete conditions for their own vehicles. operationId: deleteSelfserveVehicleCondition parameters: - name: id in: query required: true description: Condition ID schema: type: integer responses: '200': description: Successfully deleted vehicle condition content: application/json: schema: type: string example: Condition deleted '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /department/selfserve/tasks: get: tags: - Self-Serve summary: List self-serve tasks description: Retrieve a list of self-serve tasks for a department, lane, product, or condition_id. operationId: listSelfserveTasks parameters: - name: id in: query description: Filter by task ID schema: type: integer - name: department in: query description: Filter by department ID schema: type: integer - name: lane in: query description: Filter by lane ID schema: type: integer - name: product in: query description: Filter by product ID schema: type: integer - name: condition_id in: query description: Filter by condition ID schema: type: integer - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' - $ref: '#/components/parameters/FiltersParam' responses: '200': description: Successfully retrieved tasks content: application/json: schema: type: array items: $ref: '#/components/schemas/DepartmentSelfserveTask' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' post: tags: - Self-Serve summary: Add self-serve task description: Add a new self-serve task. operationId: addSelfserveTask requestBody: required: true content: application/json: schema: type: object required: - department - lane - product - task - description properties: department: type: integer lane: type: integer product: type: integer condition_id: type: integer nullable: true task: type: string description: type: string order_priority: type: integer default: 0 services: type: array description: Optional services enabled by this task. Items must be valid service enum names. items: $ref: '#/components/schemas/SelfserveLaneService' buttons: type: array description: Optional dynamic image button IDs enabled by this task. items: type: integer default: [] dynamic_images_vehicle_type: type: integer nullable: true description: Optional vehicle type selection override for the machine UI. Integer >= 0 or null. responses: '200': description: Successfully added task content: application/json: schema: $ref: '#/components/schemas/DepartmentSelfserveTask' '400': $ref: '#/components/responses/BadRequest' '500': $ref: '#/components/responses/InternalServerError' put: tags: - Self-Serve summary: Update self-serve task description: Update an existing self-serve task. operationId: updateSelfserveTask parameters: - name: id in: query required: true description: Task ID schema: type: integer requestBody: content: application/json: schema: type: object properties: department: type: integer lane: type: integer product: type: integer condition_id: type: integer nullable: true task: type: string description: type: string order_priority: type: integer services: type: array nullable: true description: Services enabled by this task. Set to null to clear all services. items: $ref: '#/components/schemas/SelfserveLaneService' buttons: type: array nullable: true description: Button IDs enabled by this task. Set to null to clear all buttons. items: type: integer dynamic_images_vehicle_type: type: integer nullable: true description: Vehicle type selection override. Set to null to clear. responses: '200': description: Successfully updated task content: application/json: schema: $ref: '#/components/schemas/DepartmentSelfserveTask' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' delete: tags: - Self-Serve summary: Delete self-serve task description: Delete a self-serve task by ID. operationId: deleteSelfserveTask parameters: - name: id in: query required: true description: Task ID schema: type: integer responses: '200': description: Successfully deleted task content: application/json: schema: type: string example: Task deleted '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /department/selfserve/tasks/attachments: get: tags: - Self-Serve summary: List task attachments description: Retrieve a list of attachments for a specific self-serve task. operationId: listSelfserveTaskAttachments parameters: - name: id in: query required: true description: Task ID schema: type: integer responses: '200': description: Successfully retrieved task attachments content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' delete: tags: - Self-Serve summary: Delete task attachment description: Remove an attachment from a specific self-serve task. operationId: deleteSelfserveTaskAttachment parameters: - name: task_id in: query required: true description: Task ID schema: type: integer - name: attachment_id in: query required: true description: Attachment ID schema: type: integer responses: '200': description: Attachment deleted successfully content: application/json: schema: type: object properties: message: type: string example: Attachment deleted successfully '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /department/selfserve/tasks/attachments/upload: post: tags: - Self-Serve summary: Upload task attachment description: Upload a new attachment to a specific self-serve task using base64 encoding. operationId: uploadSelfserveTaskAttachment requestBody: required: true content: application/json: schema: type: object required: - task_id - base64_file - file_name properties: task_id: type: integer base64_file: type: string description: Base64 encoded file content file_name: type: string description: Name of the file including extension responses: '200': description: Attachment uploaded successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' /department/selfserve/tasks/attachments/download: get: tags: - Self-Serve summary: Download task attachment description: Generate a download link for a specific self-serve task attachment. operationId: downloadSelfserveTaskAttachment parameters: - name: task_id in: query required: true description: Task ID schema: type: integer - name: attachment_id in: query required: true description: Attachment ID schema: type: integer responses: '200': description: Successfully generated download link content: application/json: schema: type: object properties: download_link: type: string format: uri '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' # Products Endpoints /products: get: tags: - Products summary: List products description: Retrieve a list of products with optional filters for customer pricing and department operationId: listProducts parameters: - name: customer_id in: query schema: type: integer description: Customer ID for custom pricing - name: department_id in: query schema: type: integer description: Department ID for department-specific pricing - name: category in: query schema: type: integer description: Filter by category ID - name: id in: query schema: type: integer description: Get specific product by ID - name: final_price in: query schema: type: boolean description: Whether to return final prices including discounts - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: Products retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Product' post: tags: - Products summary: Create product description: Create a new product operationId: createProduct requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ProductCreate' responses: '201': description: Product created successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' put: tags: - Products summary: Update product description: Update an existing product operationId: updateProduct requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ProductUpdate' responses: '200': description: Product updated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' # Categories Endpoints /categories: get: tags: - Categories summary: List categories description: Retrieve a list of product categories operationId: listCategories parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: Categories retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Category' post: tags: - Categories summary: Create category description: Create a new product category operationId: createCategory requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CategoryCreate' responses: '201': description: Category created successfully content: application/json: schema: {} put: tags: - Categories summary: Update category description: Update an existing category operationId: updateCategory requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CategoryUpdate' responses: '200': description: Category updated successfully content: application/json: schema: {} # Bookings Endpoints /bookings: get: tags: - Bookings summary: List bookings description: Retrieve a list of bookings operationId: listBookings parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: Bookings retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Booking' put: tags: - Bookings summary: Update booking description: Update an existing booking operationId: updateBooking requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BookingUpdate' responses: '200': description: Booking updated successfully content: application/json: schema: {} /user/bookings: get: tags: - Bookings summary: Get user bookings description: Retrieve bookings for the authenticated user operationId: getUserBookings responses: '200': description: User bookings retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Booking' /order-bookings: get: tags: - Bookings summary: List order bookings operationId: listOrderBookings parameters: - name: id in: query required: false schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} # Goals Endpoints /goals/department: get: tags: - Goals summary: List or get department goals description: | Retrieve a list of department goals or a single goal when `id` is provided. Access control: - A user may only access goals where the goal's `departments` set is a subset of the user's departments. - Users with the `superuser` permission may access all goals. operationId: listDepartmentGoals parameters: - name: id in: query required: false schema: { type: integer } description: When provided, returns the single goal with this id (if accessible) - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' - $ref: '#/components/parameters/FiltersParam' responses: '200': description: Goals retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/DepartmentGoal' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } post: tags: - Goals summary: Create department goal description: | Create a new department goal. Access control: - The provided `departments` must be a subset of the user's departments unless the user has `superuser`. operationId: createDepartmentGoal requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentGoalCreate' responses: '201': description: Department goal created successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentGoal' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } put: tags: - Goals summary: Update department goal description: | Update an existing department goal by `id`. Access control: - The creator (`created_by`) may update regardless of department membership. - Otherwise the user must satisfy the same subset rule as for read access, and any new `departments` provided must also be a subset unless the user has `superuser`. operationId: updateDepartmentGoal requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentGoalUpdate' responses: '200': description: Department goal updated successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentGoal' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } /goals/department/progress-alert/test: post: tags: - Goals summary: Send a test progress alert for a department goal description: | Sends a progress alert for a department goal to the destination defined in the goal's criteria. Permission required: `goals_department_progress_alert_test`. Access control: - The caller must be a superuser or belong to all departments targeted by the goal. Behavior: - Looks up the goal by `id`. - Rebuilds the criteria from stored JSON and attaches the goal's departments. - Renders the alert using the server-side renderer (respecting progress type/style/format and destination limits). - Sends the alert to Slack, Email, or SMS depending on `progress_alert_destination`, unless overridden. operationId: sendDepartmentGoalProgressAlertTest requestBody: required: true content: application/json: schema: type: object required: - id properties: id: type: integer description: The department goal id example: 42 overrideDestination: type: string description: Override the destination for this test enum: [SLACK, EMAIL, SMS, NONE] example: SLACK email_to: type: string format: email description: Email recipient when destination is EMAIL example: tester@example.com subject: type: string description: Optional email subject when destination is EMAIL example: Dept Goal Progress Test sms_to: description: One or more MSISDN recipients when destination is SMS oneOf: - type: string description: Comma or semicolon separated list example: "+4512345678, +4598765432" - type: array items: type: string example: ["+4512345678", "+4598765432"] slack_webhook: type: string description: Slack webhook URL when destination is SLACK example: https://hooks.slack.com/services/T000/B000/XXX department_id: type: integer description: Department id to use that department's Slack webhook when destination is SLACK example: 3 responses: '200': description: Alert sent successfully content: application/json: schema: type: object properties: id: type: integer description: Goal id destination: type: string description: Final destination used enum: [SLACK, EMAIL, SMS, NONE] target: description: The target used for delivery (email address, phone numbers, department id, or webhook) message_preview: type: string description: Rendered message preview provider_response: description: Provider-specific response or status message '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '500': { $ref: '#/components/responses/InternalServerError' } delete: tags: - Goals summary: Delete department goal description: | Delete a department goal by `id`. Access control: - The creator (`created_by`) may delete regardless of department membership. - Otherwise the user must satisfy the subset rule or have `superuser`. operationId: deleteDepartmentGoal parameters: - name: id in: query required: true schema: { type: integer } description: ID of the goal to delete responses: '200': description: Department goal deleted successfully content: application/json: schema: {} '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '/order-bookings': post: tags: - Bookings summary: Create order booking operationId: createOrderBooking requestBody: required: true content: application/json: schema: type: object required: [department, reg_1, datetime, items] properties: customer_number: {type: integer} department: {type: integer} reg_1: {type: string} reg_2: {type: string} reg_3: {type: string} datetime: {type: string, format: date-time} note: {type: string} reference: {type: string} po: {type: string} pickup: {type: boolean} items: type: array items: type: object required: [id, quantity] properties: id: {type: integer} quantity: {type: integer} responses: '200': description: Success put: tags: - Bookings summary: Update order booking operationId: updateOrderBooking requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} customer_number: {type: integer} department: {type: integer} reg_1: {type: string} reg_2: {type: string} reg_3: {type: string} datetime: {type: string, format: date-time} note: {type: string} reference: {type: string} po: {type: string} pickup: {type: boolean} order_id: {type: integer} items: type: array items: type: object required: [id, quantity] properties: id: {type: integer} quantity: {type: integer} responses: '200': description: Success delete: tags: - Bookings summary: Delete order booking operationId: deleteOrderBooking parameters: - name: id in: query required: true schema: {type: integer} responses: '200': description: Success /order-bookings/complete: post: tags: - Bookings summary: Complete order booking operationId: completeOrderBooking requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} safety_seal: {type: integer} responses: '200': description: Success content: application/json: schema: {} /admin/bookings/sync: post: tags: - Bookings summary: Sync booking from external system operationId: syncBooking requestBody: required: false content: application/json: schema: {} responses: '200': description: Success content: application/json: schema: {} /admin/bookings/department/count: get: tags: - Bookings summary: Get department unfulfilled bookings count operationId: getDepartmentBookingCount parameters: - name: department_id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} /user/bookings/washcertificate/download: post: tags: - Bookings summary: Get download link for own wash certificate operationId: downloadOwnWashCertificate requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} responses: '200': description: Success content: application/json: schema: {} /bookings/download_pdf: get: tags: - Bookings summary: Download booking PDF operationId: downloadBookingPdf parameters: - name: id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} /admin/bookings/delete: post: tags: - Bookings summary: Delete booking (admin) operationId: adminDeleteBooking requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} responses: '200': description: Success content: application/json: schema: {} /superuser/bookings/sync/all: post: tags: - Bookings summary: Sync all bookings from external system operationId: syncAllBookings requestBody: required: false content: application/json: schema: {} responses: '200': description: Success content: application/json: schema: {} /admin/bookings/completeWashWithoutWashCertificate: post: tags: - Bookings summary: Complete wash without wash certificate operationId: completeWashWithoutWashCertificate requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} responses: '200': description: Success content: application/json: schema: {} /user/bookings/delete: post: tags: - Bookings summary: Delete own booking operationId: deleteOwnBooking requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} responses: '200': description: Success content: application/json: schema: {} # Invoices Endpoints /invoices/draft: get: tags: - Invoices summary: List draft invoices description: Retrieve a list of draft invoices operationId: listDraftInvoices parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: Draft invoices retrieved successfully content: application/json: schema: {} /invoices/draft/close: post: tags: - Invoices summary: Close draft invoice description: Close a draft invoice operationId: closeDraftInvoice requestBody: required: true content: application/json: schema: type: object properties: id: type: integer responses: '200': description: Draft invoice closed successfully content: application/json: schema: {} /invoices/pdf: get: tags: - Invoices summary: Get invoice PDF description: Download an invoice as PDF operationId: getInvoicePdf parameters: - name: id in: query required: true schema: type: integer responses: '200': description: PDF retrieved successfully content: application/pdf: schema: type: string format: binary /user/invoices: get: tags: - Invoices summary: Get user invoices description: Retrieve invoices for the authenticated user operationId: getUserInvoices responses: '200': description: User invoices retrieved successfully content: application/json: schema: {} /collected-invoices: get: tags: - Invoices summary: List collected invoices description: Get list of collected invoices operationId: listCollectedInvoices parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: Collected invoices retrieved successfully content: application/json: schema: {} post: tags: - Invoices summary: Create collected invoice description: Create a new collected invoice operationId: createCollectedInvoice requestBody: required: false content: application/json: schema: {} responses: '201': description: Collected invoice created successfully content: application/json: schema: {} put: tags: - Invoices summary: Update collected invoice description: Update a collected invoice operationId: updateCollectedInvoice requestBody: required: false content: application/json: schema: {} responses: '200': description: Collected invoice updated successfully content: application/json: schema: {} /collected-invoices/ready-to-invoice: get: tags: - Invoices summary: Get invoices ready to process description: Get collected invoices that are ready to be processed operationId: getReadyToInvoice responses: '200': description: Ready invoices retrieved successfully content: application/json: schema: {} /collected-invoices/economic/compare: get: tags: - Invoices summary: Compare collected invoice totals with E-conomic description: | Compares a collected invoice in the system with its corresponding invoice in E-conomic. Returns totals from both sources, their difference, and any warnings detected during comparison. operationId: compareCollectedInvoiceEconomic parameters: - name: collected_invoice_id in: query required: true description: The internal collected invoice ID to compare schema: type: integer minimum: 1 responses: '200': description: Comparison completed successfully content: application/json: schema: $ref: '#/components/schemas/CollectedInvoiceEconomicCompareResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' /collected-invoices/economic/v2/details: get: tags: - Invoices summary: Get deep V2 e-conomic invoice details description: | Returns normalized internal lines and best-effort fetched draft/booked e-conomic lines for a collected invoice, including department distributions and warnings. operationId: getCollectedInvoiceEconomicV2Details parameters: - name: collected_invoice_id in: query required: true schema: type: integer minimum: 1 responses: '200': description: Details resolved successfully content: application/json: schema: $ref: '#/components/schemas/CollectedInvoiceEconomicV2DetailsResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/v2/compare: get: tags: - Invoices summary: Compare internal invoice with draft/booked (V2) operationId: compareCollectedInvoiceEconomicV2 parameters: - name: collected_invoice_id in: query required: true schema: type: integer minimum: 1 responses: '200': description: Comparison completed content: application/json: schema: $ref: '#/components/schemas/CollectedInvoiceEconomicV2CompareResponse' examples: exactMatch: summary: Exact match between internal and draft/booked value: collected_invoice_id: 123 warnings: [] comparison: totals: internal_net_total: 694 targets: draft: target: draft status: exact_match overall_match: true booked: target: booked status: exact_match overall_match: true partialMismatch: summary: Partial mismatch with line and department differences value: collected_invoice_id: 123 warnings: - Non-billable line count differs comparison: totals: internal_net_total: 694 targets: draft: target: draft status: partial_mismatch overall_match: false mismatch_reasons: - quantity_mismatch - department_total_mismatch missingBooked: summary: Missing booked target value: collected_invoice_id: 123 comparison: totals: internal_net_total: 694 targets: booked: target: booked status: missing_target overall_match: false '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/v2/compare/bulk: post: tags: - Invoices summary: Bulk compare collected invoices against draft/booked (V2) operationId: compareCollectedInvoiceEconomicV2Bulk requestBody: required: true content: application/json: schema: type: object required: [collected_invoice_ids] properties: collected_invoice_ids: type: array minItems: 1 maxItems: 200 items: type: integer minimum: 1 responses: '200': description: Bulk comparison completed content: application/json: schema: $ref: '#/components/schemas/CollectedInvoiceEconomicV2CompareBulkResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/v2/revenue-statistics: get: tags: - Invoices summary: Get overall booked revenue statistics from e-conomic (V2) description: | Aggregates booked e-conomic revenue across invoices and lines, with optional filters for date range, customer(s), department(s), currency, and barred-customer status. operationId: getCollectedInvoiceEconomicV2RevenueStatistics parameters: - name: dateFrom in: query required: false description: Start date (inclusive), defaults to first day of current month. schema: type: string format: date - name: dateTo in: query required: false description: End date (inclusive), defaults to today. schema: type: string format: date - name: customer_numbers in: query required: false description: Comma-separated customer numbers to include. schema: type: string example: "42493959,42493960" - name: department_numbers in: query required: false description: Comma-separated department numbers to include. schema: type: string example: "75,10" - name: currency in: query required: false description: Restrict to a specific invoice currency. schema: type: string example: "DKK" - name: barred in: query required: false description: Filter by e-conomic customer barred status. schema: type: string enum: [all, barred, active] default: all - name: max_pages in: query required: false description: Safety cap for paginated e-conomic reads. schema: type: integer minimum: 1 maximum: 200 default: 10 responses: '200': description: Revenue statistics resolved successfully content: application/json: schema: $ref: '#/components/schemas/CollectedInvoiceEconomicV2RevenueStatisticsResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '500': { $ref: '#/components/responses/InternalServerError' } /superuser/invoicing/period: get: tags: - Invoices summary: Get invoicing periods description: Retrieve invoicing periods for superusers operationId: getInvoicingPeriods parameters: - name: dateFrom in: query required: true schema: {type: string, format: date} - name: dateTo in: query required: true schema: {type: string, format: date} responses: '200': description: Invoicing periods retrieved successfully content: application/json: schema: {} /superuser/invoicing/period/distribution/fixed-pricing: get: tags: - Invoices summary: Get fixed pricing distribution description: Get invoicing distribution for fixed pricing items operationId: getInvoicingFixedPricingDistribution parameters: - name: dateFrom in: query required: true schema: {type: string, format: date} - name: dateTo in: query required: true schema: {type: string, format: date} responses: '200': description: Fixed pricing distribution retrieved successfully content: application/json: schema: $ref: '#/components/schemas/InvoicingFixedPricingDistributionResponse' /superuser/invoicing/period/distribution/wash-subscriptions: get: tags: - Invoices summary: Get wash subscriptions distribution description: Get invoicing distribution for wash subscriptions operationId: getInvoicingWashSubscriptionsDistribution parameters: - name: dateFrom in: query required: true schema: {type: string, format: date} - name: dateTo in: query required: true schema: {type: string, format: date} responses: '200': description: Wash subscriptions distribution retrieved successfully content: application/json: schema: $ref: '#/components/schemas/InvoicingWashSubscriptionsDistributionResponse' /superuser/invoicing/period/distribution/v2/all: get: tags: - Invoices summary: Get version-aware historical distribution (all) operationId: getInvoicingPeriodDistributionV2All parameters: - name: dateFrom in: query required: true schema: { type: string, format: date } - name: dateTo in: query required: true schema: { type: string, format: date } responses: '200': description: Version-aware historical distribution (all categories) content: application/json: schema: $ref: '#/components/schemas/InvoicingDistributionV2AllResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '500': { $ref: '#/components/responses/InternalServerError' } /superuser/invoicing/period/distribution/v2/fixed-pricing: get: tags: - Invoices summary: Get version-aware historical fixed pricing distribution operationId: getInvoicingPeriodDistributionV2FixedPricing parameters: - name: dateFrom in: query required: true schema: { type: string, format: date } - name: dateTo in: query required: true schema: { type: string, format: date } responses: '200': description: Version-aware fixed pricing distribution content: application/json: schema: $ref: '#/components/schemas/InvoicingDistributionV2FixedPricingResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '500': { $ref: '#/components/responses/InternalServerError' } /superuser/invoicing/period/distribution/v2/wash-subscriptions: get: tags: - Invoices summary: Get version-aware historical wash subscription distribution operationId: getInvoicingPeriodDistributionV2WashSubscriptions parameters: - name: dateFrom in: query required: true schema: { type: string, format: date } - name: dateTo in: query required: true schema: { type: string, format: date } responses: '200': description: Version-aware wash subscription distribution content: application/json: schema: $ref: '#/components/schemas/InvoicingDistributionV2WashSubscriptionsResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '500': { $ref: '#/components/responses/InternalServerError' } /superuser/invoicing/period/distribution/v2/customer-prices: get: tags: - Invoices summary: Get version-aware historical customer-price discount distribution operationId: getInvoicingPeriodDistributionV2CustomerPrices parameters: - name: dateFrom in: query required: true schema: { type: string, format: date } - name: dateTo in: query required: true schema: { type: string, format: date } responses: '200': description: Version-aware customer-price discount distribution content: application/json: schema: $ref: '#/components/schemas/InvoicingDistributionV2CustomerPricesResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '500': { $ref: '#/components/responses/InternalServerError' } /superuser/customers/pricing-history: get: tags: - Invoices summary: Get customer versioned pricing/subscription/discount timeline operationId: getCustomerPricingHistoryV2 parameters: - name: customer_number in: query required: true schema: type: integer minimum: 1 - name: dateFrom in: query required: true schema: { type: string, format: date } - name: dateTo in: query required: true schema: { type: string, format: date } responses: '200': description: Customer timeline resolved content: application/json: schema: $ref: '#/components/schemas/CustomerPricingHistoryResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '500': { $ref: '#/components/responses/InternalServerError' } # Vehicles Endpoints /vehicles: get: tags: - Vehicles summary: List vehicles description: | List vehicles or fetch a specific vehicle when `id` is provided. - When `id` is present, returns a single vehicle object (404 if not found). - Otherwise returns a paginated list of vehicles. Permissions: - Own scope: `list_own_vehicles` (linked to subuser node `VEHICLES_LIST`). - Broader scope: `list_vehicles_other`. Subusers may specify header `X-Customer-Number` to target a specific customer. If the broader permission is missing, the list will automatically be restricted to the effective customer context. operationId: listVehicles parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/XCustomerNumber' - name: id in: query schema: {type: integer} - name: reg in: query schema: {type: string} - name: customer_id in: query schema: {type: integer} responses: '200': description: Vehicle(s) retrieved successfully content: application/json: schema: oneOf: - $ref: '#/components/schemas/Vehicle' - type: array items: $ref: '#/components/schemas/Vehicle' '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } post: tags: - Vehicles summary: Add vehicle operationId: addVehicle description: | Create a new vehicle for a customer. Permissions: - Own scope: `add_vehicle` (linked to subuser node `VEHICLES_ADD`). - Broader scope: `add_vehicle_other`. parameters: - $ref: '#/components/parameters/XCustomerNumber' requestBody: required: true content: application/json: schema: type: object required: [reg, type, wash_subscription] properties: reg: type: string minLength: 2 maxLength: 12 description: Vehicle registration number type: type: integer description: Product ID representing the vehicle wash type wash_subscription: type: boolean reference: type: string maxLength: 255 nullable: true customer_id: type: integer description: Optional explicit target customer. Defaults to the effective customer context. responses: '200': description: Vehicle created content: application/json: schema: {} '400': { $ref: '#/components/responses/BadRequest' } '403': { $ref: '#/components/responses/Forbidden' } put: tags: - Vehicles summary: Edit vehicle operationId: editVehicle description: | Update fields on an existing vehicle. Permissions: - Own scope: `edit_vehicle` (linked to subuser node `VEHICLES_EDIT`). - Broader scope: `edit_vehicle_other`. parameters: - $ref: '#/components/parameters/XCustomerNumber' requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} reg: type: string minLength: 2 maxLength: 12 type: {type: integer} wash_subscription: {type: boolean} reference: type: string maxLength: 255 nullable: true responses: '200': description: Vehicle updated content: application/json: schema: {} '400': { $ref: '#/components/responses/BadRequest' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } delete: tags: - Vehicles summary: Delete vehicle operationId: deleteVehicle description: | Delete an existing vehicle. Permissions: - Own scope: `delete_vehicle` (linked to subuser node `VEHICLES_DELETE`). - Broader scope: `delete_vehicle_other`. parameters: - $ref: '#/components/parameters/XCustomerNumber' - name: id in: query required: true schema: {type: integer} responses: '200': description: Vehicle deleted content: application/json: schema: {} '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } /vehicles/addons/available: get: tags: - Vehicles summary: Get available vehicle addons description: | Get list of available addons for a vehicle. Permissions: - Own scope: `list_vehicle_addon_own` (linked to subuser node `VEHICLES_LIST`). - Broader scope: `list_vehicles_addon_other`. operationId: getAvailableVehicleAddons parameters: - $ref: '#/components/parameters/XCustomerNumber' - name: id in: query required: true schema: type: integer responses: '200': description: Available addons retrieved successfully content: application/json: schema: {} '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } /vehicles/addons/toggle: post: tags: - Vehicles summary: Toggle vehicle addon description: | Enable or disable a vehicle addon for a vehicle. Permissions: - Own scope: `toggle_vehicle_addon_own` (linked to subuser node `VEHICLES_EDIT`). - Broader scope: `toggle_vehicle_addon_other`. operationId: toggleVehicleAddon parameters: - $ref: '#/components/parameters/XCustomerNumber' requestBody: required: true content: application/json: schema: type: object required: [vehicle_id, addon_id] properties: vehicle_id: type: integer addon_id: type: integer responses: '200': description: Vehicle addon toggled successfully content: application/json: schema: {} '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } /department/vehicles/unknown-customer: get: tags: - Vehicles summary: Get unknown customer vehicles in department operationId: getUnknownCustomerVehicles responses: '200': description: Success content: application/json: schema: {} /department/vehicle/customer-suggestions: get: tags: - Vehicles summary: Get vehicle customer suggestions operationId: getVehicleCustomerSuggestions parameters: - name: reg in: query required: true schema: {type: string} responses: '200': description: Success content: application/json: schema: {} /vehicles/set-auto-start-on-lpr: post: tags: - Vehicles summary: Set auto start on LPR operationId: setVehicleAutoStartOnLpr description: | Enable or disable automatic start on LPR for a vehicle in XL Vask. Permissions: - Own scope: `set_auto_start_on_lpr` (linked to subuser node `VEHICLES_EDIT`). - Broader scope: `set_auto_start_on_lpr_other`. parameters: - $ref: '#/components/parameters/XCustomerNumber' requestBody: required: true content: application/json: schema: type: object required: [id, active] properties: id: {type: integer} active: {type: boolean} responses: '200': description: Success content: application/json: schema: {} '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } /vehicles/set-vehicle-type-id: post: tags: - Vehicles summary: Set vehicle type ID operationId: setVehicleTypeId description: | Set or change the XL Vask `vehicleTypeId` for a vehicle. Permissions: - Own scope: `set_vehicle_type_id` (linked to subuser node `VEHICLES_EDIT`). - Broader scope: `set_vehicle_type_id_other`. parameters: - $ref: '#/components/parameters/XCustomerNumber' requestBody: required: true content: application/json: schema: type: object required: [id, vehicleTypeId] properties: id: {type: integer} vehicleTypeId: type: string minLength: 1 maxLength: 50 responses: '200': description: Success content: application/json: schema: {} '400': { $ref: '#/components/responses/BadRequest' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } /superuser/users-with-vehicle-subscriptions: get: tags: - Vehicles summary: Get users with vehicle subscriptions operationId: getUsersWithVehicleSubscriptions responses: '200': description: Success content: application/json: schema: {} /vehicles/status: get: tags: - Vehicles summary: Get vehicle status operationId: getVehicleStatus parameters: - name: reg in: query required: true schema: {type: string} responses: '200': description: Success content: application/json: schema: {} /vehicles/search: get: tags: - Vehicles summary: Search vehicles operationId: searchVehicles parameters: - name: search in: query required: true schema: {type: string} responses: '200': description: Success content: application/json: schema: {} # Notifications Endpoints /notifications: get: tags: - Notifications summary: List notifications description: Get list of notifications operationId: listNotifications parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: Notifications retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Notification' post: tags: - Notifications summary: Create notification description: Create a new notification operationId: createNotification requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/NotificationCreate' responses: '201': description: Notification created successfully content: application/json: schema: {} delete: tags: - Notifications summary: Delete notification description: Delete a notification operationId: deleteNotification parameters: - name: id in: query required: true schema: type: integer responses: '200': description: Notification deleted successfully content: application/json: schema: {} # Statistics Endpoints /statistics/bookings/new: get: tags: - Statistics summary: Get new bookings statistics description: Get statistics for new bookings operationId: getNewBookingsStats responses: '200': description: New bookings statistics retrieved successfully content: application/json: schema: {} /orders/module/stripe/payment_intent: get: tags: - Orders summary: Get Stripe payment intent operationId: getStripePaymentIntent parameters: - name: id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} post: tags: - Orders summary: Create Stripe payment intent operationId: createStripePaymentIntent requestBody: required: true content: application/json: schema: type: object required: [id, reader] properties: id: {type: integer} reader: {type: string} tax_percentage: {type: integer} responses: '200': description: Success content: application/json: schema: {} delete: tags: - Orders summary: Delete Stripe payment intent operationId: deleteStripePaymentIntent parameters: - name: id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} /orders/module/stripe/payment_intent/capture: post: tags: - Orders summary: Capture Stripe payment intent operationId: captureStripePaymentIntent requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} responses: '200': description: Success content: application/json: schema: {} /orders/module/stripe/debug/simulate_payment: post: tags: - Orders summary: Simulate Stripe payment operationId: simulateStripePayment requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} responses: '200': description: Success content: application/json: schema: {} /statistics/orders/new: get: tags: - Statistics summary: Get new orders statistics description: Get statistics for new orders operationId: getNewOrdersStats responses: '200': description: New orders statistics retrieved successfully content: application/json: schema: {} /statistics/income/today: get: tags: - Statistics summary: Get today's income description: Get income statistics for today operationId: getTodayIncome responses: '200': description: Today's income statistics retrieved successfully content: application/json: schema: {} /statistics/income/yesterday: get: tags: - Statistics summary: Get yesterday's income description: Get income statistics for yesterday operationId: getYesterdayIncome responses: '200': description: Yesterday's income statistics retrieved successfully content: application/json: schema: {} /statistics/income/this-month: get: tags: - Statistics summary: Get this month's income description: Get income statistics for the current month operationId: getThisMonthIncome responses: '200': description: This month's income statistics retrieved successfully content: application/json: schema: {} /statistics/income/last-month: get: tags: - Statistics summary: Get last month's income description: Get income statistics for the previous month operationId: getLastMonthIncome responses: '200': description: Last month's income statistics retrieved successfully content: application/json: schema: {} /statistics/income/this-year: get: tags: - Statistics summary: Get this year's income description: Get income statistics for the current year operationId: getThisYearIncome responses: '200': description: This year's income statistics retrieved successfully content: application/json: schema: {} /statistics/income/departments: get: tags: - Statistics summary: Get total income today by departments operationId: getTotalIncomeTodayByDepartments responses: '200': description: Success content: application/json: schema: {} /statistics/economic/totals: get: tags: - Statistics summary: Get total economic statistics operationId: getEconomicTotals responses: '200': description: Success content: application/json: schema: {} /statistics/economic/totals/department_sent_invoice_totals: get: tags: - Statistics summary: Get department sent invoice totals operationId: getDepartmentSentInvoiceTotals responses: '200': description: Success content: application/json: schema: {} /statistics/economic/totals/department_draft_invoice_totals: get: tags: - Statistics summary: Get department draft invoice totals operationId: getDepartmentDraftInvoiceTotals responses: '200': description: Success content: application/json: schema: {} # Worker Endpoints /worker/version: get: tags: - Worker summary: Get worker version description: Get the current version of the system worker operationId: getWorkerVersion responses: '200': description: Worker version retrieved successfully content: application/json: schema: {} /worker/update-version: get: tags: - Worker summary: Update worker version description: Set the target version for the worker update operationId: updateWorkerVersion parameters: - name: version in: query required: true schema: type: string responses: '200': description: Version update target set successfully content: application/json: schema: {} /worker/status: get: tags: - Worker summary: Get worker status description: Get detailed status of the system worker operationId: getWorkerStatus responses: '200': description: Worker status retrieved successfully content: application/json: schema: {} /worker/debug: get: tags: - Worker summary: Debug worker description: Execute debug commands on the worker (often restricted) operationId: debugWorker responses: '200': description: Debug information retrieved successfully content: application/json: schema: {} '403': $ref: '#/components/responses/Forbidden' /worker/debug/on: get: tags: - Worker summary: Enable worker debug operationId: enableWorkerDebug responses: '200': description: Worker debug enabled content: application/json: schema: {} '403': $ref: '#/components/responses/Forbidden' /worker/debug/off: get: tags: - Worker summary: Disable worker debug operationId: disableWorkerDebug responses: '200': description: Worker debug disabled content: application/json: schema: {} '403': $ref: '#/components/responses/Forbidden' /worker/licenseplates: get: tags: - Worker summary: Get unique license plates description: Fetch all unique license plates from various database tables operationId: getWorkerLicensePlates responses: '200': description: License plates retrieved successfully content: application/json: schema: {} '403': $ref: '#/components/responses/Forbidden' /economic/doesCustomerExist: get: tags: - Modules summary: Check if customer exists in e-conomic operationId: checkEconomicCustomerExists parameters: - name: cvr in: query required: true schema: type: string responses: '200': description: Customer check completed content: application/json: schema: {} '404': $ref: '#/components/responses/NotFound' /cvr/lookup: get: tags: - Modules summary: Lookup CVR information description: Get detailed information for a CVR number operationId: lookupCvr parameters: - name: cvr in: query required: true schema: type: string responses: '200': description: CVR information retrieved successfully content: application/json: schema: {} /cvr/search: get: tags: - Modules summary: Search CVR description: Search for companies by name or CVR operationId: searchCvr parameters: - name: query in: query required: true schema: type: string minLength: 2 responses: '200': description: Search results retrieved successfully content: application/json: schema: {} # Plate Scans Endpoints /numberplatescans: get: tags: - Plate Scans summary: List plate scans description: Get a list of license plate scans operationId: listPlateScans parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' responses: '200': description: Plate scans retrieved successfully content: application/json: schema: {} post: tags: - Plate Scans summary: Record plate scan description: Record a new license plate scan operationId: recordPlateScan requestBody: required: true content: application/json: schema: type: object required: - plate - lane_id properties: plate: type: string lane_id: type: integer responses: '201': description: Plate scan recorded successfully content: application/json: schema: {} /numberplatescans/department: post: tags: - Plate Scans summary: Record plate scan for department operationId: recordDepartmentPlateScan requestBody: required: true content: application/json: schema: type: object required: - plate - department_id properties: plate: type: string department_id: type: integer responses: '201': description: Plate scan recorded successfully content: application/json: schema: {} /numberplatescans/post: get: tags: - Plate Scans summary: Get post-scan results operationId: getPlateScanPostResults responses: '200': description: Post-scan results retrieved successfully content: application/json: schema: {} /numberplatescanners: get: tags: - Plate Scans summary: List plate scanners description: Get a list of all number plate scanners operationId: listPlateScanners parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' responses: '200': description: Plate scanners retrieved successfully content: application/json: schema: {} post: tags: - Plate Scans summary: Add plate scanner operationId: addPlateScanner requestBody: required: true content: application/json: schema: type: object required: [department_id, name, notes] properties: department_id: {type: integer} name: {type: string} notes: {type: string} responses: '201': description: Plate scanner added successfully content: application/json: schema: {} put: tags: - Plate Scans summary: Update plate scanner operationId: updatePlateScanner requestBody: required: true content: application/json: schema: type: object required: [id, department_id, name, notes] properties: id: {type: integer} department_id: {type: integer} name: {type: string} notes: {type: string} responses: '200': description: Plate scanner updated successfully content: application/json: schema: {} /department/numberplatescanners: get: tags: - Plate Scans summary: List department plate scanners operationId: listDepartmentPlateScanners parameters: - name: id in: query required: true schema: type: integer responses: '200': description: Department plate scanners retrieved successfully content: application/json: schema: {} /relay/button/press/post: get: tags: - Plate Scans summary: Add button press operationId: addButtonPress parameters: - name: token in: query required: true schema: {type: string} responses: '201': description: Button press recorded content: application/json: schema: {} # Module - e-conomic Endpoints /economic/customers/import: post: tags: - Modules summary: Import e-conomic customers description: Import customers from e-conomic operationId: importEconomicCustomers requestBody: required: false content: application/json: schema: {} responses: '200': description: Customers imported successfully content: application/json: schema: {} /economic/departments: get: tags: - Modules summary: Get e-conomic departments operationId: getEconomicDepartments responses: '200': description: Success content: application/json: schema: {} /economic/products: get: tags: - Modules summary: Get e-conomic products operationId: getEconomicProducts responses: '200': description: Success content: application/json: schema: {} /modules/economic/customer: get: tags: - Modules summary: Get e-conomic customer details operationId: getEconomicCustomer parameters: - name: customer_number in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} post: tags: - Modules summary: Create e-conomic customer operationId: createEconomicCustomer requestBody: required: true content: application/json: schema: type: object required: [customer_number, cvr, email, phone, name] properties: customer_number: {type: integer} cvr: {type: integer} email: {type: string} phone: {type: integer} name: {type: string} responses: '200': description: Success content: application/json: schema: {} /economic/layouts: get: tags: - Modules summary: Get e-conomic layouts description: Get available invoice layouts from e-conomic operationId: getEconomicLayouts responses: '200': description: Layouts retrieved successfully content: application/json: schema: {} /economic/payment-terms: get: tags: - Modules summary: Get e-conomic payment terms description: Get available payment terms from e-conomic operationId: getEconomicPaymentTerms responses: '200': description: Payment terms retrieved successfully content: application/json: schema: {} /economic/invoice/draft/export: post: tags: - Modules summary: Export draft invoice to e-conomic description: Export a draft invoice to e-conomic operationId: exportDraftInvoiceToEconomic requestBody: required: false content: application/json: schema: {} responses: '200': description: Draft invoice exported successfully content: application/json: schema: {} /economic/invoice/export: post: tags: - Modules summary: Export invoice to e-conomic description: Export a booked invoice to e-conomic operationId: exportInvoiceToEconomic requestBody: required: false content: application/json: schema: {} responses: '200': description: Invoice exported successfully content: application/json: schema: {} # Module - Stripe Endpoints /modules/stripe/customers: get: tags: - Modules summary: List Stripe customers description: Get list of Stripe customers operationId: listStripeCustomers responses: '200': description: Stripe customers retrieved successfully content: application/json: schema: {} /modules/stripe/products: get: tags: - Modules summary: List Stripe products description: Get list of Stripe products operationId: listStripeProducts responses: '200': description: Stripe products retrieved successfully content: application/json: schema: {} /modules/stripe/prices: get: tags: - Modules summary: List Stripe prices description: Get list of Stripe prices operationId: listStripePrices responses: '200': description: Stripe prices retrieved successfully content: application/json: schema: {} /modules/stripe/invoice: post: tags: - Modules summary: Create Stripe invoice description: Create an invoice in Stripe operationId: createStripeInvoice requestBody: required: false content: application/json: schema: {} responses: '201': description: Stripe invoice created successfully content: application/json: schema: {} /modules/stripe/terminal/readers: get: tags: - Modules summary: List Stripe terminal readers operationId: listStripeTerminalReaders responses: '200': description: Success content: application/json: schema: {} /modules/stripe/terminal/locations: get: tags: - Modules summary: List Stripe terminal locations operationId: listStripeTerminalLocations responses: '200': description: Success content: application/json: schema: {} /modules/stripe/department/terminal/location: get: tags: - Modules summary: Get department terminal location operationId: getDepartmentTerminalLocation parameters: - name: id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} post: tags: - Modules summary: Set department terminal location operationId: setDepartmentTerminalLocation requestBody: required: true content: application/json: schema: type: object required: [id, location] properties: id: {type: integer} location: {type: string} responses: '200': description: Success content: application/json: schema: {} /modules/stripe/department/terminal/readers: get: tags: - Modules summary: Get department terminal readers operationId: getDepartmentTerminalReaders parameters: - name: id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} # Module - Backup Endpoints /modules/backup/backups: get: tags: - Modules summary: List backup modules operationId: listBackupModules responses: '200': description: Success content: application/json: schema: {} post: tags: - Modules summary: Create backup module operationId: createBackupModule requestBody: required: true content: application/json: schema: type: object required: [name, description] properties: name: {type: string} description: {type: string} responses: '200': description: Success content: application/json: schema: {} # Module - XLVask Endpoints /modules/xlvask/usageLog: get: tags: - Modules summary: Get XLVask usage logs description: Retrieve usage logs from XLVask system operationId: getXlvaskUsageLogs parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: Usage logs retrieved successfully content: application/json: schema: {} /modules/xlvask/vehicles: get: tags: - Modules summary: List XLVask vehicles description: Get list of vehicles from XLVask operationId: listXlvaskVehicles responses: '200': description: XLVask vehicles retrieved successfully content: application/json: schema: {} /modules/xlvask/customers: get: tags: - Modules summary: List XLVask customers description: Get list of customers from XLVask operationId: listXlvaskCustomers responses: '200': description: XLVask customers retrieved successfully content: application/json: schema: {} /modules/action-logs: get: tags: - Modules summary: List module action logs description: Retrieve a paginated list of module action logs with searching and filtering operationId: listModuleActionLogs parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/LimitParam' - $ref: '#/components/parameters/SearchParam' - $ref: '#/components/parameters/FiltersParam' responses: '200': description: Module action logs retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/ModuleActionLog' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' # Module - Self-Serve Endpoints /modules/self-serve/lane/status: get: tags: - Modules summary: Get self-serve lane status description: Retrieve the current status of a self-serve lane operationId: getSelfServeLaneStatus parameters: - name: lane_id in: query required: true schema: type: integer responses: '200': description: Lane status retrieved successfully content: application/json: schema: $ref: '#/components/schemas/SelfServeLaneStatus' /modules/self-serve/lane/command: post: tags: - Modules summary: Send self-serve lane command description: Send a command (e.g., start, stop, reset) to a self-serve lane operationId: sendSelfServeLaneCommand requestBody: required: true content: application/json: schema: type: object required: - lane_id - command properties: lane_id: type: integer command: type: string enum: [START, STOP, RESET, RESERVE, RELEASE] license_plate: type: string description: Required for START command responses: '200': description: Command sent successfully content: application/json: schema: $ref: '#/components/schemas/SelfServeLaneStatus' /modules/self-serve/lane/services/allowed: post: tags: - Modules summary: Set allowed services for a lane based on shown tasks description: | Updates the set of services that are allowed to be manually activated for a given self-serve lane, derived from the tasks currently shown to the user after answering the self-serve questions. This endpoint does not activate anything by itself; it only sets what is allowed to be activated. operationId: setSelfServeLaneAllowedServices requestBody: required: true content: application/json: schema: type: object required: - lane_id properties: lane_id: type: integer task_ids: type: array description: List of task IDs that are currently shown to the user items: type: integer responses: '200': description: Allowed services updated content: application/json: schema: type: object properties: lane_id: type: integer allowed_services: type: array items: type: string '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/relay/machine/enable: post: tags: - Modules summary: Manually enable MACHINE relay for a lane description: | Manually turns on the MACHINE relay for a self-serve lane if and only if the current allowed services include `MACHINE` (set via `/modules/self-serve/lane/services/allowed`). The relay is never automatically enabled; an explicit call to this endpoint is required. operationId: enableSelfServeLaneMachineRelay requestBody: required: true content: application/json: schema: type: object required: - lane_id properties: lane_id: type: integer duration: type: integer description: Optional number of seconds after which the relay should automatically turn off responses: '200': description: MACHINE relay enabled content: application/json: schema: type: object properties: lane_id: type: integer relay: type: string enabled: type: boolean duration: type: integer nullable: true '401': $ref: '#/components/responses/Unauthorized' '403': description: Not allowed to enable MACHINE relay (no matching task currently shown) content: application/json: schema: $ref: '#/components/schemas/Error' /modules/self-serve/lane/force/machine/enable: post: tags: - Modules summary: Force enable MACHINE relay and mark lane as in-wash (superusers only) description: | Superuser/emergency endpoint. Bypasses the allowed services gating and directly turns on the MACHINE relay. Also ensures the lane is marked as OCCUPIED and IN_WASH with a wash start timestamp if not already set. operationId: forceEnableSelfServeLaneMachine requestBody: required: true content: application/json: schema: type: object required: - lane_id properties: lane_id: type: integer duration: type: integer nullable: true description: Optional number of seconds after which the relay should automatically turn off license_plate: type: string nullable: true description: Optional license plate to associate with the lane responses: '200': description: MACHINE relay force-enabled and lane marked in-wash content: application/json: schema: type: object properties: lane_id: type: integer forced: type: boolean machine: type: string enum: [ENABLED] duration: type: integer nullable: true status: type: string state: type: string wash_start_time: type: integer '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/force/machine/disable: post: tags: - Modules summary: Force disable MACHINE relay but keep lane as in-wash (superusers only) description: | Superuser/emergency endpoint. Turns off the MACHINE relay while ensuring the lane remains in an IN_WASH state (simulating a started wash without machine assistance). operationId: forceDisableSelfServeLaneMachine requestBody: required: true content: application/json: schema: type: object required: - lane_id properties: lane_id: type: integer license_plate: type: string nullable: true responses: '200': description: MACHINE relay force-disabled and lane ensured in-wash content: application/json: schema: type: object properties: lane_id: type: integer forced: type: boolean machine: type: string enum: [DISABLED] status: type: string state: type: string wash_start_time: type: integer '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' # Module - Other Integration Endpoints /modules/motorapi/lookup: get: tags: - Modules summary: Lookup vehicle via MotorAPI description: Look up vehicle information using license plate operationId: motorApiLookup parameters: - name: plate in: query required: true schema: type: string responses: '200': description: Vehicle information retrieved successfully content: application/json: schema: {} /modules/virkdata/search: get: tags: - Modules summary: Search VirkData description: Search for company information in VirkData operationId: virkdataSearch parameters: - name: search in: query required: true schema: type: string responses: '200': description: Company information retrieved successfully content: application/json: schema: {} /modules/fxratesapi/rate: get: tags: - Modules summary: Get exchange rate description: Get current exchange rate operationId: getExchangeRate parameters: - name: from in: query required: true schema: type: string - name: to in: query required: true schema: type: string responses: '200': description: Exchange rate retrieved successfully content: application/json: schema: {} /modules/fxratesapi/rates: get: tags: - Modules summary: Get all exchange rates description: Get all available exchange rates operationId: getAllExchangeRates responses: '200': description: Exchange rates retrieved successfully content: application/json: schema: {} /modules/weatherapi/current: get: tags: - Modules summary: Get current weather description: Get current weather data from WeatherAPI for a location query operationId: weatherApiCurrent parameters: - name: q in: query required: true schema: type: string description: Location query (e.g. city, postal code, or latitude,longitude) responses: '200': description: Current weather retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WeatherApiObjectResponse' /modules/weatherapi/forecast: get: tags: - Modules summary: Get weather forecast description: Get forecast weather data from WeatherAPI operationId: weatherApiForecast parameters: - name: q in: query required: true schema: type: string description: Location query (e.g. city, postal code, or latitude,longitude) - name: days in: query required: false schema: type: integer minimum: 1 maximum: 14 description: Number of forecast days responses: '200': description: Forecast weather retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WeatherApiObjectResponse' /modules/weatherapi/search: get: tags: - Modules summary: Search weather locations description: Search location suggestions from WeatherAPI operationId: weatherApiSearch parameters: - name: q in: query required: true schema: type: string description: Search text responses: '200': description: Location search results retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WeatherApiObjectResponse' /departments/weather: get: tags: - Departments summary: Get department weather timeline description: Returns hourly weather, washes, hours and productivity status for a department operationId: getDepartmentWeatherTimeline parameters: - name: id in: query required: true schema: type: integer minimum: 1 description: Department ID responses: '200': description: Department weather timeline retrieved successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: data: type: array items: $ref: '#/components/schemas/DepartmentWeatherTimelineEntry' $ref: '#/components/schemas/DepartmentWeatherTimelineResponse' /modules/entra/users: get: tags: - Modules summary: List Microsoft Entra users description: Get list of users from Microsoft Entra (Azure AD) operationId: listEntraUsers responses: '200': description: Entra users retrieved successfully content: application/json: schema: {} # Attachments Endpoints /attachments/upload: post: tags: - Attachments summary: Upload attachment description: Upload a file attachment operationId: uploadAttachment requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary responses: '201': description: Attachment uploaded successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' /orders/attachments: get: tags: - Attachments summary: List order attachments description: Get attachments for an order operationId: listOrderAttachments parameters: - name: order_id in: query required: true schema: type: integer responses: '200': description: Order attachments retrieved successfully content: application/json: schema: {} /orders/attachments/upload: post: tags: - Attachments summary: Upload order attachment description: Upload an attachment to an order operationId: uploadOrderAttachment requestBody: required: true content: multipart/form-data: schema: type: object properties: order_id: type: integer file: type: string format: binary responses: '201': description: Order attachment uploaded successfully content: application/json: schema: {} /orders/attachments/download: get: tags: - Attachments summary: Download order attachment description: Download a specific order attachment operationId: downloadOrderAttachment parameters: - name: id in: query required: true schema: type: integer responses: '200': description: Attachment downloaded successfully content: application/octet-stream: schema: type: string format: binary # Forms Endpoints /form: get: tags: - Forms summary: Get form description: Retrieve a form definition operationId: getForm parameters: - name: id in: query schema: type: integer responses: '200': description: Form retrieved successfully content: application/json: schema: {} post: tags: - Forms summary: Submit form description: Submit a form operationId: submitForm requestBody: required: true content: application/json: schema: type: object required: [id, data] properties: id: type: string description: Form identifier data: type: object description: Form submission data g_recaptcha_response: type: string description: reCAPTCHA verification token (required if not authenticated) responses: '201': description: Form submitted successfully content: application/json: schema: {} # Permissions Endpoints /permissions: get: tags: - Users summary: List permissions description: Get list of all available permissions operationId: listPermissions responses: '200': description: Permissions retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Permission' # Customer Management Endpoints /customer/attributes: get: tags: - Users summary: Get customer attributes description: Get custom attributes for a customer operationId: getCustomerAttributes parameters: - name: customer_id in: query schema: type: integer responses: '200': description: Customer attributes retrieved successfully content: application/json: schema: {} post: tags: - Users summary: Add customer attribute description: Add a custom attribute to a customer operationId: addCustomerAttribute requestBody: required: false content: application/json: schema: {} responses: '201': description: Customer attribute added successfully content: application/json: schema: {} delete: tags: - Users summary: Delete customer attribute description: Remove a custom attribute from a customer operationId: deleteCustomerAttribute requestBody: required: false content: application/json: schema: {} responses: '200': description: Customer attribute deleted successfully content: application/json: schema: {} /customer/notes: get: tags: - Users summary: Get customer notes description: Get notes for a customer operationId: getCustomerNotes parameters: - name: customer_id in: query schema: type: integer responses: '200': description: Customer notes retrieved successfully content: application/json: schema: {} post: tags: - Users summary: Add customer note description: Add a note to a customer operationId: addCustomerNote requestBody: required: false content: application/json: schema: {} responses: '201': description: Customer note added successfully content: application/json: schema: {} delete: tags: - Users summary: Delete customer note description: Remove a note from a customer operationId: deleteCustomerNote requestBody: required: false content: application/json: schema: {} responses: '200': description: Customer note deleted successfully content: application/json: schema: {} /customers/search: post: tags: - Users summary: Search customers description: Search for customers using various criteria operationId: searchCustomers requestBody: required: true content: application/json: schema: type: object properties: query: type: string responses: '200': description: Customers found successfully content: application/json: schema: {} /search/system: get: tags: - Search summary: System-wide search description: Search across all supported entities with permission-aware filtering and optional intent parsing debug metadata. Customer records and customer-related entities are matched against a local e-conomic customer index (name/address/email/CVR) that is refreshed by cron. Intent parsing is invoked adaptively when lexical confidence is low or when the query looks intent-driven. Results are ordered by relevance, with recent records preferred when relevance is comparable. operationId: systemWideSearchGet parameters: - in: query name: query required: true schema: type: string description: Free-text query to search for. Supports natural-language intent fallback and domain synonyms such as `rabat` -> `discount`. - in: query name: include_types required: false schema: type: array items: $ref: '#/components/schemas/SystemSearchEntityType' style: form explode: false description: Comma-separated list of entity types to include. Defaults to all allowed types. - in: query name: exclude_types required: false schema: type: array items: $ref: '#/components/schemas/SystemSearchEntityType' style: form explode: false description: Comma-separated list of entity types to exclude. - in: query name: include_associations required: false schema: type: boolean default: true description: Include associated objects when matching a primary entity such as a customer. - in: query name: debug_intent required: false schema: type: boolean default: false description: Include intent parser diagnostics in `meta.intent_parser`. - in: query name: limit required: false schema: type: integer minimum: 1 maximum: 200 default: 50 - in: query name: offset required: false schema: type: integer minimum: 0 default: 0 responses: '200': description: Search results returned successfully content: application/json: schema: $ref: '#/components/schemas/SystemSearchResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' post: tags: - Search summary: System-wide search description: Search across all supported entities using JSON request payload. Customer-related matches include local e-conomic customer index fields. Intent parsing may run adaptively for intent-driven natural-language queries. Results are ordered by relevance, with recent records preferred when relevance is comparable. operationId: systemWideSearchPost requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SystemSearchRequest' responses: '200': description: Search results returned successfully content: application/json: schema: $ref: '#/components/schemas/SystemSearchResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /superuser/search/system/cache: delete: tags: - Search summary: Clear system search caches description: Clears both query-result cache and intent-parser cache namespaces for system-wide search. operationId: clearSystemSearchCache responses: '200': description: Cache cleared successfully content: application/json: schema: $ref: '#/components/schemas/SystemSearchCacheClearResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /superuser/search/system/cache/rebuild: post: tags: - Search summary: Queue system search cache rebuild description: Queues a cache rebuild request and clears active query/intent cache namespaces immediately. operationId: rebuildSystemSearchCache requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/SystemSearchCacheRebuildRequest' responses: '200': description: Cache rebuild queued successfully content: application/json: schema: $ref: '#/components/schemas/SystemSearchCacheRebuildResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' # Configuration Endpoints /economic/config: get: tags: [Config] summary: Get e-conomic config operationId: getEconomicConfig responses: '200': description: e-conomic configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/EconomicConfigListResponse' post: tags: [Config] summary: Update e-conomic config operationId: updateEconomicConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: e-conomic configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /reCAPTCHA/config: get: tags: [Config] summary: Get reCAPTCHA config operationId: getRecaptchaConfig responses: '200': description: reCAPTCHA configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/RecaptchaConfigListResponse' post: tags: [Config] summary: Update reCAPTCHA config operationId: updateRecaptchaConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: reCAPTCHA configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /email/config: get: tags: [Config] summary: Get email config operationId: getEmailConfig responses: '200': description: Email configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/EmailConfigListResponse' post: tags: [Config] summary: Update email config operationId: updateEmailConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: Email configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /email/config/test: post: tags: [Config] summary: Test email config operationId: testEmailConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: Email configuration test completed content: application/json: schema: $ref: '#/components/schemas/ModuleConfigTestResponse' /backups/config: get: tags: [Config] summary: Get backups config operationId: getBackupsConfig responses: '200': description: Backups configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/BackupsConfigListResponse' post: tags: [Config] summary: Update backups config operationId: updateBackupsConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: Backups configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /bird/config: get: tags: [Config] summary: Get Bird config operationId: getBirdConfig responses: '200': description: Bird configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/BirdConfigListResponse' post: tags: [Config] summary: Update Bird config operationId: updateBirdConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: Bird configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /motorapi/config: get: tags: [Config] summary: Get MotorAPI config operationId: getMotorApiConfig responses: '200': description: MotorAPI configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/MotorApiConfigListResponse' post: tags: [Config] summary: Update MotorAPI config operationId: updateMotorApiConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: MotorAPI configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /stripe/config: get: tags: [Config] summary: Get Stripe config operationId: getStripeConfig responses: '200': description: Stripe configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/StripeConfigListResponse' post: tags: [Config] summary: Update Stripe config operationId: updateStripeConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: Stripe configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /fxratesapi/config: get: tags: [Config] summary: Get FXRatesAPI config operationId: getFxRatesApiConfig responses: '200': description: FXRatesAPI configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/FxRatesApiConfigListResponse' post: tags: [Config] summary: Update FXRatesAPI config operationId: updateFxRatesApiConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: FXRatesAPI configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /weatherapi/config: get: tags: [Config] summary: Get WeatherAPI config operationId: getWeatherApiConfig responses: '200': description: WeatherAPI configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WeatherApiConfigListResponse' post: tags: [Config] summary: Update WeatherAPI config operationId: updateWeatherApiConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: WeatherAPI configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /gatewayapi/config: get: tags: [Config] summary: Get GatewayAPI config operationId: getGatewayApiConfig responses: '200': description: GatewayAPI configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/GatewayApiConfigListResponse' post: tags: [Config] summary: Update GatewayAPI config operationId: updateGatewayApiConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: GatewayAPI configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /xlvask/config: get: tags: [Config] summary: Get XLVask config operationId: getXlvaskConfig responses: '200': description: XLVask configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/XlvaskConfigListResponse' post: tags: [Config] summary: Update XLVask config operationId: updateXlvaskConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: XLVask configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /entra/config: get: tags: [Config] summary: Get Entra config operationId: getEntraConfig responses: '200': description: Entra configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/EntraConfigListResponse' post: tags: [Config] summary: Update Entra config operationId: updateEntraConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: Entra configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /limble/config: get: tags: [Config] summary: Get Limble config operationId: getLimbleConfig responses: '200': description: Limble configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/LimbleConfigListResponse' post: tags: [Config] summary: Update Limble config operationId: updateLimbleConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: Limble configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /ocrspace/config: get: tags: [Config] summary: Get OcrSpace config operationId: getOcrSpaceConfig responses: '200': description: OcrSpace configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/OcrSpaceConfigListResponse' post: tags: [Config] summary: Update OcrSpace config operationId: updateOcrSpaceConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: OcrSpace configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /openai/config: get: tags: [Config] summary: Get OpenAI config operationId: getOpenAiConfig responses: '200': description: OpenAI configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/OpenAiConfigListResponse' post: tags: [Config] summary: Update OpenAI config operationId: updateOpenAiConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: OpenAI configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /licenseplaterecognizer/config: get: tags: [Config] summary: Get LicensePlateRecognizer config operationId: getLicensePlateRecognizerConfig responses: '200': description: LicensePlateRecognizer configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/LicensePlateRecognizerConfigListResponse' post: tags: [Config] summary: Update LicensePlateRecognizer config operationId: updateLicensePlateRecognizerConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: LicensePlateRecognizer configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /virkdata/config: get: tags: [Config] summary: Get Virkdata config operationId: getVirkdataConfig responses: '200': description: Virkdata configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/VirkdataConfigListResponse' post: tags: [Config] summary: Update Virkdata config operationId: updateVirkdataConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: Virkdata configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /shelly/config: get: tags: [Config] summary: Get Shelly config operationId: getShellyConfig responses: '200': description: Shelly configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/ShellyConfigListResponse' post: tags: [Config] summary: Update Shelly config operationId: updateShellyConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: Shelly configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /selfserve/config: get: tags: [Config] summary: Get Self-Serve config operationId: getSelfServeConfig responses: '200': description: Self-serve configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/SelfServeConfigListResponse' post: tags: [Config] summary: Update Self-Serve config operationId: updateSelfServeConfig requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SelfServeConfig' responses: '200': description: Self-serve configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /branding: get: tags: - Branding summary: List branding options description: Retrieve a list of branding options or a specific branding option if ID is provided operationId: listBrandingOptions parameters: - name: id in: query required: false schema: type: integer - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: Branding options retrieved successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '403': $ref: '#/components/responses/Forbidden' post: tags: - Branding summary: Add branding option description: Create a new branding option operationId: addBrandingOption requestBody: required: true content: application/json: schema: type: object required: [name, description, cvr] properties: name: {type: string} description: {type: string} cvr: {type: integer} responses: '200': description: Branding option added successfully content: application/json: schema: {} '403': $ref: '#/components/responses/Forbidden' put: tags: - Branding summary: Edit branding option description: Update an existing branding option operationId: editBrandingOption requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} name: {type: string} description: {type: string} cvr: {type: integer} responses: '200': description: Branding option updated successfully content: application/json: schema: {} '403': $ref: '#/components/responses/Forbidden' /roles: get: tags: - Roles summary: List roles operationId: listRoles responses: '200': description: Success content: application/json: schema: {} post: tags: - Roles summary: Add role operationId: addRole requestBody: required: true content: application/json: schema: type: object required: [name] properties: name: {type: string} responses: '200': description: Success content: application/json: schema: {} put: tags: - Roles summary: Edit role operationId: editRole requestBody: required: true content: application/json: schema: type: object required: [id, name] properties: id: {type: integer} name: {type: string} responses: '200': description: Success content: application/json: schema: {} /roles/permissions: post: tags: - Roles summary: Add permission to role operationId: addRolePermission requestBody: required: true content: application/json: schema: type: object required: [role_id, permission] properties: role_id: {type: integer} permission: {type: string} responses: '200': description: Success content: application/json: schema: {} delete: tags: - Roles summary: Remove permission from role operationId: removeRolePermission parameters: - name: role_id in: query required: true schema: {type: integer} - name: permission in: query required: true schema: {type: string} responses: '200': description: Success content: application/json: schema: {} /roles/clone: post: tags: - Roles summary: Clone role operationId: cloneRole requestBody: required: true content: application/json: schema: type: object required: [role_id, name] properties: role_id: {type: integer} name: {type: string} responses: '200': description: Success content: application/json: schema: {} /modules/washcertificates: get: tags: - Modules summary: List wash certificates operationId: listWashCertificates responses: '200': description: Success content: application/json: schema: {} /modules/xlvask/services/usage/orders: get: tags: - Modules summary: Get XLVask usage orders operationId: getXlvaskUsageOrders responses: '200': description: Success content: application/json: schema: {} /modules/xlvask/services/usage/orders/fast-link: get: tags: - Modules summary: Get XLVask usage orders fast link operationId: getXlvaskUsageOrdersFastLink responses: '200': description: Success content: application/json: schema: {} /superuser/department: get: tags: - Departments summary: List departments (superuser) operationId: listSuperuserDepartments responses: '200': description: Success content: application/json: schema: {} /superuser/department/prices: get: tags: - Departments summary: Get department prices operationId: getDepartmentPrices parameters: - name: department_id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} post: tags: - Departments summary: Set department price operationId: setDepartmentPrice requestBody: required: true content: application/json: schema: type: object required: [department_id, product_id, price] properties: department_id: {type: integer} product_id: {type: integer} price: {type: integer} responses: '200': description: Success content: application/json: schema: {} /superuser/department/variables: get: tags: - Departments summary: Get department variables operationId: getDepartmentVariables parameters: - name: department_id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} post: tags: - Departments summary: Set department variable operationId: setDepartmentVariable requestBody: required: true content: application/json: schema: type: object required: [department_id, variable, value] properties: department_id: {type: integer} variable: {type: string} value: {type: string} responses: '200': description: Success content: application/json: schema: {} /departments/daily-reports: get: tags: - Departments summary: List daily reports operationId: listDailyReports responses: '200': description: Success content: application/json: schema: {} post: tags: - Departments summary: Add daily report operationId: addDailyReport requestBody: required: true content: application/json: schema: type: object required: [department_id, date, report] properties: department_id: {type: integer} date: {type: string} report: {type: string} responses: '200': description: Success content: application/json: schema: {} put: tags: - Departments summary: Edit daily report operationId: editDailyReport requestBody: required: true content: application/json: schema: type: object required: [id, report] properties: id: {type: integer} report: {type: string} responses: '200': description: Success content: application/json: schema: {} /departments/daily-reports/get: get: tags: - Departments summary: Get daily report operationId: getDailyReport parameters: - name: department_id in: query required: true schema: {type: integer} - name: date in: query required: true schema: {type: string} responses: '200': description: Success content: application/json: schema: {} /departments/daily-reports/product-count: get: tags: - Departments summary: Get product count for daily reports operationId: getDailyReportProductCount parameters: - name: date in: query required: true schema: {type: string} - name: date_to in: query required: false schema: {type: string} - name: department_id in: query required: true schema: {type: integer} - name: product_id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} /departments/daily-reports/transaction-count: get: tags: - Departments summary: Get transaction count for daily reports operationId: getDailyReportTransactionCount parameters: - name: date in: query required: true schema: {type: string} - name: date_to in: query required: false schema: {type: string} - name: department_id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} /departments/daily-reports/bookings-count: get: tags: - Departments summary: Get bookings count for daily reports operationId: getDailyReportBookingsCount parameters: - name: date in: query required: true schema: {type: string} - name: department_id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} # Account Security - Passkeys /account/security/passkeys: get: tags: - Security summary: List passkeys for the authenticated user operationId: listPasskeys responses: '200': description: A list of passkeys content: application/json: schema: type: array items: $ref: '#/components/schemas/Passkey' '400': description: Invalid session or request content: application/json: schema: $ref: '#/components/schemas/Error' post: tags: - Security summary: Create/add a passkey for the authenticated user operationId: createPasskey requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PasskeyCreateRequest' responses: '200': description: Passkey created content: application/json: schema: type: object properties: id: type: integer '400': description: Invalid session or request content: application/json: schema: $ref: '#/components/schemas/Error' /account/security/passkeys/{id}: patch: tags: - Security summary: Rename a passkey operationId: renamePasskey parameters: - in: path name: id required: true schema: type: integer requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PasskeyRenameRequest' responses: '200': description: Passkey renamed content: application/json: schema: {} '404': description: Not found content: application/json: schema: $ref: '#/components/schemas/Error' delete: tags: - Security summary: Delete a passkey operationId: deletePasskey parameters: - in: path name: id required: true schema: type: integer responses: '200': description: Passkey deleted content: application/json: schema: {} '404': description: Not found content: application/json: schema: $ref: '#/components/schemas/Error' components: securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: JWT description: JWT token obtained from /auth/login or /auth/employee/login parameters: PageParam: name: page in: query description: Page number for pagination schema: type: integer minimum: 1 default: 1 PerPageParam: name: per_page in: query description: Number of items per page schema: type: integer minimum: 1 maximum: 100 default: 30 SearchParam: name: search in: query description: Search query string schema: type: string LimitParam: name: limit in: query description: Number of items per page schema: type: integer minimum: 1 maximum: 1000 default: 100 FiltersParam: name: filters in: query description: Filters for the list (e.g., module:selfserve,status_code:200) schema: type: string XCustomerNumber: name: X-Customer-Number in: header required: false description: | Target customer number for subuser requests. Ignored for classic user sessions. Required on customer-scoped endpoints when authenticated as a subuser unless the target customer can be inferred from context. schema: type: integer responses: BadRequest: description: Bad request - Invalid input parameters content: application/json: schema: $ref: '#/components/schemas/Error' Unauthorized: description: Unauthorized - Invalid or missing authentication token content: application/json: schema: $ref: '#/components/schemas/Error' Forbidden: description: Forbidden - Insufficient permissions content: application/json: schema: $ref: '#/components/schemas/Error' NotFound: description: Not found - Resource does not exist content: application/json: schema: $ref: '#/components/schemas/Error' InternalServerError: description: Internal server error content: application/json: schema: $ref: '#/components/schemas/Error' schemas: Error: type: object properties: error: type: string description: Error message code: type: integer description: HTTP status code SystemSearchEntityType: type: string enum: - objects - module_config - orders - order_items - customers - employees - users - subusers - customer_discounts - customer_fixed_prices - departments - permissions - roles - invoices - vehicles - bookings - bookings_new - branding - categories - currency_conversion_rates - customer_codes - customer_default_department - customer_notes - customer_vehicles_addons - department_categories - department_daily_reports - department_gates - department_goals - department_lanes - department_notification_sms - department_relays - department_selfserve_condition_rules - department_selfserve_conditions - department_selfserve_questions - department_selfserve_tasks - department_selfserve_vehicle_conditions - department_time_bookings_entries - department_time_bookings_opening_hours - department_time_bookings_types - department_variables - fxratesapi_conversion_rates - module_action_logs - motorapi_lookups - notifications - order_bookings - plate_scanners - plate_scans - product_options - products - stripe_module_customers - stripe_module_orders - stripe_payment_intents - subuser_grants - xlvask_customers - xlvask_potential_order_matches - xlvask_usage_log_wash_items - xlvask_usage_logs - xlvask_vehicle_types - xlvask_vehicles SystemSearchRequest: type: object required: - query properties: query: type: string description: Free-text query to search for. Customer lookups include local e-conomic index fields and lexical synonym expansion (for example `rabat` -> `discount`). include_types: type: array items: $ref: '#/components/schemas/SystemSearchEntityType' description: Limit search to these entity types. exclude_types: type: array items: $ref: '#/components/schemas/SystemSearchEntityType' description: Exclude these entity types from search. include_associations: type: boolean default: true description: Include associated records for matched core entities. debug_intent: type: boolean default: false description: Include parser diagnostics in `meta.intent_parser`. limit: type: integer minimum: 1 maximum: 200 default: 50 offset: type: integer minimum: 0 default: 0 SystemSearchResult: type: object properties: entity_type: $ref: '#/components/schemas/SystemSearchEntityType' entity_id: type: string title: type: string description: type: string customer_number: type: integer nullable: true department_id: type: integer nullable: true score: type: integer association_reason: type: string nullable: true payload: type: object additionalProperties: true required: - entity_type - entity_id - title - score SystemSearchIntentParserMeta: type: object properties: invoked: type: boolean source: type: string enum: [cache, openai, none] status: type: string confidence: type: number minimum: 0 maximum: 1 expanded_terms: type: array items: type: string entity_hints: type: array items: $ref: '#/components/schemas/SystemSearchEntityType' fallback_reason: type: string nullable: true required: - invoked - source - status - confidence - expanded_terms - entity_hints SystemSearchMeta: type: object properties: query: type: string limit: type: integer offset: type: integer total: type: integer allowed_types: type: array items: $ref: '#/components/schemas/SystemSearchEntityType' cache: type: object properties: hit: type: boolean required: [hit] intent_parser: $ref: '#/components/schemas/SystemSearchIntentParserMeta' required: - query - limit - offset - total - allowed_types - cache SystemSearchPayload: type: object properties: results: type: array items: $ref: '#/components/schemas/SystemSearchResult' grouped_results: type: object additionalProperties: type: array items: $ref: '#/components/schemas/SystemSearchResult' meta: $ref: '#/components/schemas/SystemSearchMeta' required: - results - grouped_results - meta SystemSearchResponse: type: object properties: success: type: boolean data: $ref: '#/components/schemas/SystemSearchPayload' meta: type: object additionalProperties: true includes: type: object additionalProperties: true required: - success - data - meta - includes SystemSearchCacheClearResponse: type: object properties: success: type: boolean data: type: object properties: message: type: string query_cache_cleared: type: boolean intent_cache_cleared: type: boolean required: - message - query_cache_cleared - intent_cache_cleared meta: type: object additionalProperties: true includes: type: object additionalProperties: true required: - success - data - meta - includes SystemSearchCacheRebuildRequest: type: object properties: scope: type: string enum: [all, types, dirty] default: all types: type: array items: $ref: '#/components/schemas/SystemSearchEntityType' SystemSearchCacheRebuildResponse: type: object properties: success: type: boolean data: type: object properties: message: type: string request: type: object properties: scope: type: string enum: [all, types, dirty] types: type: array items: $ref: '#/components/schemas/SystemSearchEntityType' requested_at: type: integer required: - scope - types - requested_at query_cache_cleared: type: boolean intent_cache_cleared: type: boolean required: - message - request - query_cache_cleared - intent_cache_cleared meta: type: object additionalProperties: true includes: type: object additionalProperties: true required: - success - data - meta - includes ModuleConfigValue: oneOf: - type: string - type: integer - type: number - type: boolean - type: array items: {} - type: object additionalProperties: true nullable: true ModuleConfigEnvelopeBase: type: object properties: success: type: boolean meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - meta - includes EconomicConfigEntry: type: object properties: module: { type: string, enum: [economic] } variable: { type: string, enum: [adminFeeMonthly, adminFeeOrder, feeProductId, invoiceLayoutNumber, paymentTermsNumber] } type: { type: string, enum: [string, int] } value: oneOf: - type: string - type: integer required: [module, variable, type, value] RecaptchaConfigEntry: type: object properties: module: { type: string, enum: [reCAPTCHA] } variable: { type: string, enum: [enabled, secret_key_v2, site_key_v2] } type: { type: string, enum: [bool, string] } value: oneOf: - type: boolean - type: string required: [module, variable, type, value] EmailConfigEntry: type: object properties: module: { type: string, enum: [Email] } variable: type: string enum: [enabled, mailersend_api_key, mailersend_enabled, smtp_encryption, smtp_from, smtp_from_name, smtp_host, smtp_password, smtp_port, smtp_reply_to, smtp_reply_to_name, smtp_username] type: { type: string, enum: [bool, string, int] } value: oneOf: - type: boolean - type: string - type: integer required: [module, variable, type, value] BackupsConfigEntry: type: object properties: module: { type: string, enum: [Backups] } variable: { type: string, enum: [enabled] } type: { type: string, enum: [bool] } value: { type: boolean } required: [module, variable, type, value] BirdConfigEntry: type: object properties: module: { type: string, enum: [bird] } variable: { type: string, enum: [api_key, enabled, server_url, workplaceId, channelId] } type: { type: string, enum: [string, bool] } value: oneOf: - type: string - type: boolean required: [module, variable, type, value] MotorApiConfigEntry: type: object properties: module: { type: string, enum: [motorapi] } variable: { type: string, enum: [daily_limit, enabled, secret_key] } type: { type: string, enum: [int, bool, string] } value: oneOf: - type: integer - type: boolean - type: string required: [module, variable, type, value] StripeConfigEntry: type: object properties: module: { type: string, enum: [Stripe] } variable: { type: string, enum: [economic_customer_number, enabled, publishable_key, secret_key] } type: { type: string, enum: [int, bool, string] } value: oneOf: - type: integer - type: boolean - type: string required: [module, variable, type, value] FxRatesApiConfigEntry: type: object properties: module: { type: string, enum: [fxratesapi] } variable: { type: string, enum: [daily_limit, enabled, secret_key] } type: { type: string, enum: [int, bool, string] } value: oneOf: - type: integer - type: boolean - type: string required: [module, variable, type, value] WeatherApiConfigEntry: type: object properties: module: { type: string, enum: [weatherapi] } variable: { type: string, enum: [enabled, secret_key] } type: { type: string, enum: [bool, string] } value: oneOf: - type: boolean - type: string required: [module, variable, type, value] GatewayApiConfigEntry: type: object properties: module: { type: string, enum: [GatewayAPI] } variable: { type: string, enum: [api_key, api_secret, api_token, enabled, sender] } type: { type: string, enum: [string, bool] } value: oneOf: - type: string - type: boolean required: [module, variable, type, value] XlvaskConfigEntry: type: object properties: module: { type: string, enum: [xlvask] } variable: { type: string, enum: [enabled, password, synchronization_enabled, username] } type: { type: string, enum: [bool, string] } value: oneOf: - type: boolean - type: string required: [module, variable, type, value] EntraConfigEntry: type: object properties: module: { type: string, enum: [Entra] } variable: { type: string, enum: [enabled, entra_client_id, entra_client_secret, entra_tenant_id] } type: { type: string, enum: [bool, string] } value: oneOf: - type: boolean - type: string required: [module, variable, type, value] LimbleConfigEntry: type: object properties: module: { type: string, enum: [limble] } variable: { type: string, enum: [client_id, client_secret, enabled, webhooks_enabled] } type: { type: string, enum: [string, bool] } value: oneOf: - type: string - type: boolean required: [module, variable, type, value] OcrSpaceConfigEntry: type: object properties: module: { type: string, enum: [ocrSpace] } variable: { type: string, enum: [api_key, enabled] } type: { type: string, enum: [string, bool] } value: oneOf: - type: string - type: boolean required: [module, variable, type, value] OpenAiConfigEntry: type: object properties: module: { type: string, enum: [openAI] } variable: { type: string, enum: [api_key, enabled] } type: { type: string, enum: [string, bool] } value: oneOf: - type: string - type: boolean required: [module, variable, type, value] LicensePlateRecognizerConfigEntry: type: object properties: module: { type: string, enum: [licenseplaterecognizer] } variable: { type: string, enum: [api_key, enabled] } type: { type: string, enum: [string, bool] } value: oneOf: - type: string - type: boolean required: [module, variable, type, value] VirkdataConfigEntry: type: object properties: module: { type: string, enum: [virkdata] } variable: { type: string, enum: [enabled, monthly_limit, secret_key] } type: { type: string, enum: [bool, int, string] } value: oneOf: - type: boolean - type: integer - type: string required: [module, variable, type, value] ShellyConfigEntry: type: object properties: module: { type: string, enum: [shelly] } variable: { type: string, enum: [enabled, secret_key, server_url] } type: { type: string, enum: [bool, string] } value: oneOf: - type: boolean - type: string required: [module, variable, type, value] SelfServeConfigEntry: type: object properties: module: { type: string, enum: [selfserve] } variable: { type: string, enum: [enabled, minute_product] } type: { type: string, enum: [bool, int] } value: oneOf: - type: boolean - type: integer required: [module, variable, type, value] EconomicConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/EconomicConfigEntry' } } required: [data] RecaptchaConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/RecaptchaConfigEntry' } } required: [data] EmailConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/EmailConfigEntry' } } required: [data] BackupsConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/BackupsConfigEntry' } } required: [data] BirdConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/BirdConfigEntry' } } required: [data] MotorApiConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/MotorApiConfigEntry' } } required: [data] StripeConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/StripeConfigEntry' } } required: [data] FxRatesApiConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/FxRatesApiConfigEntry' } } required: [data] WeatherApiConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/WeatherApiConfigEntry' } } required: [data] GatewayApiConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/GatewayApiConfigEntry' } } required: [data] XlvaskConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/XlvaskConfigEntry' } } required: [data] EntraConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/EntraConfigEntry' } } required: [data] LimbleConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/LimbleConfigEntry' } } required: [data] OcrSpaceConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/OcrSpaceConfigEntry' } } required: [data] OpenAiConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/OpenAiConfigEntry' } } required: [data] LicensePlateRecognizerConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/LicensePlateRecognizerConfigEntry' } } required: [data] VirkdataConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/VirkdataConfigEntry' } } required: [data] ShellyConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/ShellyConfigEntry' } } required: [data] SelfServeConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/SelfServeConfigEntry' } } required: [data] DepartmentWeatherStatus: type: string enum: [unknown, healthy, degraded, unhealthy] DepartmentWeatherCondition: type: string enum: [clear, mostly_clear, partly_cloudy, mostly_cloudy, overcast, rain, showers, thunderstorm, snow, fog] DepartmentWeatherTimelineEntry: type: object properties: time: type: string example: '01:00' weather: $ref: '#/components/schemas/DepartmentWeatherCondition' washes: type: integer minimum: 0 example: 0 hours: type: integer minimum: 0 example: 10 status: $ref: '#/components/schemas/DepartmentWeatherStatus' required: [time, weather, washes, hours, status] WeatherApiObjectResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: type: object additionalProperties: true required: [data] DepartmentWeatherTimelineResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: type: array items: $ref: '#/components/schemas/DepartmentWeatherTimelineEntry' required: [data] ModuleConfigEntry: type: object properties: module: type: string description: Module name variable: type: string description: Configuration variable key type: type: string description: Stored value type in module_config value: $ref: '#/components/schemas/ModuleConfigValue' required: - module - variable - type - value ModuleConfigListResponse: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/ModuleConfigEntry' meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes ModuleConfigUpdateResponse: type: object properties: success: type: boolean data: type: boolean meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes ModuleConfigTestResponse: type: object properties: success: type: boolean data: type: string meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes User: type: object properties: id: type: integer description: User ID customer_number: type: integer description: e-conomic customer number display_name: type: string description: User's display name group_id: type: integer description: User group/role ID phone_country_code: type: integer description: Phone country code phone: type: integer description: Phone number email: type: string format: email description: Email address sms_notifications_enabled: type: boolean description: SMS notifications enabled email_notifications_enabled: type: boolean description: Email notifications enabled created_at: type: string format: date-time updated_at: type: string format: date-time SubuserGrant: type: object properties: id: type: integer billing_customer_number: type: integer description: e-conomic customer number subuser: type: integer description: Subuser ID enabled: type: boolean note: type: string nullable: true permissions: type: array description: List of permission node keys items: type: string example: BOOKINGS_LIST created_at: type: string format: date-time nullable: true updated_at: type: string format: date-time nullable: true deleted_at: type: string format: date-time nullable: true SubuserGrantCreateRequest: type: object required: - customer_number - subuser_id properties: customer_number: type: integer description: e-conomic customer number subuser_id: type: integer description: Subuser ID to grant permissions for enabled: type: boolean default: true note: type: string nullable: true maxLength: 65535 permissions: type: array description: Optional list of permission keys; defaults will be applied if omitted items: type: string SubuserGrantUpdateRequest: type: object properties: enabled: type: boolean note: type: string nullable: true maxLength: 65535 permissions: type: array items: type: string SubuserGrantSummary: type: object description: Summary of a subuser grant grouped by billing customer number properties: billing_customer_number: type: integer description: e-conomic customer number this grant applies to permissions: type: array description: List of permission node keys enabled for this customer items: type: string SubuserSelf: type: object description: Authenticated subuser profile with enabled grants properties: id: type: integer username: type: string name: type: string nullable: true email: type: string format: email nullable: true phone_country_code: type: integer nullable: true phone: type: integer nullable: true grants: type: array description: Enabled, non-deleted grants for the subuser grouped by billing customer number items: $ref: '#/components/schemas/SubuserGrantSummary' created_at: type: string format: date-time nullable: true updated_at: type: string format: date-time nullable: true suspended_at: type: string format: date-time nullable: true two_factor_enabled: type: boolean description: Indicates if 2FA is enabled for this account PermissionNode: type: object properties: key: type: string description: Permission node key name: type: string description: type: string type: type: string description: Permission type (e.g., TOGGLE) default: type: boolean PermissionNodeGroup: type: object properties: group: type: string description: type: string nodes: type: array items: $ref: '#/components/schemas/PermissionNode' UserCreate: type: object required: - customer_number - password properties: customer_number: type: integer password: type: string format: password display_name: type: string group_id: type: integer email: type: string format: email phone: type: integer phone_country_code: type: integer UserUpdate: type: object properties: id: type: integer customer_number: type: integer display_name: type: string group_id: type: integer email: type: string format: email phone: type: integer phone_country_code: type: integer Order: type: object properties: id: type: integer description: Order ID customer_id: type: integer description: Customer number customer_name: type: string description: Customer name user_id: type: integer description: User ID cashier_id: type: integer description: Cashier user ID cashier_name: type: string description: Cashier name department_id: type: integer description: Department ID status: type: string description: Order status total_net_amount: type: number format: float description: Total order amount po: type: string description: Purchase order number nullable: true lane: type: string description: Lane information nullable: true created_at: type: string format: date-time updated_at: type: string format: date-time CollectedInvoiceEconomicCompareResponse: type: object description: Result of comparing a collected invoice with its E-conomic counterpart properties: collected_invoice_id: type: integer description: The internal collected invoice ID example: 123 draft_id: type: integer nullable: true description: E-conomic draft invoice ID, if present example: 456 booked_id: type: integer nullable: true description: E-conomic booked invoice ID, if present example: 28368 warnings: type: array description: List of warnings detected during comparison items: type: string example: - "Total amount mismatch for draft invoice ID 456: E-Conomic total is 867.5, internal total is 694" draft_total: type: number format: float nullable: true description: Total amount from the E-conomic draft (gross) example: 867.5 booked_total: type: number format: float nullable: true description: Total amount from the E-conomic booked invoice (gross minus VAT if applicable) example: 694 difference: type: number format: float nullable: true description: Selected e-conomic total (draft when available, otherwise booked) minus internal_total example: 0 internal_total: type: number format: float description: Internal total amount for the collected invoice example: 694 required: - collected_invoice_id - internal_total CollectedInvoiceEconomicV2DetailsResponse: type: object properties: collected_invoice_id: type: integer external_id: type: string order_ids: type: array items: type: integer economic: type: object properties: draft_id: type: integer nullable: true booked_id: type: integer nullable: true customer: $ref: '#/components/schemas/CollectedInvoiceEconomicV2CustomerSummary' internal: type: object required: [normalized] properties: normalized: $ref: '#/components/schemas/EconomicV2NormalizedInvoice' draft: type: object properties: exists: type: boolean raw: type: object nullable: true additionalProperties: true normalized: allOf: - $ref: '#/components/schemas/EconomicV2NormalizedInvoice' nullable: true booked: type: object properties: exists: type: boolean raw: type: object nullable: true additionalProperties: true normalized: allOf: - $ref: '#/components/schemas/EconomicV2NormalizedInvoice' nullable: true warnings: type: array items: type: string required: - collected_invoice_id - order_ids - economic - customer - internal - draft - booked - warnings CollectedInvoiceEconomicV2CustomerSummary: type: object properties: internal_customer_number: type: integer nullable: true draft_customer_number: type: integer nullable: true booked_customer_number: type: integer nullable: true exists: type: boolean name: type: string nullable: true barred: type: boolean nullable: true required: - exists CollectedInvoiceEconomicV2CompareResponse: type: object properties: collected_invoice_id: type: integer details: $ref: '#/components/schemas/CollectedInvoiceEconomicV2DetailsResponse' comparison: $ref: '#/components/schemas/EconomicV2Comparison' warnings: type: array items: type: string required: - collected_invoice_id - details - comparison - warnings CollectedInvoiceEconomicV2CompareBulkResponse: type: object properties: requested: type: integer compared: type: integer failed: type: integer results: type: array items: $ref: '#/components/schemas/CollectedInvoiceEconomicV2CompareResponse' errors: type: array items: type: object properties: collected_invoice_id: type: integer error: type: string required: - requested - compared - failed - results - errors CollectedInvoiceEconomicV2RevenueStatisticsResponse: type: object properties: filters: type: object properties: dateFrom: type: string format: date dateTo: type: string format: date customer_numbers: type: array items: type: integer department_numbers: type: array items: type: integer currency: type: string nullable: true barred: type: string enum: [all, barred, active] max_pages: type: integer summary: $ref: '#/components/schemas/EconomicV2RevenueSummary' customers: type: array items: $ref: '#/components/schemas/EconomicV2RevenueCustomerStat' departments: type: array items: $ref: '#/components/schemas/EconomicV2RevenueDepartmentStat' currencies: type: array items: $ref: '#/components/schemas/EconomicV2RevenueCurrencyStat' warnings: type: array items: type: string required: - filters - summary - customers - departments - currencies - warnings EconomicV2RevenueSummary: type: object properties: invoice_count: type: integer line_count: type: integer unique_customers: type: integer net_amount: type: number vat_amount: type: number gross_amount: type: number average_invoice_net_amount: type: number required: - invoice_count - line_count - unique_customers - net_amount - vat_amount - gross_amount - average_invoice_net_amount EconomicV2RevenueCustomerStat: type: object properties: customer_number: type: integer customer_name: type: string nullable: true barred: type: boolean nullable: true invoice_count: type: integer net_amount: type: number vat_amount: type: number gross_amount: type: number required: - customer_number - invoice_count - net_amount - vat_amount - gross_amount EconomicV2RevenueDepartmentStat: type: object properties: department_key: type: string department_number: type: integer nullable: true invoice_count: type: integer line_count: type: integer net_amount: type: number vat_amount: type: number gross_amount: type: number required: - department_key - invoice_count - line_count - net_amount - vat_amount - gross_amount EconomicV2RevenueCurrencyStat: type: object properties: currency: type: string invoice_count: type: integer net_amount: type: number vat_amount: type: number gross_amount: type: number required: - currency - invoice_count - net_amount - vat_amount - gross_amount EconomicV2NormalizedInvoice: type: object properties: source: type: string enum: [internal, draft, booked] totals: type: object properties: net_total: type: number line_net_total: type: number line_count: type: integer billable_line_count: type: integer difference_from_line_sum: type: number nullable: true departments: $ref: '#/components/schemas/EconomicV2DepartmentDistribution' lines: type: array items: $ref: '#/components/schemas/EconomicV2NormalizedLineItem' warnings: type: array items: type: string required: - source - totals - departments - lines - warnings EconomicV2NormalizedLineItem: type: object properties: index: type: integer source: type: string source_order_id: type: integer nullable: true source_line_id: type: integer nullable: true line_type: type: string enum: [product, discount, text] billable: type: boolean product_number: type: string nullable: true product_id: type: integer nullable: true description: type: string reference: type: string quantity: type: number unit_net_price: type: number line_net_amount: type: number department_distribution: $ref: '#/components/schemas/EconomicV2DepartmentDistribution' match_key: type: string required: - source - line_type - billable - description - reference - quantity - unit_net_price - line_net_amount - department_distribution - match_key EconomicV2DepartmentDistribution: type: object additionalProperties: type: number example: "75": 100 EconomicV2Comparison: type: object properties: totals: type: object properties: internal_net_total: type: number targets: type: object properties: draft: $ref: '#/components/schemas/EconomicV2TargetComparison' booked: $ref: '#/components/schemas/EconomicV2TargetComparison' warnings: type: array items: type: string required: - totals - targets - warnings EconomicV2TargetComparison: type: object properties: target: type: string enum: [draft, booked] status: type: string enum: [exact_match, partial_mismatch, total_mismatch, missing_target] overall_match: type: boolean totals: $ref: '#/components/schemas/EconomicV2TotalsComparison' lines: type: object properties: summary: type: object properties: internal_billable_count: type: integer target_billable_count: type: integer mismatch_count: type: integer diff: type: array items: $ref: '#/components/schemas/EconomicV2LineDiffEntry' departments: type: object properties: matches: type: boolean diff: type: array items: $ref: '#/components/schemas/EconomicV2DepartmentDiffEntry' mismatch_reasons: type: array items: type: string warnings: type: array items: type: string required: - target - status - overall_match - totals - lines - departments - mismatch_reasons - warnings EconomicV2TotalsComparison: type: object properties: internal_net_total: type: number nullable: true target_net_total: type: number nullable: true difference: type: number nullable: true abs_difference: type: number nullable: true matches: type: boolean required: - matches EconomicV2LineDiffEntry: type: object properties: match_key: type: string reasons: type: array items: type: string internal_line: allOf: - $ref: '#/components/schemas/EconomicV2NormalizedLineItem' nullable: true target_line: allOf: - $ref: '#/components/schemas/EconomicV2NormalizedLineItem' nullable: true required: - match_key - reasons EconomicV2DepartmentDiffEntry: type: object properties: department_key: type: string internal_amount: type: number target_amount: type: number difference: type: number matches: type: boolean required: - department_key - internal_amount - target_amount - difference - matches InvoicingDistributionV2Transaction: type: object properties: id: type: integer date: type: string format: date-time amount: type: number booked: type: boolean department_id: type: integer excluded: type: boolean required: [id, date, amount, booked, department_id, excluded] InvoicingDistributionV2Customer: type: object properties: id: type: integer nullable: true customer_number: type: integer customer_name: type: string transactions: type: array items: $ref: '#/components/schemas/InvoicingDistributionV2Transaction' requires_action: type: boolean meta: type: object additionalProperties: true required: [customer_number, customer_name, transactions, requires_action, meta] InvoicingDistributionV2CategoryResponse: type: object properties: customers: type: array items: $ref: '#/components/schemas/InvoicingDistributionV2Customer' collective_results: type: object additionalProperties: true warnings: type: array items: type: string required: [customers, collective_results, warnings] InvoicingDistributionV2FixedPricingResponse: $ref: '#/components/schemas/InvoicingDistributionV2CategoryResponse' InvoicingDistributionV2WashSubscriptionsResponse: $ref: '#/components/schemas/InvoicingDistributionV2CategoryResponse' InvoicingDistributionV2CustomerPricesResponse: $ref: '#/components/schemas/InvoicingDistributionV2CategoryResponse' InvoicingDistributionV2AllResponse: type: object properties: fixed_pricing: $ref: '#/components/schemas/InvoicingDistributionV2FixedPricingResponse' wash_subscriptions: $ref: '#/components/schemas/InvoicingDistributionV2WashSubscriptionsResponse' customer_prices: $ref: '#/components/schemas/InvoicingDistributionV2CustomerPricesResponse' required: [fixed_pricing, wash_subscriptions, customer_prices] PricingHistoryVersionEntry: type: object properties: id: type: integer type: type: string enum: [fixed_pricing, vehicle_subscription, discount_override] customer_number: type: integer effective_from: type: string format: date-time effective_to: type: string format: date-time nullable: true source: type: string confidence: type: number minimum: 0 maximum: 1 inferred: type: boolean metadata_json: oneOf: - type: string - type: object additionalProperties: true - type: array items: {} nullable: true required: - id - type - customer_number - effective_from - source - confidence - inferred CustomerPricingHistoryResponse: type: object properties: customer_number: type: integer fixed_pricing: type: array items: type: object additionalProperties: true vehicle_subscriptions: type: array items: type: object additionalProperties: true discount_overrides: type: array items: type: object additionalProperties: true timeline: type: array items: $ref: '#/components/schemas/PricingHistoryVersionEntry' required: - customer_number - fixed_pricing - vehicle_subscriptions - discount_overrides - timeline InvoicingWashSubscriptionsDistributionResponse: type: object properties: success: type: boolean example: true data: type: array items: $ref: '#/components/schemas/InvoicingWashSubscriptionsDistributionCustomer' meta: $ref: '#/components/schemas/InvoicingFixedPricingDistributionMeta' includes: type: object additionalProperties: true required: - success - data - meta - includes InvoicingWashSubscriptionsDistributionCustomer: type: object properties: id: type: integer nullable: true customer_number: type: integer customer_name: type: string transactions: type: array items: $ref: '#/components/schemas/InvoicingFixedPricingDistributionTransaction' requires_action: type: boolean meta: type: object properties: subscription: type: object additionalProperties: true required: - subscription required: - customer_number - customer_name - transactions - requires_action - meta InvoicingFixedPricingDistributionResponse: type: object properties: success: type: boolean example: true data: type: array items: $ref: '#/components/schemas/InvoicingFixedPricingDistributionCustomer' meta: $ref: '#/components/schemas/InvoicingFixedPricingDistributionMeta' includes: $ref: '#/components/schemas/InvoicingFixedPricingDistributionIncludes' required: - success - data - meta - includes InvoicingFixedPricingDistributionCustomer: type: object properties: id: type: integer customer_number: type: integer customer_name: type: string transactions: type: array items: $ref: '#/components/schemas/InvoicingFixedPricingDistributionTransaction' requires_action: type: boolean meta: type: object properties: fixed_pricing: $ref: '#/components/schemas/InvoicingFixedPricingDistributionFixedPricing' required: - fixed_pricing required: - id - customer_number - customer_name - transactions - requires_action - meta InvoicingFixedPricingDistributionTransaction: type: object properties: id: type: integer date: type: string description: Datetime in `YYYY-MM-DD HH:mm:ss` format. example: '2026-02-02 10:43:41' amount: type: number booked: type: boolean excluded: type: boolean required: - id - date - amount - booked - excluded InvoicingFixedPricingDistributionFixedPricing: type: object properties: customer_number: type: integer price: type: number description: type: string original_price: type: number department_totals: $ref: '#/components/schemas/InvoicingFixedPricingDistributionNumberMapOrEmptyArray' department_totals_relative: $ref: '#/components/schemas/InvoicingFixedPricingDistributionNumberMapOrEmptyArray' required: - customer_number - price - description - original_price - department_totals - department_totals_relative InvoicingFixedPricingDistributionMeta: type: object properties: date_from: type: string description: Datetime in `YYYY-MM-DD HH:mm:ss` format. example: '2026-02-01 00:00:00' date_to: type: string description: Datetime in `YYYY-MM-DD HH:mm:ss` format. example: '2026-02-28 23:59:59' required: - date_from - date_to InvoicingFixedPricingDistributionIncludes: type: object properties: debug_invoicing_period_customers_with_orders_in_date_range: $ref: '#/components/schemas/InvoicingFixedPricingDistributionExecutionTime' debug_invoicing_period_process_customer_numbers: $ref: '#/components/schemas/InvoicingFixedPricingDistributionExecutionTime' debug_invoicing_period_get_transactions_for_customers_in_date_range: $ref: '#/components/schemas/InvoicingFixedPricingDistributionExecutionTime' debug_invoicing_period_calculate_transaction_totals: $ref: '#/components/schemas/InvoicingFixedPricingDistributionExecutionTime' debug_invoicing_period_construct_customer_objects: $ref: '#/components/schemas/InvoicingFixedPricingDistributionExecutionTime' collective_fixed_pricing_results: $ref: '#/components/schemas/InvoicingFixedPricingDistributionCollectiveResults' additionalProperties: true InvoicingFixedPricingDistributionExecutionTime: type: object properties: execution_time: type: number required: - execution_time InvoicingFixedPricingDistributionCollectiveResults: type: object properties: total_fixed_price: type: number total_original_price: type: number total_department_totals: $ref: '#/components/schemas/InvoicingFixedPricingDistributionNumberMapOrEmptyArray' total_department_totals_relative: $ref: '#/components/schemas/InvoicingFixedPricingDistributionNumberMapOrEmptyArray' total_department_totals_parsed: type: object additionalProperties: type: number total_department_totals_relative_parsed: type: object additionalProperties: type: number required: - total_fixed_price - total_original_price - total_department_totals - total_department_totals_relative - total_department_totals_parsed - total_department_totals_relative_parsed InvoicingFixedPricingDistributionNumberMapOrEmptyArray: oneOf: - type: object additionalProperties: type: number - type: array maxItems: 0 SelfServeLaneStatus: type: object properties: id: type: integer description: Lane ID status: type: string description: Current lane status (e.g., IDLE, OCCUPIED) mode: type: string description: Current lane mode (e.g., AUTOMATIC, MANUAL) state: type: string description: Current lane state (e.g., READY, WASHING) wash_start_time: type: integer description: Timestamp when the wash started (0 if not washing) nullable: true elapsed_wash_time: type: integer description: Elapsed wash time in seconds nullable: true license_plate: type: string description: License plate of the vehicle in the lane nullable: true customer_number: type: integer description: Customer number associated with the current lane use nullable: true SelfServeConfig: type: object properties: enabled: type: boolean description: Whether the self-serve module is enabled minute_product: type: integer description: The product ID used for minute-based billing SelfserveLaneService: type: string description: Allowed self-serve lane service name enum: - MACHINE DepartmentSelfserveQuestion: type: object properties: id: type: integer description: Question ID department: type: integer description: Department ID lane: type: integer description: Lane ID product: type: integer description: Product ID condition_id: type: integer description: Question condition object ID nullable: true question: type: string description: Question text description: type: string description: Question description order_priority: type: integer description: Display order priority (lower numbers shown first) created_at: type: string format: date-time updated_at: type: string format: date-time DepartmentSelfserveTask: type: object properties: id: type: integer description: Task ID department: type: integer description: Department ID lane: type: integer description: Lane ID product: type: integer description: Product ID condition_id: type: integer description: Condition ID (if conditional task) nullable: true task: type: string description: Task text description: type: string description: Task description order_priority: type: integer description: Display order priority (lower numbers shown first) services: type: array description: Services enabled by this task. Each item must be a valid service enum name. items: $ref: '#/components/schemas/SelfserveLaneService' default: [] buttons: type: array description: Dynamic image button IDs enabled by this task. items: type: integer default: [] dynamic_images_vehicle_type: type: integer nullable: true description: Optional vehicle type selection override for the machine UI. created_at: type: string format: date-time updated_at: type: string format: date-time DepartmentSelfserveCondition: type: object properties: id: type: integer description: Condition ID department: type: integer description: Department ID lane: type: integer description: Lane ID product: type: integer description: Product ID condition_id: type: integer description: Optional condition ID nullable: true name: type: string description: Condition name description: type: string description: Condition description created_at: type: string format: date-time updated_at: type: string format: date-time DepartmentSelfserveConditionRule: type: object properties: id: type: integer description: Rule ID condition_id: type: integer description: Condition object ID type: type: string description: Condition type (e.g., IS_TRUE, IS_FALSE) object_type: type: string description: The object type to which the condition applies (e.g., question, task, etc.) object_id: type: integer description: The object id to which the condition applies name: type: string description: Condition name description: type: string description: Condition description created_at: type: string format: date-time updated_at: type: string format: date-time DepartmentSelfserveVehicleCondition: type: object properties: id: type: integer description: Vehicle condition ID department: type: integer description: Department ID lane: type: integer description: Lane ID customer_id: type: integer description: Customer ID nullable: true reg: type: string description: Vehicle registration number question: type: integer description: Question ID value: type: boolean description: Answer value created_at: type: string format: date-time updated_at: type: string format: date-time deleted_at: type: string format: date-time nullable: true OrderCreate: type: object required: - customer_id - department_id properties: customer_id: type: integer department_id: type: integer cashier_id: type: integer po: type: string lane: type: string OrderUpdate: type: object properties: id: type: integer customer_id: type: integer department_id: type: integer status: type: string po: type: string lane: type: string OrderItem: type: object properties: id: type: integer order_id: type: integer product_id: type: integer product_name: type: string quantity: type: integer unit_price: type: number format: float discount: type: number format: float total_price: type: number format: float OrderItemCreate: type: object required: - order_id - product_id - quantity properties: order_id: type: integer product_id: type: integer quantity: type: integer discount: type: number format: float OrderItemUpdate: type: object required: - id properties: id: type: integer quantity: type: integer discount: type: number format: float Department: type: object properties: id: type: integer name: type: string description: type: string economic_department_id: type: integer visible: type: boolean dimension: type: integer branding: type: integer longitude: type: number format: float latitude: type: number format: float created_at: type: string format: date-time updated_at: type: string format: date-time DepartmentGuest: type: object properties: id: type: integer name: type: string longitude: type: number format: float latitude: type: number format: float address: type: string description: Department address (same as description) self_serve_enabled: type: boolean lanes: type: array items: $ref: '#/components/schemas/DepartmentLaneGuest' DepartmentLaneGuest: type: object properties: id: type: integer name: type: string status: type: string products: type: array items: type: integer machine_available: type: boolean DepartmentCreate: type: object required: - name - economic_department_id properties: name: type: string description: type: string economic_department_id: type: integer visible: type: boolean longitude: type: number format: float latitude: type: number format: float DepartmentUpdate: type: object required: - id properties: id: type: integer name: type: string description: type: string visible: type: boolean longitude: type: number format: float latitude: type: number format: float DepartmentLane: type: object properties: id: type: integer department: type: integer name: type: string relay_in_id: type: string relay_out_id: type: string relay_machine_id: type: string dynamic_image_id: type: integer nullable: true minimum: 1 created_at: type: string format: date-time updated_at: type: string format: date-time DepartmentLaneCreate: type: object required: - department - name properties: department: type: integer name: type: string relay_in_id: type: string relay_out_id: type: string relay_machine_id: type: string dynamic_image_id: type: integer nullable: true minimum: 1 DepartmentLaneUpdate: type: object required: - id properties: id: type: integer department: type: integer name: type: string relay_in_id: type: string relay_out_id: type: string relay_machine_id: type: string dynamic_image_id: type: integer nullable: true minimum: 1 DepartmentGate: type: object properties: id: type: integer department: type: integer is_entrance: type: boolean is_exit: type: boolean name: type: string config: $ref: '#/components/schemas/DepartmentGateConfig' created_at: type: string format: date-time updated_at: type: string format: date-time DepartmentGateConfig: type: object required: - type properties: type: type: string example: PHONE_CALL phone_number: type: string nullable: true example: +4512345678 call_duration_threshold: type: integer nullable: true example: 10 description: | Configuration for the department gate. If type is 'PHONE_CALL', 'phone_number' and 'call_duration_threshold' are required. DepartmentGateCreate: type: object required: - department - is_entrance - is_exit - name - config properties: department: type: integer is_entrance: type: boolean is_exit: type: boolean name: type: string config: $ref: '#/components/schemas/DepartmentGateConfig' DepartmentGateUpdate: type: object required: - id properties: id: type: integer is_entrance: type: boolean is_exit: type: boolean name: type: string config: $ref: '#/components/schemas/DepartmentGateConfig' DepartmentRelay: type: object properties: id: type: integer department: type: integer relay_id: type: string name: type: string type: type: string enum: [SWITCH, TRIGGER] config: $ref: '#/components/schemas/DepartmentRelayConfig' created_at: type: string format: date-time updated_at: type: string format: date-time DepartmentRelayConfig: type: object properties: what_happens: type: string nullable: true example: open_gate webhook_token: type: string nullable: true example: secret_token description: | Configuration for the department relay. If the relay 'type' is 'TRIGGER', 'what_happens' and 'webhook_token' are required. DepartmentRelayCreate: type: object required: - department - relay_id - name - type - config properties: department: type: integer relay_id: type: string name: type: string type: type: string enum: [SWITCH, TRIGGER] config: $ref: '#/components/schemas/DepartmentRelayConfig' DepartmentRelayUpdate: type: object required: - id properties: id: type: integer relay_id: type: string name: type: string type: type: string enum: [SWITCH, TRIGGER] config: $ref: '#/components/schemas/DepartmentRelayConfig' Product: type: object properties: id: type: integer name: type: string description: type: string price: type: number format: float category_id: type: integer category_name: type: string visible: type: boolean created_at: type: string format: date-time updated_at: type: string format: date-time ProductCreate: type: object required: - name - price - category_id properties: name: type: string description: type: string price: type: number format: float category_id: type: integer visible: type: boolean ProductUpdate: type: object required: - id properties: id: type: integer name: type: string description: type: string price: type: number format: float category_id: type: integer visible: type: boolean Category: type: object properties: id: type: integer name: type: string description: type: string created_at: type: string format: date-time updated_at: type: string format: date-time CategoryCreate: type: object required: - name properties: name: type: string description: type: string CategoryUpdate: type: object required: - id properties: id: type: integer name: type: string description: type: string ModuleActionLog: type: object properties: id: type: integer description: Log ID module: type: string description: The module name action: type: string description: The action name status_code: type: integer description: HTTP status code data: type: object description: The action data (JSON decoded) created_at: type: string format: date-time description: Log creation timestamp Booking: type: object properties: id: type: integer customer_id: type: integer department_id: type: integer booking_time: type: string format: date-time status: type: string created_at: type: string format: date-time BookingUpdate: type: object required: - id properties: id: type: integer status: type: string booking_time: type: string format: date-time Vehicle: type: object properties: id: type: integer user_id: type: integer description: Internal user ID owning the customer account reg: type: string description: Vehicle registration number customer_id: type: integer customer_name: type: string type: type: integer description: Product ID representing the vehicle wash type reference: type: string nullable: true description: Optional external reference/label wash_subscription: type: boolean barred: type: boolean description: True if the associated customer is barred addons: type: object properties: enabled: { type: integer } available: { type: integer } list: type: array items: type: object last_order_id: type: integer nullable: true xlvask: type: object nullable: true description: XL Vask vehicle data when available vehicle_types: type: array items: type: object created_at: type: string format: date-time Notification: type: object properties: id: type: integer user_id: type: integer title: type: string message: type: string read: type: boolean created_at: type: string format: date-time NotificationCreate: type: object required: - user_id - title - message properties: user_id: type: integer title: type: string message: type: string Permission: type: object properties: name: type: string description: Permission identifier description: type: string description: Human-readable description GoalsCriteria: type: object description: Goal evaluation criteria properties: type: type: string description: Criteria type enum: [PRODUCT, REVENUE, VISITS, NONE] example: PRODUCT target: type: number description: Target value for the goal example: 100 label: type: string description: Optional short label/title for this goal criteria (max 255 characters) example: Q1 Revenue Goal start: type: string format: date-time description: Start of the evaluation window (ISO 8601) end: type: string format: date-time description: End of the evaluation window (ISO 8601) users: type: array description: List of user customer numbers included in the criteria items: { type: integer } departments: type: array description: List of department IDs included in the criteria items: { type: integer } products: type: array description: List of product IDs included in the criteria items: { type: integer } progress_alert_frequency: type: string description: | Frequency of progress alerts for the goal. Accepted values: DAILY, WEEKLY, MONTHLY, CHANGED, NONE. The canonical field name is snake_case `progress_alert_frequency`. For backward-compatibility the API also accepts camelCase `progressAlertFrequency` on input. enum: [DAILY, WEEKLY, MONTHLY, CHANGED, NONE] example: DAILY progress_alert_destination: type: string description: | Destination/channel where progress alerts should be delivered. Accepted values: SLACK, EMAIL, SMS, NONE. The canonical field name is snake_case `progress_alert_destination`. For backward-compatibility the API also accepts camelCase `progressAlertDestination` on input. enum: [SLACK, EMAIL, SMS, NONE] example: NONE progress_alert_progress_type: type: string description: | What part of the progress should be included in alert messages. Accepted values: ALL, PERCENTAGE_ONLY, COUNT_ONLY, COUNT_AND_TARGET, NONE. The canonical field name is snake_case `progress_alert_progress_type`. For backward-compatibility the API also accepts camelCase `progressAlertProgressType` on input. enum: [ALL, PERCENTAGE_ONLY, COUNT_ONLY, COUNT_AND_TARGET, NONE] example: ALL progress_alert_style: type: string description: | Presentation style of the alert. Accepted values: DEPARTMENT_COMPARE, COLLECTIVE, SINGLE_DEPARTMENT, NONE. The canonical field name is snake_case `progress_alert_style`. For backward-compatibility the API also accepts camelCase `progressAlertStyle` on input. enum: [DEPARTMENT_COMPARE, COLLECTIVE, SINGLE_DEPARTMENT, NONE] example: NONE progress_alert_format: type: string nullable: true description: | Optional custom template for the alert body. Supports tokens `{label}`, `{percent}`, `{count}`, `{target}`, `{timeframe}`, `{departments}`, `{prefix}`, `{body}`. Max length depends on destination: 160 characters for SMS; 1024 characters for EMAIL/SLACK/other. The canonical field name is snake_case `progress_alert_format`. For backward-compatibility the API also accepts camelCase `progressAlertFormat` on input. progress_alert_weekdays: type: array description: | Weekdays on which progress alerts should be sent. Use one or more of: MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY. The canonical field name is snake_case `progress_alert_weekdays`. For backward-compatibility the API also accepts camelCase `progressAlertWeekdays` on input. items: type: string enum: [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY] example: [MONDAY, WEDNESDAY, FRIDAY] progress_alert_time_of_day: type: string nullable: true description: | Time of day (with timezone) when progress alerts should be sent. Format: `HH:MMZ` or `HH:MM±HH:MM` (24-hour clock with UTC offset). Examples: `14:30Z`, `09:15+02:00`, `18:45-05:00`. The canonical field name is snake_case `progress_alert_time_of_day`. For backward-compatibility the API also accepts camelCase `progressAlertTimeOfDay` on input. pattern: '^([01]\d|2[0-3]):[0-5]\d(?:Z|[+-](?:[01]\d|2[0-3]):?[0-5]\d)$' example: "14:30+02:00" department_daily_targets: type: object description: | Optional per-department custom daily targets. Keys are department IDs and values are non-negative numbers representing the target per operating day for that department. If omitted, the daily target is split evenly across selected departments. The canonical field name is snake_case `department_daily_targets`. For backward-compatibility the API also accepts camelCase `departmentDailyTargets` on input. x-additionalPropertiesName: department_id additionalProperties: type: number minimum: 0 example: "12": 3 "15": 5 GoalProgressDetails: type: object properties: count: type: number description: Current progress value target: type: number description: Target value for the period date_from: type: string nullable: true description: Inclusive period start datetime (ISO-8601), null when no lower bound applies example: "2026-01-01T00:00:00+00:00" date_end: type: string nullable: true description: Inclusive period end datetime (ISO-8601), null when no upper bound applies example: "2026-02-26T23:59:59+00:00" DepartmentGoalProgress: type: object title: Department progress details description: | Goal progress details for a single department across multiple timeframes. Timeframes are clamped to the goal timeframe (never before goal start and never after goal end). properties: all: $ref: '#/components/schemas/GoalProgressDetails' today: $ref: '#/components/schemas/GoalProgressDetails' week: $ref: '#/components/schemas/GoalProgressDetails' month: $ref: '#/components/schemas/GoalProgressDetails' year: $ref: '#/components/schemas/GoalProgressDetails' to_date: $ref: '#/components/schemas/GoalProgressDetails' DepartmentGoal: type: object properties: id: type: integer created_by: type: integer description: ID of the user who created the goal departments: type: array items: { type: integer } criteria: $ref: '#/components/schemas/GoalsCriteria' progress: type: object description: | Goal progress details for various timeframes. `year` starts at January 1 of the current year or the goal start, whichever is later. `to_date` starts at the goal start and ends at today (also clamped by goal end). properties: all: $ref: '#/components/schemas/GoalProgressDetails' today: $ref: '#/components/schemas/GoalProgressDetails' week: $ref: '#/components/schemas/GoalProgressDetails' month: $ref: '#/components/schemas/GoalProgressDetails' year: $ref: '#/components/schemas/GoalProgressDetails' to_date: $ref: '#/components/schemas/GoalProgressDetails' departmental_distribution: type: object description: | Progress details broken down by department. Keys are department IDs. Includes `all`, `today`, `week`, `month`, `year`, and `to_date` timeframes. x-additionalPropertiesName: department_id additionalProperties: $ref: '#/components/schemas/DepartmentGoalProgress' example: "12": all: count: 15 target: 100 today: count: 2 target: 5 week: count: 10 target: 35 month: count: 15 target: 100 year: count: 15 target: 100 to_date: count: 15 target: 100 created_at: type: string description: Creation timestamp updated_at: type: string description: Update timestamp DepartmentGoalCreate: type: object required: [departments, criteria] properties: departments: type: array items: { type: integer } criteria: $ref: '#/components/schemas/GoalsCriteria' DepartmentGoalUpdate: type: object required: [id] properties: id: type: integer departments: type: array items: { type: integer } criteria: $ref: '#/components/schemas/GoalsCriteria' Passkey: type: object properties: id: type: integer credential_id: type: string description: Base64URL-encoded credential ID name: type: string nullable: true algorithm: type: string example: ES256 transports: type: array items: type: string example: ["usb", "nfc", "ble", "internal"] sign_count: type: integer created_at: type: string format: date-time updated_at: type: string format: date-time PasskeyCreateRequest: type: object required: [credential_id, public_key, algorithm, transports] properties: credential_id: type: string description: Base64URL-encoded credential ID returned from WebAuthn public_key: type: string description: Base64URL-encoded public key (COSE or PEM as stored) algorithm: type: string example: ES256 transports: type: array items: type: string name: type: string nullable: true PasskeyRenameRequest: type: object required: [name] properties: name: type: string BirdVoiceCall: type: object properties: id: type: string example: "4015cf84-8028-46a1-a0d9-9213e5bf4f09" channelId: type: string example: "a2545e48-fe8c-5741-9bdc-42a081076bc9" originator: type: object properties: number: type: object properties: type: { type: string, example: "pstn" } number: { type: string, example: "+4532330288" } countryIsoCode: { type: string, example: "DK" } receiver: type: object properties: contact: type: object properties: id: { type: string, example: "18229adf-af8c-404a-b036-ae193c22e33c" } identifierKey: { type: string, example: "phonenumber" } identifierValue: { type: string, example: "+4542331128" } number: type: object properties: type: { type: string, example: "pstn" } number: { type: string, example: "+4542331128" } countryIsoCode: { type: string, example: "DK" } from: type: string example: "+4532330288" to: type: string example: "+4542331128" direction: type: string example: "outgoing" status: type: string example: "completed" type: type: string example: "pstn" duration: type: integer example: 3 hangupCauseCode: type: integer example: 16 hangupSource: type: string example: "callee" sipInsights: type: object properties: hangupSipCode: { type: string, example: "200" } qualityInsights: type: object properties: mos: { type: string, example: "4.50" } pdd: { type: string, example: "2.15" } price: type: object properties: currencyCode: { type: string, example: "EUR" } amount: { type: string, example: "0.0079" } createdAt: { type: string, format: date-time, example: "2026-02-27T13:45:36.216Z" } updatedAt: { type: string, format: date-time, example: "2026-02-27T13:45:46.532Z" } ringingAt: { type: string, format: date-time, example: "2026-02-27T13:45:38.367Z" } answeredAt: { type: string, format: date-time, example: "2026-02-27T13:45:43.359Z" } endedAt: { type: string, format: date-time, example: "2026-02-27T13:45:46.368Z" } BirdVoiceCallListResponse: type: object properties: success: { type: boolean, example: true } data: type: object properties: nextPageToken: type: string example: "WzE3NzIxNTY5NTI0MDUsIjk5ZDU4M2VkLTQyMzAtNDExNy1hOTQ0LTllY2JjNzhmYWJlMSJd" results: type: array items: { $ref: '#/components/schemas/BirdVoiceCall' } meta: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] includes: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] BirdVoiceCallSingleResponse: type: object properties: success: { type: boolean, example: true } data: { $ref: '#/components/schemas/BirdVoiceCall' } meta: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] includes: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] BirdTestOutboundCallResponse: type: object properties: success: { type: boolean, example: true } data: type: object properties: to: { type: string, example: "+45 42 33 11 28" } to_e164: { type: string, example: "+4542331128" } call_id: { type: string, nullable: true, example: "4015cf84-8028-46a1-a0d9-9213e5bf4f09" } final_status: { type: string, nullable: true, example: "completed" } hangup_sent: { type: boolean, example: true } created_call: { $ref: '#/components/schemas/BirdVoiceCall' } last_call_snapshot: { $ref: '#/components/schemas/BirdVoiceCall' } hangup_response: { $ref: '#/components/schemas/BirdVoiceCall' } meta: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] includes: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] BirdNumber: type: object properties: id: type: string example: "019c73dc-60f1-76c4-98b0-c4318706b938" workspaceId: type: string example: "3d5fae4f-9c2d-41aa-9840-28b18e6a94bc" type: type: string example: "national" country: type: string example: "DK" number: type: string example: "+4532330288" status: type: string example: "active" capabilities: type: object properties: voice: { $ref: '#/components/schemas/BirdNumberCapability' } sms: { $ref: '#/components/schemas/BirdNumberCapability' } mms: { $ref: '#/components/schemas/BirdNumberCapability' } fax: { $ref: '#/components/schemas/BirdNumberCapability' } whatsapp: { $ref: '#/components/schemas/BirdNumberCapability' } monthlyRecurringPrice: type: object properties: currencyCode: { type: string, example: "EUR" } amount: { type: integer, example: 1000000 } exponent: { type: integer, example: -6 } complianceRequirements: type: array items: type: object additionalProperties: true configurations: type: object additionalProperties: true createdAt: { type: string, format: date-time, example: "2026-02-19T03:05:48.529Z" } updatedAt: { type: string, format: date-time, example: "2026-02-19T03:08:17.753Z" } activatedAt: { type: string, format: date-time, nullable: true, example: "2026-02-19T03:05:48.529Z" } deactivatedAt: { type: string, format: date-time, nullable: true } deactivatesAt: { type: string, format: date-time, nullable: true } subscription: type: object additionalProperties: true endpointSubscription: type: object additionalProperties: true requirements: type: array items: type: object additionalProperties: true whatsApp: type: object additionalProperties: true endpoint: type: object additionalProperties: true BirdNumberCapability: type: object properties: inbound: { type: boolean } outbound: { type: boolean } BirdNumberListResponse: type: object properties: success: { type: boolean, example: true } data: type: object properties: results: type: array items: { $ref: '#/components/schemas/BirdNumber' } meta: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] includes: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] BirdNumberSingleResponse: type: object properties: success: { type: boolean, example: true } data: { $ref: '#/components/schemas/BirdNumber' } meta: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] includes: oneOf: - type: array items: {} - type: object additionalProperties: true example: []