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: Security description: Account security and passkey management 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 format: uuid required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string format: uuid required: false description: Bird Channel identifier (falls back to module configuration if omitted) requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BirdVoiceCallCreateRequest' 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 format: uuid required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string format: uuid required: false description: Bird Channel identifier (falls back to module configuration if omitted) - in: query name: limit schema: type: integer minimum: 1 maximum: 1000 - in: query name: pageToken schema: type: string - in: query name: startAt schema: type: string format: date-time - in: query name: endAt schema: type: string format: date-time - in: query name: status schema: type: string - in: query name: type schema: type: string - in: query name: to schema: type: string - in: query name: from schema: type: string - in: query name: duration schema: type: integer - in: query name: direction schema: type: string - in: query name: id schema: type: string format: uuid - in: query name: tag schema: oneOf: - type: string - type: array items: type: string responses: '200': description: A list of calls content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallListResponse' } /bird/voice/calls/log: get: tags: - Bird summary: List workspace call log entries operationId: birdListVoiceCallsLog parameters: - in: query name: workspaceId schema: type: string format: uuid required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: limit schema: type: integer minimum: 1 maximum: 1000 - in: query name: pageToken schema: type: string - in: query name: startAt schema: type: string format: date-time - in: query name: endAt schema: type: string format: date-time - in: query name: channelId schema: oneOf: - type: string - type: array items: type: string format: uuid - in: query name: status schema: type: string - in: query name: type schema: type: string - in: query name: to schema: type: string - in: query name: from schema: type: string - in: query name: duration schema: type: integer - in: query name: direction schema: type: string - in: query name: id schema: type: string format: uuid - in: query name: tag schema: oneOf: - type: string - type: array items: type: string responses: '200': description: Workspace call log entries content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallsLogResponse' } /bird/voice/calls/{id}: get: tags: - Bird summary: Get a voice call by ID operationId: birdGetVoiceCall parameters: - in: query name: workspaceId schema: type: string format: uuid required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string format: uuid required: false description: Bird Channel identifier (falls back to module configuration if omitted) - in: path name: id required: true schema: type: string format: uuid responses: '200': description: Call details content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallSingleResponse' } patch: tags: - Bird summary: Update a voice call by ID operationId: birdUpdateVoiceCall parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BirdVoiceCallUpdateRequest' responses: '200': description: Call update accepted content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallSingleResponse' } /bird/voice/calls/{id}/answer: post: tags: - Bird summary: Answer an incoming voice call by ID operationId: birdAnswerVoiceCall parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/BirdVoiceCallAnswerRequest' responses: '200': description: Answer command accepted content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallCommandResponse' } /bird/voice/calls/{id}/ringing: post: tags: - Bird summary: Mark voice call as ringing by ID operationId: birdRingingVoiceCall parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/BirdVoiceCallRingingRequest' responses: '200': description: Ringing command accepted content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallCommandResponse' } /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 format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/BirdVoiceCallHangupRequest' responses: '200': description: Hangup requested content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallCommandResponse' } /bird/voice/calls/{id}/playback: post: tags: - Bird summary: Playback media on a voice call by ID operationId: birdPlaybackVoiceCall parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BirdVoiceCallPlaybackRequest' responses: '200': description: Playback command accepted content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallCommandResponse' } /bird/voice/calls/{id}/say: post: tags: - Bird summary: Say a message on an active voice call and optionally hang up operationId: birdSayOnVoiceCall parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BirdVoiceCallSayRequest' responses: '200': description: Say command accepted content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallCommandResponse' } /bird/voice/calls/{id}/gather: post: tags: - Bird summary: Gather input from a voice call by ID operationId: birdGatherVoiceCall parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BirdVoiceCallGatherRequest' responses: '200': description: Gather command accepted content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallCommandResponse' } /bird/voice/calls/{id}/bridge: post: tags: - Bird summary: Bridge a voice call by ID operationId: birdBridgeVoiceCall parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BirdVoiceCallBridgeRequest' responses: '200': description: Bridge command accepted content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallBridgeResponse' } /bird/voice/calls/{id}/record: post: tags: - Bird summary: Record call audio by ID operationId: birdRecordVoiceCall parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BirdVoiceCallRecordRequest' responses: '200': description: Record command accepted content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallCommandResponse' } /bird/voice/calls/{id}/recordings: post: tags: - Bird summary: Create a call recording session operationId: birdCreateVoiceCallRecording parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/BirdVoiceCallRecordingCreateRequest' responses: '200': description: Recording session created content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallRecordingSingleResponse' } get: tags: - Bird summary: List call recordings for a voice call operationId: birdListVoiceCallRecordings parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid - in: query name: limit schema: type: integer minimum: 1 maximum: 1000 - in: query name: pageToken schema: type: string responses: '200': description: List of call recordings content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallRecordingListResponse' } /bird/voice/calls/{id}/recordings/{recordingId}: get: tags: - Bird summary: Get a single call recording operationId: birdGetVoiceCallRecording parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid - in: path name: recordingId required: true schema: type: string format: uuid responses: '200': description: Call recording details content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallRecordingSingleResponse' } patch: tags: - Bird summary: Update call recording state operationId: birdUpdateVoiceCallRecording parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid - in: path name: recordingId required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BirdVoiceCallRecordingUpdateRequest' responses: '200': description: Recording update accepted content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallRecordingSingleResponse' } /bird/voice/calls/{id}/insights: get: tags: - Bird summary: Get voice call insights operationId: birdGetVoiceCallInsights parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid responses: '200': description: Voice call insights content: application/json: schema: { $ref: '#/components/schemas/BirdVoiceCallInsightsResponse' } /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 format: uuid required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string format: uuid required: false description: Bird Channel identifier (falls back to module configuration if omitted) requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/BirdTestOutboundCallRequest' responses: '200': description: Test call created and either hung up or timed out content: application/json: schema: { $ref: '#/components/schemas/BirdTestOutboundCallResponse' } /bird/voice/calls/webhook/inbound: post: tags: - Bird summary: Process inbound Bird voice call lifecycle operationId: birdInboundVoiceCallWebhook description: > Stateful inbound-call webhook that answers the call immediately, runs a DTMF-driven IVR for phone-controlled entrance and exit gates, and enforces a timeout hangup flow. During the first 300 seconds from call start, DTMF input is extracted from payload fields such as `dtmf`, `digit`, `digits`, `keys`, `result.keys`, and nested `conditions[].value`. Values like `1#` are normalized to a single menu digit before the route resolves the department selection and then the gate type selection (`1=entrance`, `2=exit`). When a valid gate is resolved, the webhook triggers the corresponding `department_gates` phone-call gate, announces the result, sends a hangup command, and keeps the completed state cached until Bird reports a terminal call status. At or after 300 seconds, the webhook says `timeout reached`, waits 10 seconds, sends a hangup command once, and polls Bird call status until terminal. parameters: - in: query name: workspaceId schema: type: string format: uuid required: false description: Bird Workspace identifier (fallbacks to payload/state/module configuration) - in: query name: channelId schema: type: string format: uuid required: false description: Bird Channel identifier (fallbacks to payload/state/module configuration) - in: query name: callId schema: type: string required: false description: Bird Call identifier (fallbacks to payload fields) requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/BirdInboundCallWebhookRequest' responses: '200': description: Lifecycle phase result for this webhook invocation content: application/json: schema: $ref: '#/components/schemas/BirdInboundCallWebhookResponse' # Bird Numbers /bird/numbers: get: tags: - Bird summary: List your numbers operationId: birdListNumbers parameters: - in: query name: workspaceId required: false schema: type: string format: uuid 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 format: uuid 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 format: uuid 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 format: uuid required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string format: uuid required: false description: Bird Channel identifier (falls back to module configuration if omitted) requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BirdFlashCallCreateRequest' responses: '200': description: Flash call created content: application/json: schema: $ref: '#/components/schemas/BirdFlashCallSingleResponse' get: tags: - Bird summary: List flash calls operationId: birdListFlashCalls parameters: - in: query name: workspaceId schema: type: string format: uuid required: false description: Bird Workspace identifier (falls back to module configuration if omitted) - in: query name: channelId schema: type: string format: uuid required: false description: Bird Channel identifier (falls back to module configuration if omitted) - in: query name: limit schema: type: integer minimum: 1 maximum: 1000 - in: query name: pageToken schema: type: string - in: query name: startAt schema: type: string format: date-time - in: query name: endAt schema: type: string format: date-time - in: query name: status schema: type: string - in: query name: to schema: type: string - in: query name: from schema: type: string - in: query name: duration schema: type: integer - in: query name: id schema: type: string format: uuid responses: '200': description: A list of flash calls content: application/json: schema: $ref: '#/components/schemas/BirdFlashCallListResponse' /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 format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid responses: '200': description: Flash call details content: application/json: schema: $ref: '#/components/schemas/BirdFlashCallSingleResponse' post: tags: - Bird summary: Complete/end a flash call by ID operationId: birdEndFlashCall parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false - in: path name: id required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BirdFlashCallEndRequest' responses: '200': description: Flash call completed content: application/json: schema: $ref: '#/components/schemas/BirdFlashCallSingleResponse' /bird/voice/flash-calls/hangup: post: tags: - Bird summary: Hang up flash calls using payload criteria operationId: birdHangupFlashCall parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BirdFlashCallHangupRequest' responses: '200': description: Flash call hangup accepted content: application/json: schema: $ref: '#/components/schemas/BirdFlashCallHangupResponse' /bird/voice/flash-calls/end: post: tags: - Bird summary: Compatibility alias for flash hangup endpoint description: Deprecated alias for `/bird/voice/flash-calls/hangup`. deprecated: true operationId: birdEndFlashCallByNumbers parameters: - in: query name: workspaceId schema: type: string format: uuid required: false - in: query name: channelId schema: type: string format: uuid required: false requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BirdFlashCallHangupRequest' responses: '200': description: Flash call hangup accepted (alias) content: application/json: schema: $ref: '#/components/schemas/BirdFlashCallHangupResponse' # 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 runtime_config: type: object properties: economic: type: object properties: transaction_draft_customer_number: type: integer nullable: true additionalProperties: false additionalProperties: true '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 x-api-coverage: happy: true 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 visible, active departments by default. Superuser department access may filter archived departments with `filters=archived:1`. 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' - name: filters in: query schema: type: string description: Comma-separated field filters. `archived:1` is only honored for users with superuser department access. 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/machine-types: get: tags: - Self-Serve summary: List reusable self-serve machine types operationId: listSelfserveMachineTypes parameters: - name: id in: query required: true schema: type: integer - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' responses: '200': description: Successfully retrieved machine types content: application/json: schema: oneOf: - $ref: '#/components/schemas/SelfserveMachineType' - type: array items: $ref: '#/components/schemas/SelfserveMachineType' '404': $ref: '#/components/responses/NotFound' post: tags: - Self-Serve summary: Add reusable self-serve machine type operationId: addSelfserveMachineType requestBody: required: true content: application/json: schema: type: object required: - name properties: name: type: string description: type: string nullable: true responses: '200': description: Successfully added machine type content: application/json: schema: $ref: '#/components/schemas/SelfserveMachineType' put: tags: - Self-Serve summary: Update reusable self-serve machine type operationId: updateSelfserveMachineType parameters: - name: id in: query required: true schema: type: integer requestBody: content: application/json: schema: type: object properties: name: type: string description: type: string nullable: true responses: '200': description: Successfully updated machine type content: application/json: schema: $ref: '#/components/schemas/SelfserveMachineType' '404': $ref: '#/components/responses/NotFound' delete: tags: - Self-Serve summary: Delete reusable self-serve machine type operationId: deleteSelfserveMachineType parameters: - name: id in: query required: true schema: type: integer responses: '200': description: Successfully deleted machine type content: application/json: schema: type: string example: Machine type deleted '404': $ref: '#/components/responses/NotFound' /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. Questions are typically shared across departments and lanes by omitting department, lane, and product, which default to 0. operationId: addSelfserveQuestion requestBody: required: true content: application/json: schema: type: object required: - question - description properties: department: type: integer default: 0 lane: type: integer default: 0 product: type: integer default: 0 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 - name: machine_type_id in: query description: Filter by reusable machine type ID schema: type: integer - name: machine_type_id in: query description: Filter by reusable machine type 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. Either provide a reusable machine_type_id or a legacy department/lane/product scope. operationId: addSelfserveCondition requestBody: required: true content: application/json: schema: type: object required: - name - description properties: department: type: integer default: 0 lane: type: integer default: 0 product: type: integer default: 0 machine_type_id: type: integer nullable: true 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 machine_type_id: type: integer nullable: true 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. This answer mutation does not activate machines or synchronize live relay state; hardware changes are handled only by the explicit wash start flow. 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 vehicle_type: type: integer nullable: true description: Optional product/vehicle type override used when refreshing the self-serve summary. vehicle_type_id: type: integer nullable: true description: Alias for vehicle_type. responses: '200': description: Successfully added vehicle condition content: application/json: schema: $ref: '#/components/schemas/DepartmentSelfserveVehicleConditionMutationResponse' '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. This answer mutation does not activate machines or synchronize live relay state; hardware changes are handled only by the explicit wash start flow. 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 vehicle_type: type: integer nullable: true description: Optional product/vehicle type override used when refreshing the self-serve summary. vehicle_type_id: type: integer nullable: true description: Alias for vehicle_type. responses: '200': description: Successfully updated vehicle condition content: application/json: schema: $ref: '#/components/schemas/DepartmentSelfserveVehicleConditionMutationResponse' '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: object properties: message: type: string example: Condition deleted selfserve: allOf: - $ref: '#/components/schemas/SelfserveWashSummary' nullable: true '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /department/selfserve/vehicle/allowed: get: tags: - Self-Serve summary: Check whether self-serve is allowed for a vehicle on a lane description: Customers with own self-serve permissions may evaluate any registration plate for their wash. Persisted self-serve answers are only applied when they are scoped to the authenticated customer. operationId: getSelfserveVehicleAllowed parameters: - name: lane_id in: query required: true schema: type: integer - name: reg in: query required: true schema: type: string - name: vehicle_type_id in: query required: false description: Optional vehicle type override used when no vehicle is found by registration plate. schema: type: integer minimum: 0 - name: vehicle_type in: query required: false description: Backward-compatible alias of `vehicle_type_id`. schema: type: integer minimum: 0 responses: '200': description: Successfully evaluated self-serve eligibility content: application/json: schema: $ref: '#/components/schemas/SelfserveVehicleAllowedResponse' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /department/selfserve/washes/summary: get: tags: - Self-Serve summary: Get self-serve wash summary operationId: getSelfserveWashSummary parameters: - name: session_id in: query required: false schema: type: integer - name: lane_id in: query required: false schema: type: integer - name: reg in: query required: false schema: type: string - name: vehicle_type_id in: query required: false description: Optional vehicle type override used to refresh summary data for unknown or reassigned plates. schema: type: integer minimum: 0 - name: vehicle_type in: query required: false description: Backward-compatible alias of `vehicle_type_id`. schema: type: integer minimum: 0 responses: '200': description: Successfully retrieved self-serve wash summary content: application/json: schema: $ref: '#/components/schemas/SelfserveWashSummary' '403': $ref: '#/components/responses/Forbidden' '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. Either provide a reusable machine_type_id or a legacy department/lane/product scope. operationId: addSelfserveTask requestBody: required: true content: application/json: schema: type: object required: - task - description properties: department: type: integer default: 0 lane: type: integer default: 0 product: type: integer default: 0 machine_type_id: type: integer nullable: true 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 machine_type_id: type: integer nullable: true 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' /department/selfserve/studio/graph: get: tags: - Self-Serve summary: Get all-in-one self-serve studio graph description: Returns the replacement studio workspace graph with nodes, edges, resolved lookup labels, validation, layout, versioning, simulator defaults, gateway workspace, and permissions. Vehicle type lookups and scope nodes are derived from selectable wash products. operationId: getSelfserveStudioGraph parameters: - name: department in: query required: true schema: type: integer responses: '200': description: Studio graph returned content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioGraph' put: tags: - Self-Serve summary: Bulk save all-in-one self-serve studio graph changes description: Creates, updates, deletes, connects, disconnects, and reorders questions, conditions, rules, tasks, scopes, attachments metadata, and gateway references while keeping layout separate from runtime behavior. operationId: saveSelfserveStudioGraph requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioGraphSaveRequest' responses: '200': description: Studio graph saved content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioGraph' '422': $ref: '#/components/responses/BadRequest' /department/selfserve/studio/layout: put: tags: - Self-Serve summary: Save self-serve studio canvas layout description: Persists canvas-only node positions and viewport state. Layout does not affect runtime wash behavior. operationId: saveSelfserveStudioLayout requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioLayoutSaveRequest' responses: '200': description: Layout saved content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioLayout' /department/selfserve/studio/validate: post: tags: - Self-Serve summary: Validate self-serve studio graph operationId: validateSelfserveStudioGraph requestBody: required: true content: application/json: schema: type: object required: [department] properties: department: type: integer responses: '200': description: Validation result content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioValidation' /department/selfserve/studio/simulate: post: tags: - Self-Serve summary: Simulate self-serve studio runtime operationId: simulateSelfserveStudioGraph requestBody: required: true content: application/json: schema: type: object required: [department, lane_id, reg] properties: department: { type: integer } lane_id: { type: integer } reg: { type: string } customer_number: { type: integer, nullable: true } vehicle_type_id: { type: integer, nullable: true } config_source: type: string enum: [draft, published] default: draft answer_overrides: type: array items: type: object required: [question_id] properties: question_id: { type: integer } value: type: boolean nullable: true include_hardware: type: boolean default: true mode: type: string enum: [full_dry_run] default: full_dry_run responses: '200': description: Simulator result content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioSimulationResponse' /department/selfserve/studio/publish: post: tags: - Self-Serve summary: Publish self-serve studio draft operationId: publishSelfserveStudioDraft requestBody: required: true content: application/json: schema: type: object required: [department] properties: department: { type: integer } responses: '200': description: Published version content: application/json: schema: $ref: '#/components/schemas/SelfserveConfigVersion' /department/selfserve/studio/rollback: post: tags: - Self-Serve summary: Roll back self-serve studio to an earlier version operationId: rollbackSelfserveStudioDraft requestBody: required: true content: application/json: schema: type: object required: [department, target_version_id] properties: department: { type: integer } target_version_id: { type: integer } responses: '200': description: Rollback version content: application/json: schema: $ref: '#/components/schemas/SelfserveConfigVersion' /department/selfserve/studio/gateway-action: post: tags: - Self-Serve summary: Run permission-gated edge gateway action from studio operationId: runSelfserveStudioGatewayAction requestBody: required: true content: application/json: schema: type: object required: [department, gateway_id, action, confirm] properties: department: { type: integer } gateway_id: { type: integer } action: type: string enum: [discovery, discover, update, uninstall, cancel, rotate_credentials, bindings] confirm: type: boolean description: Required for dangerous gateway actions such as uninstall and credential rotation. operation_id: { type: integer, nullable: true } request: type: object additionalProperties: true bindings: type: array items: type: object additionalProperties: true responses: '200': description: Gateway action result content: application/json: schema: type: object additionalProperties: true # 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: post: tags: - Invoices summary: Export collected invoice to e-conomic description: | Exports collected invoice to e-conomic. Uses async queue when available, otherwise falls back to synchronous processing. operationId: queueCollectedInvoiceEconomicTransfer requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: type: integer minimum: 1 send_as_is: type: boolean default: false responses: '200': description: Collected invoice export processed synchronously (fallback) content: application/json: schema: $ref: '#/components/schemas/EconomicTransferSynchronousFallbackResponse' '202': description: Collected invoice transfer queued content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueEnqueueResponse' '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/stripe/book: post: tags: - Invoices summary: Export Stripe collected invoice to e-conomic description: | Exports a Stripe-backed collected invoice to e-conomic. Uses async queue when available, otherwise falls back to synchronous processing. operationId: queueStripeCollectedInvoiceEconomicTransfer requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: type: integer minimum: 1 responses: '200': description: Stripe collected invoice export processed synchronously (fallback) content: application/json: schema: $ref: '#/components/schemas/EconomicTransferSynchronousFallbackResponse' '202': description: Stripe collected invoice transfer queued content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueEnqueueResponse' '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/queue: get: tags: - Invoices summary: List collected-invoice e-conomic transfer queue jobs operationId: listCollectedInvoiceEconomicQueueJobs parameters: - name: status in: query required: false description: Comma-separated queue statuses to filter by. style: form explode: false schema: type: array items: $ref: '#/components/schemas/EconomicTransferQueueStatus' uniqueItems: true example: [QUEUED, FAILED] - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 500 default: 50 - name: offset in: query required: false schema: type: integer minimum: 0 default: 0 responses: '200': description: Queue jobs retrieved content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueListResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/queue/monitor: get: tags: - Invoices summary: Monitor current-user visible collected-invoice e-conomic transfer queue jobs operationId: monitorCollectedInvoiceEconomicQueueJobs parameters: - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 100 default: 50 responses: '200': description: Queue monitor state retrieved content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueMonitorResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/queue/status: get: tags: - Invoices summary: Get collected-invoice e-conomic transfer queue job status operationId: getCollectedInvoiceEconomicQueueJobStatus parameters: - name: job_id in: query required: true schema: type: integer minimum: 1 responses: '200': description: Queue job status content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueStatusResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/queue/retry: post: tags: - Invoices summary: Retry failed collected-invoice queue job operationId: retryCollectedInvoiceEconomicQueueJob requestBody: required: true content: application/json: schema: type: object required: [job_id] properties: job_id: type: integer minimum: 1 responses: '200': description: Queue job retried content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueRetryResponse' '400': { $ref: '#/components/responses/BadRequest' } '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/queue/dismiss: post: tags: - Invoices summary: Clear one completed or failed collected-invoice queue job for the current user operationId: dismissCollectedInvoiceEconomicQueueJob requestBody: required: true content: application/json: schema: type: object required: [job_id] properties: job_id: type: integer minimum: 1 responses: '200': description: Queue job cleared content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueDismissResponse' '400': { $ref: '#/components/responses/BadRequest' } '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/queue/dismiss-terminal: post: tags: - Invoices summary: Clear all visible completed or failed collected-invoice queue jobs for the current user operationId: dismissCollectedInvoiceEconomicTerminalQueueJobs responses: '200': description: Terminal queue jobs cleared content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueDismissTerminalResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/queue/run: post: tags: - Invoices summary: Run one collected-invoice queue batch immediately operationId: runCollectedInvoiceEconomicQueueBatch requestBody: required: false content: application/json: schema: type: object properties: limit: type: integer minimum: 1 maximum: 10 default: 10 responses: '200': description: Queue batch processed content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueRunResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /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/invoicing/period/distribution/v2/booked-department-75: get: tags: - Invoices summary: Get booked e-conomic department 75 redistribution operationId: getInvoicingPeriodDistributionV2BookedDepartment75 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: Actual booked e-conomic department 75 net amounts redistributed to internal departments content: application/json: schema: $ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75Response' '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: Record machine start button press webhook operationId: addButtonPress parameters: - name: token in: query required: false schema: {type: string} - name: lane_id in: query required: false schema: type: integer - name: reg in: query required: false schema: type: string responses: '201': description: Button press recorded and linked to a self-serve wash session content: application/json: schema: $ref: '#/components/schemas/MachineButtonPressWebhookResponse' '404': $ref: '#/components/responses/NotFound' post: tags: - Plate Scans summary: Record machine start button press webhook operationId: addButtonPressPost requestBody: required: false content: application/json: schema: type: object properties: token: type: string lane_id: type: integer reg: type: string responses: '201': description: Button press recorded and linked to a self-serve wash session content: application/json: schema: $ref: '#/components/schemas/MachineButtonPressWebhookResponse' '404': $ref: '#/components/responses/NotFound' # 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: Exports draft invoice using queue processing when available, with synchronous fallback when queue dependencies are unavailable. operationId: queueDraftInvoiceExportToEconomic requestBody: required: true content: application/json: schema: type: object required: [order_id] properties: order_id: type: integer minimum: 1 responses: '200': description: Draft invoice export processed synchronously (fallback) content: application/json: schema: $ref: '#/components/schemas/EconomicTransferSynchronousFallbackResponse' '202': description: Draft invoice export queued content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueEnqueueResponse' '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' } /economic/invoice/draft/export/status: get: tags: - Modules summary: Get queued draft export job status operationId: getDraftInvoiceExportQueueStatus parameters: - name: job_id in: query required: true schema: type: integer minimum: 1 responses: '200': description: Queue job status content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueStatusResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /economic/invoice/draft/export/retry: post: tags: - Modules summary: Retry failed draft export queue job operationId: retryDraftInvoiceExportQueueJob requestBody: required: true content: application/json: schema: type: object required: [job_id] properties: job_id: type: integer minimum: 1 responses: '200': description: Queue job retried content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueRetryResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /economic/invoice/export: post: tags: - Modules summary: Export invoice to e-conomic description: Exports booked invoice using queue processing when available, with synchronous fallback when queue dependencies are unavailable. operationId: queueInvoiceExportToEconomic requestBody: required: true content: application/json: schema: type: object required: [order_id] properties: order_id: type: integer minimum: 1 responses: '200': description: Invoice export processed synchronously (fallback) content: application/json: schema: $ref: '#/components/schemas/EconomicTransferSynchronousFallbackResponse' '202': description: Invoice export queued content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueEnqueueResponse' '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' } /economic/invoice/export/status: get: tags: - Modules summary: Get queued invoice export job status operationId: getInvoiceExportQueueStatus parameters: - name: job_id in: query required: true schema: type: integer minimum: 1 responses: '200': description: Queue job status content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueStatusResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /economic/invoice/export/retry: post: tags: - Modules summary: Retry failed invoice export queue job operationId: retryInvoiceExportQueueJob requestBody: required: true content: application/json: schema: type: object required: [job_id] properties: job_id: type: integer minimum: 1 responses: '200': description: Queue job retried content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueRetryResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } # 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/wash/my-active-wash: get: tags: - Modules summary: Get the authenticated customer's active self-serve wash description: | Returns the current authenticated customer's open self-serve wash session, if one exists. Regular customers must only receive their own active wash details from this endpoint. operationId: getMyActiveSelfServeWash responses: '200': description: Authenticated customer's active wash details resolved content: application/json: schema: type: object properties: lane_id: type: integer nullable: true in_progress: type: boolean session: type: object nullable: true properties: id: type: integer lane_id: type: integer nullable: true department_id: type: integer nullable: true status: type: string reg: type: string customer_number: type: integer nullable: true vehicle_id: type: integer nullable: true vehicle_type_id: type: integer nullable: true included_minutes: type: integer nullable: true machine_type_id: type: integer nullable: true machine_relay_enabled: type: boolean machine_relay_enabled_at: type: string nullable: true machine_start_triggered: type: boolean machine_start_triggered_at: type: string nullable: true wash_started_at: type: string nullable: true created_at: type: string updated_at: type: string nullable: true customer: type: object nullable: true properties: id: type: integer nullable: true customer_number: type: integer nullable: true display_name: type: string nullable: true email: type: string nullable: true phone_country_code: type: integer nullable: true phone: type: string nullable: true vehicle: type: object nullable: true properties: id: type: integer customer_id: type: integer type: type: integer reg: type: string reference: type: string nullable: true /modules/self-serve/lane/wash/in-progress: get: tags: - Modules summary: Get in-progress self-serve wash customer and vehicle details description: | Returns the current open self-serve wash session details for a lane (if any), including resolved customer and vehicle details. operationId: getSelfServeLaneWashInProgress parameters: - name: lane_id in: query required: true schema: type: integer responses: '200': description: In-progress wash details resolved content: application/json: schema: type: object properties: lane_id: type: integer in_progress: type: boolean session: type: object nullable: true properties: id: type: integer status: type: string reg: type: string customer_number: type: integer nullable: true vehicle_id: type: integer nullable: true vehicle_type_id: type: integer nullable: true included_minutes: type: integer nullable: true machine_type_id: type: integer nullable: true machine_relay_enabled: type: boolean machine_relay_enabled_at: type: string nullable: true machine_start_triggered: type: boolean machine_start_triggered_at: type: string nullable: true wash_started_at: type: string nullable: true created_at: type: string updated_at: type: string nullable: true customer: type: object nullable: true properties: id: type: integer nullable: true customer_number: type: integer nullable: true display_name: type: string nullable: true email: type: string nullable: true phone_country_code: type: integer nullable: true phone: type: string nullable: true vehicle: type: object nullable: true properties: id: type: integer customer_id: type: integer type: type: integer reg: type: string reference: type: string nullable: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /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. Property gate commands (`OPEN_PROPERTY_ACCESS_GATE`, `OPEN_PROPERTY_EXIT_GATE`) are also supported here. Property gate command permissions are bypassed for authenticated customers with an active self-serve wash in the target department. 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, OPEN_PROPERTY_ACCESS_GATE, OPEN_PROPERTY_EXIT_GATE] license_plate: type: string description: Required for START command customer_number: type: integer description: Required for START and RESERVE commands. The authenticated customer's number is applied server-side when omitted by user clients. defer_relay_side_effects: type: boolean default: false description: For START, open the entrance gate as part of the command but defer cleaner and machine relay activation to explicit relay endpoints. responses: '200': description: Command sent successfully content: application/json: schema: $ref: '#/components/schemas/SelfServeLaneStatus' '400': description: Command execution failed content: application/json: schema: type: object properties: message: type: string example: 'Failed to execute command: Failed to open property access gate.' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /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/gate/open: post: tags: - Modules summary: Open a self-serve lane gate description: | Opens either the ENTRANCE or EXIT gate relay for a self-serve lane. Failures return a sanitized gate-specific message. operationId: openSelfServeLaneGate requestBody: required: true content: application/json: schema: type: object required: - lane_id - gate properties: lane_id: type: integer gate: type: string enum: [ENTRANCE, EXIT] responses: '200': description: Lane gate opened content: application/json: schema: type: object properties: lane_id: type: integer gate: type: string enum: [ENTRANCE, EXIT] opened: type: boolean state: type: string '400': description: Gate open failed content: application/json: schema: type: object properties: message: type: string example: Failed to open entrance gate. '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/relay/machine/status: get: tags: - Modules summary: Get MACHINE relay status for a lane description: | Reads the current Shelly MACHINE relay status (`on`/`off`) for the given lane. operationId: getSelfServeLaneMachineRelayStatus parameters: - name: lane_id in: query required: true schema: type: integer responses: '200': description: MACHINE relay status retrieved content: application/json: schema: $ref: '#/components/schemas/SelfServeLaneMachineRelayStatus' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/relay/machine_program_picker/status: get: tags: - Modules summary: Get MACHINE_PROGRAM_PICKER relay status for a lane description: | Reads the current Shelly MACHINE_PROGRAM_PICKER relay status (`on`/`off`) for the given lane. operationId: getSelfServeLaneMachineProgramPickerRelayStatus parameters: - name: lane_id in: query required: true schema: type: integer responses: '200': description: MACHINE_PROGRAM_PICKER relay status retrieved content: application/json: schema: $ref: '#/components/schemas/SelfServeLaneMachineRelayStatus' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/relay/machine_program_picker/set: post: tags: - Modules summary: Set MACHINE_PROGRAM_PICKER relay status for a lane description: | Sets the Shelly MACHINE_PROGRAM_PICKER relay state for the lane to on or off and returns the latest status. operationId: setSelfServeLaneMachineProgramPickerRelayStatus requestBody: required: true content: application/json: schema: type: object required: - lane_id - on properties: lane_id: type: integer on: type: boolean responses: '200': description: MACHINE_PROGRAM_PICKER relay status updated content: application/json: schema: type: object properties: lane_id: type: integer relay: type: string enum: [MACHINE_PROGRAM_PICKER] requested_on: type: boolean relay_id: type: string online: type: boolean on: type: boolean '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/relay/machine_cleaner/status: get: tags: - Modules summary: Get MACHINE_CLEANER relay status for a lane description: | Reads the current Shelly MACHINE_CLEANER relay status (`on`/`off`) for the given lane. operationId: getSelfServeLaneMachineCleanerRelayStatus parameters: - name: lane_id in: query required: true schema: type: integer responses: '200': description: MACHINE_CLEANER relay status retrieved content: application/json: schema: $ref: '#/components/schemas/SelfServeLaneMachineRelayStatus' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/relay/machine_cleaner/set: post: tags: - Modules summary: Set MACHINE_CLEANER relay status for a lane description: | Sets the Shelly MACHINE_CLEANER relay state for the lane to on or off and returns the latest status. operationId: setSelfServeLaneMachineCleanerRelayStatus requestBody: required: true content: application/json: schema: type: object required: - lane_id - on properties: lane_id: type: integer on: type: boolean responses: '200': description: MACHINE_CLEANER relay status updated content: application/json: schema: type: object properties: lane_id: type: integer relay: type: string enum: [MACHINE_CLEANER] requested_on: type: boolean relay_id: type: string online: type: boolean on: type: boolean '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/relay/machine/set: post: tags: - Modules summary: Set MACHINE relay status for a lane description: | Sets the Shelly MACHINE relay state for the lane to on or off and returns the latest status. operationId: setSelfServeLaneMachineRelayStatus requestBody: required: true content: application/json: schema: type: object required: - lane_id - on properties: lane_id: type: integer on: type: boolean responses: '200': description: MACHINE relay status updated content: application/json: schema: type: object properties: lane_id: type: integer relay: type: string enum: [MACHINE] requested_on: type: boolean relay_id: type: string online: type: boolean on: type: boolean '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' /modules/workfeed/employees: get: tags: - Modules summary: List Workfeed employees description: List employees from Workfeed (`GET /companies/{CompanyID}/employees`) operationId: workfeedListEmployees responses: '200': description: Workfeed employees retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WorkfeedEmployeeListResponse' /modules/workfeed/employees/{id}: get: tags: - Modules summary: Get Workfeed employee description: Retrieve a single Workfeed employee by identifier operationId: workfeedGetEmployee parameters: - name: id in: path required: true schema: type: string description: Workfeed employee identifier responses: '200': description: Workfeed employee retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WorkfeedEmployeeSingleResponse' /modules/workfeed/shifts: get: tags: - Modules summary: List Workfeed shifts description: List Workfeed shifts (`GET /companies/{CompanyID}/shifts`) operationId: workfeedListShifts parameters: - name: startFrom in: query required: true schema: type: string format: date-time description: Only return shifts starting on or after this timestamp (ISO 8601) - name: startTo in: query required: true schema: type: string format: date-time description: Only return shifts starting before this timestamp (ISO 8601) - name: employeeID in: query required: false schema: type: string description: Filter shifts by employee ID - name: released in: query required: false schema: type: boolean description: Filter by released/published status responses: '200': description: Workfeed shifts retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WorkfeedShiftListResponse' /modules/workfeed/shifts/{id}: get: tags: - Modules summary: Get Workfeed shift description: Retrieve a single Workfeed shift by identifier operationId: workfeedGetShift parameters: - name: id in: path required: true schema: type: string description: Workfeed shift identifier responses: '200': description: Workfeed shift retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WorkfeedShiftSingleResponse' /modules/workfeed/departments: get: tags: - Modules summary: List Workfeed departments description: List departments from Workfeed (`GET /companies/{CompanyID}/departments`) operationId: workfeedListDepartments responses: '200': description: Workfeed departments retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WorkfeedDepartmentListResponse' /departments/weather: get: tags: - Departments summary: Get department weather timeline description: Returns hourly weather, washes, Workfeed employee-hours, and productivity status aggregated across selected departments (server local time). Default range is start of yesterday (`00:00`) to end of today (`23:00`). Use `date_from` and `date_to` (`YYYY-MM-DD`) together to override the range. If department coordinates are missing/invalid or WeatherAPI cannot resolve the location, weather data falls back silently and timeline slots default to `mostly_clear`. Slots return `unknown` status when they have no evaluable employee-hours or when one or more selected departments are missing department weather targets. operationId: getDepartmentWeatherTimeline parameters: - name: id in: query required: false schema: type: array items: type: integer minimum: 1 minItems: 1 uniqueItems: true style: form explode: true description: Department ID list. Repeat `id` to select multiple departments (`?id=1&id=2`). - name: ids in: query required: false schema: type: string example: '1,2,3' description: Optional CSV alternative for department IDs. Merged with `id` if both are provided. At least one of `id` or `ids` must be provided. - name: date_from in: query required: false schema: type: string format: date example: '2026-03-23' description: Optional range start date (`YYYY-MM-DD`). Must be used together with `date_to`. - name: date_to in: query required: false schema: type: string format: date example: '2026-03-24' description: Optional range end date (`YYYY-MM-DD`, inclusive). Must be used together with `date_from`. responses: '200': description: Department weather timeline retrieved successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentWeatherTimelineResponse' example: success: true meta: [] includes: [] data: - date: '2026-03-23' time: '00:00' current: false weather: mostly_cloudy washes: 1 hours: 2.0 status: degraded - date: '2026-03-24' time: '13:00' current: true weather: rain washes: 0 hours: 2.5 status: unhealthy - date: '2026-03-24' time: '14:00' current: false weather: mostly_clear washes: 0 hours: 1.0 status: unknown /departments/weather/targets: get: tags: - Departments summary: Get department weather status targets description: Returns department-specific weather productivity thresholds used by `/departments/weather` to classify `healthy`, `degraded`, and `unhealthy` statuses. operationId: getDepartmentWeatherTargets parameters: - name: id in: query required: false schema: type: array items: type: integer minimum: 1 minItems: 1 uniqueItems: true style: form explode: true description: Department ID list. Repeat `id` to select multiple departments (`?id=1&id=2`). - name: ids in: query required: false schema: type: string example: '1,2,3' description: Optional CSV alternative for department IDs. Merged with `id` if both are provided. At least one of `id` or `ids` must be provided. responses: '200': description: Department weather targets retrieved successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentWeatherTargetsResponse' example: success: true meta: [] includes: [] data: - department_id: 1 degraded_threshold: 1.0 healthy_threshold: 1.3 configured: true - department_id: 2 degraded_threshold: null healthy_threshold: null configured: false put: tags: - Departments summary: Upsert department weather status targets description: Creates or updates the weather productivity thresholds for one department. `healthy_threshold` must be greater than or equal to `degraded_threshold`. operationId: upsertDepartmentWeatherTarget requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentWeatherTargetUpsertRequest' example: department_id: 1 degraded_threshold: 1.0 healthy_threshold: 1.3 responses: '200': description: Department weather targets updated successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: $ref: '#/components/schemas/DepartmentWeatherTarget' required: [data] example: success: true meta: [] includes: [] data: department_id: 1 degraded_threshold: 1.0 healthy_threshold: 1.3 configured: true /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' /superuser/system/status: get: tags: - Superuser summary: Aggregated system status snapshot description: Returns a read-only snapshot of runtime health, dependency connectivity, module configuration/probe status, and active user session activity for the superuser dashboard. operationId: getSuperuserSystemStatus parameters: - in: query name: force required: false schema: type: boolean default: false description: Bypass cached external module probes for this request. responses: '200': description: System status snapshot returned successfully content: application/json: schema: $ref: '#/components/schemas/SuperuserSystemStatusResponse' '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: getRecaptchaModuleConfig 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' /workfeed/config: get: tags: [Config] summary: Get Workfeed config operationId: getWorkfeedConfig responses: '200': description: Workfeed configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WorkfeedConfigListResponse' examples: default: summary: Workfeed module configuration value: success: true data: - module: workfeed variable: enabled type: bool value: true - module: workfeed variable: api_url type: string value: https://europe-west1-production-eu-327a3.cloudfunctions.net/api - module: workfeed variable: api_key type: string value: wf_live_xxxxxxxxxxxxxxxxx - module: workfeed variable: CompanyID type: string value: "123456" meta: [] includes: [] post: tags: [Config] summary: Update Workfeed config operationId: updateWorkfeedConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: Workfeed 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/overview: get: tags: - Departments summary: Get daily report overview operationId: getDailyReportOverview parameters: - name: date in: query required: true schema: {type: string} - name: date_to in: query required: false schema: {type: string} - name: department_ids in: query required: true schema: type: string responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/DepartmentDailyReportOverviewResponse' /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/complaints: get: tags: - Departments summary: List or fetch daily report customer complaints operationId: listDailyReportComplaints parameters: - name: id in: query required: false schema: {type: integer} - name: page in: query required: false schema: {type: integer} - name: limit in: query required: false schema: {type: integer} - name: search in: query required: false schema: {type: string} - name: filters in: query required: false schema: {type: string} - name: order in: query required: false schema: {type: string} responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/DepartmentDailyReportComplaintCollectionResponse' post: tags: - Departments summary: Create daily report customer complaint operationId: createDailyReportComplaint requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentDailyReportComplaintCreateRequest' responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/DepartmentDailyReportComplaintResponse' put: tags: - Departments summary: Update daily report customer complaint operationId: updateDailyReportComplaint requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentDailyReportComplaintUpdateRequest' responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/DepartmentDailyReportComplaintResponse' delete: tags: - Departments summary: Delete daily report customer complaint operationId: deleteDailyReportComplaint parameters: - name: id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/DepartmentDailyReportComplaintDeleteResponse' /departments/daily-reports/complaints/customers: get: tags: - Departments summary: Search selectable customers for daily report complaints operationId: searchDailyReportComplaintCustomers parameters: - name: search in: query required: true schema: type: string minLength: 2 - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 20 default: 10 responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/DepartmentDailyReportComplaintCustomerSearchResponse' /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: $ref: '#/components/schemas/DepartmentDailyReportTransactionCountResponse' /departments/daily-reports/outside-hours-trend: get: tags: - Departments summary: Get outside-hours trend for daily reports operationId: getDailyReportOutsideHoursTrend parameters: - name: date in: query required: true schema: {type: string} - name: date_to in: query required: true schema: {type: string} - name: department_ids in: query required: true schema: type: string responses: '200': description: Success content: application/json: schema: $ref: '#/components/schemas/DepartmentDailyReportOutsideHoursTrendResponse' /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' Conflict: description: Conflict - Request could not be completed due to current resource state 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' ServiceUnavailable: description: Service unavailable - Required async queue dependencies are unavailable 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 SuperuserSystemStatusResponse: type: object properties: success: type: boolean data: $ref: '#/components/schemas/SuperuserSystemStatusPayload' meta: type: object additionalProperties: true includes: type: object additionalProperties: true required: - success - data - meta - includes SuperuserSystemStatusPayload: type: object properties: overall_status: $ref: '#/components/schemas/SuperuserSystemStatusEnum' generated_at: type: string format: date-time refresh_after_seconds: type: integer runtime: type: object properties: cpu: $ref: '#/components/schemas/SuperuserRuntimeMetric' memory: $ref: '#/components/schemas/SuperuserRuntimeMetric' disk: $ref: '#/components/schemas/SuperuserRuntimeMetric' dependencies: type: object properties: database: $ref: '#/components/schemas/SuperuserDependencyStatus' redis: $ref: '#/components/schemas/SuperuserDependencyStatus' minio: $ref: '#/components/schemas/SuperuserMinioDependencyStatus' modules: type: array items: $ref: '#/components/schemas/SuperuserModuleStatus' sessions: $ref: '#/components/schemas/SuperuserSessionStatus' warnings: type: array items: type: string required: - overall_status - generated_at - refresh_after_seconds - runtime - dependencies - modules - sessions - warnings SuperuserSystemStatusEnum: type: string enum: [ok, degraded, down] SuperuserModuleStatusEnum: type: string enum: [disabled, not_configured, configured, ok, degraded, down] SuperuserRuntimeMetric: type: object properties: status: $ref: '#/components/schemas/SuperuserSystemStatusEnum' usage_percent: type: number format: float nullable: true used_bytes: type: integer nullable: true free_bytes: type: integer nullable: true total_bytes: type: integer nullable: true path: type: string nullable: true source: type: string nullable: true checked_at: type: string format: date-time SuperuserDependencyStatus: type: object properties: status: $ref: '#/components/schemas/SuperuserSystemStatusEnum' latency_ms: type: number format: float nullable: true database: oneOf: - type: integer - type: string nullable: true server_version: type: string nullable: true http_status: type: integer nullable: true checked_at: type: string format: date-time error: type: string nullable: true SuperuserMinioDependencyStatus: type: object properties: status: $ref: '#/components/schemas/SuperuserSystemStatusEnum' latency_ms: type: number format: float nullable: true endpoint: type: string nullable: true http_status: type: integer nullable: true buckets: type: array items: type: object properties: name: type: string status: type: string error: type: string nullable: true checked_at: type: string format: date-time error: type: string nullable: true SuperuserModuleStatus: type: object properties: key: type: string enabled: type: boolean configured: type: boolean probe_supported: type: boolean status: $ref: '#/components/schemas/SuperuserModuleStatusEnum' status_reason: type: string nullable: true checked_at: type: string format: date-time required: - key - enabled - configured - probe_supported - status - checked_at SuperuserSessionStatus: type: object properties: active_window_minutes: type: integer active_users: type: integer active_sessions: type: integer recent_sessions: type: array items: type: object properties: session_kind: type: string principal_id: type: integer display_name: type: string context_label: type: string nullable: true customer_number_context: type: integer nullable: true device_type: type: string user_agent: type: string last_route: type: string first_seen_at: type: string format: date-time nullable: true last_seen_at: type: string format: date-time nullable: true active: type: boolean required: - active_window_minutes - active_users - active_sessions - recent_sessions 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, transactionDraftCustomerNumber] } type: { type: string, enum: [string, int] } value: oneOf: - type: string - type: integer nullable: true 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] WorkfeedConfigEnabledEntry: type: object properties: module: { type: string, enum: [workfeed] } variable: { type: string, enum: [enabled] } type: { type: string, enum: [bool] } value: { type: boolean } required: [module, variable, type, value] WorkfeedConfigApiUrlEntry: type: object properties: module: { type: string, enum: [workfeed] } variable: { type: string, enum: [api_url] } type: { type: string, enum: [string] } value: { type: string, example: "https://europe-west1-production-eu-327a3.cloudfunctions.net/api" } required: [module, variable, type, value] WorkfeedConfigApiKeyEntry: type: object properties: module: { type: string, enum: [workfeed] } variable: { type: string, enum: [api_key] } type: { type: string, enum: [string] } value: { type: string, example: "wf_live_xxxxxxxxxxxxxxxxx" } required: [module, variable, type, value] WorkfeedConfigCompanyIdEntry: type: object properties: module: { type: string, enum: [workfeed] } variable: { type: string, enum: [CompanyID] } type: { type: string, enum: [string] } value: { type: string, example: "123456" } required: [module, variable, type, value] WorkfeedConfigEntry: oneOf: - $ref: '#/components/schemas/WorkfeedConfigEnabledEntry' - $ref: '#/components/schemas/WorkfeedConfigApiUrlEntry' - $ref: '#/components/schemas/WorkfeedConfigApiKeyEntry' - $ref: '#/components/schemas/WorkfeedConfigCompanyIdEntry' discriminator: propertyName: variable mapping: enabled: '#/components/schemas/WorkfeedConfigEnabledEntry' api_url: '#/components/schemas/WorkfeedConfigApiUrlEntry' api_key: '#/components/schemas/WorkfeedConfigApiKeyEntry' CompanyID: '#/components/schemas/WorkfeedConfigCompanyIdEntry' 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, machine_wash_minutes_included] } 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] WorkfeedConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/WorkfeedConfigEntry' } } required: [data] example: success: true data: - module: workfeed variable: enabled type: bool value: true - module: workfeed variable: api_url type: string value: https://europe-west1-production-eu-327a3.cloudfunctions.net/api - module: workfeed variable: api_key type: string value: wf_live_xxxxxxxxxxxxxxxxx - module: workfeed variable: CompanyID type: string value: "123456" meta: [] includes: [] 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 description: Productivity health for the slot. `unknown` is returned when the slot has not started yet, has no employee-hours, or one or more selected departments are missing department weather targets. enum: [unknown, healthy, degraded, unhealthy] DepartmentWeatherCondition: type: string enum: [clear, mostly_clear, partly_cloudy, mostly_cloudy, overcast, rain, showers, thunderstorm, snow, fog] DepartmentWeatherTarget: type: object properties: department_id: type: integer minimum: 1 degraded_threshold: type: number nullable: true minimum: 0 description: Minimum washes per hour ratio required for `degraded`. `null` when target is not configured. healthy_threshold: type: number nullable: true minimum: 0 description: Minimum washes per hour ratio required for `healthy`. `null` when target is not configured. configured: type: boolean description: Whether both weather status thresholds are configured and valid for the department. required: [department_id, degraded_threshold, healthy_threshold, configured] DepartmentWeatherTargetUpsertRequest: type: object required: [department_id, degraded_threshold, healthy_threshold] properties: department_id: type: integer minimum: 1 degraded_threshold: type: number minimum: 0 healthy_threshold: type: number minimum: 0 description: Must be greater than or equal to `degraded_threshold`. DepartmentWeatherTimelineEntry: type: object properties: date: type: string format: date description: Calendar date for the hourly slot (`YYYY-MM-DD`). example: '2026-03-24' time: type: string description: Hour label for the slot in 24-hour format (`HH:00`). example: '01:00' current: type: boolean description: True when this slot matches the current server hour. example: false weather: $ref: '#/components/schemas/DepartmentWeatherCondition' washes: type: integer minimum: 0 example: 0 hours: type: number format: float minimum: 0 example: 2.5 description: Sum of Workfeed employee-hours in the department for this exact hour slot status: $ref: '#/components/schemas/DepartmentWeatherStatus' required: [date, time, current, weather, washes, hours, status] WeatherApiObjectResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: type: object additionalProperties: true required: [data] WorkfeedDepartment: type: object properties: id: { type: string, example: "PKHaOSgFA4uguOqmLfWv" } name: { type: string, example: "API Testing Account 😎" } isDeleted: { type: boolean, example: false } createTime: { type: string, format: date-time, example: "2023-10-25T09:43:06.650Z" } updateTime: { type: string, format: date-time, example: "2023-10-25T09:43:06.650Z" } additionalProperties: true WorkfeedEmployee: type: object properties: id: { type: string, example: "J9QAIiTG0nRC1OsC5YvHfLWdDHn1" } firstname: { type: string, example: "API 2" } lastname: { type: string, example: "Test 2" } email: { type: string, nullable: true, example: "test2@example.com" } phone: { type: string, nullable: true, example: "12345678" } roleIDs: type: array items: { type: string } example: ["hLbEKIPTlMh3ehotXl0w"] departmentIDs: type: array items: { type: string } example: ["PKHaOSgFA4uguOqmLfWv"] primaryDepartmentID: { type: string, nullable: true } street: { type: string, nullable: true, example: "" } city: { type: string, nullable: true, example: "" } zip: { type: string, nullable: true, example: "" } accessLevel: { type: string, nullable: true, example: "employee" } wage: { type: number, nullable: true } minHours: { type: number, nullable: true } maxHours: { type: number, nullable: true } isDeleted: { type: boolean, example: false } ssn: { type: string, nullable: true, example: "" } imageURL: { type: string, nullable: true, format: uri } createTime: { type: string, format: date-time, example: "2023-10-28T18:22:45.695Z" } updateTime: { type: string, format: date-time, example: "2023-10-28T18:34:37.191Z" } additionalProperties: true WorkfeedShiftComment: type: object properties: message: { type: string, nullable: true, example: "Comments! 😍" } creatorID: { type: string, nullable: true, example: "API" } createdOn: { type: string, format: date-time, nullable: true, example: "2023-10-27T09:43:36.542Z" } additionalProperties: true WorkfeedShiftCustomBreak: type: object properties: creatorID: { type: string, nullable: true, example: "automatic" } duration: { type: number, nullable: true, example: 1 } createdOn: { type: string, format: date-time, nullable: true, example: "2023-10-27T09:10:30.336Z" } additionalProperties: true WorkfeedShiftApproval: type: object properties: approver: { type: string, nullable: true, example: "automatic" } date: { type: string, format: date-time, nullable: true, example: "2023-12-27T09:10:30.336Z" } originalStart: { type: string, format: date-time, nullable: true, example: "2023-12-07T09:10:30.336Z" } originalEnd: { type: string, format: date-time, nullable: true, example: "2023-12-08T09:10:30.336Z" } additionalProperties: true WorkfeedShift: type: object properties: id: { type: string, example: "Trcu7MKFomu5y5zv1B8G" } start: { type: string, format: date-time, example: "2023-10-23T08:00:00.000Z" } end: { type: string, format: date-time, example: "2023-10-23T16:00:00.000Z" } employeeID: { type: string, nullable: true } roleID: { type: string, nullable: true, example: "hLbEKIPTlMh3ehotXl0w" } departmentID: { type: string, nullable: true, example: "PKHaOSgFA4uguOqmLfWv" } released: { type: boolean, example: false } isForSale: { type: boolean, nullable: true, example: false } comment: allOf: - $ref: '#/components/schemas/WorkfeedShiftComment' nullable: true customBreak: allOf: - $ref: '#/components/schemas/WorkfeedShiftCustomBreak' nullable: true overlappingLeaveID: { type: string, nullable: true } approval: allOf: - $ref: '#/components/schemas/WorkfeedShiftApproval' nullable: true grossPay: { type: number, nullable: true } tagIDs: type: array items: { type: string } createTime: { type: string, format: date-time, example: "2023-10-27T09:10:30.336Z" } updateTime: { type: string, format: date-time, example: "2023-10-27T09:10:30.422Z" } additionalProperties: true WorkfeedEmployeeListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: type: array items: $ref: '#/components/schemas/WorkfeedEmployee' required: [data] WorkfeedEmployeeSingleResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: $ref: '#/components/schemas/WorkfeedEmployee' required: [data] WorkfeedShiftListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: type: array items: $ref: '#/components/schemas/WorkfeedShift' required: [data] WorkfeedShiftSingleResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: $ref: '#/components/schemas/WorkfeedShift' required: [data] WorkfeedDepartmentListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: type: array items: $ref: '#/components/schemas/WorkfeedDepartment' required: [data] DepartmentWeatherTimelineResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: type: array description: Hourly contiguous slots from start of range (`00:00`) to end of range (`23:00`, inclusive). Defaults to yesterday+today (48 entries) when date_from/date_to are not provided. items: $ref: '#/components/schemas/DepartmentWeatherTimelineEntry' required: [data] DepartmentWeatherTargetsResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: type: array items: $ref: '#/components/schemas/DepartmentWeatherTarget' 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 EconomicTransferQueueStatus: type: string enum: - QUEUED - PROCESSING - COMPLETED - FAILED EconomicTransferQueueJob: type: object properties: id: type: integer transfer_type: type: string enum: - ORDER_DRAFT_EXPORT - ORDER_INVOICE_EXPORT - COLLECTED_INVOICE_EXPORT status: $ref: '#/components/schemas/EconomicTransferQueueStatus' progress_percent: type: integer minimum: 0 maximum: 100 progress_message: type: string nullable: true attempts: type: integer minimum: 0 max_attempts: type: integer minimum: 1 error_message: type: string nullable: true payload: type: object nullable: true additionalProperties: true result: type: object nullable: true additionalProperties: true details_summary: type: object nullable: true additionalProperties: true created_by: type: integer nullable: true created_at: type: string format: date-time nullable: true updated_at: type: string format: date-time nullable: true started_at: type: string format: date-time nullable: true completed_at: type: string format: date-time nullable: true next_retry_at: type: string format: date-time nullable: true required: - id - transfer_type - status - progress_percent - attempts - max_attempts EconomicTransferQueueEnqueueResponse: type: object properties: success: type: boolean data: type: object properties: message: type: string job_id: type: integer minimum: 1 job: $ref: '#/components/schemas/EconomicTransferQueueJob' required: - message - job_id - job meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferSynchronousFallbackResponse: type: object properties: success: type: boolean data: type: object properties: message: type: string mode: type: string enum: [synchronous_fallback] result: type: object additionalProperties: true required: - message - mode - result meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferQueueStatusResponse: type: object properties: success: type: boolean data: $ref: '#/components/schemas/EconomicTransferQueueJob' meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferQueueRetryResponse: type: object properties: success: type: boolean data: type: object properties: message: type: string job: $ref: '#/components/schemas/EconomicTransferQueueJob' required: - message - job meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferQueueRunResponse: type: object properties: success: type: boolean data: type: object properties: message: type: string processed: type: integer minimum: 0 completed: type: integer minimum: 0 failed: type: integer minimum: 0 jobs: type: array items: type: integer minimum: 1 limit: type: integer minimum: 1 maximum: 10 transfer_type: type: string enum: - COLLECTED_INVOICE_EXPORT required: - message - processed - completed - failed - jobs - limit - transfer_type meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferQueueListResponse: type: object properties: success: type: boolean data: type: object properties: items: type: array items: $ref: '#/components/schemas/EconomicTransferQueueJob' count: type: integer minimum: 0 total: type: integer minimum: 0 limit: type: integer minimum: 1 offset: type: integer minimum: 0 has_more: type: boolean required: - items - count - total - limit - offset - has_more meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferQueueMonitorResponse: type: object properties: success: type: boolean data: type: object properties: jobs: type: array items: $ref: '#/components/schemas/EconomicTransferQueueJob' counts: type: object properties: queued: type: integer minimum: 0 in_progress: type: integer minimum: 0 failed: type: integer minimum: 0 completed: type: integer minimum: 0 total: type: integer minimum: 0 required: - queued - in_progress - failed - completed - total progress_percent: type: integer minimum: 0 maximum: 100 limit: type: integer minimum: 1 maximum: 100 required: - jobs - counts - progress_percent - limit meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferQueueDismissResponse: type: object properties: success: type: boolean data: type: object properties: message: type: string job: $ref: '#/components/schemas/EconomicTransferQueueJob' required: - message - job meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferQueueDismissTerminalResponse: type: object properties: success: type: boolean data: type: object properties: message: type: string dismissed_count: type: integer minimum: 0 required: - message - dismissed_count meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes 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' InvoicingDistributionV2BookedDepartment75Group: type: object properties: month: type: string example: '2026-01' source_category: type: string enum: [fixed_pricing, wash_subscriptions, unclassified] invoice_ids: type: array items: type: integer booked_net_amount: type: number department_distribution: $ref: '#/components/schemas/EconomicV2DepartmentDistribution' undistributed_net_amount: type: number required: - month - source_category - invoice_ids - booked_net_amount - department_distribution - undistributed_net_amount InvoicingDistributionV2BookedDepartment75Meta: type: object properties: booked_net_amount: type: number distributed_net_amount: type: number undistributed_net_amount: type: number department_distribution: $ref: '#/components/schemas/EconomicV2DepartmentDistribution' booked_groups: type: array items: $ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75Group' required: - booked_net_amount - distributed_net_amount - undistributed_net_amount - department_distribution - booked_groups InvoicingDistributionV2BookedDepartment75Customer: allOf: - $ref: '#/components/schemas/InvoicingDistributionV2Customer' - type: object properties: meta: type: object properties: booked_department_75: $ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75Meta' required: - booked_department_75 InvoicingDistributionV2BookedDepartment75CollectiveResults: type: object properties: booked_net_amount: type: number distributed_net_amount: type: number undistributed_net_amount: type: number department_distribution: $ref: '#/components/schemas/EconomicV2DepartmentDistribution' department_distribution_parsed: type: object additionalProperties: type: number required: - booked_net_amount - distributed_net_amount - undistributed_net_amount - department_distribution - department_distribution_parsed InvoicingDistributionV2BookedDepartment75Response: type: object properties: customers: type: array items: $ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75Customer' collective_results: $ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75CollectiveResults' warnings: type: array items: type: string required: [customers, collective_results, warnings] InvoicingDistributionV2AllResponse: type: object properties: fixed_pricing: $ref: '#/components/schemas/InvoicingDistributionV2FixedPricingResponse' wash_subscriptions: $ref: '#/components/schemas/InvoicingDistributionV2WashSubscriptionsResponse' customer_prices: $ref: '#/components/schemas/InvoicingDistributionV2CustomerPricesResponse' booked_department_75: $ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75Response' required: [fixed_pricing, wash_subscriptions, customer_prices, booked_department_75] 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 SelfServeLaneMachineRelayStatus: type: object properties: lane_id: type: integer relay: type: string enum: [MACHINE, MACHINE_PROGRAM_PICKER, MACHINE_CLEANER] relay_id: type: string online: type: boolean on: type: boolean 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 machine_wash_minutes_included: type: integer description: Included machine wash minutes before minute-based billing starts SelfserveLaneService: type: string description: Allowed self-serve lane service name enum: - MACHINE SelfserveStudioNode: type: object required: [id, position, data] properties: id: type: string type: type: string nullable: true position: type: object required: [x, y] properties: x: { type: number } y: { type: number } data: type: object additionalProperties: true properties: kind: type: string enum: [question, condition, rule, task, lane, machine_type, vehicle_type, edge_gateway, relay_binding, relay, runtime_checkpoint] object_id: oneOf: - type: integer - type: string nullable: true label: type: string raw: type: object additionalProperties: true SelfserveStudioEdge: type: object required: [id, source, target] properties: id: { type: string } source: { type: string } target: { type: string } type: { type: string, nullable: true } label: { type: string, nullable: true } data: type: object additionalProperties: true SelfserveStudioLayout: type: object properties: nodes: type: object additionalProperties: type: object properties: x: { type: number } y: { type: number } viewport: type: object additionalProperties: true runtime_affecting: type: boolean enum: [false] SelfserveStudioValidation: type: object properties: valid: type: boolean errors: type: array items: { type: string } warnings: type: array items: { type: string } items: type: array items: type: object properties: severity: type: string enum: [error, warning] message: type: string stats: type: object additionalProperties: true validated_at: type: string format: date-time SelfserveConfigVersion: type: object properties: id: { type: integer } department_id: { type: integer } status: type: string enum: [DRAFT, PUBLISHED, ARCHIVED] version_number: { type: integer } config: type: object additionalProperties: true validation_result: $ref: '#/components/schemas/SelfserveStudioValidation' source_version_id: { type: integer, nullable: true } created_by: { type: integer, nullable: true } published_at: { type: string, nullable: true } created_at: { type: string, nullable: true } updated_at: { type: string, nullable: true } SelfserveStudioGraph: type: object required: [nodes, edges, lookups, validation, layout, versions, simulator_defaults, gateway_workspace, permissions] properties: nodes: type: array items: $ref: '#/components/schemas/SelfserveStudioNode' edges: type: array items: $ref: '#/components/schemas/SelfserveStudioEdge' lookups: type: object additionalProperties: true validation: $ref: '#/components/schemas/SelfserveStudioValidation' layout: $ref: '#/components/schemas/SelfserveStudioLayout' versions: type: array items: $ref: '#/components/schemas/SelfserveConfigVersion' active_config: type: object nullable: true additionalProperties: true draft: type: object additionalProperties: true simulator_defaults: type: object additionalProperties: true gateway_workspace: type: object additionalProperties: true permissions: type: object additionalProperties: type: boolean meta: type: object additionalProperties: true SelfserveStudioGraphOperation: type: object properties: action: type: string enum: [create, update, delete, connect, disconnect, reorder] entity: type: string enum: [question, condition, rule, task] id: type: integer nullable: true source: type: string nullable: true target: type: string nullable: true data: type: object additionalProperties: true items: type: array items: type: object additionalProperties: true SelfserveStudioGraphSaveRequest: type: object required: [department] properties: department: { type: integer } operations: type: array items: $ref: '#/components/schemas/SelfserveStudioGraphOperation' nodes: type: array items: $ref: '#/components/schemas/SelfserveStudioNode' layout: $ref: '#/components/schemas/SelfserveStudioLayout' SelfserveStudioLayoutSaveRequest: type: object required: [department, layout] properties: department: { type: integer } layout: $ref: '#/components/schemas/SelfserveStudioLayout' SelfserveMachineType: type: object properties: id: type: integer name: type: string description: type: string nullable: true created_at: type: string format: date-time updated_at: type: string format: date-time nullable: true SelfserveVisibleQuestion: type: object properties: id: type: integer question: type: string description: type: string condition_id: type: integer nullable: true order_priority: type: integer answer: type: boolean nullable: true SelfserveTaskDecision: type: object properties: id: type: integer task: type: string description: type: string condition_id: type: integer nullable: true order_priority: type: integer services: type: array items: $ref: '#/components/schemas/SelfserveLaneService' buttons: type: array items: type: integer SelfserveWashSession: type: object properties: id: type: integer lane_id: type: integer department_id: type: integer machine_type_id: type: integer nullable: true customer_number: type: integer nullable: true vehicle_id: type: integer nullable: true vehicle_type_id: type: integer nullable: true reg: type: string status: type: string allowed: type: boolean machine_relay_enabled: type: boolean machine_relay_enabled_at: type: string format: date-time nullable: true machine_start_triggered: type: boolean machine_start_triggered_at: type: string format: date-time nullable: true wash_started_at: type: string format: date-time nullable: true order_id: type: integer nullable: true completed_at: type: string format: date-time nullable: true metadata: type: object additionalProperties: true created_at: type: string format: date-time updated_at: type: string format: date-time nullable: true SelfserveWashQuestionAnswer: type: object properties: question_id: type: integer question: type: string answer: type: boolean nullable: true answered_at: type: string format: date-time nullable: true SelfserveWashTaskSnapshot: type: object properties: task_id: type: integer nullable: true task: type: string description: type: string nullable: true services: type: array items: $ref: '#/components/schemas/SelfserveLaneService' buttons: type: array items: type: integer SelfserveWashEvent: type: object properties: id: type: integer type: type: string payload: type: object additionalProperties: true nullable: true created_at: type: string format: date-time SelfserveStudioSimulationDebug: type: object required: [summary, parameters, stages, questions, conditions, rules, tasks, hardware, graph_annotations, recommendations] properties: summary: type: object additionalProperties: true parameters: type: object additionalProperties: true stages: type: array items: type: object additionalProperties: true questions: type: array items: type: object additionalProperties: true conditions: type: array items: type: object additionalProperties: true rules: type: array items: type: object additionalProperties: true tasks: type: array items: type: object additionalProperties: true hardware: type: object additionalProperties: true graph_annotations: type: object properties: nodes: type: object additionalProperties: type: object additionalProperties: true edges: type: object additionalProperties: type: object additionalProperties: true recommendations: type: array items: type: object additionalProperties: true SelfserveStudioSimulationResponse: allOf: - $ref: '#/components/schemas/SelfserveVehicleAllowedResponse' - type: object properties: simulator_version: { type: integer } dry_run: { type: boolean, enum: [true] } mode: { type: string, enum: [full_dry_run] } config_source: { type: string, enum: [draft, published] } debug: $ref: '#/components/schemas/SelfserveStudioSimulationDebug' SelfserveVehicleAllowedResponse: type: object properties: lane: $ref: '#/components/schemas/DepartmentLane' machine_type: allOf: - $ref: '#/components/schemas/SelfserveMachineType' nullable: true vehicle: type: object additionalProperties: true nullable: true reg: type: string customer_number: type: integer nullable: true vehicle_type_id: type: integer nullable: true questions: type: array items: $ref: '#/components/schemas/SelfserveVisibleQuestion' tasks: type: array items: $ref: '#/components/schemas/SelfserveTaskDecision' allowed_services: type: array items: $ref: '#/components/schemas/SelfserveLaneService' machine_available: type: boolean all_visible_questions_answered: type: boolean allowed: type: boolean blocked_reason: type: string nullable: true session: allOf: - $ref: '#/components/schemas/SelfserveWashSession' nullable: true config_source: type: string nullable: true evaluation_trace: type: object nullable: true additionalProperties: true SelfserveWashSummary: type: object properties: session: $ref: '#/components/schemas/SelfserveWashSession' lane: allOf: - $ref: '#/components/schemas/DepartmentLane' nullable: true machine_type: allOf: - $ref: '#/components/schemas/SelfserveMachineType' nullable: true questions: type: array items: $ref: '#/components/schemas/SelfserveWashQuestionAnswer' tasks: type: array items: $ref: '#/components/schemas/SelfserveWashTaskSnapshot' events: type: array items: $ref: '#/components/schemas/SelfserveWashEvent' DepartmentSelfserveVehicleConditionMutationResponse: type: object properties: condition: $ref: '#/components/schemas/DepartmentSelfserveVehicleCondition' selfserve: $ref: '#/components/schemas/SelfserveWashSummary' MachineButtonPressWebhookResponse: type: object properties: message: type: string scanner: type: string lane_id: type: integer selfserve: $ref: '#/components/schemas/SelfserveWashSummary' 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 machine_type_id: type: integer description: Reusable machine type ID nullable: true 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 machine_type_id: type: integer description: Reusable machine type ID nullable: true 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 archived: 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 selfserve_enabled: 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 archived: 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 archived: 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 relay_machine_program_picker_id: type: string relay_machine_cleaner_id: type: string dynamic_image_id: type: integer nullable: true minimum: 1 machine_type_id: type: integer nullable: true selfserve_enabled: type: boolean default: true status: type: string 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 relay_machine_program_picker_id: type: string relay_machine_cleaner_id: type: string dynamic_image_id: type: integer nullable: true minimum: 1 machine_type_id: type: integer nullable: true selfserve_enabled: type: boolean default: true 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 relay_machine_program_picker_id: type: string relay_machine_cleaner_id: type: string dynamic_image_id: type: integer nullable: true minimum: 1 machine_type_id: type: integer nullable: true selfserve_enabled: type: boolean 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 target_duration: type: string nullable: true description: | Optional advanced target duration mode. Accepted values: ENTIRE_DURATION, WEEKS, MONTHS, YEARS. When omitted, legacy target behavior is preserved for backward compatibility. The canonical field name is snake_case `target_duration`. For backward-compatibility the API also accepts camelCase `targetDuration` on input. enum: [ENTIRE_DURATION, WEEKS, MONTHS, YEARS] example: WEEKS target_duration_every: type: integer nullable: true minimum: 1 description: | Optional cadence value used with `target_duration` WEEKS, MONTHS, or YEARS. Example: with `target_duration=WEEKS` and `target_duration_every=2`, the target applies every second week. Ignored when `target_duration=ENTIRE_DURATION`. The canonical field name is snake_case `target_duration_every`. For backward-compatibility the API also accepts camelCase `targetDurationEvery` on input. example: 1 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 format: uuid example: "4015cf84-8028-46a1-a0d9-9213e5bf4f09" workspaceId: type: string format: uuid example: "3d5fae4f-9c2d-41aa-9840-28b18e6a94bc" channelId: type: string format: uuid example: "a2545e48-fe8c-5741-9bdc-42a081076bc9" callFlowId: type: string format: uuid nullable: true originator: type: object additionalProperties: true receiver: type: object additionalProperties: true 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 nullable: true hangupSource: type: string nullable: true sipInsights: type: object additionalProperties: true qualityInsights: type: object additionalProperties: true price: type: object additionalProperties: true createdAt: { type: string, format: date-time, nullable: true } updatedAt: { type: string, format: date-time, nullable: true } ringingAt: { type: string, format: date-time, nullable: true } answeredAt: { type: string, format: date-time, nullable: true } endedAt: { type: string, format: date-time, nullable: true } BirdVoiceCallCommandCondition: type: object properties: variable: { type: string } operator: { type: string } value: { type: string } BirdVoiceCallCommandResult: type: object properties: id: type: string format: uuid callId: type: string format: uuid callFlowId: type: string format: uuid nullable: true status: type: string command: type: string conditions: type: array items: $ref: '#/components/schemas/BirdVoiceCallCommandCondition' BirdVoiceCallBridgeResult: allOf: - $ref: '#/components/schemas/BirdVoiceCallCommandResult' - type: object properties: bridgeCallId: type: string format: uuid nullable: true BirdVoiceCallRecording: type: object properties: id: type: string format: uuid callId: type: string format: uuid status: type: string example: ongoing duration: type: integer nullable: true stereo: type: boolean nullable: true mediaUrl: type: string nullable: true createdAt: type: string format: date-time nullable: true updatedAt: type: string format: date-time nullable: true BirdVoiceCallInsights: type: object description: Voice call insights payload as returned by Bird. additionalProperties: true BirdFlashCall: type: object properties: id: type: string format: uuid workspaceId: type: string format: uuid nullable: true channelId: type: string format: uuid nullable: true from: type: string nullable: true to: type: string nullable: true receivedCli: type: string nullable: true result: type: string nullable: true status: type: string nullable: true duration: type: integer nullable: true createdAt: type: string format: date-time nullable: true updatedAt: type: string format: date-time nullable: true BirdVoiceCallListResponse: type: object properties: success: { type: boolean, example: true } data: type: object properties: nextPageToken: type: string nullable: true 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: [] BirdVoiceCallCommandResponse: type: object properties: success: { type: boolean, example: true } data: $ref: '#/components/schemas/BirdVoiceCallCommandResult' meta: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] includes: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] BirdVoiceCallBridgeResponse: type: object properties: success: { type: boolean, example: true } data: $ref: '#/components/schemas/BirdVoiceCallBridgeResult' meta: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] includes: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] BirdVoiceCallRecordingListResponse: type: object properties: success: { type: boolean, example: true } data: type: object properties: nextPageToken: type: string nullable: true results: type: array items: $ref: '#/components/schemas/BirdVoiceCallRecording' meta: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] includes: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] BirdVoiceCallRecordingSingleResponse: type: object properties: success: { type: boolean, example: true } data: $ref: '#/components/schemas/BirdVoiceCallRecording' meta: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] includes: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] BirdVoiceCallInsightsResponse: type: object properties: success: { type: boolean, example: true } data: $ref: '#/components/schemas/BirdVoiceCallInsights' meta: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] includes: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] BirdVoiceCallsLogResponse: type: object properties: success: { type: boolean, example: true } data: type: object properties: nextPageToken: type: string nullable: true 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: [] BirdTestOutboundCallRequest: type: object additionalProperties: false properties: from: type: string description: Caller E.164 number to use for the test call example: "+4599988877" to: type: string description: Target E.164 number. Defaults to configured test number if omitted. example: "+4542331128" timeout: type: integer minimum: 1 description: Backward-compatible alias mapped to ringTimeout ringTimeout: type: integer minimum: 3 maximum: 120 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 enum: [rejected, busy] description: Optional hangup cause passed through to Bird 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/BirdVoiceCallCommandResult' } meta: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] includes: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] BirdInboundCallWebhookRequest: type: object additionalProperties: true properties: callId: type: string example: "4015cf84-8028-46a1-a0d9-9213e5bf4f09" workspaceId: type: string format: uuid channelId: type: string format: uuid status: type: string description: Optional inbound call status from webhook payload example: ongoing dtmf: type: string description: DTMF value when present, for example `1` or `1#` example: "5" digit: type: string description: Alternate DTMF field, also accepts values such as `1#` example: "5" digits: type: string description: Alternate DTMF field example: "5" keys: type: string description: Alternate DTMF field returned by gather results example: "1#" call: type: object additionalProperties: true data: type: object additionalProperties: true event: oneOf: - type: string - type: object additionalProperties: true BirdInboundCallWebhookResponse: type: object properties: success: type: boolean example: true data: type: object properties: phase: type: string enum: [lock_not_acquired, input_window, timeout_window, completed, terminal_completion] stage: type: string nullable: true enum: [department_select, gate_type_select, completed] call_id: type: string example: "4015cf84-8028-46a1-a0d9-9213e5bf4f09" completed: type: boolean elapsed_seconds: type: integer nullable: true timeout_seconds: type: integer nullable: true input_received: type: boolean nullable: true input_changed: type: boolean nullable: true last_input: type: string nullable: true selected_department_id: type: integer nullable: true selected_gate_type: type: string nullable: true enum: [entrance, exit] gate_id: type: integer nullable: true gate_opened: type: boolean nullable: true answered_at: type: integer nullable: true timeout_announced_at: type: integer nullable: true hangup_sent_at: type: integer nullable: true poll_attempts: type: integer nullable: true poll_error: type: string nullable: true terminal_status: type: string nullable: true reason: type: string nullable: true meta: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] includes: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] BirdVoiceCallCreateRequest: type: object additionalProperties: false required: [to] properties: from: { type: string } to: { type: string } ringTimeout: { type: integer, minimum: 3, maximum: 120 } maxDuration: { type: integer, minimum: 1 } sendKeys: { type: string } record: { type: boolean } recordStart: { type: string, enum: [record-from-answer, record-from-ringing] } flowStart: { type: string, enum: [from-answer, from-ringing] } stereo: { type: boolean } callFlow: type: array items: type: object additionalProperties: true scheduledFor: { type: string, format: date-time } notification: type: object additionalProperties: false properties: url: { type: string } amdSettings: type: object additionalProperties: true tags: type: array items: { type: string } BirdVoiceCallUpdateRequest: type: object additionalProperties: false properties: status: type: string enum: [completed] callFlow: type: array items: type: object additionalProperties: true BirdVoiceCallAnswerRequest: type: object additionalProperties: false properties: {} BirdVoiceCallRingingRequest: type: object additionalProperties: false properties: {} BirdVoiceCallHangupRequest: type: object additionalProperties: false properties: cause: type: string enum: [rejected, busy] BirdVoiceCallPlaybackRequest: type: object additionalProperties: false required: [media] properties: media: type: array minItems: 1 items: { type: string } loop: { type: integer, minimum: 0 } timeout: { type: integer, minimum: 0 } pauseMilliseconds: { type: integer, minimum: 0, maximum: 30000 } BirdVoiceCallSayRequest: type: object additionalProperties: false required: [text] properties: text: { type: string } locale: { type: string } voice: { type: string } loop: { type: integer, minimum: 0 } timeout: { type: integer, minimum: 0 } hangup: { type: boolean } BirdVoiceCallGatherRequest: type: object additionalProperties: false properties: maxNumKeys: { type: integer, minimum: 1 } endKey: { type: string, enum: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#'] } timeout: { type: integer, minimum: 0 } retries: { type: integer, minimum: 0 } input: { type: string, enum: [dtmf, speech, 'dtmf speech'] } speechLocale: { type: string } playback: $ref: '#/components/schemas/BirdVoiceCallPlaybackRequest' say: $ref: '#/components/schemas/BirdVoiceCallSayRequest' BirdVoiceCallBridgeRequest: type: object additionalProperties: false required: [to] properties: from: { type: string } to: { type: string } ringTimeout: { type: integer, minimum: 3, maximum: 120 } maxDuration: { type: integer, minimum: 1 } ringTone: { type: string } hangupAfterBridge: { type: boolean } record: { type: boolean } recordStart: { type: string, enum: [record-from-answer, record-from-ringing] } recordStereo: { type: boolean } callFlow: type: array items: type: object additionalProperties: true notification: type: object additionalProperties: false properties: url: { type: string } amdSettings: type: object additionalProperties: true BirdVoiceCallRecordRequest: type: object additionalProperties: false properties: endKey: { type: string, enum: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#'] } maxLength: { type: integer, minimum: 1 } timeout: { type: integer, minimum: 0 } beep: { type: boolean } transcribe: { type: boolean } transcribeLocale: { type: string } BirdVoiceCallRecordingCreateRequest: type: object additionalProperties: false properties: maxLength: { type: integer, minimum: 1 } stereo: { type: boolean } BirdVoiceCallRecordingUpdateRequest: type: object additionalProperties: false required: [status] properties: status: type: string enum: [paused, ongoing, completed] BirdFlashCallCreateRequest: type: object additionalProperties: false required: [to] properties: from: { type: string } to: { type: string } ringTimeout: { type: integer, minimum: 3, maximum: 120 } BirdFlashCallEndRequest: type: object additionalProperties: false required: [result] properties: receivedCli: { type: string } result: type: string enum: [unknown, verified, canceled, timeout, wrong_cli] BirdFlashCallHangupRequest: oneOf: - type: object additionalProperties: false required: [result] properties: receivedCli: { type: string } result: type: string enum: [unknown, verified, canceled, timeout, wrong_cli] - type: object additionalProperties: false required: [from, to] properties: from: { type: string } to: { type: string } receivedCli: { type: string } result: type: string enum: [unknown, verified, canceled, timeout, wrong_cli] BirdFlashCallListResponse: type: object properties: success: { type: boolean, example: true } data: type: object properties: nextPageToken: type: string nullable: true results: type: array items: $ref: '#/components/schemas/BirdFlashCall' meta: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] includes: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] BirdFlashCallSingleResponse: type: object properties: success: { type: boolean, example: true } data: $ref: '#/components/schemas/BirdFlashCall' meta: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] includes: oneOf: - type: array items: {} - type: object additionalProperties: true example: [] BirdFlashCallHangupResult: type: object properties: id: type: string format: uuid nullable: true result: type: string nullable: true receivedCli: type: string nullable: true from: type: string nullable: true to: type: string nullable: true BirdFlashCallHangupResponse: type: object properties: success: { type: boolean, example: true } data: $ref: '#/components/schemas/BirdFlashCallHangupResult' 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: [] DepartmentDailyReportOutsideHoursBreakdown: type: object properties: orders: { type: integer } xlvask: { type: integer } selfserve: { type: integer } DepartmentDailyReportOutsideHoursSummary: type: object properties: department_ids: type: array items: { type: integer } date: { type: string } date_to: { type: string } total: { type: integer } by_source: $ref: '#/components/schemas/DepartmentDailyReportOutsideHoursBreakdown' has_missing_opening_hours: { type: boolean } missing_department_ids: type: array items: { type: integer } DepartmentDailyReportMetric: type: object properties: state: { type: string } value: type: number nullable: true out_of: type: number nullable: true message: type: string nullable: true by_source: $ref: '#/components/schemas/DepartmentDailyReportOutsideHoursBreakdown' has_missing_opening_hours: { type: boolean } missing_department_ids: type: array items: { type: integer } DepartmentDailyReportComplaintCreateRequest: type: object required: [department_id, wash_date, category, description] properties: department_id: { type: integer } customer_number: type: integer nullable: true wash_date: type: string format: date category: $ref: '#/components/schemas/DepartmentDailyReportComplaintCategory' description: type: string minLength: 1 maxLength: 4000 DepartmentDailyReportComplaintUpdateRequest: type: object required: [id] properties: id: { type: integer } department_id: { type: integer } customer_number: type: integer nullable: true wash_date: type: string format: date category: $ref: '#/components/schemas/DepartmentDailyReportComplaintCategory' description: type: string minLength: 1 maxLength: 4000 DepartmentDailyReportComplaintCategory: type: string enum: - wash_quality - wash_price - damage_paint - damage_mirrors - damage_cables_electronics - damage_plastic_parts - damage_other - service - other DepartmentDailyReportComplaintCustomerSearchResult: type: object properties: customer_number: type: integer customer_name: type: string nullable: true DepartmentDailyReportComplaint: type: object properties: id: { type: integer } department_id: { type: integer } department_name: type: string nullable: true customer_number: type: integer nullable: true customer_name: type: string nullable: true wash_date: type: string format: date nullable: true category: allOf: - $ref: '#/components/schemas/DepartmentDailyReportComplaintCategory' nullable: true description: { type: string } created_by: { type: integer } created_by_name: type: string nullable: true created_at: type: string format: date-time DepartmentDailyReportComplaintResponse: type: object properties: success: { type: boolean, example: true } data: $ref: '#/components/schemas/DepartmentDailyReportComplaint' DepartmentDailyReportComplaintCollectionResponse: type: object properties: success: { type: boolean, example: true } data: oneOf: - $ref: '#/components/schemas/DepartmentDailyReportComplaint' - type: array items: $ref: '#/components/schemas/DepartmentDailyReportComplaint' DepartmentDailyReportComplaintCustomerSearchResponse: type: object properties: success: { type: boolean, example: true } data: type: array items: $ref: '#/components/schemas/DepartmentDailyReportComplaintCustomerSearchResult' DepartmentDailyReportComplaintDeleteResponse: type: object properties: success: { type: boolean, example: true } data: type: object properties: message: { type: string } DepartmentDailyReportProductTile: type: object properties: product_id: { type: integer } slug: { type: string } title: { type: string } state: { type: string } value: { type: integer } out_of: { type: integer } DepartmentDailyReportOverviewPayload: type: object properties: department_ids: type: array items: { type: integer } date: { type: string } date_to: { type: string } metrics: type: object additionalProperties: $ref: '#/components/schemas/DepartmentDailyReportMetric' products: type: array items: $ref: '#/components/schemas/DepartmentDailyReportProductTile' DepartmentDailyReportOverviewResponse: type: object properties: success: { type: boolean, example: true } data: $ref: '#/components/schemas/DepartmentDailyReportOverviewPayload' DepartmentDailyReportTransactionCountPayload: type: object properties: quantity: { type: integer } products: { type: integer } earnings: { type: integer } washes: { type: integer } water_usage: { type: integer } date: { type: string } date_to: { type: string } department_id: { type: integer } outside_hours: $ref: '#/components/schemas/DepartmentDailyReportOutsideHoursSummary' DepartmentDailyReportTransactionCountResponse: type: object properties: success: { type: boolean, example: true } data: $ref: '#/components/schemas/DepartmentDailyReportTransactionCountPayload' DepartmentDailyReportOutsideHoursTrendPoint: type: object properties: date: { type: string } total: { type: integer } by_source: $ref: '#/components/schemas/DepartmentDailyReportOutsideHoursBreakdown' has_missing_opening_hours: { type: boolean } missing_department_ids: type: array items: { type: integer } DepartmentDailyReportOutsideHoursTrendPayload: type: object properties: department_ids: type: array items: { type: integer } date: { type: string } date_to: { type: string } points: type: array items: $ref: '#/components/schemas/DepartmentDailyReportOutsideHoursTrendPoint' has_missing_opening_hours: { type: boolean } missing_department_ids: type: array items: { type: integer } DepartmentDailyReportOutsideHoursTrendResponse: type: object properties: success: { type: boolean, example: true } data: $ref: '#/components/schemas/DepartmentDailyReportOutsideHoursTrendPayload'