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 owns department and gate selection for phone-controlled gates. The preferred Bird Flow Builder integration is the native-flow mode: send top-level `callId`, `channelId`, and `workspaceId` to fetch IVR prompt data, then submit the selected DTMF digits as `keys` in a follow-up request. In this mode the webhook returns a plain `200 OK` JSON body with `prompt`, `stage`, and `gather` settings that Bird native voice steps can consume directly. For backward compatibility, the webhook also supports the older `{ payload, request, waitConditions }` contract and returns a raw `202 Accepted` Bird `callCommand` gather envelope that resumes on `call_command_gather_finished`. The Bird Flow itself must answer the inbound call before invoking this HTTP step; the backend answer attempt is only a best-effort fallback. Department options are generated from `department_gates` records with `config.type=PHONE_CALL`, ordered by `departments.order_priority`, and support multi-digit DTMF selections such as `10#`. After a department is chosen, the webhook returns compact gate options, for example `Press 1 for exit` when exit is the only available phone-controlled gate for that department. When a valid gate is confirmed, the webhook opens the gate through the corresponding `department_gates` phone-call record and returns a `200 OK` completion result. 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: Native-flow gather data or gate action result for this webhook invocation content: application/json: schema: oneOf: - $ref: '#/components/schemas/BirdInboundCallWebhookFlowGatherResponse' - $ref: '#/components/schemas/BirdInboundCallWebhookActionResultResponse' '202': description: Gather command accepted and returned to Bird content: application/json: schema: $ref: '#/components/schemas/BirdInboundCallWebhookGatherAcceptedResponse' '400': description: Malformed Bird webhook payload content: application/json: schema: $ref: '#/components/schemas/BirdInboundCallWebhookTransportErrorResponse' # 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 summary: List orders description: Retrieve a paginated list of orders operationId: listOrders parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' - name: show_wash_subscription in: query schema: type: string enum: [true, false] responses: '200': description: Orders retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Order' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' post: tags: - Orders summary: Create new order description: Create a new wash order operationId: createOrder requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OrderCreate' responses: '201': description: Order created successfully content: application/json: schema: $ref: '#/components/schemas/Order' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' delete: tags: - Orders summary: Delete order description: Delete an existing order operationId: deleteOrder parameters: - name: id in: query required: true schema: type: integer responses: '200': description: Order deleted successfully content: application/json: schema: {} '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' put: tags: - Orders summary: Update order (alias) description: Update an existing order operationId: updateOrders requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OrderUpdate' responses: '200': description: Order updated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' /order: get: tags: - Orders summary: Get order details description: Get detailed information about a specific order operationId: getOrder parameters: - name: id in: query required: true schema: type: integer responses: '200': description: Order retrieved successfully content: application/json: schema: $ref: '#/components/schemas/Order' '404': $ref: '#/components/responses/NotFound' put: tags: - Orders summary: Update order description: Update an existing order operationId: updateOrder requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OrderUpdate' responses: '200': description: Order updated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /user/orders: get: tags: - Orders summary: Get current user's orders description: Retrieve orders for the authenticated user operationId: getUserOrders parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: Orders retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Order' /user/order: get: tags: - Orders summary: Get user's specific order description: Get details of a specific order for the authenticated user operationId: getUserOrder parameters: - name: id in: query required: true schema: type: integer responses: '200': description: Order retrieved successfully content: application/json: schema: $ref: '#/components/schemas/Order' /orders/mark_as_completed: post: tags: - Orders summary: Mark order as completed description: Mark an order as completed operationId: markOrderCompleted requestBody: required: true content: application/json: schema: type: object required: - id properties: id: type: integer responses: '200': description: Order marked as completed successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' /order/wash-certificate: post: tags: - Orders summary: Generate wash certificate description: Generate a wash certificate for an order operationId: generateWashCertificate requestBody: required: true content: application/json: schema: type: object properties: order_id: type: integer responses: '200': description: Wash certificate generated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' # Order Items Endpoints /order/items: get: tags: - Order Items summary: List order items description: Get all items for a specific order operationId: listOrderItems parameters: - name: order_id in: query required: true schema: type: integer responses: '200': description: Order items retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/OrderItem' post: tags: - Order Items summary: Add item to order description: Add a new item to an existing order operationId: addOrderItem requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OrderItemCreate' responses: '201': description: Order item added successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' put: tags: - Order Items summary: Update order item description: Update an existing order item operationId: updateOrderItem requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OrderItemUpdate' responses: '200': description: Order item updated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' delete: tags: - Order Items summary: Delete order item description: Remove an item from an order operationId: deleteOrderItem parameters: - name: id in: query required: true schema: type: integer responses: '200': description: Order item deleted successfully content: application/json: schema: {} '404': $ref: '#/components/responses/NotFound' # Departments Endpoints /departments: get: tags: - Departments summary: List departments description: Retrieve a list of all visible departments operationId: listDepartments parameters: - name: id in: query schema: type: integer description: Filter by specific department ID - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' responses: '200': description: Departments retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Department' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Departments summary: Create department description: Create a new department operationId: createDepartment requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentCreate' responses: '201': description: Department created successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' put: tags: - Departments summary: Update department description: Update an existing department operationId: updateDepartment requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentUpdate' responses: '200': description: Department updated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' /departments/categories: get: tags: - Departments summary: Get department categories description: Get product categories available in a department operationId: getDepartmentCategories parameters: - name: department_id in: query required: true schema: type: integer responses: '200': description: Department categories retrieved successfully content: application/json: schema: {} post: tags: - Departments summary: Add category to department description: Associate a product category with a department operationId: addDepartmentCategory requestBody: required: true content: application/json: schema: type: object properties: department_id: type: integer category_id: type: integer responses: '201': description: Category added to department successfully content: application/json: schema: {} delete: tags: - Departments summary: Remove category from department operationId: removeDepartmentCategory requestBody: required: false content: application/json: schema: {} responses: '200': description: Success content: application/json: schema: {} /departments/self-serve/enabled: get: tags: - Departments summary: Get department self-serve status description: Check if self-serve is enabled for a specific department operationId: getDepartmentSelfServeEnabled parameters: - name: id in: query required: true description: Department ID schema: type: integer responses: '200': description: Successfully retrieved status content: application/json: schema: type: object properties: enabled: type: boolean '404': $ref: '#/components/responses/NotFound' put: tags: - Departments summary: Update department self-serve status description: Enable or disable self-serve for a specific department operationId: updateDepartmentSelfServeEnabled parameters: - name: id in: query required: true description: Department ID schema: type: integer - name: enabled in: query required: true description: Enabled status (true/false) schema: type: string enum: ['true', 'false'] responses: '200': description: Status updated successfully content: application/json: schema: type: object properties: message: type: string '404': $ref: '#/components/responses/NotFound' /departments/order/recommended: get: tags: - Departments summary: Get recommended order for department operationId: getDepartmentRecommendedOrder parameters: - name: department_id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} /department/lanes: get: tags: - Departments summary: List department lanes description: Retrieve a list of all department lanes operationId: listDepartmentLanes parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' responses: '200': description: Department lanes retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/DepartmentLane' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Departments summary: Create department lane description: Create a new department lane operationId: createDepartmentLane requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentLaneCreate' responses: '201': description: Department lane created successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' put: tags: - Departments summary: Update department lane description: Update an existing department lane operationId: updateDepartmentLane requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentLaneUpdate' responses: '200': description: Department lane updated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /department/gates: get: tags: - Departments summary: List department gates description: Retrieve department gates, optionally filtered by id operationId: listDepartmentGates parameters: - name: id in: query required: false schema: type: integer minimum: 1 - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' responses: '200': description: Department gates retrieved successfully content: application/json: schema: oneOf: - $ref: '#/components/schemas/DepartmentGate' - type: array items: $ref: '#/components/schemas/DepartmentGate' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Departments summary: Create department gate operationId: createDepartmentGate requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentGateCreate' responses: '201': description: Department gate created successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentGate' put: tags: - Departments summary: Update department gate operationId: updateDepartmentGate requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentGateUpdate' responses: '200': description: Department gate updated successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentGate' '404': $ref: '#/components/responses/NotFound' delete: tags: - Departments summary: Delete department gate operationId: deleteDepartmentGate parameters: - name: id in: query required: true schema: type: integer minimum: 1 responses: '200': description: Department gate deleted content: application/json: schema: {} /department/relays: get: tags: - Departments summary: List department relays description: Retrieve department relays, optionally filtered by id operationId: listDepartmentRelays parameters: - name: id in: query required: false schema: type: integer minimum: 1 - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' responses: '200': description: Department relays retrieved successfully content: application/json: schema: oneOf: - $ref: '#/components/schemas/DepartmentRelay' - type: array items: $ref: '#/components/schemas/DepartmentRelay' '401': $ref: '#/components/responses/Unauthorized' post: tags: - Departments summary: Create department relay operationId: createDepartmentRelay requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentRelayCreate' responses: '201': description: Department relay created successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentRelay' put: tags: - Departments summary: Update department relay operationId: updateDepartmentRelay requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentRelayUpdate' responses: '200': description: Department relay updated successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentRelay' '404': $ref: '#/components/responses/NotFound' delete: tags: - Departments summary: Delete department relay operationId: deleteDepartmentRelay parameters: - name: id in: query required: true schema: type: integer minimum: 1 responses: '200': description: Department relay deleted content: application/json: schema: {} /department/lanes/dynamic-image: get: tags: - Departments summary: Generate dynamic image for a department lane description: | Returns a composed machine UI image for the specified department lane. You can optionally highlight button indices, set the current step indicator, and toggle only-current-step mode. operationId: getDepartmentLaneDynamicImage parameters: - name: department in: query required: true description: Department ID schema: type: integer minimum: 1 - name: lane in: query required: true description: Lane ID schema: type: integer minimum: 1 - name: buttons in: query required: false description: Highlighted button IDs (0-indexed). Accepts CSV, JSON array, or repeated query params. schema: oneOf: - type: string - type: array items: type: integer - name: current_step in: query required: false description: Current step indicator (non-negative integer) schema: type: integer minimum: 0 - name: only_current_step in: query required: false description: If true, only draw the current step highlight schema: type: boolean - name: vehicle_type in: query required: false description: Vehicle type selection override (nullable non-negative integer) schema: type: integer minimum: 0 responses: '200': description: Dynamic image rendered successfully content: image/png: schema: type: string format: binary '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /guest/validation/customer-number: post: tags: - Users summary: Validate customer number description: Check if a customer number is valid and exists operationId: validateCustomerNumber security: [] requestBody: required: true content: application/json: schema: type: object required: - customer_number properties: customer_number: type: integer responses: '200': description: Customer number validation successful content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' /guest/departments: get: tags: - Departments summary: List public departments description: Get list of departments without authentication operationId: listGuestDepartments security: [] parameters: - name: include_lanes in: query description: Whether to include lane status and self-serve information schema: type: boolean responses: '200': description: Departments retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/DepartmentGuest' /department/selfserve/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. operationId: addSelfserveVehicleCondition requestBody: required: true content: application/json: schema: type: object required: - department - lane - reg - question properties: department: type: integer lane: type: integer reg: type: string question: type: integer value: type: boolean customer_id: type: integer nullable: true responses: '200': description: Successfully added vehicle condition content: application/json: schema: $ref: '#/components/schemas/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. operationId: updateSelfserveVehicleCondition parameters: - name: id in: query required: true description: Condition ID schema: type: integer requestBody: content: application/json: schema: type: object properties: department: type: integer lane: type: integer reg: type: string question: type: integer value: type: boolean customer_id: type: integer nullable: true responses: '200': description: Successfully updated vehicle condition content: application/json: schema: $ref: '#/components/schemas/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 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' # 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/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/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/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. 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 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. Departments with `shelly_transport_mode=gateway` use the edge gateway/local edge agent path; otherwise Shelly cloud remains the default. `transport=local` or `transport=gateway` is a diagnostic local-only override, while `transport=cloud` forces Shelly cloud. 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] transport: type: string enum: [local, gateway, cloud] description: Optional diagnostic Shelly transport override 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 transport: type: string nullable: true '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. Transport follows the department `shelly_transport_mode`: `cloud` is the default, while `gateway` uses the edge gateway/local edge agent with the binding fallback policy. `transport=local` or `transport=gateway` forces local-only diagnostics; `transport=cloud` forces Shelly cloud. operationId: getSelfServeLaneMachineRelayStatus parameters: - name: lane_id in: query required: true schema: type: integer - name: transport in: query required: false schema: type: string enum: [local, gateway, cloud] description: Optional diagnostic Shelly transport override 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. Transport follows the department `shelly_transport_mode`: `cloud` is the default, while `gateway` uses the edge gateway/local edge agent with the binding fallback policy. `transport=local` or `transport=gateway` forces local-only diagnostics; `transport=cloud` forces Shelly cloud. operationId: getSelfServeLaneMachineProgramPickerRelayStatus parameters: - name: lane_id in: query required: true schema: type: integer - name: transport in: query required: false schema: type: string enum: [local, gateway, cloud] description: Optional diagnostic Shelly transport override 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. Transport follows the department `shelly_transport_mode`. `gateway` uses local edge agent dispatch with binding fallback; `transport=local` or `transport=gateway` forces local-only diagnostics, and `transport=cloud` forces Shelly cloud. operationId: setSelfServeLaneMachineProgramPickerRelayStatus requestBody: required: true content: application/json: schema: type: object required: - lane_id - on properties: lane_id: type: integer on: type: boolean transport: type: string enum: [local, gateway, cloud] description: Optional diagnostic Shelly transport override 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 status: type: object description: Normalized Shelly switch status; includes `switch:0.output` additionalProperties: true transport: type: string nullable: true binding: type: object description: Optional edge gateway relay binding diagnostics additionalProperties: true execution: type: object description: Optional edge gateway relay execution diagnostics additionalProperties: true raw: type: object description: Optional raw gateway/cloud transport payload additionalProperties: true '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. Transport follows the department `shelly_transport_mode`: `cloud` is the default, while `gateway` uses the edge gateway/local edge agent with the binding fallback policy. `transport=local` or `transport=gateway` forces local-only diagnostics; `transport=cloud` forces Shelly cloud. operationId: getSelfServeLaneMachineCleanerRelayStatus parameters: - name: lane_id in: query required: true schema: type: integer - name: transport in: query required: false schema: type: string enum: [local, gateway, cloud] description: Optional diagnostic Shelly transport override 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. Transport follows the department `shelly_transport_mode`. `gateway` uses local edge agent dispatch with binding fallback; `transport=local` or `transport=gateway` forces local-only diagnostics, and `transport=cloud` forces Shelly cloud. operationId: setSelfServeLaneMachineCleanerRelayStatus requestBody: required: true content: application/json: schema: type: object required: - lane_id - on properties: lane_id: type: integer on: type: boolean transport: type: string enum: [local, gateway, cloud] description: Optional diagnostic Shelly transport override 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 status: type: object description: Normalized Shelly switch status; includes `switch:0.output` additionalProperties: true transport: type: string nullable: true binding: type: object description: Optional edge gateway relay binding diagnostics additionalProperties: true execution: type: object description: Optional edge gateway relay execution diagnostics additionalProperties: true raw: type: object description: Optional raw gateway/cloud transport payload additionalProperties: true '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. Transport follows the department `shelly_transport_mode`. `gateway` uses local edge agent dispatch with binding fallback; `transport=local` or `transport=gateway` forces local-only diagnostics, and `transport=cloud` forces Shelly cloud. operationId: setSelfServeLaneMachineRelayStatus requestBody: required: true content: application/json: schema: type: object required: - lane_id - on properties: lane_id: type: integer on: type: boolean transport: type: string enum: [local, gateway, cloud] description: Optional diagnostic Shelly transport override 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 status: type: object description: Normalized Shelly switch status; includes `switch:0.output` additionalProperties: true transport: type: string nullable: true binding: type: object description: Optional edge gateway relay binding diagnostics additionalProperties: true execution: type: object description: Optional edge gateway relay execution diagnostics additionalProperties: true raw: type: object description: Optional raw gateway/cloud transport payload additionalProperties: true '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. Transport follows the department `shelly_transport_mode`; `transport=local` or `transport=gateway` forces local-only diagnostics, and `transport=cloud` forces Shelly cloud. 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 transport: type: string enum: [local, gateway, cloud] description: Optional diagnostic Shelly transport override 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 transport: type: string 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. Transport follows the department `shelly_transport_mode`; `transport=local` or `transport=gateway` forces local-only diagnostics, and `transport=cloud` forces Shelly cloud. 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 transport: type: string enum: [local, gateway, cloud] description: Optional diagnostic Shelly transport override 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 transport: type: string nullable: true '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). Transport follows the department `shelly_transport_mode`; `transport=local` or `transport=gateway` forces local-only diagnostics, and `transport=cloud` forces Shelly cloud. 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 transport: type: string enum: [local, gateway, cloud] description: Optional diagnostic Shelly transport override 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 transport: type: string nullable: true '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 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 status: type: object description: Normalized Shelly switch status; includes `switch:0.output` additionalProperties: true transport: type: string nullable: true description: Diagnostic override used for the request, or null when department defaults applied binding: type: object description: Optional edge gateway relay binding diagnostics additionalProperties: true execution: type: object description: Optional edge gateway relay execution diagnostics additionalProperties: true raw: type: object description: Optional raw gateway/cloud transport payload additionalProperties: true SelfServeConfig: type: object properties: enabled: type: boolean description: Whether the self-serve module is enabled minute_product: type: integer description: The product ID used for minute-based billing 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 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 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 session: allOf: - $ref: '#/components/schemas/SelfserveWashSession' nullable: 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 dimension: type: integer branding: type: integer longitude: type: number format: float latitude: type: number format: float created_at: type: string format: date-time updated_at: type: string format: date-time DepartmentGuest: type: object properties: id: type: integer name: type: string longitude: type: number format: float latitude: type: number format: float address: type: string description: Department address (same as description) self_serve_enabled: type: boolean lanes: type: array items: $ref: '#/components/schemas/DepartmentLaneGuest' DepartmentLaneGuest: type: object properties: id: type: integer name: type: string status: type: string products: type: array items: type: integer machine_available: type: boolean DepartmentCreate: type: object required: - name - economic_department_id properties: name: type: string description: type: string economic_department_id: type: integer visible: type: boolean longitude: type: number format: float latitude: type: number format: float DepartmentUpdate: type: object required: - id properties: id: type: integer name: type: string description: type: string visible: type: boolean longitude: type: number format: float latitude: type: number format: float DepartmentLane: type: object properties: id: type: integer department: type: integer name: type: string relay_in_id: type: string relay_out_id: type: string relay_machine_id: type: string 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 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 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 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 payload: type: object additionalProperties: true properties: endKey: type: string example: "#" retries: type: integer example: 3 timeout: type: integer example: 30 say: type: object additionalProperties: true properties: locale: type: string example: "en-US" voice: type: string example: "female" request: type: object additionalProperties: true properties: callId: type: string example: "4015cf84-8028-46a1-a0d9-9213e5bf4f09" workspaceId: type: string format: uuid channelId: type: string format: uuid waitConditions: type: object additionalProperties: true properties: timeout: type: string example: "PT10M" events: type: array items: type: object additionalProperties: true properties: action: type: string example: "continue" name: type: string example: "call_command_gather_finished" event: type: object additionalProperties: true result: type: object additionalProperties: true resumeData: type: object additionalProperties: true dtmf: type: string description: DTMF value when present, for example `1`, `1#`, or `10#` example: "10#" digit: type: string description: Alternate DTMF field, also accepts values such as `10#` example: "10#" digits: type: string description: Alternate DTMF field example: "10#" keys: type: string description: Alternate DTMF field returned by gather results example: "10#" key: type: string example: "1" input: oneOf: - type: string - type: object additionalProperties: true conditions: type: array items: type: object additionalProperties: true BirdInboundCallWebhookResponse: type: object oneOf: - $ref: '#/components/schemas/BirdInboundCallWebhookFlowGatherResponse' - $ref: '#/components/schemas/BirdInboundCallWebhookActionResultResponse' - $ref: '#/components/schemas/BirdInboundCallWebhookGatherAcceptedResponse' - $ref: '#/components/schemas/BirdInboundCallWebhookTransportErrorResponse' BirdInboundCallWebhookFlowGatherResponse: type: object properties: requestId: type: string example: "request-123" callId: type: string example: "4015cf84-8028-46a1-a0d9-9213e5bf4f09" status: type: string enum: [gather] completed: type: boolean enum: [false] stage: type: string enum: [department_select, gate_type_select] prompt: type: string gather: type: object properties: input: type: string enum: [dtmf] maxNumKeys: type: integer endKey: type: string example: "#" timeout: type: integer retries: type: integer say: type: object properties: locale: type: string example: "en-US" voice: type: string example: "female" text: type: string selection: type: object properties: departmentId: type: integer nullable: true departmentName: type: string nullable: true gateType: type: string nullable: true enum: [entrance, exit] gateId: type: integer nullable: true invalidSelectionCount: type: integer resumed: type: boolean statusCode: type: integer enum: [200] statusText: type: string enum: [OK] BirdInboundCallWebhookActionResultResponse: type: object properties: requestId: type: string example: "request-123" result: type: object properties: callId: type: string example: "4015cf84-8028-46a1-a0d9-9213e5bf4f09" status: type: string enum: [completed, failed, ignored] action: type: string enum: [gate_opened, gate_open_failed, no_action, ignored] message: type: string departmentId: type: integer nullable: true gateType: type: string nullable: true enum: [entrance, exit] gateId: type: integer nullable: true gateOpened: type: boolean resumeData: type: object additionalProperties: true properties: action: type: string example: "continue" completed: type: boolean example: true result: type: string example: "gate_opened" gateOpened: type: boolean example: true completedAt: type: string format: date-time statusCode: type: integer enum: [200] statusText: type: string enum: [OK] BirdInboundCallWebhookGatherAcceptedResponse: type: object properties: event: type: object additionalProperties: true requestId: type: string example: "request-123" result: type: object properties: callId: type: string example: "4015cf84-8028-46a1-a0d9-9213e5bf4f09" command: type: string enum: [gather] id: type: string status: type: string enum: [accepted] resumeData: type: object additionalProperties: true properties: action: type: string example: "continue" statusCode: type: integer enum: [202] statusText: type: string enum: [Accepted] suspendedAt: type: string format: date-time resumedAt: type: string format: date-time nullable: true BirdInboundCallWebhookTransportErrorResponse: type: object properties: requestId: type: string statusCode: type: integer enum: [400, 500] statusText: type: string enum: [Bad Request, Internal Server Error] error: type: object properties: message: type: string 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'