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: Limited Backoffice description: Limited backoffice employee and department management - 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: Error Reports description: Authenticated application error reporting - name: Plate Scans description: License plate scanning operations - name: Config description: Module configuration management - name: Release Manager description: Release channel, deployment, and operation 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: /error-reports: post: tags: - Error Reports summary: Submit an authenticated user error report operationId: submitErrorReport security: - BearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ErrorReportSubmissionRequest' responses: '201': description: Error report submitted content: application/json: schema: $ref: '#/components/schemas/ErrorReportResponse' '400': description: Validation error content: application/json: schema: $ref: '#/components/schemas/Error' '401': description: Authentication required content: application/json: schema: $ref: '#/components/schemas/Error' /superuser/error-reports: get: tags: - Error Reports summary: List error reports for superusers operationId: listSuperuserErrorReports security: - BearerAuth: [] parameters: - name: status in: query required: false schema: type: string enum: [open, resolved, all] default: open - name: q in: query required: false schema: type: string - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 200 default: 50 - name: offset in: query required: false schema: type: integer minimum: 0 default: 0 responses: '200': description: Error reports retrieved content: application/json: schema: $ref: '#/components/schemas/ErrorReportListResponse' '403': description: Missing superuser error report permission content: application/json: schema: $ref: '#/components/schemas/Error' /superuser/error-reports/{id}: get: tags: - Error Reports summary: Get an error report detail operationId: getSuperuserErrorReport security: - BearerAuth: [] parameters: - name: id in: path required: true schema: type: integer minimum: 1 responses: '200': description: Error report retrieved content: application/json: schema: $ref: '#/components/schemas/ErrorReportResponse' '404': description: Error report not found content: application/json: schema: $ref: '#/components/schemas/Error' /superuser/error-reports/{id}/status: patch: tags: - Error Reports summary: Mark an error report open or resolved operationId: updateSuperuserErrorReportStatus security: - BearerAuth: [] parameters: - name: id in: path required: true schema: type: integer minimum: 1 requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ErrorReportStatusUpdateRequest' responses: '200': description: Error report status updated content: application/json: schema: $ref: '#/components/schemas/ErrorReportResponse' '400': description: Validation error content: application/json: schema: $ref: '#/components/schemas/Error' '403': description: Missing superuser error report resolve permission content: application/json: schema: $ref: '#/components/schemas/Error' # 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) /superuser/users/{user_id}/subusers: get: tags: - Subusers summary: List subusers for a superuser customer account description: Returns paginated chauffeur accounts for the customer number resolved from the selected user. Each chauffeur appears once; visible customer access grants are returned in `grants`. operationId: listSuperuserUserSubusers security: - BearerAuth: [] parameters: - name: user_id in: path required: true schema: type: integer minimum: 1 - 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 schema: { type: boolean } responses: '200': description: User-scoped subuser list content: application/json: schema: type: array items: $ref: '#/components/schemas/SubuserManagementRow' '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' } /superuser/users/{user_id}/subusers/summary: get: tags: - Subusers summary: Summarize subusers for a superuser customer account operationId: summarizeSuperuserUserSubusers security: - BearerAuth: [] parameters: - name: user_id in: path required: true schema: type: integer minimum: 1 responses: '200': description: User-scoped subuser summary content: application/json: schema: $ref: '#/components/schemas/SubuserManagementSummary' '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' } /superuser/users/{user_id}/subusers/invite: post: tags: - Subusers summary: Invite a subuser for a superuser customer account operationId: inviteSuperuserUserSubuser security: - BearerAuth: [] parameters: - name: user_id in: path required: true schema: type: integer minimum: 1 requestBody: required: true content: application/json: schema: type: object required: [name, phone_country_code, phone] properties: name: { type: string, minLength: 3, maxLength: 255 } phone_country_code: { type: integer } phone: { type: integer } note: { type: string, nullable: true, maxLength: 65535 } enabled: { type: boolean, default: true } permission_template_key: type: string enum: [deactivated, driver, booking_coordinator, fleet_admin] permissions: type: array items: { type: string } responses: '200': description: Driver invited or linked '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' } /superuser/users/{user_id}/subusers/{subuser_id}/invite/resend: post: tags: - Subusers summary: Resend a user-scoped subuser invite operationId: resendSuperuserUserSubuserInvite security: - BearerAuth: [] parameters: - name: user_id in: path required: true schema: { type: integer, minimum: 1 } - name: subuser_id in: path required: true schema: { type: integer, minimum: 1 } responses: '200': description: Invite resent '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '409': { $ref: '#/components/responses/Conflict' } '500': { $ref: '#/components/responses/InternalServerError' } /superuser/users/{user_id}/subusers/grants/{grant_id}: patch: tags: - Subusers summary: Update a user-scoped subuser grant operationId: updateSuperuserUserSubuserGrant security: - BearerAuth: [] parameters: - name: user_id in: path required: true schema: { type: integer, minimum: 1 } - name: grant_id in: path required: true schema: { type: integer, minimum: 1 } requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SubuserGrantUpdateRequest' responses: '200': description: Grant updated '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' } /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' } /subusers/permission-templates: get: tags: - Subusers summary: List simplified subuser permission templates description: Returns backend-owned driver access profiles and grouped capability metadata for subuser grants. operationId: listSubuserPermissionTemplates security: - BearerAuth: [] responses: '200': description: Permission templates fetched content: application/json: schema: type: object properties: templates: type: array items: $ref: '#/components/schemas/SubuserPermissionTemplate' groups: type: array items: $ref: '#/components/schemas/SubuserPermissionGroup' '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '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 default_distribution_department_id: type: integer 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: '200': description: Matching e-conomic customer already existed and local registration was completed content: application/json: schema: {} '201': description: Customer registered successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '409': description: Customer number conflict between the submitted phone number and the existing or created e-conomic customer content: application/json: schema: {} /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} /limited-backoffice/employees/{employeeId}/login-link: post: tags: - Limited Backoffice summary: Create a managed employee QR login link description: Create a reusable auth-token login link for an active employee managed through the limited backoffice. operationId: createLimitedBackofficeEmployeeLoginLink parameters: - name: employeeId in: path required: true schema: type: integer minimum: 1 responses: '200': description: Login link created successfully content: application/json: schema: type: object properties: employee_id: type: integer login_path: type: string example: /login/qr?token=abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' # 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} superuser_new_customer_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 - name: confirmed in: query required: false description: Must be true to delete an order that is completed or has active items or attachments. schema: type: boolean responses: '200': description: Order deleted successfully content: application/json: schema: {} '409': description: Order deletion requires explicit confirmation content: application/json: schema: type: object properties: success: type: boolean example: false data: type: object properties: message: type: string example: Order deletion requires confirmation requires_confirmation: type: boolean example: true protected_reasons: type: array items: type: string enum: [completed, order_items, attachments] order_item_count: type: integer example: 2 attachment_count: type: integer example: 1 completed_at: type: string nullable: true meta: type: object includes: type: object '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 step tokens in order. Accepts 0-indexed button IDs, "reset", "start", and "program_picker" as CSV, JSON array, or repeated query params. schema: oneOf: - type: string - type: array items: oneOf: - type: integer - type: string - 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 vehicle_type: type: integer nullable: true description: Optional product/vehicle type override used when refreshing the self-serve summary. vehicle_type_id: type: integer nullable: true description: Alias for vehicle_type. activate_machine: type: boolean default: true description: Whether the session synchronization may enable the machine relay. User wash-start saves answers with false. sync_relay_state: type: boolean default: true description: Whether the answer mutation should synchronize live relay state. 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 vehicle_type: type: integer nullable: true description: Optional product/vehicle type override used when refreshing the self-serve summary. vehicle_type_id: type: integer nullable: true description: Alias for vehicle_type. activate_machine: type: boolean default: true description: Whether the session synchronization may enable the machine relay. sync_relay_state: type: boolean default: true description: Whether the mutation should synchronize live relay state. responses: '200': description: Successfully updated vehicle condition content: application/json: schema: $ref: '#/components/schemas/DepartmentSelfserveVehicleConditionMutationResponse' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' delete: tags: - Self-Serve summary: Delete vehicle condition description: Delete a vehicle condition. Customers can only delete conditions for their own vehicles. operationId: deleteSelfserveVehicleCondition parameters: - name: id in: query required: true description: Condition ID schema: type: integer responses: '200': description: Successfully deleted vehicle condition content: application/json: schema: type: object properties: message: type: string example: Condition deleted selfserve: allOf: - $ref: '#/components/schemas/SelfserveWashSummary' nullable: true '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /department/selfserve/vehicle/allowed: get: tags: - Self-Serve summary: Check whether self-serve is allowed for a vehicle on a lane description: Customers with own self-serve permissions may evaluate any registration plate for their wash. Persisted self-serve answers are only applied when they are scoped to the authenticated customer. operationId: getSelfserveVehicleAllowed parameters: - name: lane_id in: query required: true schema: type: integer - name: reg in: query required: true schema: type: string - name: vehicle_type_id in: query required: false description: Optional vehicle type override used when no vehicle is found by registration plate. schema: type: integer minimum: 0 - name: vehicle_type in: query required: false description: Backward-compatible alias of `vehicle_type_id`. schema: type: integer minimum: 0 responses: '200': description: Successfully evaluated self-serve eligibility content: application/json: schema: $ref: '#/components/schemas/SelfserveVehicleAllowedResponse' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /department/selfserve/washes/summary: get: tags: - Self-Serve summary: Get self-serve wash summary operationId: getSelfserveWashSummary parameters: - name: session_id in: query required: false schema: type: integer - name: lane_id in: query required: false schema: type: integer - name: reg in: query required: false schema: type: string - name: vehicle_type_id in: query required: false description: Optional vehicle type override used to refresh summary data for unknown or reassigned plates. schema: type: integer minimum: 0 - name: vehicle_type in: query required: false description: Backward-compatible alias of `vehicle_type_id`. schema: type: integer minimum: 0 responses: '200': description: Successfully retrieved self-serve wash summary content: application/json: schema: $ref: '#/components/schemas/SelfserveWashSummary' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /department/selfserve/tasks: get: tags: - Self-Serve summary: List self-serve tasks description: Retrieve a list of self-serve tasks for a department, lane, product, or condition_id. operationId: listSelfserveTasks parameters: - name: id in: query description: Filter by task ID schema: type: integer - name: department in: query description: Filter by department ID schema: type: integer - name: lane in: query description: Filter by lane ID schema: type: integer - name: product in: query description: Filter by product ID schema: type: integer - name: condition_id in: query description: Filter by condition ID schema: type: integer - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' - $ref: '#/components/parameters/FiltersParam' responses: '200': description: Successfully retrieved tasks content: application/json: schema: type: array items: $ref: '#/components/schemas/DepartmentSelfserveTask' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' post: tags: - Self-Serve summary: Add self-serve task description: Add a new self-serve task. Either provide a reusable machine_type_id or a legacy department/lane/product scope. operationId: addSelfserveTask requestBody: required: true content: application/json: schema: type: object required: - task - description properties: department: type: integer default: 0 lane: type: integer default: 0 product: type: integer default: 0 machine_type_id: type: integer nullable: true condition_id: type: integer nullable: true task: type: string description: type: string order_priority: type: integer default: 0 services: type: array description: Optional services enabled by this task. Items must be valid service enum names. items: $ref: '#/components/schemas/SelfserveLaneService' buttons: type: array description: Optional dynamic image button IDs enabled by this task. items: type: integer default: [] dynamic_images_vehicle_type: type: integer nullable: true description: Optional vehicle type selection override for the machine UI. Integer >= 0 or null. responses: '200': description: Successfully added task content: application/json: schema: $ref: '#/components/schemas/DepartmentSelfserveTask' '400': $ref: '#/components/responses/BadRequest' '500': $ref: '#/components/responses/InternalServerError' put: tags: - Self-Serve summary: Update self-serve task description: Update an existing self-serve task. operationId: updateSelfserveTask parameters: - name: id in: query required: true description: Task ID schema: type: integer requestBody: content: application/json: schema: type: object properties: department: type: integer lane: type: integer product: type: integer machine_type_id: type: integer nullable: true condition_id: type: integer nullable: true task: type: string description: type: string order_priority: type: integer services: type: array nullable: true description: Services enabled by this task. Set to null to clear all services. items: $ref: '#/components/schemas/SelfserveLaneService' buttons: type: array nullable: true description: Button IDs enabled by this task. Set to null to clear all buttons. items: type: integer dynamic_images_vehicle_type: type: integer nullable: true description: Vehicle type selection override. Set to null to clear. responses: '200': description: Successfully updated task content: application/json: schema: $ref: '#/components/schemas/DepartmentSelfserveTask' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' delete: tags: - Self-Serve summary: Delete self-serve task description: Delete a self-serve task by ID. operationId: deleteSelfserveTask parameters: - name: id in: query required: true description: Task ID schema: type: integer responses: '200': description: Successfully deleted task content: application/json: schema: type: string example: Task deleted '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /department/selfserve/tasks/attachments: get: tags: - Self-Serve summary: List task attachments description: Retrieve a list of attachments for a specific self-serve task. operationId: listSelfserveTaskAttachments parameters: - name: id in: query required: true description: Task ID schema: type: integer responses: '200': description: Successfully retrieved task attachments content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' delete: tags: - Self-Serve summary: Delete task attachment description: Remove an attachment from a specific self-serve task. operationId: deleteSelfserveTaskAttachment parameters: - name: task_id in: query required: true description: Task ID schema: type: integer - name: attachment_id in: query required: true description: Attachment ID schema: type: integer responses: '200': description: Attachment deleted successfully content: application/json: schema: type: object properties: message: type: string example: Attachment deleted successfully '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /department/selfserve/tasks/attachments/upload: post: tags: - Self-Serve summary: Upload task attachment description: Upload a new attachment to a specific self-serve task using base64 encoding. operationId: uploadSelfserveTaskAttachment requestBody: required: true content: application/json: schema: type: object required: - task_id - base64_file - file_name properties: task_id: type: integer base64_file: type: string description: Base64 encoded file content file_name: type: string description: Name of the file including extension responses: '200': description: Attachment uploaded successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' /department/selfserve/tasks/attachments/download: get: tags: - Self-Serve summary: Download task attachment description: Generate a download link for a specific self-serve task attachment. operationId: downloadSelfserveTaskAttachment parameters: - name: task_id in: query required: true description: Task ID schema: type: integer - name: attachment_id in: query required: true description: Attachment ID schema: type: integer responses: '200': description: Successfully generated download link content: application/json: schema: type: object properties: download_link: type: string format: uri '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /department/selfserve/studio/graph: get: tags: - Self-Serve summary: Get all-in-one self-serve studio graph description: Returns the replacement studio workspace graph backed by the schema_version 2 draft config. Conditions own grouped expression trees directly; standalone rule nodes are omitted from v2 graphs. operationId: getSelfserveStudioGraph parameters: - name: department in: query required: true schema: type: integer responses: '200': description: Studio graph returned content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioGraph' put: tags: - Self-Serve summary: Bulk save all-in-one self-serve studio graph changes description: Creates, updates, deletes, connects, disconnects, reorders, and upserts self-serve answer paths by editing the schema_version 2 draft config JSON. Condition connections create expression predicates; Path Editor upserts create normal generated condition and task nodes; layout remains separate from runtime behavior. operationId: saveSelfserveStudioGraph requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioGraphSaveRequest' responses: '200': description: Studio graph saved content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioGraph' '422': $ref: '#/components/responses/BadRequest' /department/selfserve/studio/layout: put: tags: - Self-Serve summary: Save self-serve studio canvas layout description: Persists canvas-only node positions and viewport state. Layout does not affect runtime wash behavior. operationId: saveSelfserveStudioLayout requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioLayoutSaveRequest' responses: '200': description: Layout saved content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioLayout' /department/selfserve/studio/validate: post: tags: - Self-Serve summary: Validate self-serve studio graph operationId: validateSelfserveStudioGraph requestBody: required: true content: application/json: schema: type: object required: [department] properties: department: type: integer responses: '200': description: Validation result content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioValidation' /department/selfserve/studio/simulate: post: tags: - Self-Serve summary: Simulate self-serve studio runtime operationId: simulateSelfserveStudioGraph requestBody: required: true content: application/json: schema: type: object required: [department, lane_id, reg] properties: department: { type: integer } lane_id: { type: integer } reg: { type: string } customer_number: { type: integer, nullable: true } vehicle_type_id: { type: integer, nullable: true } config_source: type: string enum: [draft, published] default: draft answer_overrides: type: array items: type: object required: [question_id] properties: question_id: { type: integer } value: type: boolean nullable: true include_hardware: type: boolean default: true mode: type: string enum: [full_dry_run] default: full_dry_run responses: '200': description: Simulator result content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioSimulationResponse' /department/selfserve/studio/path-outcomes: post: tags: - Self-Serve summary: Project grouped self-serve studio question path outcomes description: Enumerates feasible yes/no answer paths for the selected studio scope and groups terminal paths by resulting tasks, services, and dry-run signal timeline. No live hardware commands are sent. operationId: projectSelfserveStudioPathOutcomes requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioPathOutcomesRequest' responses: '200': description: Grouped path outcomes returned content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioPathOutcomesResponse' '422': $ref: '#/components/responses/BadRequest' /department/selfserve/studio/path-outcomes/stream: post: tags: - Self-Serve summary: Stream self-serve studio question path outcome progress description: Streams newline-delimited JSON progress events while enumerating the complete feasible yes/no answer path space. Progress events contain the same response shape as the final result with partial outcomes and paths. operationId: streamSelfserveStudioPathOutcomes requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioPathOutcomesRequest' responses: '200': description: Newline-delimited path outcome progress events content: application/x-ndjson: schema: type: string '422': $ref: '#/components/responses/BadRequest' /department/selfserve/studio/path-confirmations: post: tags: - Self-Serve summary: Confirm or reset a projected self-serve studio path description: Stores confirmation for a projected terminal path using its stable path and result signatures. Projections report confirmed, unconfirmed, or stale when the resulting tasks, buttons, services, or signals change. operationId: confirmSelfserveStudioPath requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioPathConfirmationRequest' responses: '200': description: Path confirmation updated content: application/json: schema: $ref: '#/components/schemas/SelfserveStudioPathConfirmation' '422': $ref: '#/components/responses/BadRequest' /department/selfserve/studio/publish: post: tags: - Self-Serve summary: Publish self-serve studio draft operationId: publishSelfserveStudioDraft requestBody: required: true content: application/json: schema: type: object required: [department] properties: department: { type: integer } responses: '200': description: Published version content: application/json: schema: $ref: '#/components/schemas/SelfserveConfigVersion' /department/selfserve/studio/rollback: post: tags: - Self-Serve summary: Roll back self-serve studio to an earlier version operationId: rollbackSelfserveStudioDraft requestBody: required: true content: application/json: schema: type: object required: [department, target_version_id] properties: department: { type: integer } target_version_id: { type: integer } responses: '200': description: Rollback version content: application/json: schema: $ref: '#/components/schemas/SelfserveConfigVersion' /department/selfserve/studio/gateway-action: post: tags: - Self-Serve summary: Run permission-gated edge gateway action from studio operationId: runSelfserveStudioGatewayAction requestBody: required: true content: application/json: schema: type: object required: [department, gateway_id, action] properties: department: { type: integer } gateway_id: { type: integer } action: type: string enum: [discovery, discover, update, uninstall, cancel, rotate_credentials, bindings] confirm: type: boolean description: Required for dangerous gateway actions such as uninstall and credential rotation. operation_id: { type: integer, nullable: true } request: type: object additionalProperties: true bindings: type: array items: type: object additionalProperties: true responses: '200': description: Gateway action result content: application/json: schema: type: object additionalProperties: true # Products Endpoints /products: get: tags: - Products summary: List products description: Retrieve a list of products with optional filters for customer pricing and department operationId: listProducts parameters: - name: customer_id in: query schema: type: integer description: Customer ID for custom pricing - name: department_id in: query schema: type: integer description: Department ID for department-specific pricing - name: category in: query schema: type: integer description: Filter by category ID - name: id in: query schema: type: integer description: Get specific product by ID - name: final_price in: query schema: type: boolean description: Whether to return final prices including discounts - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: Products retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Product' post: tags: - Products summary: Create product description: Create a new product operationId: createProduct requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ProductCreate' responses: '201': description: Product created successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' put: tags: - Products summary: Update product description: Update an existing product operationId: updateProduct requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ProductUpdate' responses: '200': description: Product updated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' # Categories Endpoints /categories: get: tags: - Categories summary: List categories description: Retrieve a list of product categories operationId: listCategories parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: Categories retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Category' post: tags: - Categories summary: Create category description: Create a new product category operationId: createCategory requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CategoryCreate' responses: '201': description: Category created successfully content: application/json: schema: {} put: tags: - Categories summary: Update category description: Update an existing category operationId: updateCategory requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CategoryUpdate' responses: '200': description: Category updated successfully content: application/json: schema: {} # Bookings Endpoints /bookings: get: tags: - Bookings summary: List bookings description: Retrieve a list of bookings operationId: listBookings parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: Bookings retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Booking' put: tags: - Bookings summary: Update booking description: Update an existing booking operationId: updateBooking requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BookingUpdate' responses: '200': description: Booking updated successfully content: application/json: schema: {} /user/bookings: get: tags: - Bookings summary: Get user bookings description: Retrieve bookings for the authenticated user operationId: getUserBookings responses: '200': description: User bookings retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/Booking' /order-bookings: get: tags: - Bookings summary: List order bookings operationId: listOrderBookings parameters: - name: id in: query required: false schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} # Goals Endpoints /goals/department: get: tags: - Goals summary: List or get department goals description: | Retrieve a list of department goals or a single goal when `id` is provided. Access control: - A user may only access goals where the goal's `departments` set is a subset of the user's departments. - Users with the `superuser` permission may access all goals. operationId: listDepartmentGoals parameters: - name: id in: query required: false schema: { type: integer } description: When provided, returns the single goal with this id (if accessible) - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' - $ref: '#/components/parameters/FiltersParam' responses: '200': description: Goals retrieved successfully content: application/json: schema: type: array items: $ref: '#/components/schemas/DepartmentGoal' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } post: tags: - Goals summary: Create department goal description: | Create a new department goal. Access control: - The provided `departments` must be a subset of the user's departments unless the user has `superuser`. operationId: createDepartmentGoal requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentGoalCreate' responses: '201': description: Department goal created successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentGoal' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } put: tags: - Goals summary: Update department goal description: | Update an existing department goal by `id`. Access control: - The creator (`created_by`) may update regardless of department membership. - Otherwise the user must satisfy the same subset rule as for read access, and any new `departments` provided must also be a subset unless the user has `superuser`. operationId: updateDepartmentGoal requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentGoalUpdate' responses: '200': description: Department goal updated successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentGoal' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } /goals/department/progress-alert/test: post: tags: - Goals summary: Send a test progress alert for a department goal description: | Sends a progress alert for a department goal to the destination defined in the goal's criteria. Permission required: `goals_department_progress_alert_test`. Access control: - The caller must be a superuser or belong to all departments targeted by the goal. Behavior: - Looks up the goal by `id`. - Rebuilds the criteria from stored JSON and attaches the goal's departments. - Renders the alert using the server-side renderer (respecting progress type/style/format and destination limits). - Sends the alert to Slack, Email, or SMS depending on `progress_alert_destination`, unless overridden. operationId: sendDepartmentGoalProgressAlertTest requestBody: required: true content: application/json: schema: type: object required: - id properties: id: type: integer description: The department goal id example: 42 overrideDestination: type: string description: Override the destination for this test enum: [SLACK, EMAIL, SMS, NONE] example: SLACK email_to: type: string format: email description: Email recipient when destination is EMAIL example: tester@example.com subject: type: string description: Optional email subject when destination is EMAIL example: Dept Goal Progress Test sms_to: description: One or more MSISDN recipients when destination is SMS oneOf: - type: string description: Comma or semicolon separated list example: "+4512345678, +4598765432" - type: array items: type: string example: ["+4512345678", "+4598765432"] slack_webhook: type: string description: Slack webhook URL when destination is SLACK example: https://hooks.slack.com/services/T000/B000/XXX department_id: type: integer description: Department id to use that department's Slack webhook when destination is SLACK example: 3 responses: '200': description: Alert sent successfully content: application/json: schema: type: object properties: id: type: integer description: Goal id destination: type: string description: Final destination used enum: [SLACK, EMAIL, SMS, NONE] target: description: The target used for delivery (email address, phone numbers, department id, or webhook) message_preview: type: string description: Rendered message preview provider_response: description: Provider-specific response or status message '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '500': { $ref: '#/components/responses/InternalServerError' } delete: tags: - Goals summary: Delete department goal description: | Delete a department goal by `id`. Access control: - The creator (`created_by`) may delete regardless of department membership. - Otherwise the user must satisfy the subset rule or have `superuser`. operationId: deleteDepartmentGoal parameters: - name: id in: query required: true schema: { type: integer } description: ID of the goal to delete responses: '200': description: Department goal deleted successfully content: application/json: schema: {} '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '/order-bookings': post: tags: - Bookings summary: Create order booking operationId: createOrderBooking requestBody: required: true content: application/json: schema: type: object required: [department, reg_1, datetime, items] properties: customer_number: {type: integer} department: {type: integer} reg_1: {type: string} reg_2: {type: string} reg_3: {type: string} datetime: {type: string, format: date-time} note: {type: string} reference: {type: string} po: {type: string} pickup: {type: boolean} items: type: array items: type: object required: [id, quantity] properties: id: {type: integer} quantity: {type: integer} responses: '200': description: Success put: tags: - Bookings summary: Update order booking operationId: updateOrderBooking requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} customer_number: {type: integer} department: {type: integer} reg_1: {type: string} reg_2: {type: string} reg_3: {type: string} datetime: {type: string, format: date-time} note: {type: string} reference: {type: string} po: {type: string} pickup: {type: boolean} order_id: {type: integer, nullable: true} 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/booking-confirmation/resend: post: tags: - Bookings summary: Resend order booking confirmation description: Resends the customer booking confirmation email for an order booking. Requires `resend_booking_confirmations` and access to the booking's department. operationId: resendOrderBookingConfirmation requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} responses: '200': description: Booking confirmation resent successfully content: application/json: schema: {} '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /order-bookings/completion-confirmation/resend: post: tags: - Bookings summary: Resend order booking completion confirmation description: Resends the customer completion confirmation email with the wash certificate for a completed order booking. Requires `complete_bookings` and access to the booking's department. operationId: resendOrderBookingCompletionConfirmation requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} responses: '200': description: Completion confirmation resent successfully content: application/json: schema: {} '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '409': { $ref: '#/components/responses/Conflict' } /order-bookings/complete: post: tags: - Bookings summary: Complete order booking operationId: completeOrderBooking requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} safety_seal: {type: integer} responses: '200': description: Success content: application/json: schema: {} /admin/bookings/sync: post: tags: - Bookings summary: Sync booking from external system operationId: syncBooking requestBody: required: false content: application/json: schema: {} responses: '200': description: Success content: application/json: schema: {} /admin/bookings/department/count: get: tags: - Bookings summary: Get department unfulfilled bookings count operationId: getDepartmentBookingCount parameters: - name: department_id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} /user/bookings/washcertificate/download: post: tags: - Bookings summary: Get download link for own wash certificate operationId: downloadOwnWashCertificate requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} responses: '200': description: Success content: application/json: schema: {} /bookings/download_pdf: get: tags: - Bookings summary: Download booking PDF operationId: downloadBookingPdf parameters: - name: id in: query required: true schema: {type: integer} responses: '200': description: Success content: application/json: schema: {} /admin/bookings/delete: post: tags: - Bookings summary: Delete booking (admin) operationId: adminDeleteBooking requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} responses: '200': description: Success content: application/json: schema: {} /superuser/bookings/sync/all: post: tags: - Bookings summary: Sync all bookings from external system operationId: syncAllBookings requestBody: required: false content: application/json: schema: {} responses: '200': description: Success content: application/json: schema: {} /admin/bookings/completeWashWithoutWashCertificate: post: tags: - Bookings summary: Complete wash without wash certificate operationId: completeWashWithoutWashCertificate requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} responses: '200': description: Success content: application/json: schema: {} /user/bookings/delete: post: tags: - Bookings summary: Delete own booking operationId: deleteOwnBooking requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: {type: integer} responses: '200': description: Success content: application/json: schema: {} # Invoices Endpoints /invoices/draft: get: tags: - Invoices summary: List draft invoices description: Retrieve a list of draft invoices operationId: listDraftInvoices parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: Draft invoices retrieved successfully content: application/json: schema: {} /invoices/draft/close: post: tags: - Invoices summary: Close draft invoice description: Close a draft invoice operationId: closeDraftInvoice requestBody: required: true content: application/json: schema: type: object properties: id: type: integer responses: '200': description: Draft invoice closed successfully content: application/json: schema: {} /invoices/pdf: get: tags: - Invoices summary: Get invoice PDF description: Download an invoice as PDF operationId: getInvoicePdf parameters: - name: id in: query required: true schema: type: integer responses: '200': description: PDF retrieved successfully content: application/pdf: schema: type: string format: binary /user/invoices: get: tags: - Invoices summary: Get user invoices description: Retrieve invoices for the authenticated user operationId: getUserInvoices responses: '200': description: User invoices retrieved successfully content: application/json: schema: {} /collected-invoices: get: tags: - Invoices summary: List collected invoices description: Get list of collected invoices operationId: listCollectedInvoices parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: Collected invoices retrieved successfully content: application/json: schema: {} post: tags: - Invoices summary: Create collected invoice description: Create a new collected invoice operationId: createCollectedInvoice requestBody: required: false content: application/json: schema: {} responses: '201': description: Collected invoice created successfully content: application/json: schema: {} put: tags: - Invoices summary: Update collected invoice description: Update a collected invoice operationId: updateCollectedInvoice requestBody: required: false content: application/json: schema: {} responses: '200': description: Collected invoice updated successfully content: application/json: schema: {} /collected-invoices/ready-to-invoice: get: tags: - Invoices summary: Get invoices ready to process description: Get collected invoices that are ready to be processed operationId: getReadyToInvoice responses: '200': description: Ready invoices retrieved successfully content: application/json: schema: {} /collected-invoices/economic: post: tags: - Invoices summary: Export collected invoice to e-conomic description: | Exports collected invoice to e-conomic. Uses async queue when available, otherwise falls back to synchronous processing. operationId: queueCollectedInvoiceEconomicTransfer requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: type: integer minimum: 1 send_as_is: type: boolean default: false responses: '200': description: Collected invoice export processed synchronously (fallback) content: application/json: schema: $ref: '#/components/schemas/EconomicTransferSynchronousFallbackResponse' '202': description: Collected invoice transfer queued content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueEnqueueResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/stripe/book: post: tags: - Invoices summary: Export Stripe collected invoice to e-conomic description: | Exports a Stripe-backed collected invoice to e-conomic. Uses async queue when available, otherwise falls back to synchronous processing. operationId: queueStripeCollectedInvoiceEconomicTransfer requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: type: integer minimum: 1 responses: '200': description: Stripe collected invoice export processed synchronously (fallback) content: application/json: schema: $ref: '#/components/schemas/EconomicTransferSynchronousFallbackResponse' '202': description: Stripe collected invoice transfer queued content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueEnqueueResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/queue: get: tags: - Invoices summary: List collected-invoice e-conomic transfer queue jobs operationId: listCollectedInvoiceEconomicQueueJobs parameters: - name: status in: query required: false description: Comma-separated queue statuses to filter by. style: form explode: false schema: type: array items: $ref: '#/components/schemas/EconomicTransferQueueStatus' uniqueItems: true example: [QUEUED, FAILED] - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 500 default: 50 - name: offset in: query required: false schema: type: integer minimum: 0 default: 0 responses: '200': description: Queue jobs retrieved content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueListResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/queue/monitor: get: tags: - Invoices summary: Monitor current-user visible collected-invoice e-conomic transfer queue jobs operationId: monitorCollectedInvoiceEconomicQueueJobs parameters: - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 100 default: 50 responses: '200': description: Queue monitor state retrieved content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueMonitorResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/queue/status: get: tags: - Invoices summary: Get collected-invoice e-conomic transfer queue job status operationId: getCollectedInvoiceEconomicQueueJobStatus parameters: - name: job_id in: query required: true schema: type: integer minimum: 1 responses: '200': description: Queue job status content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueStatusResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/queue/retry: post: tags: - Invoices summary: Retry failed collected-invoice queue job operationId: retryCollectedInvoiceEconomicQueueJob requestBody: required: true content: application/json: schema: type: object required: [job_id] properties: job_id: type: integer minimum: 1 responses: '200': description: Queue job retried content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueRetryResponse' '400': { $ref: '#/components/responses/BadRequest' } '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/queue/dismiss: post: tags: - Invoices summary: Clear one completed or failed collected-invoice queue job for the current user operationId: dismissCollectedInvoiceEconomicQueueJob requestBody: required: true content: application/json: schema: type: object required: [job_id] properties: job_id: type: integer minimum: 1 responses: '200': description: Queue job cleared content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueDismissResponse' '400': { $ref: '#/components/responses/BadRequest' } '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/queue/dismiss-terminal: post: tags: - Invoices summary: Clear all visible completed or failed collected-invoice queue jobs for the current user operationId: dismissCollectedInvoiceEconomicTerminalQueueJobs responses: '200': description: Terminal queue jobs cleared content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueDismissTerminalResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/queue/run: post: tags: - Invoices summary: Run one collected-invoice queue batch immediately operationId: runCollectedInvoiceEconomicQueueBatch requestBody: required: false content: application/json: schema: type: object properties: limit: type: integer minimum: 1 maximum: 10 default: 10 responses: '200': description: Queue batch processed content: application/json: schema: $ref: '#/components/schemas/EconomicTransferQueueRunResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '503': { $ref: '#/components/responses/ServiceUnavailable' } '500': { $ref: '#/components/responses/InternalServerError' } /collected-invoices/economic/compare: get: tags: - Invoices summary: Compare collected invoice totals with E-conomic description: | Compares a collected invoice in the system with its corresponding invoice in E-conomic. Returns totals from both sources, their difference, and any warnings detected during comparison. operationId: compareCollectedInvoiceEconomic parameters: - name: collected_invoice_id in: query required: true description: The internal collected invoice ID to compare schema: type: integer minimum: 1 responses: '200': description: Comparison completed successfully content: application/json: schema: $ref: '#/components/schemas/CollectedInvoiceEconomicCompareResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' /collected-invoices/economic/pdf: get: tags: - Invoices summary: Download a collected invoice e-conomic PDF description: | Resolves the requested collected invoice context and returns a presigned URL for the draft or booked e-conomic invoice PDF. operationId: downloadCollectedInvoiceEconomicPdf parameters: - name: collected_invoice_id in: query required: true description: The internal collected invoice ID schema: type: integer minimum: 1 - name: type in: query required: true description: Which e-conomic invoice PDF to download schema: type: string enum: - draft - booked responses: '200': description: PDF URL resolved successfully content: application/json: schema: $ref: '#/components/schemas/CollectedInvoiceEconomicPdfResponse' '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: $ref: '#/components/schemas/InvoicingPeriodResponseEnvelope' /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' } /superuser/users/{user_id}/vehicles: get: tags: [Vehicles] summary: List vehicles for a selected superuser user operationId: listSuperuserUserVehicles parameters: - name: user_id in: path required: true schema: { type: integer, minimum: 1 } - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' responses: '200': description: User-scoped vehicles retrieved content: application/json: schema: type: array items: $ref: '#/components/schemas/Vehicle' '400': { $ref: '#/components/responses/BadRequest' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } post: tags: [Vehicles] summary: Add a vehicle for a selected superuser user operationId: addSuperuserUserVehicle parameters: - name: user_id in: path required: true schema: { type: integer, minimum: 1 } requestBody: required: true content: application/json: schema: type: object required: [reg, type] properties: reg: { type: string, minLength: 2, maxLength: 12 } type: { type: integer } wash_subscription: { type: boolean, default: false } reference: { type: string, nullable: true, maxLength: 255 } customer_id: type: integer description: Optional guard value; must match the selected user's customer number. responses: '200': description: Vehicle created content: application/json: schema: $ref: '#/components/schemas/Vehicle' '400': { $ref: '#/components/responses/BadRequest' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } put: tags: [Vehicles] summary: Edit a vehicle for a selected superuser user operationId: editSuperuserUserVehicle parameters: - name: user_id in: path required: true schema: { type: integer, minimum: 1 } requestBody: required: true content: application/json: schema: type: object required: [id] properties: id: { type: integer, minimum: 1 } reg: { type: string, minLength: 2, maxLength: 12 } type: { type: integer } wash_subscription: { type: boolean } reference: { type: string, nullable: true, maxLength: 255 } customer_id: type: integer description: Optional guard value; moving vehicles between customers is not allowed here. responses: '200': description: Vehicle updated content: application/json: schema: $ref: '#/components/schemas/Vehicle' '400': { $ref: '#/components/responses/BadRequest' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } delete: tags: [Vehicles] summary: Delete a vehicle for a selected superuser user operationId: deleteSuperuserUserVehicle parameters: - name: user_id in: path required: true schema: { type: integer, minimum: 1 } - name: id in: query required: true schema: { type: integer, minimum: 1 } responses: '200': { description: Vehicle deleted } '400': { $ref: '#/components/responses/BadRequest' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } /superuser/users/{user_id}/vehicles/summary: get: tags: [Vehicles] summary: Summarize vehicles for a selected superuser user operationId: summarizeSuperuserUserVehicles parameters: - name: user_id in: path required: true schema: { type: integer, minimum: 1 } responses: '200': description: User-scoped vehicle summary content: application/json: schema: $ref: '#/components/schemas/VehicleManagementSummary' '400': { $ref: '#/components/responses/BadRequest' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } # 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] 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 default: false description: Defaults to false when omitted. 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: type: object properties: data: type: object properties: api_commit_sha: type: string description: Running API commit SHA, or unknown when unavailable. /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' /relay/machine/on/post: get: tags: - Plate Scans summary: Record Shelly machine ON signal webhook description: Accepts Shelly Cloud webhook/query parameters for input.toggle_on or switch.on and records the physical machine start signal for self-serve billing. operationId: recordShellyMachineOnSignal parameters: - name: token in: query required: false schema: {type: string} - name: lane_id in: query required: false schema: type: integer - name: relay_id in: query required: false schema: type: string - name: event in: query required: false schema: type: string enum: [input.toggle_on, switch.on] - name: reg in: query required: false schema: type: string responses: '201': description: Machine ON signal recorded and linked to a self-serve wash session content: application/json: schema: $ref: '#/components/schemas/MachineButtonPressWebhookResponse' '202': description: Shelly signal was recognized but ignored '400': $ref: '#/components/responses/BadRequest' post: tags: - Plate Scans summary: Record Shelly machine ON signal webhook description: Accepts Shelly Cloud JSON webhook payloads for input.toggle_on or switch.on and records the physical machine start signal for self-serve billing. operationId: recordShellyMachineOnSignalPost requestBody: required: false content: application/json: schema: type: object properties: token: type: string lane_id: type: integer relay_id: type: string event: type: string enum: [input.toggle_on, switch.on] component: type: string example: input:0 state: type: boolean output: type: boolean reg: type: string responses: '201': description: Machine ON signal recorded and linked to a self-serve wash session content: application/json: schema: $ref: '#/components/schemas/MachineButtonPressWebhookResponse' '202': description: Shelly signal was recognized but ignored '400': $ref: '#/components/responses/BadRequest' # 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/wash/my-active-wash: get: tags: - Modules summary: Get the authenticated customer's active self-serve wash description: | Returns the latest active self-serve wash for the authenticated customer, without requiring the frontend to know or poll a lane id. operationId: getMyActiveSelfServeWash responses: '200': description: Active self-serve wash details resolved content: application/json: schema: type: object properties: lane_id: type: integer status: type: string in_progress: type: boolean elapsed_minutes: type: integer session: type: object nullable: true customer: type: object nullable: true vehicle: type: object nullable: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /modules/self-serve/sessions: get: tags: - Modules summary: List self-serve wash sessions description: Retrieve paginated self-serve wash sessions with search, filters, ordering, and active/open-only support. operationId: listSelfServeSessions parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/LimitParam' - $ref: '#/components/parameters/SearchParam' - $ref: '#/components/parameters/FiltersParam' - name: order in: query required: false schema: type: string example: id:DESC - name: open_only in: query required: false schema: type: boolean responses: '200': description: Self-serve wash sessions retrieved successfully content: application/json: schema: type: array items: allOf: - $ref: '#/components/schemas/SelfserveWashSession' - type: object properties: elapsed_minutes: type: integer open: type: boolean '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/sessions/{id}: get: tags: - Modules summary: Get self-serve wash session detail operationId: getSelfServeSessionDetail parameters: - name: id in: path required: true schema: type: integer responses: '200': description: Self-serve wash session detail retrieved successfully content: application/json: schema: $ref: '#/components/schemas/SelfserveWashSummary' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /modules/self-serve/lane/force/stop: post: tags: - Modules summary: Force stop a self-serve wash session description: | Clears the current self-serve wash session and lane runtime with RESET behavior only. This administrative action does not signal relays or gates. When billing is requested, only elapsed-minute billing is attempted before runtime is cleared. operationId: forceStopSelfServeLane requestBody: required: true content: application/json: schema: type: object required: - lane_id - bill properties: lane_id: type: integer session_id: type: integer nullable: true bill: type: boolean reason: type: string nullable: true responses: '200': description: Self-serve wash force stopped successfully content: application/json: schema: $ref: '#/components/schemas/SelfserveForceStopResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': $ref: '#/components/responses/Conflict' /modules/self-serve/lane/command: post: tags: - Modules summary: Send self-serve lane command description: | Send a command (e.g., start, stop, reset) to a self-serve lane. Property gate commands (`OPEN_PROPERTY_ACCESS_GATE`, `OPEN_PROPERTY_EXIT_GATE`) are also supported here. Property gate commands require the matching explicit command permissions. Operator callers require the base command permission plus the command-specific permission. Authenticated customers with `list_own_department_selfserve_vehicle_conditions` may send `START` on enabled self-serve lanes. Customer `STOP` requires the customer's active self-serve wash in the target department. operationId: sendSelfServeLaneCommand requestBody: required: true content: application/json: schema: type: object required: - lane_id - command properties: lane_id: type: integer command: type: string enum: [START, STOP, RESET, RESERVE, RELEASE, OPEN_PROPERTY_ACCESS_GATE, OPEN_PROPERTY_EXIT_GATE] license_plate: type: string description: Required for START command wash_type: type: string enum: [Manual, Machine] description: Optional customer-selected wash type for START. When provided, Manual and Machine start actions use this explicit choice instead of inferring mode from allowed services. wash_mode: type: string enum: [manual, machine] description: Lowercase alias for wash_type accepted by backend clients. customer_number: type: integer description: Required for START and RESERVE commands. The authenticated customer's number is applied server-side when omitted by user clients. defer_relay_side_effects: type: boolean default: false description: For START, open the entrance gate as part of the command but defer cleaner and machine relay activation to explicit relay endpoints. responses: '200': description: Command sent successfully content: application/json: schema: $ref: '#/components/schemas/SelfServeLaneStatus' '400': description: Command execution failed content: application/json: schema: type: object properties: message: type: string example: 'Failed to execute command: Failed to open property access gate.' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/services/allowed: post: tags: - Modules summary: Set allowed services for a lane based on shown tasks description: | Updates the set of services that are allowed to be manually activated for a given self-serve lane, derived from the tasks currently shown to the user after answering the self-serve questions. This endpoint does not activate anything by itself; it only sets what is allowed to be activated. Operator callers require `modules_selfserve_lane_services_set_allowed`; authenticated customers with `list_own_department_selfserve_vehicle_conditions` may update their enabled self-serve lane before confirming a wash start. operationId: setSelfServeLaneAllowedServices requestBody: required: true content: application/json: schema: type: object required: - lane_id properties: lane_id: type: integer task_ids: type: array description: List of task IDs that are currently shown to the user items: type: integer responses: '200': description: Allowed services updated content: application/json: schema: type: object properties: lane_id: type: integer allowed_services: type: array items: type: string '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/gate/open: post: tags: - Modules summary: Open a self-serve lane gate description: | Opens either the ENTRANCE or EXIT gate relay for a self-serve lane. Failures return a sanitized gate-specific message. operationId: openSelfServeLaneGate requestBody: required: true content: application/json: schema: type: object required: - lane_id - gate properties: lane_id: type: integer gate: type: string enum: [ENTRANCE, EXIT] responses: '200': description: Lane gate opened content: application/json: schema: type: object properties: lane_id: type: integer gate: type: string enum: [ENTRANCE, EXIT] opened: type: boolean state: type: string '400': description: Gate open failed content: application/json: schema: type: object properties: message: type: string example: Failed to open entrance gate. '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/relay/machine/status: get: tags: - Modules summary: Get MACHINE relay status for a lane description: | Reads the current Shelly MACHINE relay status (`on`/`off`) for the given lane. operationId: getSelfServeLaneMachineRelayStatus parameters: - name: lane_id in: query required: true schema: type: integer responses: '200': description: MACHINE relay status retrieved content: application/json: schema: $ref: '#/components/schemas/SelfServeLaneMachineRelayStatus' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/relay/machine_program_picker/status: get: tags: - Modules summary: Get MACHINE_PROGRAM_PICKER relay status for a lane description: | Reads the current Shelly MACHINE_PROGRAM_PICKER relay status (`on`/`off`) for the given lane. operationId: getSelfServeLaneMachineProgramPickerRelayStatus parameters: - name: lane_id in: query required: true schema: type: integer responses: '200': description: MACHINE_PROGRAM_PICKER relay status retrieved content: application/json: schema: $ref: '#/components/schemas/SelfServeLaneMachineRelayStatus' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/relay/machine_program_picker/set: post: tags: - Modules summary: Set MACHINE_PROGRAM_PICKER relay status for a lane description: | Sets the Shelly MACHINE_PROGRAM_PICKER relay state for the lane to on or off and returns the latest status. operationId: setSelfServeLaneMachineProgramPickerRelayStatus requestBody: required: true content: application/json: schema: type: object required: - lane_id - on properties: lane_id: type: integer on: type: boolean responses: '200': description: MACHINE_PROGRAM_PICKER relay status updated content: application/json: schema: type: object properties: lane_id: type: integer relay: type: string enum: [MACHINE_PROGRAM_PICKER] requested_on: type: boolean relay_id: type: string online: type: boolean on: type: boolean '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/relay/machine_cleaner/status: get: tags: - Modules summary: Get MACHINE_CLEANER relay status for a lane description: | Reads the current Shelly MACHINE_CLEANER relay status (`on`/`off`) for the given lane. operationId: getSelfServeLaneMachineCleanerRelayStatus parameters: - name: lane_id in: query required: true schema: type: integer responses: '200': description: MACHINE_CLEANER relay status retrieved content: application/json: schema: $ref: '#/components/schemas/SelfServeLaneMachineRelayStatus' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/relay/machine_cleaner/set: post: tags: - Modules summary: Set MACHINE_CLEANER relay status for a lane description: | Sets the Shelly MACHINE_CLEANER relay state for the lane to on or off and returns the latest status. operationId: setSelfServeLaneMachineCleanerRelayStatus requestBody: required: true content: application/json: schema: type: object required: - lane_id - on properties: lane_id: type: integer on: type: boolean responses: '200': description: MACHINE_CLEANER relay status updated content: application/json: schema: type: object properties: lane_id: type: integer relay: type: string enum: [MACHINE_CLEANER] requested_on: type: boolean relay_id: type: string online: type: boolean on: type: boolean '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/relay/machine/set: post: tags: - Modules summary: Set MACHINE relay status for a lane description: | Sets the Shelly MACHINE relay state for the lane to on or off and returns the latest status. operationId: setSelfServeLaneMachineRelayStatus requestBody: required: true content: application/json: schema: type: object required: - lane_id - on properties: lane_id: type: integer on: type: boolean responses: '200': description: MACHINE relay status updated content: application/json: schema: type: object properties: lane_id: type: integer relay: type: string enum: [MACHINE] requested_on: type: boolean relay_id: type: string online: type: boolean on: type: boolean '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/relay/machine/enable: post: tags: - Modules summary: Manually enable MACHINE relay for a lane description: | Manually turns on the MACHINE relay for a self-serve lane if and only if the current allowed services include `MACHINE` (set via `/modules/self-serve/lane/services/allowed`). The relay is never automatically enabled; an explicit call to this endpoint is required. Operator callers require `modules_selfserve_lane_relay_enable_machine`; authenticated customers with `list_own_department_selfserve_vehicle_conditions` may enable it only for their active self-serve wash. operationId: enableSelfServeLaneMachineRelay requestBody: required: true content: application/json: schema: type: object required: - lane_id properties: lane_id: type: integer duration: type: integer description: Optional number of seconds after which the relay should automatically turn off responses: '200': description: MACHINE relay enabled content: application/json: schema: type: object properties: lane_id: type: integer relay: type: string enabled: type: boolean duration: type: integer nullable: true '401': $ref: '#/components/responses/Unauthorized' '403': description: Not allowed to enable MACHINE relay (no matching task currently shown) content: application/json: schema: $ref: '#/components/schemas/Error' /modules/self-serve/lane/force/machine/enable: post: tags: - Modules summary: Force enable MACHINE relay and mark lane as in-wash (superusers only) description: | Superuser/emergency endpoint. Bypasses the allowed services gating and directly turns on the MACHINE relay. Also ensures the lane is marked as OCCUPIED and IN_WASH with a wash start timestamp if not already set. operationId: forceEnableSelfServeLaneMachine requestBody: required: true content: application/json: schema: type: object required: - lane_id properties: lane_id: type: integer duration: type: integer nullable: true description: Optional number of seconds after which the relay should automatically turn off license_plate: type: string nullable: true description: Optional license plate to associate with the lane responses: '200': description: MACHINE relay force-enabled and lane marked in-wash content: application/json: schema: type: object properties: lane_id: type: integer forced: type: boolean machine: type: string enum: [ENABLED] duration: type: integer nullable: true status: type: string state: type: string wash_start_time: type: integer '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /modules/self-serve/lane/force/machine/disable: post: tags: - Modules summary: Force disable MACHINE relay but keep lane as in-wash (superusers only) description: | Superuser/emergency endpoint. Turns off the MACHINE relay while ensuring the lane remains in an IN_WASH state (simulating a started wash without machine assistance). operationId: forceDisableSelfServeLaneMachine requestBody: required: true content: application/json: schema: type: object required: - lane_id properties: lane_id: type: integer license_plate: type: string nullable: true responses: '200': description: MACHINE relay force-disabled and lane ensured in-wash content: application/json: schema: type: object properties: lane_id: type: integer forced: type: boolean machine: type: string enum: [DISABLED] status: type: string state: type: string wash_start_time: type: integer '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' # Module - Other Integration Endpoints /modules/motorapi/lookup: get: tags: - Modules summary: Lookup vehicle via MotorAPI description: Look up vehicle information using license plate operationId: motorApiLookup parameters: - name: plate in: query required: true schema: type: string responses: '200': description: Vehicle information retrieved successfully content: application/json: schema: {} /modules/virkdata/search: get: tags: - Modules summary: Search VirkData description: Search for company information in VirkData operationId: virkdataSearch parameters: - name: search in: query required: true schema: type: string responses: '200': description: Company information retrieved successfully content: application/json: schema: {} /modules/fxratesapi/rate: get: tags: - Modules summary: Get exchange rate description: Get current exchange rate operationId: getExchangeRate parameters: - name: from in: query required: true schema: type: string - name: to in: query required: true schema: type: string responses: '200': description: Exchange rate retrieved successfully content: application/json: schema: {} /modules/fxratesapi/rates: get: tags: - Modules summary: Get all exchange rates description: Get all available exchange rates operationId: getAllExchangeRates responses: '200': description: Exchange rates retrieved successfully content: application/json: schema: {} /modules/weatherapi/current: get: tags: - Modules summary: Get current weather description: Get current weather data from WeatherAPI for a location query operationId: weatherApiCurrent parameters: - name: q in: query required: true schema: type: string description: Location query (e.g. city, postal code, or latitude,longitude) responses: '200': description: Current weather retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WeatherApiObjectResponse' /modules/weatherapi/forecast: get: tags: - Modules summary: Get weather forecast description: Get forecast weather data from WeatherAPI operationId: weatherApiForecast parameters: - name: q in: query required: true schema: type: string description: Location query (e.g. city, postal code, or latitude,longitude) - name: days in: query required: false schema: type: integer minimum: 1 maximum: 14 description: Number of forecast days responses: '200': description: Forecast weather retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WeatherApiObjectResponse' /modules/weatherapi/search: get: tags: - Modules summary: Search weather locations description: Search location suggestions from WeatherAPI operationId: weatherApiSearch parameters: - name: q in: query required: true schema: type: string description: Search text responses: '200': description: Location search results retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WeatherApiObjectResponse' /modules/workfeed/employees: get: tags: - Modules summary: List Workfeed employees description: List employees from Workfeed (`GET /companies/{CompanyID}/employees`) operationId: workfeedListEmployees responses: '200': description: Workfeed employees retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WorkfeedEmployeeListResponse' /modules/workfeed/employees/{id}: get: tags: - Modules summary: Get Workfeed employee description: Retrieve a single Workfeed employee by identifier operationId: workfeedGetEmployee parameters: - name: id in: path required: true schema: type: string description: Workfeed employee identifier responses: '200': description: Workfeed employee retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WorkfeedEmployeeSingleResponse' /modules/workfeed/shifts: get: tags: - Modules summary: List Workfeed shifts description: List Workfeed shifts (`GET /companies/{CompanyID}/shifts`) operationId: workfeedListShifts parameters: - name: startFrom in: query required: true schema: type: string format: date-time description: Only return shifts starting on or after this timestamp (ISO 8601) - name: startTo in: query required: true schema: type: string format: date-time description: Only return shifts starting before this timestamp (ISO 8601) - name: employeeID in: query required: false schema: type: string description: Filter shifts by employee ID - name: released in: query required: false schema: type: boolean description: Filter by released/published status responses: '200': description: Workfeed shifts retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WorkfeedShiftListResponse' /modules/workfeed/shifts/{id}: get: tags: - Modules summary: Get Workfeed shift description: Retrieve a single Workfeed shift by identifier operationId: workfeedGetShift parameters: - name: id in: path required: true schema: type: string description: Workfeed shift identifier responses: '200': description: Workfeed shift retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WorkfeedShiftSingleResponse' /modules/workfeed/departments: get: tags: - Modules summary: List Workfeed departments description: List departments from Workfeed (`GET /companies/{CompanyID}/departments`) operationId: workfeedListDepartments responses: '200': description: Workfeed departments retrieved successfully content: application/json: schema: $ref: '#/components/schemas/WorkfeedDepartmentListResponse' /departments/weather: get: tags: - Departments summary: Get department weather timeline description: Returns hourly weather, washes, Workfeed employee-hours, and productivity status aggregated across selected departments (server local time). Default range is start of yesterday (`00:00`) to end of today (`23:00`). Use `date_from` and `date_to` (`YYYY-MM-DD`) together to override the range. If department coordinates are missing/invalid or WeatherAPI cannot resolve the location, weather data falls back silently and timeline slots default to `mostly_clear`. Slots return `unknown` status when they have no evaluable employee-hours or when one or more selected departments are missing department weather targets. operationId: getDepartmentWeatherTimeline parameters: - name: id in: query required: false schema: type: array items: type: integer minimum: 1 minItems: 1 uniqueItems: true style: form explode: true description: Department ID list. Repeat `id` to select multiple departments (`?id=1&id=2`). - name: ids in: query required: false schema: type: string example: '1,2,3' description: Optional CSV alternative for department IDs. Merged with `id` if both are provided. At least one of `id` or `ids` must be provided. - name: date_from in: query required: false schema: type: string format: date example: '2026-03-23' description: Optional range start date (`YYYY-MM-DD`). Must be used together with `date_to`. - name: date_to in: query required: false schema: type: string format: date example: '2026-03-24' description: Optional range end date (`YYYY-MM-DD`, inclusive). Must be used together with `date_from`. responses: '200': description: Department weather timeline retrieved successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentWeatherTimelineResponse' example: success: true meta: [] includes: [] data: - date: '2026-03-23' time: '00:00' current: false weather: mostly_cloudy washes: 1 hours: 2.0 status: degraded - date: '2026-03-24' time: '13:00' current: true weather: rain washes: 0 hours: 2.5 status: unhealthy - date: '2026-03-24' time: '14:00' current: false weather: mostly_clear washes: 0 hours: 1.0 status: unknown /departments/weather/targets: get: tags: - Departments summary: Get department weather status targets description: Returns department-specific weather productivity thresholds used by `/departments/weather` to classify `healthy`, `degraded`, and `unhealthy` statuses. operationId: getDepartmentWeatherTargets parameters: - name: id in: query required: false schema: type: array items: type: integer minimum: 1 minItems: 1 uniqueItems: true style: form explode: true description: Department ID list. Repeat `id` to select multiple departments (`?id=1&id=2`). - name: ids in: query required: false schema: type: string example: '1,2,3' description: Optional CSV alternative for department IDs. Merged with `id` if both are provided. At least one of `id` or `ids` must be provided. responses: '200': description: Department weather targets retrieved successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentWeatherTargetsResponse' example: success: true meta: [] includes: [] data: - department_id: 1 degraded_threshold: 1.0 healthy_threshold: 1.3 configured: true - department_id: 2 degraded_threshold: null healthy_threshold: null configured: false put: tags: - Departments summary: Upsert department weather status targets description: Creates or updates the weather productivity thresholds for one department. `healthy_threshold` must be greater than or equal to `degraded_threshold`. operationId: upsertDepartmentWeatherTarget requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentWeatherTargetUpsertRequest' example: department_id: 1 degraded_threshold: 1.0 healthy_threshold: 1.3 responses: '200': description: Department weather targets updated successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: $ref: '#/components/schemas/DepartmentWeatherTarget' required: [data] example: success: true meta: [] includes: [] data: department_id: 1 degraded_threshold: 1.0 healthy_threshold: 1.3 configured: true /modules/entra/users: get: tags: - Modules summary: List Microsoft Entra users description: Get list of users from Microsoft Entra (Azure AD) operationId: listEntraUsers responses: '200': description: Entra users retrieved successfully content: application/json: schema: {} # Attachments Endpoints /attachments/upload: post: tags: - Attachments summary: Upload attachment description: Upload a file attachment operationId: uploadAttachment requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary responses: '201': description: Attachment uploaded successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' /orders/attachments: get: tags: - Attachments summary: List order attachments description: Get attachments for an order operationId: listOrderAttachments parameters: - name: order_id in: query required: true schema: type: integer responses: '200': description: Order attachments retrieved successfully content: application/json: schema: {} /orders/attachments/upload: post: tags: - Attachments summary: Upload order attachment description: Upload an attachment to an order operationId: uploadOrderAttachment requestBody: required: true content: multipart/form-data: schema: type: object properties: order_id: type: integer file: type: string format: binary responses: '201': description: Order attachment uploaded successfully content: application/json: schema: {} /orders/attachments/download: get: tags: - Attachments summary: Get order attachment download link description: Return a legacy HTTPS download link for a specific order attachment operationId: downloadOrderAttachment parameters: - name: order_id in: query required: true schema: type: integer minimum: 1 - name: attachment_id in: query required: true schema: type: integer minimum: 1 responses: '200': description: Attachment download link resolved successfully content: application/json: schema: $ref: '#/components/schemas/OrderAttachmentDownloadLinkResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } '502': description: Attachment storage is unavailable /orders/attachments/content: get: tags: - Attachments summary: Stream authenticated order attachment content description: Streams an attachment after validating order scope and attachment ownership operationId: streamOrderAttachmentContent parameters: - name: order_id in: query required: true schema: type: integer minimum: 1 - name: attachment_id in: query required: true schema: type: integer minimum: 1 - name: disposition in: query required: false schema: type: string enum: [inline, attachment] default: inline responses: '200': description: Attachment content streamed successfully headers: Content-Disposition: schema: type: string content: application/octet-stream: schema: type: string format: binary image/*: schema: type: string format: binary application/pdf: 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' } '502': description: Attachment storage is unavailable # 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 supported entities with permission-aware filtering, strict relevance filtering, and a single capped result set. Customer records and customer-related entities are matched against a local e-conomic customer index (name/address/email/CVR) that is refreshed by cron. 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 deterministic 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: max_results required: false schema: type: integer minimum: 1 maximum: 50 default: 50 description: Maximum number of relevance-ranked results to return. Values above 50 are capped to 50. 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 supported entities using JSON request payload. Customer-related matches include local e-conomic customer index fields. Results use strict relevance filtering and 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 query-result caches 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 document-index rebuild request and clears active query cache entries 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' /superuser/replication: get: tags: - Superuser summary: Database, Redis, and MinIO replication topology operationId: getSuperuserReplication parameters: - in: query name: refresh required: false schema: type: boolean default: false description: Refresh host connectivity and replication status before returning the topology. responses: '200': description: Replication topology returned successfully content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationResponse' '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/replication/databases: post: tags: - Superuser summary: Add database replication host credentials operationId: addSuperuserDatabaseReplicationHost requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationHostCreateRequest' responses: '201': description: Database replication host added content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationHostResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/replication/redis: post: tags: - Superuser summary: Add Redis replication host credentials operationId: addSuperuserRedisReplicationHost requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationHostCreateRequest' responses: '201': description: Redis replication host added content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationHostResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/replication/minio: post: tags: - Superuser summary: Add MinIO replication host credentials operationId: addSuperuserMinioReplicationHost requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationHostCreateRequest' responses: '201': description: MinIO replication host added content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationHostResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/replication/compose-template: post: tags: - Superuser summary: Generate a replication-ready Docker Compose template operationId: generateSuperuserReplicationComposeTemplate requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationComposeTemplateRequest' responses: '200': description: Docker Compose template generated successfully content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationComposeTemplateResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/replication/test-credentials: post: tags: - Superuser summary: Test replication host credentials before saving operationId: testSuperuserReplicationCredentials requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationUnsavedCredentialTestRequest' responses: '200': description: Credential test returned successfully content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationOperationResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/replication/{kind}/{id}/test: post: tags: - Superuser summary: Test replication host connectivity and privileges operationId: testSuperuserReplicationHost parameters: - $ref: '#/components/parameters/SuperuserReplicationKindParam' - $ref: '#/components/parameters/SuperuserReplicationHostIdParam' responses: '200': description: Host test result returned content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationOperationResponse' '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } /superuser/replication/{kind}/{id}/provision: post: tags: - Superuser summary: Provision a host as a replica of the current primary operationId: provisionSuperuserReplicationHost parameters: - $ref: '#/components/parameters/SuperuserReplicationKindParam' - $ref: '#/components/parameters/SuperuserReplicationHostIdParam' responses: '200': description: Replica provisioning started or completed content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationOperationResponse' '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/replication/{kind}/{id}/promote: post: tags: - Superuser summary: Promote a caught-up replica to primary operationId: promoteSuperuserReplicationHost parameters: - $ref: '#/components/parameters/SuperuserReplicationKindParam' - $ref: '#/components/parameters/SuperuserReplicationHostIdParam' responses: '200': description: Replica promoted to primary content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationOperationResponse' '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/replication/{kind}/{id}: patch: tags: - Superuser summary: Rename a replication host operationId: renameSuperuserReplicationHost parameters: - $ref: '#/components/parameters/SuperuserReplicationKindParam' - $ref: '#/components/parameters/SuperuserReplicationHostIdParam' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationHostRenameRequest' responses: '200': description: Replication host renamed content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationHostResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } delete: tags: - Superuser summary: Remove an inactive or unhealthy replication host operationId: removeSuperuserReplicationHost parameters: - $ref: '#/components/parameters/SuperuserReplicationKindParam' - $ref: '#/components/parameters/SuperuserReplicationHostIdParam' responses: '200': description: Replication host removed content: application/json: schema: $ref: '#/components/schemas/SuperuserReplicationOperationResponse' '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/coolify: get: tags: - Superuser summary: Coolify-managed replicated infrastructure state operationId: getSuperuserCoolify responses: '200': description: Coolify summary returned successfully content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyResponse' '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/coolify/load-balancer: get: tags: - Superuser summary: Coolify public gateway Load Balancer state operationId: getSuperuserCoolifyLoadBalancer responses: '200': description: Load Balancer summary returned successfully content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyLoadBalancerResponse' '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/coolify/load-balancer/reconcile: post: tags: - Superuser summary: Reconcile Coolify public gateway Load Balancer state operationId: reconcileSuperuserCoolifyLoadBalancer requestBody: required: false content: application/json: schema: type: object properties: dry_run: type: boolean default: true enforce: type: boolean default: false responses: '200': description: Load Balancer reconcile result returned content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyLoadBalancerReconcileResponse' '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/coolify/load-balancer/routes/deploy: post: tags: - Superuser summary: Deploy the Coolify API route for the public gateway host operationId: deploySuperuserCoolifyGatewayRoutes requestBody: required: false content: application/json: schema: type: object properties: dry_run: type: boolean default: true enforce: type: boolean default: false responses: '200': description: Gateway application route deploy result returned content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyGatewayRouteDeployResponse' '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/coolify/load-balancer/api/deploy: post: tags: - Superuser summary: Deploy the latest Coolify API code for the public gateway host operationId: deploySuperuserCoolifyGatewayApiCode requestBody: required: false content: application/json: schema: type: object properties: dry_run: type: boolean default: true enforce: type: boolean default: false deploy_routes: type: boolean default: true responses: '200': description: Gateway API code deployment result returned content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyGatewayRouteDeployResponse' '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/coolify/gateways: get: tags: - Superuser summary: List Coolify public gateway Load Balancer targets operationId: listSuperuserCoolifyGateways responses: '200': description: Gateway targets returned content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyGatewaysResponse' '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } post: tags: - Superuser summary: Create or update a Coolify public gateway Load Balancer target operationId: saveSuperuserCoolifyGateway requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyGatewaySaveRequest' responses: '201': description: Gateway target saved content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyGatewayResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/coolify/gateways/{id}/test: post: tags: - Superuser summary: Probe a Coolify public gateway target operationId: testSuperuserCoolifyGateway parameters: - in: path name: id required: true schema: type: integer responses: '200': description: Gateway target probe result returned content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyOperationResponse' '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/coolify/instances: post: tags: - Superuser summary: Create Coolify API connection operationId: createSuperuserCoolifyInstance requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyInstanceCreateRequest' responses: '201': description: Coolify instance created content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyInstanceResponse' '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/coolify/instances/{id}/test: post: tags: - Superuser summary: Test Coolify API connection operationId: testSuperuserCoolifyInstance parameters: - in: path name: id required: true schema: type: integer responses: '200': description: Coolify connection test returned content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyOperationResponse' '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } /superuser/coolify/instances/{id}/placement: get: tags: - Superuser summary: Discover Coolify placement options operationId: discoverSuperuserCoolifyInstancePlacement parameters: - in: path name: id required: true schema: type: integer responses: '200': description: Coolify project, environment, and server options returned content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyPlacementResponse' '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } '404': { $ref: '#/components/responses/NotFound' } /superuser/coolify/targets: get: tags: - Superuser summary: List Coolify-managed replication targets operationId: listSuperuserCoolifyTargets parameters: - in: query name: kind required: false schema: type: string enum: [database, redis, minio] responses: '200': description: Coolify targets returned content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyTargetsResponse' '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } post: tags: - Superuser summary: Create Coolify-managed passive replication target operationId: createSuperuserCoolifyTarget requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyTargetCreateRequest' responses: '201': description: Coolify target created content: application/json: schema: $ref: '#/components/schemas/SuperuserCoolifyOperationResponse' '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/coolify/targets/{id}/reconcile: post: tags: - Superuser summary: Reconcile a passive Coolify target operationId: reconcileSuperuserCoolifyTarget parameters: - in: path name: id required: true schema: type: integer responses: '200': { description: Reconcile completed or queued } '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/coolify/targets/{id}/deploy: post: tags: - Superuser summary: Deploy and provision a passive Coolify target operationId: deploySuperuserCoolifyTarget parameters: - in: path name: id required: true schema: type: integer responses: '200': { description: Deploy and provision flow completed or queued } '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/coolify/targets/{id}/restart: post: tags: - Superuser summary: Restart a passive Coolify target operationId: restartSuperuserCoolifyTarget parameters: - in: path name: id required: true schema: type: integer responses: '200': { description: Restart requested } '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/coolify/targets/{id}/failover: post: tags: - Superuser summary: Promote a Coolify-managed replica through replication failover operationId: failoverSuperuserCoolifyTarget parameters: - in: path name: id required: true schema: type: integer responses: '200': { description: Failover action returned } '409': { $ref: '#/components/responses/Conflict' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /superuser/coolify/targets/{id}: delete: tags: - Superuser summary: Delete a Coolify target mapping with destructive confirmation operationId: deleteSuperuserCoolifyTarget parameters: - in: path name: id required: true schema: type: integer requestBody: required: true content: application/json: schema: type: object required: [confirm] properties: confirm: type: string description: Must equal delete-coolify-target-{id}. delete_resource: type: boolean default: false responses: '200': { description: Target deleted } '409': { $ref: '#/components/responses/Conflict' } '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' /slack/config: get: tags: [Config] summary: Get Slack config operationId: getSlackConfig responses: '200': description: Slack configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/SlackConfigListResponse' post: tags: [Config] summary: Update Slack config operationId: updateSlackConfig requestBody: required: false content: application/json: schema: {} responses: '200': description: Slack configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/ModuleConfigUpdateResponse' /slack/config/test: post: tags: [Config] summary: Test Slack customer registration webhook operationId: testSlackCustomerRegistrationWebhook responses: '200': description: Slack customer registration webhook test completed successfully content: application/json: schema: $ref: '#/components/schemas/SlackConfigTestResponse' '400': description: Slack customer registration webhook URL is not configured '502': description: Slack customer registration webhook test failed /slack/config/internal-department-goal-progress: get: tags: [Config] summary: Get Slack internal department goal progress config operationId: getSlackInternalDepartmentGoalProgressConfig responses: '200': description: Slack internal department goal progress configuration retrieved successfully content: application/json: schema: $ref: '#/components/schemas/SlackInternalDepartmentGoalProgressConfigResponse' post: tags: [Config] summary: Update Slack internal department goal progress config operationId: updateSlackInternalDepartmentGoalProgressConfig requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SlackInternalDepartmentGoalProgressConfigUpdate' responses: '200': description: Slack internal department goal progress configuration updated successfully content: application/json: schema: $ref: '#/components/schemas/SlackInternalDepartmentGoalProgressConfigResponse' /slack/config/internal-department-goal-progress/test: post: tags: [Config] summary: Test Slack internal department goal progress webhook operationId: testSlackInternalDepartmentGoalProgressWebhook responses: '200': description: Slack internal department goal progress webhook test completed successfully content: application/json: schema: $ref: '#/components/schemas/SlackConfigTestResponse' '400': description: Slack internal department goal progress webhook URL is not configured '502': description: Slack internal department goal progress webhook test failed /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} address: {type: string, nullable: true} phone_country_code: {type: integer, nullable: true} phone: {type: integer, nullable: true} email: {type: string, nullable: true} website: {type: string, nullable: true} banner: {type: string, nullable: true} logo: {type: string, nullable: true} favicon: {type: string, nullable: true} signature: {type: string, nullable: true} responses: '200': description: Branding option added successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '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, nullable: true} description: {type: string, nullable: true} cvr: {type: integer, nullable: true} address: {type: string, nullable: true} phone_country_code: {type: integer, nullable: true} phone: {type: integer, nullable: true} email: {type: string, nullable: true} website: {type: string, nullable: true} banner: {type: string, nullable: true} logo: {type: string, nullable: true} favicon: {type: string, nullable: true} signature: {type: string, nullable: true} responses: '200': description: Branding option updated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /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/limited-backoffice-permission-templates: get: tags: - Roles summary: List limited backoffice permission templates operationId: listLimitedBackofficeRolePermissionTemplates responses: '200': description: Success content: application/json: schema: type: array items: type: object properties: key: {type: string} label: {type: string} description: {type: string} permissions: type: array items: {type: string} /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/branding: put: tags: - Departments summary: Set department branding description: Assign an existing branding option to a department, or clear the department branding by sending a null branding_id. operationId: setDepartmentBranding requestBody: required: true content: application/json: schema: type: object required: [department_id, branding_id] properties: department_id: {type: integer} branding_id: {type: integer, nullable: true} responses: '200': description: Department branding updated successfully content: application/json: schema: {} '400': $ref: '#/components/responses/BadRequest' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /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/customer-pricing: get: tags: - Departments summary: Get department-specific customer pricing description: Returns customer price overrides and the department product catalog when custom-only pricing is enabled for the department. operationId: getSuperuserDepartmentCustomerPricing parameters: - name: department_id in: query required: true schema: type: integer minimum: 1 - name: user_id in: query required: false description: Internal user id. Provide either user_id or customer_number. schema: type: integer minimum: 1 - name: customer_number in: query required: false description: Customer number. Provide either user_id or customer_number. schema: type: integer minimum: 1 responses: '200': description: Department customer pricing content: application/json: schema: $ref: '#/components/schemas/DepartmentCustomerPricingResponse' '400': $ref: '#/components/responses/BadRequest' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' put: tags: - Departments summary: Replace department-specific customer pricing description: Replaces the complete override set for one customer in one custom-only department. operationId: setSuperuserDepartmentCustomerPricing requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentCustomerPricingUpdateRequest' responses: '200': description: Department customer pricing updated content: application/json: schema: $ref: '#/components/schemas/DepartmentCustomerPricingResponse' '400': $ref: '#/components/responses/BadRequest' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' /limited-backoffice/departments/{departmentId}/customer-pricing: get: tags: - Limited Backoffice summary: Get limited-backoffice department customer pricing description: Returns department-specific customer pricing for an assigned custom-only department. operationId: getLimitedBackofficeDepartmentCustomerPricing parameters: - name: departmentId in: path required: true schema: type: integer minimum: 1 - name: user_id in: query required: false description: Internal user id. Provide either user_id or customer_number. schema: type: integer minimum: 1 - name: customer_number in: query required: false description: Customer number. Provide either user_id or customer_number. schema: type: integer minimum: 1 responses: '200': description: Department customer pricing content: application/json: schema: $ref: '#/components/schemas/DepartmentCustomerPricingResponse' '400': $ref: '#/components/responses/BadRequest' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' put: tags: - Limited Backoffice summary: Replace limited-backoffice department customer pricing description: Replaces the complete override set for one customer in an assigned custom-only department. operationId: setLimitedBackofficeDepartmentCustomerPricing parameters: - name: departmentId in: path required: true schema: type: integer minimum: 1 requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentCustomerPricingLimitedUpdateRequest' responses: '200': description: Department customer pricing updated content: application/json: schema: $ref: '#/components/schemas/DepartmentCustomerPricingResponse' '400': $ref: '#/components/responses/BadRequest' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' /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/product-targets: put: tags: - Departments summary: Set daily report product target description: Requires set_department_daily_report_product_targets and department_access_:department_id. Send a null target_percentage to clear the target. operationId: setDailyReportProductTarget requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DepartmentDailyReportProductTargetRequest' responses: '200': description: Daily report product target updated successfully content: application/json: schema: $ref: '#/components/schemas/DepartmentDailyReportProductTargetResponse' '400': $ref: '#/components/responses/BadRequest' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /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' /superuser/releases/operations: get: tags: - Release Manager summary: List release operation runs operationId: listReleaseOperations parameters: - in: query name: channel_id schema: type: integer - in: query name: operation_type schema: type: string - in: query name: status schema: type: string - in: query name: limit schema: type: integer minimum: 1 maximum: 200 responses: '200': description: Release operation runs content: application/json: schema: type: object properties: success: type: boolean data: type: array items: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /superuser/releases/operations/{id}: get: tags: - Release Manager summary: Get release operation details operationId: getReleaseOperation parameters: - in: path name: id required: true schema: type: integer responses: '200': description: Release operation details content: application/json: schema: type: object properties: success: type: boolean data: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /superuser/releases/test-runs: post: tags: - Release Manager summary: Run Release Manager diagnostics operationId: runReleaseTest requestBody: required: false content: application/json: schema: type: object additionalProperties: true responses: '202': description: Release test operation started content: application/json: schema: type: object properties: success: type: boolean data: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': $ref: '#/components/responses/Conflict' /superuser/releases/channels/{id}/sync: post: tags: - Release Manager summary: Sync latest branch commits into a release channel operationId: syncReleaseChannel parameters: - in: path name: id required: true schema: type: integer responses: '202': description: Channel sync operation started content: application/json: schema: type: object properties: success: type: boolean data: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': $ref: '#/components/responses/Conflict' /superuser/releases/issues/actions: post: tags: - Release Manager summary: Run a Release Manager issue action operationId: runReleaseIssueAction requestBody: required: true content: application/json: schema: type: object properties: issue_key: type: string action_id: type: string inputs: type: object additionalProperties: true confirm: type: boolean additionalProperties: true responses: '200': description: Issue action result content: application/json: schema: type: object properties: success: type: boolean data: type: object additionalProperties: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' 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 SuperuserReplicationKindParam: name: kind in: path required: true schema: type: string enum: [databases, redis, minio] SuperuserReplicationHostIdParam: name: id in: path required: true schema: type: integer minimum: 1 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 DepartmentCustomerPricingUpdateRequest: type: object required: - department_id - overrides properties: department_id: type: integer minimum: 1 user_id: type: integer minimum: 1 customer_number: type: integer minimum: 1 overrides: type: array items: $ref: '#/components/schemas/DepartmentCustomerPricingOverrideInput' DepartmentCustomerPricingLimitedUpdateRequest: type: object required: - overrides properties: user_id: type: integer minimum: 1 customer_number: type: integer minimum: 1 overrides: type: array items: $ref: '#/components/schemas/DepartmentCustomerPricingOverrideInput' DepartmentCustomerPricingOverrideInput: type: object required: - is_category - product_or_category_id properties: is_category: type: boolean product_or_category_id: oneOf: - type: integer - type: string description: Product id, category id, or `global` for the customer-wide department discount. discount: type: integer minimum: 0 maximum: 100 percentage: type: integer minimum: 0 maximum: 100 fixed_price: type: integer nullable: true minimum: 0 DepartmentCustomerPricingOverride: allOf: - $ref: '#/components/schemas/DepartmentCustomerPricingOverrideInput' - type: object properties: id: type: integer department_id: type: integer user_id: type: integer percentage: type: integer minimum: 0 maximum: 100 created_at: type: string updated_at: type: string DepartmentCustomerPricingProduct: type: object properties: id: type: integer name: type: string description: type: string category: type: integer apply_category_discount: type: boolean base_price: type: integer department_price: type: integer nullable: true effective_price: type: integer missing_department_price: type: boolean DepartmentCustomerPricingCategory: type: object properties: id: type: integer name: type: string description: type: string products: type: array items: $ref: '#/components/schemas/DepartmentCustomerPricingProduct' DepartmentCustomerPricingResponse: type: object properties: success: type: boolean data: type: object properties: department: type: object properties: id: type: integer name: type: string description: type: string custom_pricing_only: type: boolean customer: type: object properties: id: type: integer customer_number: type: integer display_name: type: string overrides: type: array items: $ref: '#/components/schemas/DepartmentCustomerPricingOverride' categories: type: array items: $ref: '#/components/schemas/DepartmentCustomerPricingCategory' meta: type: object additionalProperties: true includes: type: object additionalProperties: true ErrorReportSubmissionRequest: type: object required: - before_error - expected - actual - data_collection_accepted properties: before_error: type: string maxLength: 4000 description: What the user was doing before the error occurred expected: type: string maxLength: 4000 description: What the user expected would happen actual: type: string maxLength: 4000 description: What actually happened data_collection_accepted: type: boolean description: Required acceptance of collecting diagnostic error data and a screenshot when one can be attached screenshot: type: string nullable: true description: Optional PNG, JPEG, or WebP data URI of the current app viewport. Reports are accepted without an attachment when capture or upload fails. route_path: type: string nullable: true page_url: type: string nullable: true release_trace_id: type: string nullable: true request_errors: type: array items: type: object additionalProperties: true vue_errors: type: array items: type: object additionalProperties: true context: type: object additionalProperties: true ErrorReportStatusUpdateRequest: type: object required: - status properties: status: type: string enum: [open, resolved] resolution_note: type: string nullable: true maxLength: 2000 ErrorReportResponse: type: object properties: success: type: boolean data: $ref: '#/components/schemas/ErrorReport' meta: type: object additionalProperties: true includes: type: object additionalProperties: true ErrorReportListResponse: type: object properties: success: type: boolean data: type: object properties: items: type: array items: $ref: '#/components/schemas/ErrorReport' counts: type: object properties: open: type: integer resolved: type: integer all: type: integer limit: type: integer offset: type: integer meta: type: object additionalProperties: true includes: type: object additionalProperties: true ErrorReport: type: object properties: id: type: integer status: type: string enum: [open, resolved] reporter: type: object additionalProperties: true route_path: type: string nullable: true page_url: type: string nullable: true release_trace_id: type: string nullable: true frontend_version: type: string nullable: true api_version: type: string nullable: true screenshot: type: object nullable: true additionalProperties: true answers: type: object properties: before_error: type: string expected: type: string actual: type: string request_error_count: type: integer vue_error_count: type: integer request_errors: type: array items: type: object additionalProperties: true vue_errors: type: array items: type: object additionalProperties: true runtime_context: type: object additionalProperties: true resolved_at: type: string nullable: true resolved_by_user_id: type: integer nullable: true created_at: type: string updated_at: type: string nullable: true 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 SuperuserReplicationResponse: type: object properties: success: type: boolean data: $ref: '#/components/schemas/SuperuserReplicationSummary' meta: type: object additionalProperties: true includes: type: object additionalProperties: true SuperuserReplicationHostResponse: type: object properties: success: type: boolean data: $ref: '#/components/schemas/SuperuserReplicationHost' meta: type: object additionalProperties: true includes: type: object additionalProperties: true SuperuserReplicationOperationResponse: type: object properties: success: type: boolean data: type: object additionalProperties: true meta: type: object additionalProperties: true includes: type: object additionalProperties: true SuperuserCoolifyResponse: type: object properties: success: type: boolean data: type: object properties: generated_at: type: string format: date-time instances: type: array items: $ref: '#/components/schemas/SuperuserCoolifyInstance' targets: type: array items: $ref: '#/components/schemas/SuperuserCoolifyTarget' availability: type: object additionalProperties: true load_balancer: $ref: '#/components/schemas/SuperuserCoolifyLoadBalancer' meta: type: object additionalProperties: true includes: type: object additionalProperties: true SuperuserCoolifyInstanceResponse: type: object properties: success: type: boolean data: $ref: '#/components/schemas/SuperuserCoolifyInstance' meta: type: object additionalProperties: true includes: type: object additionalProperties: true SuperuserCoolifyTargetsResponse: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/SuperuserCoolifyTarget' meta: type: object additionalProperties: true includes: type: object additionalProperties: true SuperuserCoolifyOperationResponse: type: object properties: success: type: boolean data: type: object additionalProperties: true meta: type: object additionalProperties: true includes: type: object additionalProperties: true SuperuserCoolifyLoadBalancerResponse: type: object properties: success: type: boolean data: $ref: '#/components/schemas/SuperuserCoolifyLoadBalancer' meta: type: object additionalProperties: true includes: type: object additionalProperties: true SuperuserCoolifyLoadBalancerReconcileResponse: type: object properties: success: type: boolean data: type: object properties: ok: type: boolean dry_run: type: boolean mutated: type: boolean config: $ref: '#/components/schemas/SuperuserCoolifyLoadBalancerConfig' load_balancer: $ref: '#/components/schemas/SuperuserCoolifyLoadBalancerPublicState' drift: type: object additionalProperties: true planned: type: array items: type: object additionalProperties: true applied: type: array items: type: object additionalProperties: true skipped: type: array items: type: object additionalProperties: true errors: type: array items: type: object additionalProperties: true gateways: type: array items: $ref: '#/components/schemas/SuperuserCoolifyGateway' meta: type: object additionalProperties: true includes: type: object additionalProperties: true SuperuserCoolifyGatewayRouteDeployResponse: type: object properties: success: type: boolean data: type: object properties: ok: type: boolean dry_run: type: boolean mutated: type: boolean public_host: type: string public_url: type: string planned: type: array items: type: object additionalProperties: true applied: type: array items: type: object additionalProperties: true skipped: type: array items: type: object additionalProperties: true errors: type: array items: type: object additionalProperties: true warnings: type: array items: type: string coverage: type: object additionalProperties: true gateways: type: array items: $ref: '#/components/schemas/SuperuserCoolifyGateway' meta: type: object additionalProperties: true includes: type: object additionalProperties: true SuperuserCoolifyGatewaysResponse: type: object properties: success: type: boolean data: type: array items: $ref: '#/components/schemas/SuperuserCoolifyGateway' meta: type: object additionalProperties: true includes: type: object additionalProperties: true SuperuserCoolifyGatewayResponse: type: object properties: success: type: boolean data: $ref: '#/components/schemas/SuperuserCoolifyGateway' meta: type: object additionalProperties: true includes: type: object additionalProperties: true SuperuserCoolifyLoadBalancer: type: object properties: configured: type: boolean status: type: string enum: [not_configured, ok, degraded, down] config: $ref: '#/components/schemas/SuperuserCoolifyLoadBalancerConfig' gateways: type: array items: $ref: '#/components/schemas/SuperuserCoolifyGateway' load_balancer: nullable: true allOf: - $ref: '#/components/schemas/SuperuserCoolifyLoadBalancerPublicState' drift: type: object additionalProperties: true last_error: type: string nullable: true SuperuserCoolifyLoadBalancerConfig: type: object properties: automation_enabled: type: boolean automation_mode: type: string enum: [report_only, enforce] load_balancer_id: type: string public_gateway_host: type: string token_set: type: boolean token_source: type: string nullable: true required_services: type: array items: $ref: '#/components/schemas/SuperuserCoolifyLoadBalancerService' SuperuserCoolifyLoadBalancerPublicState: type: object properties: id: type: integer nullable: true name: type: string ipv4: type: string nullable: true ipv6: type: string nullable: true location: type: string nullable: true algorithm: type: string nullable: true targets: type: array items: type: string services: type: array items: $ref: '#/components/schemas/SuperuserCoolifyLoadBalancerService' SuperuserCoolifyLoadBalancerService: type: object properties: protocol: type: string enum: [http, tcp] listen_port: type: integer destination_port: type: integer proxyprotocol: type: boolean SuperuserCoolifyGateway: type: object properties: id: type: integer instance_id: type: integer nullable: true hostname: type: string target_ip: type: string enabled: type: boolean priority: type: integer health_state: type: string lb_state: type: string last_probe: type: object nullable: true additionalProperties: true last_probed_at: type: string nullable: true last_reconciled_at: type: string nullable: true created_at: type: string nullable: true updated_at: type: string nullable: true SuperuserCoolifyGatewaySaveRequest: type: object required: - hostname - target_ip properties: id: type: integer instance_id: type: integer nullable: true hostname: type: string target_ip: type: string enabled: type: boolean default: true priority: type: integer default: 100 SuperuserCoolifyInstance: type: object properties: id: type: integer label: type: string base_url: type: string api_token_set: type: boolean default_project_uuid: type: string nullable: true default_environment_uuid: type: string nullable: true default_environment_name: type: string nullable: true default_server_uuid: type: string nullable: true default_destination_uuid: type: string nullable: true status: type: string last_checked_at: type: string nullable: true last_error: type: string nullable: true SuperuserCoolifyPlacementResponse: type: object properties: success: type: boolean data: type: object properties: generated_at: type: string instance: $ref: '#/components/schemas/SuperuserCoolifyInstance' servers: type: array items: $ref: '#/components/schemas/SuperuserCoolifyPlacementServer' projects: type: array items: $ref: '#/components/schemas/SuperuserCoolifyPlacementProject' environments: type: array items: $ref: '#/components/schemas/SuperuserCoolifyPlacementEnvironment' destination_discovery_supported: type: boolean errors: type: object additionalProperties: true SuperuserCoolifyPlacementServer: type: object properties: id: type: integer nullable: true uuid: type: string name: type: string description: type: string ip: type: string user: type: string port: type: integer nullable: true proxy_type: type: string swarm_cluster: type: string is_reachable: type: boolean nullable: true is_usable: type: boolean nullable: true SuperuserCoolifyPlacementProject: type: object properties: id: type: integer nullable: true uuid: type: string name: type: string description: type: string SuperuserCoolifyPlacementEnvironment: type: object properties: id: type: integer nullable: true uuid: type: string name: type: string description: type: string project_id: type: integer nullable: true project_uuid: type: string project_name: type: string SuperuserCoolifyTarget: type: object properties: id: type: integer instance_id: type: integer instance_label: type: string kind: type: string enum: [database, redis, minio] label: type: string role: type: string enum: [replica] server_uuid: type: string nullable: true project_uuid: type: string nullable: true environment_uuid: type: string nullable: true environment_name: type: string nullable: true destination_uuid: type: string nullable: true resource_uuid: type: string nullable: true resource_type: type: string resource_name: type: string nullable: true deployment_status: type: string availability_state: type: string enum: [protected, degraded, failover_ready, failover_blocked, failing_over, destructive_action_required] last_reconcile_status: type: string nullable: true replication: type: object additionalProperties: true SuperuserCoolifyInstanceCreateRequest: type: object required: - label - base_url - api_token properties: label: type: string base_url: type: string api_token: type: string format: password SuperuserCoolifyTargetCreateRequest: allOf: - $ref: '#/components/schemas/SuperuserReplicationHostCreateRequest' - type: object required: - kind - host properties: instance_id: type: integer kind: type: string enum: [database, redis, minio] role: type: string enum: [replica] default: replica server_uuid: type: string project_uuid: type: string environment_uuid: type: string environment_name: type: string destination_uuid: type: string deploy: type: boolean default: false SuperuserReplicationComposeTemplateResponse: type: object properties: success: type: boolean data: $ref: '#/components/schemas/SuperuserReplicationComposeTemplate' meta: type: object additionalProperties: true includes: type: object additionalProperties: true SuperuserReplicationComposeTemplate: type: object properties: kind: type: string enum: [database, redis, minio] engine: type: string enum: [mariadb, redis, minio] role: type: string enum: [primary, replica] service_name: type: string host_port: type: integer console_port: type: integer nullable: true server_id: type: integer nullable: true compose: type: string description: Complete docker-compose.yml content with secret environment placeholders. env: type: string description: Example .env content for the placeholders used by compose. seed_command: type: string description: One-time MariaDB seed command to initialize a replica from the primary before provisioning. credentials: $ref: '#/components/schemas/SuperuserReplicationGeneratedCredentials' steps: type: array items: type: string SuperuserReplicationGeneratedCredentials: type: object properties: label: type: string host: type: string port: type: integer endpoint: type: string scheme: type: string enum: [http, https] buckets: type: array items: type: string console_port: type: integer replication_transfer_limit: type: string nullable: true space_headroom_percent: type: number format: float database: oneOf: - type: string - type: integer username: type: string password: type: string format: password admin_username: type: string admin_password: type: string format: password replication_username: type: string replication_password: type: string format: password ssl_mode: type: string allow_preseeded_replica: type: boolean SuperuserReplicationSummary: type: object properties: generated_at: type: string format: date-time database: $ref: '#/components/schemas/SuperuserReplicationKindSummary' redis: $ref: '#/components/schemas/SuperuserReplicationKindSummary' minio: $ref: '#/components/schemas/SuperuserReplicationKindSummary' write_freeze: type: object additionalProperties: true SuperuserReplicationKindSummary: type: object properties: primary: $ref: '#/components/schemas/SuperuserReplicationHost' nullable: true hosts: type: array items: $ref: '#/components/schemas/SuperuserReplicationHost' replication: $ref: '#/components/schemas/SuperuserReplicationStatus' SuperuserReplicationStatus: type: object properties: status: type: string enum: [ok, degraded, down, not_configured] min_percent: type: number format: float average_percent: type: number format: float replicas: type: array items: $ref: '#/components/schemas/SuperuserReplicationHost' blockers: type: array items: type: string SuperuserReplicationHost: type: object properties: id: type: integer kind: type: string enum: [database, redis, minio] label: type: string host: type: string port: type: integer database: oneOf: - type: string - type: integer nullable: true endpoint: type: string nullable: true scheme: type: string enum: [http, https] nullable: true buckets: type: array items: type: string console_port: type: integer nullable: true replication_transfer_limit: type: string nullable: true description: MinIO replication and seed bandwidth cap passed to mc --limit-upload/--limit-download, for example 25Mi. Use 0 to disable. space_headroom_percent: type: number format: float nullable: true role: type: string enum: [primary, replica, inactive] status: type: string replication_source_id: type: integer nullable: true replication_percent: type: number format: float last_status: type: object additionalProperties: true credential_summary: type: object additionalProperties: true deployment_provider: type: string enum: [manual, coolify] coolify: type: object nullable: true additionalProperties: true availability_state: type: string nullable: true enum: [protected, degraded, failover_ready, failover_blocked, failing_over, destructive_action_required] SuperuserReplicationHostCreateRequest: type: object required: - host - port properties: label: type: string host: type: string description: Hostname or MinIO endpoint. MinIO hosts may include http(s) scheme; the backend stores the host without scheme. endpoint: type: string description: Optional MinIO endpoint alias for host. port: type: integer database: oneOf: - type: string - type: integer username: type: string description: Database/Redis username or MinIO access key. password: type: string format: password description: Database/Redis password or MinIO secret key. scheme: type: string enum: [http, https] description: MinIO endpoint scheme. buckets: type: array items: type: string description: MinIO buckets to replicate. console_port: type: integer description: Optional MinIO console port for UI display. replication_transfer_limit: type: string description: Optional MinIO replication and seed bandwidth cap. Defaults to 25Mi. Use 0 to disable. space_headroom_percent: type: number format: float description: MinIO free-space headroom required before provisioning. Defaults to 20. admin_username: type: string admin_password: type: string format: password replication_username: type: string replication_password: type: string format: password ssl_mode: type: string deployment_provider: type: string enum: [manual, coolify] options: type: object properties: allow_preseeded_replica: type: boolean description: Allow configuring replication when the replica has already been safely seeded outside the orchestrator. Required for MariaDB, which does not support MySQL Clone. scheme: type: string enum: [http, https] buckets: type: array items: type: string console_port: type: integer replication_transfer_limit: type: string space_headroom_percent: type: number format: float additionalProperties: true SuperuserReplicationHostRenameRequest: type: object required: - label properties: label: type: string minLength: 1 maxLength: 128 SuperuserReplicationUnsavedCredentialTestRequest: allOf: - $ref: '#/components/schemas/SuperuserReplicationHostCreateRequest' - type: object required: - kind properties: kind: type: string enum: [database, databases, mysql, redis, minio, s3, object-storage, object_storage] role: type: string enum: [primary, replica] SuperuserReplicationComposeTemplateRequest: type: object properties: kind: type: string enum: [database, databases, mysql, redis, minio, s3, object-storage, object_storage] default: database role: type: string enum: [primary, replica] default: replica service_name: type: string volume_name: type: string image: type: string database: type: string description: MariaDB database name to create on first startup. username: type: string description: MariaDB application username to create on first startup. password: type: string format: password description: Optional application password to reuse instead of generating one. admin_password: type: string format: password description: Optional MariaDB root password to reuse instead of generating one. replication_username: type: string description: Replication username to place in generated credentials. replication_password: type: string format: password description: Optional replication password to reuse instead of generating one. host_port: type: integer minimum: 1 maximum: 65535 server_id: type: integer minimum: 1 description: MariaDB server-id. Must be unique across the primary and replicas. primary_host: type: string description: Redis primary host used when generating a Redis replica template. primary_port: type: integer minimum: 1 maximum: 65535 description: Redis primary port used when generating a Redis replica template. primary_password: type: string format: password description: Redis primary password used in the generated Redis replica .env file. If omitted, the .env keeps the value blank for manual entry. primary_username: type: string description: Optional Redis primary ACL username used in the generated Redis replica .env file. Leave blank or default for the default Redis user. buckets: type: array items: type: string description: MinIO buckets to create, version, and replicate. console_port: type: integer minimum: 1 maximum: 65535 description: MinIO console port exposed by the generated compose service. replication_transfer_limit: type: string description: MinIO replication and seed bandwidth cap included in generated credentials. Defaults to 25Mi. Use 0 to disable. 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] SuperuserModuleUsage: type: object properties: provider: type: string enum: [licenseplaterecognizer] calls_used: type: integer minimum: 0 quota_calls: type: integer minimum: 1 calls_remaining: type: integer minimum: 0 usage_percent: type: number format: float minimum: 0 version: type: string nullable: true required: - provider - calls_used - quota_calls - calls_remaining - usage_percent 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 replication: $ref: '#/components/schemas/SuperuserReplicationStatus' 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 replication: $ref: '#/components/schemas/SuperuserReplicationStatus' 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 status_reason_key: type: string nullable: true status_reason_params: type: object additionalProperties: true usage: $ref: '#/components/schemas/SuperuserModuleUsage' 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 deterministic 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. max_results: type: integer minimum: 1 maximum: 50 default: 50 description: Maximum number of relevance-ranked results to return. Values above 50 are capped to 50. 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 SystemSearchMeta: type: object properties: query: type: string max_results: type: integer returned: type: integer truncated: type: boolean allowed_types: type: array items: $ref: '#/components/schemas/SystemSearchEntityType' cache: type: object properties: hit: type: boolean required: [hit] required: - query - max_results - returned - truncated - 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, defaultDepartmentId] } 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] SlackConfigEntry: type: object properties: module: { type: string, enum: [Slack] } variable: { type: string, enum: [customer_registration_webhook_url, internal_department_goal_progress_webhook_url, internal_department_ids] } type: { type: string, enum: [string] } value: oneOf: - type: string example: https://hooks.slack.com/services/... - type: string example: '[1,2,3]' required: [module, variable, type, value] SlackConfigTestResult: type: object properties: configured: { type: boolean } sent: { type: boolean } message: { type: string } required: [configured, sent, message] SlackInternalDepartmentGoalProgressDepartment: type: object properties: id: { type: integer } name: { type: string } order_priority: { type: integer } required: [id, name, order_priority] SlackInternalDepartmentGoalProgressConfig: type: object properties: internal_department_goal_progress_webhook_url: type: string example: https://hooks.slack.com/services/... internal_department_ids: type: array items: { type: integer } example: [1, 2, 3] departments: type: array items: $ref: '#/components/schemas/SlackInternalDepartmentGoalProgressDepartment' required: [internal_department_goal_progress_webhook_url, internal_department_ids, departments] SlackInternalDepartmentGoalProgressConfigUpdate: type: object properties: internal_department_goal_progress_webhook_url: type: string example: https://hooks.slack.com/services/... internal_department_ids: type: array items: { type: integer } example: [1, 2, 3] required: [internal_department_goal_progress_webhook_url, internal_department_ids] 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] SlackConfigListResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { type: array, items: { $ref: '#/components/schemas/SlackConfigEntry' } } required: [data] SlackConfigTestResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { $ref: '#/components/schemas/SlackConfigTestResult' } required: [data] SlackInternalDepartmentGoalProgressConfigResponse: allOf: - $ref: '#/components/schemas/ModuleConfigEnvelopeBase' - type: object properties: data: { $ref: '#/components/schemas/SlackInternalDepartmentGoalProgressConfig' } 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 limited_backoffice_managed: type: boolean description: Whether the user is managed through limited backoffice and has locked raw role/customer edits 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 SubuserManagementRow: type: object description: Chauffeur management row. Superuser list endpoints return one row per chauffeur account; grant-specific fields mirror a primary grant for backwards compatibility, and all visible customer grants are listed in `grants`. 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 } setup_required: { type: boolean } invite_accepted: { type: boolean } can_resend_invite: { type: boolean } profile_editable_by_manager: { type: boolean } customer_number: { type: integer } customer_name: { type: string, nullable: true } grant_id: { type: integer, nullable: true } grant_enabled: { type: boolean } grant_note: { type: string, nullable: true } grant_permissions: type: array items: { type: string } permissions: type: array items: { type: string } permission_template_key: type: string enum: [deactivated, driver, booking_coordinator, fleet_admin, custom] permission_groups: type: array items: $ref: '#/components/schemas/SubuserPermissionGroup' grant_created_at: { type: string, format: date-time, nullable: true } grant_updated_at: { type: string, format: date-time, nullable: true } grants: type: array description: Visible customer access grants for this chauffeur, grouped under the single chauffeur row. items: $ref: '#/components/schemas/SubuserManagementGrant' grant_count: { type: integer } customer_numbers: type: array items: { type: integer } access_state: type: string enum: [active, pending_setup, disabled, inactive] SubuserManagementGrant: type: object properties: grant_id: { type: integer } customer_number: { type: integer } customer_name: { type: string, nullable: true } grant_enabled: { type: boolean } grant_note: { type: string, nullable: true } grant_permissions: type: array items: { type: string } permissions: type: array items: { type: string } permission_template_key: type: string enum: [deactivated, driver, booking_coordinator, fleet_admin, custom] permission_groups: type: array items: $ref: '#/components/schemas/SubuserPermissionGroup' grant_created_at: { type: string, format: date-time, nullable: true } grant_updated_at: { type: string, format: date-time, nullable: true } access_state: type: string enum: [active, pending_setup, disabled, inactive] SubuserManagementSummary: type: object properties: total: { type: integer } active: { type: integer } pending_setup: { type: integer } disabled: { type: integer } 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 permission_template_key: type: string enum: [deactivated, driver, booking_coordinator, fleet_admin] SubuserGrantUpdateRequest: type: object properties: enabled: type: boolean note: type: string nullable: true maxLength: 65535 permissions: type: array items: type: string permission_template_key: type: string enum: [deactivated, driver, booking_coordinator, fleet_admin] SubuserPermissionGroup: type: object properties: key: type: string capabilities: type: array items: type: string SubuserPermissionTemplate: type: object properties: key: type: string enum: [deactivated, driver, booking_coordinator, fleet_admin] label: type: string description: type: string enabled: type: boolean permissions: type: array items: type: string permission_groups: type: array items: $ref: '#/components/schemas/SubuserPermissionGroup' SubuserGrantSummary: type: object description: Summary of a subuser grant grouped by billing customer number properties: billing_customer_number: type: integer description: e-conomic customer number this grant applies to permissions: type: array description: List of permission node keys enabled for this customer items: type: string SubuserSelf: type: object description: Authenticated subuser profile with enabled grants properties: id: type: integer username: type: string name: type: string nullable: true email: type: string format: email nullable: true phone_country_code: type: integer nullable: true phone: type: integer nullable: true grants: type: array description: Enabled, non-deleted grants for the subuser grouped by billing customer number items: $ref: '#/components/schemas/SubuserGrantSummary' created_at: type: string format: date-time nullable: true updated_at: type: string format: date-time nullable: true suspended_at: type: string format: date-time nullable: true two_factor_enabled: type: boolean description: Indicates if 2FA is enabled for this account PermissionNode: type: object properties: key: type: string description: Permission node key name: type: string description: type: string type: type: string description: Permission type (e.g., TOGGLE) default: type: boolean PermissionNodeGroup: type: object properties: group: type: string description: type: string nodes: type: array items: $ref: '#/components/schemas/PermissionNode' UserCreate: type: object required: - customer_number - password properties: customer_number: type: integer password: type: string format: password display_name: type: string group_id: type: integer email: type: string format: email phone: type: integer phone_country_code: type: integer UserUpdate: type: object properties: id: type: integer customer_number: type: integer display_name: type: string group_id: type: integer email: type: string format: email phone: type: integer phone_country_code: type: integer Order: type: object properties: id: type: integer description: Order ID customer_id: type: integer description: Customer number customer_name: type: string description: Customer name user_id: type: integer description: User ID cashier_id: type: integer description: Cashier user ID cashier_name: type: string description: Cashier name department_id: type: integer description: Department ID status: type: string description: Order status total_net_amount: type: number format: float description: Total order amount po: type: string description: Purchase order number nullable: true lane: type: string description: Lane information nullable: true created_at: type: string format: date-time updated_at: type: string format: date-time EconomicTransferQueueStatus: type: string enum: - QUEUED - PROCESSING - COMPLETED - FAILED EconomicTransferQueueJob: type: object properties: id: type: integer transfer_type: type: string enum: - ORDER_DRAFT_EXPORT - ORDER_INVOICE_EXPORT - COLLECTED_INVOICE_EXPORT status: $ref: '#/components/schemas/EconomicTransferQueueStatus' progress_percent: type: integer minimum: 0 maximum: 100 progress_message: type: string nullable: true attempts: type: integer minimum: 0 max_attempts: type: integer minimum: 1 error_message: type: string nullable: true payload: type: object nullable: true additionalProperties: true result: type: object nullable: true additionalProperties: true details_summary: type: object nullable: true additionalProperties: true created_by: type: integer nullable: true created_at: type: string format: date-time nullable: true updated_at: type: string format: date-time nullable: true started_at: type: string format: date-time nullable: true completed_at: type: string format: date-time nullable: true next_retry_at: type: string format: date-time nullable: true required: - id - transfer_type - status - progress_percent - attempts - max_attempts EconomicTransferQueueEnqueueResponse: type: object properties: success: type: boolean data: type: object properties: message: type: string job_id: type: integer minimum: 1 job: $ref: '#/components/schemas/EconomicTransferQueueJob' required: - message - job_id - job meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferSynchronousFallbackResponse: type: object properties: success: type: boolean data: type: object properties: message: type: string mode: type: string enum: [synchronous_fallback] result: type: object additionalProperties: true required: - message - mode - result meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferQueueStatusResponse: type: object properties: success: type: boolean data: $ref: '#/components/schemas/EconomicTransferQueueJob' meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferQueueRetryResponse: type: object properties: success: type: boolean data: type: object properties: message: type: string job: $ref: '#/components/schemas/EconomicTransferQueueJob' required: - message - job meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferQueueRunResponse: type: object properties: success: type: boolean data: type: object properties: message: type: string processed: type: integer minimum: 0 completed: type: integer minimum: 0 failed: type: integer minimum: 0 jobs: type: array items: type: integer minimum: 1 limit: type: integer minimum: 1 maximum: 10 transfer_type: type: string enum: - COLLECTED_INVOICE_EXPORT required: - message - processed - completed - failed - jobs - limit - transfer_type meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferQueueListResponse: type: object properties: success: type: boolean data: type: object properties: items: type: array items: $ref: '#/components/schemas/EconomicTransferQueueJob' count: type: integer minimum: 0 total: type: integer minimum: 0 limit: type: integer minimum: 1 offset: type: integer minimum: 0 has_more: type: boolean required: - items - count - total - limit - offset - has_more meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferQueueMonitorResponse: type: object properties: success: type: boolean data: type: object properties: jobs: type: array items: $ref: '#/components/schemas/EconomicTransferQueueJob' counts: type: object properties: queued: type: integer minimum: 0 in_progress: type: integer minimum: 0 failed: type: integer minimum: 0 completed: type: integer minimum: 0 total: type: integer minimum: 0 required: - queued - in_progress - failed - completed - total progress_percent: type: integer minimum: 0 maximum: 100 limit: type: integer minimum: 1 maximum: 100 required: - jobs - counts - progress_percent - limit meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferQueueDismissResponse: type: object properties: success: type: boolean data: type: object properties: message: type: string job: $ref: '#/components/schemas/EconomicTransferQueueJob' required: - message - job meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes EconomicTransferQueueDismissTerminalResponse: type: object properties: success: type: boolean data: type: object properties: message: type: string dismissed_count: type: integer minimum: 0 required: - message - dismissed_count meta: oneOf: - type: array items: {} - type: object additionalProperties: true includes: oneOf: - type: array items: {} - type: object additionalProperties: true required: - success - data - meta - includes OrderAttachmentDownloadLinkResponse: type: object required: [success, data, meta, includes] properties: success: type: boolean enum: [true] data: type: object required: [download_link] properties: download_link: type: string format: uri pattern: '^https://' meta: type: object additionalProperties: true includes: type: object additionalProperties: true InvoicingPeriodResponseEnvelope: type: object required: [success, data, meta, includes] properties: success: type: boolean enum: [true] data: $ref: '#/components/schemas/InvoicingPeriodData' meta: type: object additionalProperties: true includes: type: object additionalProperties: true InvoicingPeriodData: type: object required: [dateFrom, dateTo, types] properties: dateFrom: type: string format: date dateTo: type: string format: date types: type: object additionalProperties: type: array items: $ref: '#/components/schemas/InvoicingPeriodCustomer' InvoicingPeriodCustomer: type: object required: [customer_number, customer_name, transactions, invoice_collections] additionalProperties: true properties: customer_number: type: integer customer_name: type: string transactions: type: array items: $ref: '#/components/schemas/InvoicingPeriodTransaction' invoice_collections: type: array items: $ref: '#/components/schemas/InvoicingPeriodInvoiceCollection' InvoicingPeriodTransaction: type: object required: [id, booked, invoice_state] additionalProperties: true properties: id: type: integer invoice_collection_id: type: integer nullable: true booked: type: boolean invoice_state: type: string enum: [open, closed, economic_draft, economic_booked] completed_at: type: string format: date-time nullable: true amount: type: number format: float InvoicingPeriodInvoiceCollection: type: object required: - id - invoice_collection_id - customer_number - state - order_ids - order_count - total_net_amount additionalProperties: true properties: id: type: integer invoice_collection_id: type: integer customer_number: type: integer name: type: string external_id: type: string nullable: true booked_invoice_id: type: integer nullable: true state: type: string enum: [open, closed, economic_draft, economic_booked] order_ids: type: array items: type: integer order_count: type: integer minimum: 0 total_net_amount: type: number format: float 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 CollectedInvoiceEconomicPdfResponse: type: object description: Presigned PDF URL for a draft or booked e-conomic invoice attached to a collected invoice properties: collected_invoice_id: type: integer example: 123 type: type: string enum: - draft - booked example: booked economic_invoice_id: type: integer example: 28368 url: type: string format: uri required: - collected_invoice_id - type - economic_invoice_id - url CollectedInvoiceEconomicV2DetailsResponse: type: object properties: collected_invoice_id: type: integer external_id: type: string order_ids: type: array items: type: integer economic: type: object required: [draft_id, booked_id, state, available_pdf_type] properties: draft_id: type: integer nullable: true booked_id: type: integer nullable: true state: type: string enum: [draft, booked, none] description: Current authoritative e-conomic target state available_pdf_type: type: string enum: [draft, booked] nullable: true description: PDF target that callers should request customer: $ref: '#/components/schemas/CollectedInvoiceEconomicV2CustomerSummary' internal: type: object required: [normalized] properties: normalized: $ref: '#/components/schemas/EconomicV2NormalizedInvoice' draft: type: object properties: exists: type: boolean raw: type: object nullable: true additionalProperties: true normalized: allOf: - $ref: '#/components/schemas/EconomicV2NormalizedInvoice' nullable: true booked: type: object properties: exists: type: boolean raw: type: object nullable: true additionalProperties: true normalized: allOf: - $ref: '#/components/schemas/EconomicV2NormalizedInvoice' nullable: true warnings: type: array items: type: string required: - collected_invoice_id - order_ids - economic - customer - internal - draft - booked - warnings CollectedInvoiceEconomicV2CustomerSummary: type: object properties: internal_customer_number: type: integer nullable: true draft_customer_number: type: integer nullable: true booked_customer_number: type: integer nullable: true exists: type: boolean name: type: string nullable: true barred: type: boolean nullable: true required: - exists CollectedInvoiceEconomicV2CompareResponse: type: object properties: collected_invoice_id: type: integer details: $ref: '#/components/schemas/CollectedInvoiceEconomicV2DetailsResponse' comparison: $ref: '#/components/schemas/EconomicV2Comparison' warnings: type: array items: type: string required: - collected_invoice_id - details - comparison - warnings CollectedInvoiceEconomicV2CompareBulkResponse: type: object properties: requested: type: integer compared: type: integer failed: type: integer results: type: array items: $ref: '#/components/schemas/CollectedInvoiceEconomicV2CompareResponse' errors: type: array items: type: object properties: collected_invoice_id: type: integer error: type: string required: - requested - compared - failed - results - errors CollectedInvoiceEconomicV2RevenueStatisticsResponse: type: object properties: filters: type: object properties: dateFrom: type: string format: date dateTo: type: string format: date customer_numbers: type: array items: type: integer department_numbers: type: array items: type: integer currency: type: string nullable: true barred: type: string enum: [all, barred, active] max_pages: type: integer summary: $ref: '#/components/schemas/EconomicV2RevenueSummary' customers: type: array items: $ref: '#/components/schemas/EconomicV2RevenueCustomerStat' departments: type: array items: $ref: '#/components/schemas/EconomicV2RevenueDepartmentStat' currencies: type: array items: $ref: '#/components/schemas/EconomicV2RevenueCurrencyStat' warnings: type: array items: type: string required: - filters - summary - customers - departments - currencies - warnings EconomicV2RevenueSummary: type: object properties: invoice_count: type: integer line_count: type: integer unique_customers: type: integer net_amount: type: number vat_amount: type: number gross_amount: type: number average_invoice_net_amount: type: number required: - invoice_count - line_count - unique_customers - net_amount - vat_amount - gross_amount - average_invoice_net_amount EconomicV2RevenueCustomerStat: type: object properties: customer_number: type: integer customer_name: type: string nullable: true barred: type: boolean nullable: true invoice_count: type: integer net_amount: type: number vat_amount: type: number gross_amount: type: number required: - customer_number - invoice_count - net_amount - vat_amount - gross_amount EconomicV2RevenueDepartmentStat: type: object properties: department_key: type: string department_number: type: integer nullable: true invoice_count: type: integer line_count: type: integer net_amount: type: number vat_amount: type: number gross_amount: type: number required: - department_key - invoice_count - line_count - net_amount - vat_amount - gross_amount EconomicV2RevenueCurrencyStat: type: object properties: currency: type: string invoice_count: type: integer net_amount: type: number vat_amount: type: number gross_amount: type: number required: - currency - invoice_count - net_amount - vat_amount - gross_amount EconomicV2NormalizedInvoice: type: object properties: source: type: string enum: [internal, draft, booked] totals: type: object properties: net_total: type: number line_net_total: type: number line_count: type: integer billable_line_count: type: integer difference_from_line_sum: type: number nullable: true departments: $ref: '#/components/schemas/EconomicV2DepartmentDistribution' lines: type: array items: $ref: '#/components/schemas/EconomicV2NormalizedLineItem' warnings: type: array items: type: string required: - source - totals - departments - lines - warnings EconomicV2NormalizedLineItem: type: object properties: index: type: integer source: type: string source_order_id: type: integer nullable: true source_line_id: type: integer nullable: true line_type: type: string enum: [product, discount, text] billable: type: boolean product_number: type: string nullable: true product_id: type: integer nullable: true description: type: string reference: type: string quantity: type: number unit_net_price: type: number line_net_amount: type: number department_distribution: $ref: '#/components/schemas/EconomicV2DepartmentDistribution' match_key: type: string required: - source - line_type - billable - description - reference - quantity - unit_net_price - line_net_amount - department_distribution - match_key EconomicV2DepartmentDistribution: type: object additionalProperties: type: number example: "75": 100 EconomicV2Comparison: type: object properties: totals: type: object properties: internal_net_total: type: number targets: type: object properties: draft: $ref: '#/components/schemas/EconomicV2TargetComparison' booked: $ref: '#/components/schemas/EconomicV2TargetComparison' warnings: type: array items: type: string required: - totals - targets - warnings EconomicV2TargetComparison: type: object properties: target: type: string enum: [draft, booked] status: type: string enum: [exact_match, partial_mismatch, total_mismatch, missing_target] overall_match: type: boolean totals: $ref: '#/components/schemas/EconomicV2TotalsComparison' lines: type: object properties: summary: type: object properties: internal_billable_count: type: integer target_billable_count: type: integer mismatch_count: type: integer diff: type: array items: $ref: '#/components/schemas/EconomicV2LineDiffEntry' departments: type: object properties: matches: type: boolean diff: type: array items: $ref: '#/components/schemas/EconomicV2DepartmentDiffEntry' mismatch_reasons: type: array items: type: string warnings: type: array items: type: string required: - target - status - overall_match - totals - lines - departments - mismatch_reasons - warnings EconomicV2TotalsComparison: type: object properties: internal_net_total: type: number nullable: true target_net_total: type: number nullable: true difference: type: number nullable: true abs_difference: type: number nullable: true matches: type: boolean required: - matches EconomicV2LineDiffEntry: type: object properties: match_key: type: string reasons: type: array items: type: string internal_line: allOf: - $ref: '#/components/schemas/EconomicV2NormalizedLineItem' nullable: true target_line: allOf: - $ref: '#/components/schemas/EconomicV2NormalizedLineItem' nullable: true required: - match_key - reasons EconomicV2DepartmentDiffEntry: type: object properties: department_key: type: string internal_amount: type: number target_amount: type: number difference: type: number matches: type: boolean required: - department_key - internal_amount - target_amount - difference - matches InvoicingDistributionV2Transaction: type: object properties: id: type: integer date: type: string format: date-time amount: type: number booked: type: boolean department_id: type: integer excluded: type: boolean required: [id, date, amount, booked, department_id, excluded] InvoicingDistributionV2Customer: type: object properties: id: type: integer nullable: true customer_number: type: integer customer_name: type: string transactions: type: array items: $ref: '#/components/schemas/InvoicingDistributionV2Transaction' requires_action: type: boolean meta: type: object additionalProperties: true required: [customer_number, customer_name, transactions, requires_action, meta] InvoicingDistributionV2CategoryResponse: type: object properties: customers: type: array items: $ref: '#/components/schemas/InvoicingDistributionV2Customer' collective_results: type: object additionalProperties: true warnings: type: array items: type: string required: [customers, collective_results, warnings] InvoicingDistributionV2FixedPricingResponse: $ref: '#/components/schemas/InvoicingDistributionV2CategoryResponse' InvoicingDistributionV2WashSubscriptionsResponse: $ref: '#/components/schemas/InvoicingDistributionV2CategoryResponse' InvoicingDistributionV2CustomerPricesResponse: $ref: '#/components/schemas/InvoicingDistributionV2CategoryResponse' InvoicingDistributionV2BookedDepartment75Group: type: object properties: month: type: string example: '2026-01' source_category: type: string enum: [fixed_pricing, wash_subscriptions, unclassified] invoice_ids: type: array items: type: integer booked_net_amount: type: number department_distribution: $ref: '#/components/schemas/EconomicV2DepartmentDistribution' undistributed_net_amount: type: number required: - month - source_category - invoice_ids - booked_net_amount - department_distribution - undistributed_net_amount InvoicingDistributionV2BookedDepartment75Meta: type: object properties: booked_net_amount: type: number distributed_net_amount: type: number undistributed_net_amount: type: number department_distribution: $ref: '#/components/schemas/EconomicV2DepartmentDistribution' booked_groups: type: array items: $ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75Group' required: - booked_net_amount - distributed_net_amount - undistributed_net_amount - department_distribution - booked_groups InvoicingDistributionV2BookedDepartment75Customer: allOf: - $ref: '#/components/schemas/InvoicingDistributionV2Customer' - type: object properties: meta: type: object properties: booked_department_75: $ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75Meta' required: - booked_department_75 InvoicingDistributionV2BookedDepartment75CollectiveResults: type: object properties: booked_net_amount: type: number distributed_net_amount: type: number undistributed_net_amount: type: number department_distribution: $ref: '#/components/schemas/EconomicV2DepartmentDistribution' department_distribution_parsed: type: object additionalProperties: type: number required: - booked_net_amount - distributed_net_amount - undistributed_net_amount - department_distribution - department_distribution_parsed InvoicingDistributionV2BookedDepartment75Response: type: object properties: customers: type: array items: $ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75Customer' collective_results: $ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75CollectiveResults' warnings: type: array items: type: string required: [customers, collective_results, warnings] InvoicingDistributionV2AllResponse: type: object properties: fixed_pricing: $ref: '#/components/schemas/InvoicingDistributionV2FixedPricingResponse' wash_subscriptions: $ref: '#/components/schemas/InvoicingDistributionV2WashSubscriptionsResponse' customer_prices: $ref: '#/components/schemas/InvoicingDistributionV2CustomerPricesResponse' booked_department_75: $ref: '#/components/schemas/InvoicingDistributionV2BookedDepartment75Response' required: [fixed_pricing, wash_subscriptions, customer_prices, booked_department_75] PricingHistoryVersionEntry: type: object properties: id: type: integer type: type: string enum: [fixed_pricing, vehicle_subscription, discount_override] customer_number: type: integer effective_from: type: string format: date-time effective_to: type: string format: date-time nullable: true source: type: string confidence: type: number minimum: 0 maximum: 1 inferred: type: boolean metadata_json: oneOf: - type: string - type: object additionalProperties: true - type: array items: {} nullable: true required: - id - type - customer_number - effective_from - source - confidence - inferred CustomerPricingHistoryResponse: type: object properties: customer_number: type: integer fixed_pricing: type: array items: type: object additionalProperties: true vehicle_subscriptions: type: array items: type: object additionalProperties: true discount_overrides: type: array items: type: object additionalProperties: true timeline: type: array items: $ref: '#/components/schemas/PricingHistoryVersionEntry' required: - customer_number - fixed_pricing - vehicle_subscriptions - discount_overrides - timeline InvoicingWashSubscriptionsDistributionResponse: type: object properties: success: type: boolean example: true data: type: array items: $ref: '#/components/schemas/InvoicingWashSubscriptionsDistributionCustomer' meta: $ref: '#/components/schemas/InvoicingFixedPricingDistributionMeta' includes: type: object additionalProperties: true required: - success - data - meta - includes InvoicingWashSubscriptionsDistributionCustomer: type: object properties: id: type: integer nullable: true customer_number: type: integer customer_name: type: string transactions: type: array items: $ref: '#/components/schemas/InvoicingFixedPricingDistributionTransaction' requires_action: type: boolean meta: type: object properties: subscription: type: object additionalProperties: true required: - subscription required: - customer_number - customer_name - transactions - requires_action - meta InvoicingFixedPricingDistributionResponse: type: object properties: success: type: boolean example: true data: type: array items: $ref: '#/components/schemas/InvoicingFixedPricingDistributionCustomer' meta: $ref: '#/components/schemas/InvoicingFixedPricingDistributionMeta' includes: $ref: '#/components/schemas/InvoicingFixedPricingDistributionIncludes' required: - success - data - meta - includes InvoicingFixedPricingDistributionCustomer: type: object properties: id: type: integer customer_number: type: integer customer_name: type: string transactions: type: array items: $ref: '#/components/schemas/InvoicingFixedPricingDistributionTransaction' requires_action: type: boolean meta: type: object properties: fixed_pricing: $ref: '#/components/schemas/InvoicingFixedPricingDistributionFixedPricing' required: - fixed_pricing required: - id - customer_number - customer_name - transactions - requires_action - meta InvoicingFixedPricingDistributionTransaction: type: object properties: id: type: integer date: type: string description: Datetime in `YYYY-MM-DD HH:mm:ss` format. example: '2026-02-02 10:43:41' amount: type: number booked: type: boolean excluded: type: boolean required: - id - date - amount - booked - excluded InvoicingFixedPricingDistributionFixedPricing: type: object properties: customer_number: type: integer price: type: number description: type: string original_price: type: number department_totals: $ref: '#/components/schemas/InvoicingFixedPricingDistributionNumberMapOrEmptyArray' department_totals_relative: $ref: '#/components/schemas/InvoicingFixedPricingDistributionNumberMapOrEmptyArray' required: - customer_number - price - description - original_price - department_totals - department_totals_relative InvoicingFixedPricingDistributionMeta: type: object properties: date_from: type: string description: Datetime in `YYYY-MM-DD HH:mm:ss` format. example: '2026-02-01 00:00:00' date_to: type: string description: Datetime in `YYYY-MM-DD HH:mm:ss` format. example: '2026-02-28 23:59:59' required: - date_from - date_to InvoicingFixedPricingDistributionIncludes: type: object properties: debug_invoicing_period_customers_with_orders_in_date_range: $ref: '#/components/schemas/InvoicingFixedPricingDistributionExecutionTime' debug_invoicing_period_process_customer_numbers: $ref: '#/components/schemas/InvoicingFixedPricingDistributionExecutionTime' debug_invoicing_period_get_transactions_for_customers_in_date_range: $ref: '#/components/schemas/InvoicingFixedPricingDistributionExecutionTime' debug_invoicing_period_calculate_transaction_totals: $ref: '#/components/schemas/InvoicingFixedPricingDistributionExecutionTime' debug_invoicing_period_construct_customer_objects: $ref: '#/components/schemas/InvoicingFixedPricingDistributionExecutionTime' collective_fixed_pricing_results: $ref: '#/components/schemas/InvoicingFixedPricingDistributionCollectiveResults' additionalProperties: true InvoicingFixedPricingDistributionExecutionTime: type: object properties: execution_time: type: number required: - execution_time InvoicingFixedPricingDistributionCollectiveResults: type: object properties: total_fixed_price: type: number total_original_price: type: number total_department_totals: $ref: '#/components/schemas/InvoicingFixedPricingDistributionNumberMapOrEmptyArray' total_department_totals_relative: $ref: '#/components/schemas/InvoicingFixedPricingDistributionNumberMapOrEmptyArray' total_department_totals_parsed: type: object additionalProperties: type: number total_department_totals_relative_parsed: type: object additionalProperties: type: number required: - total_fixed_price - total_original_price - total_department_totals - total_department_totals_relative - total_department_totals_parsed - total_department_totals_relative_parsed InvoicingFixedPricingDistributionNumberMapOrEmptyArray: oneOf: - type: object additionalProperties: type: number - type: array maxItems: 0 SelfServeLaneStatus: type: object properties: id: type: integer description: Lane ID status: type: string description: Current lane status (e.g., IDLE, OCCUPIED) mode: type: string description: Current lane mode (e.g., AUTOMATIC, MANUAL) state: type: string description: Current lane state (e.g., READY, WASHING) wash_start_time: type: integer description: Timestamp when the wash started (0 if not washing) nullable: true elapsed_wash_time: type: integer description: Elapsed wash time in seconds nullable: true license_plate: type: string description: License plate of the vehicle in the lane nullable: true customer_number: type: integer description: Customer number associated with the current lane use nullable: true SelfServeLaneMachineRelayStatus: type: object properties: lane_id: type: integer relay: type: string enum: [MACHINE, MACHINE_PROGRAM_PICKER, MACHINE_CLEANER] relay_id: type: string online: type: boolean on: type: boolean SelfServeConfig: type: object properties: enabled: type: boolean description: Whether the self-serve module is enabled minute_product: type: integer description: The product ID used for minute-based billing machine_wash_minutes_included: type: integer description: Included machine wash minutes before minute-based billing starts SelfserveLaneService: type: string description: Allowed self-serve lane service name enum: - MACHINE SelfserveStudioNode: type: object required: [id, position, data] properties: id: type: string type: type: string nullable: true position: type: object required: [x, y] properties: x: { type: number } y: { type: number } data: type: object additionalProperties: true properties: kind: type: string enum: [question, condition, rule, task, lane, machine_type, vehicle_type, edge_gateway, relay_binding, relay, runtime_checkpoint] object_id: oneOf: - type: integer - type: string nullable: true label: type: string raw: type: object additionalProperties: true SelfserveStudioEdge: type: object required: [id, source, target] properties: id: { type: string } source: { type: string } target: { type: string } type: { type: string, nullable: true } label: { type: string, nullable: true } data: type: object additionalProperties: true SelfserveStudioLayout: type: object properties: nodes: type: object additionalProperties: type: object properties: x: { type: number } y: { type: number } viewport: type: object additionalProperties: true runtime_affecting: type: boolean enum: [false] SelfserveStudioValidation: type: object properties: valid: type: boolean errors: type: array items: { type: string } warnings: type: array items: { type: string } items: type: array items: type: object properties: severity: type: string enum: [error, warning] message: type: string stats: type: object additionalProperties: true validated_at: type: string format: date-time SelfserveConfigVersion: type: object properties: id: { type: integer } department_id: { type: integer } status: type: string enum: [DRAFT, PUBLISHED, ARCHIVED] version_number: { type: integer } config: $ref: '#/components/schemas/SelfserveStudioV2Config' validation_result: $ref: '#/components/schemas/SelfserveStudioValidation' source_version_id: { type: integer, nullable: true } created_by: { type: integer, nullable: true } published_at: { type: string, nullable: true } created_at: { type: string, nullable: true } updated_at: { type: string, nullable: true } SelfserveStudioV2Config: type: object required: [schema_version, questions, conditions, rules, tasks] properties: schema_version: type: integer enum: [2] department_id: type: integer questions: type: array items: type: object additionalProperties: true conditions: type: array items: $ref: '#/components/schemas/SelfserveStudioV2Condition' rules: type: array description: Empty in schema_version 2; retained only for backward-compatible payload shape. maxItems: 0 items: type: object tasks: type: array items: type: object additionalProperties: true v2_meta: type: object additionalProperties: true migration_issues: type: array items: type: object additionalProperties: true SelfserveStudioV2Condition: type: object required: [id, expression] properties: id: type: integer name: type: string description: type: string nullable: true expression: $ref: '#/components/schemas/SelfserveStudioV2Expression' additionalProperties: true SelfserveStudioV2Expression: oneOf: - $ref: '#/components/schemas/SelfserveStudioV2ExpressionGroup' - $ref: '#/components/schemas/SelfserveStudioV2ExpressionPredicate' SelfserveStudioV2ExpressionGroup: type: object required: [type, operator, children] properties: type: type: string enum: [group] operator: type: string enum: [ALL, ANY] children: type: array items: $ref: '#/components/schemas/SelfserveStudioV2Expression' SelfserveStudioV2ExpressionPredicate: type: object required: [type, subject_type, subject_id, operator] properties: type: type: string enum: [predicate] subject_type: type: string enum: [question, condition] subject_id: type: integer operator: type: string enum: [IS_TRUE, IS_FALSE, IS_SET, IS_TRUE_OR_NOT_SET, IS_FALSE_OR_NOT_SET] SelfserveStudioGraph: type: object required: [nodes, edges, lookups, validation, layout, versions, simulator_defaults, gateway_workspace, permissions] properties: nodes: type: array items: $ref: '#/components/schemas/SelfserveStudioNode' edges: type: array items: $ref: '#/components/schemas/SelfserveStudioEdge' lookups: type: object additionalProperties: true validation: $ref: '#/components/schemas/SelfserveStudioValidation' layout: $ref: '#/components/schemas/SelfserveStudioLayout' versions: type: array items: $ref: '#/components/schemas/SelfserveConfigVersion' active_config: allOf: - $ref: '#/components/schemas/SelfserveStudioV2Config' nullable: true draft: type: object additionalProperties: true simulator_defaults: type: object additionalProperties: true gateway_workspace: type: object additionalProperties: true permissions: type: object additionalProperties: type: boolean meta: type: object additionalProperties: true SelfserveStudioGraphOperation: type: object properties: action: type: string enum: [create, update, delete, connect, disconnect, reorder, upsert, upsert_path] entity: type: string enum: [question, condition, task, action, path] description: Standalone rule operations are not accepted for schema_version 2 drafts. id: type: integer nullable: true source: type: string nullable: true target: type: string nullable: true data: type: object additionalProperties: true items: type: array items: type: object additionalProperties: true SelfserveStudioGraphSaveRequest: type: object required: [department] properties: department: { type: integer } operations: type: array items: $ref: '#/components/schemas/SelfserveStudioGraphOperation' nodes: type: array items: $ref: '#/components/schemas/SelfserveStudioNode' layout: $ref: '#/components/schemas/SelfserveStudioLayout' SelfserveStudioLayoutSaveRequest: type: object required: [department, layout] properties: department: { type: integer } layout: $ref: '#/components/schemas/SelfserveStudioLayout' SelfserveMachineType: type: object properties: id: type: integer name: type: string description: type: string nullable: true created_at: type: string format: date-time updated_at: type: string format: date-time nullable: true SelfserveVisibleQuestion: type: object properties: id: type: integer question: type: string description: type: string condition_id: type: integer nullable: true order_priority: type: integer answer: type: boolean nullable: true SelfserveTaskDecision: type: object properties: id: type: integer task: type: string description: type: string condition_id: type: integer nullable: true order_priority: type: integer services: type: array items: $ref: '#/components/schemas/SelfserveLaneService' buttons: type: array items: type: integer SelfserveWashSession: type: object properties: id: type: integer lane_id: type: integer department_id: type: integer machine_type_id: type: integer nullable: true customer_number: type: integer nullable: true vehicle_id: type: integer nullable: true vehicle_type_id: type: integer nullable: true reg: type: string status: type: string enum: - PENDING_QUESTIONS - READY_FOR_MACHINE_START - MACHINE_NOT_ALLOWED - MACHINE_RELAY_ENABLED - MACHINE_STARTED - COMPLETED - FORCE_STOPPED 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 enum: - SESSION_SYNCED - MACHINE_RELAY_ENABLED - MACHINE_START_TRIGGERED - SESSION_COMPLETED - SESSION_FORCE_STOPPED payload: type: object additionalProperties: true nullable: true created_at: type: string format: date-time SelfserveStudioSimulationDebug: type: object required: [summary, parameters, stages, questions, conditions, rules, tasks, hardware, graph_annotations, recommendations] properties: summary: type: object additionalProperties: true parameters: type: object additionalProperties: true stages: type: array items: type: object additionalProperties: true questions: type: array items: type: object additionalProperties: true conditions: type: array items: type: object additionalProperties: true rules: type: array items: type: object additionalProperties: true tasks: type: array items: type: object additionalProperties: true actions: type: array items: type: object additionalProperties: true dynamic_image_buttons: type: array items: type: object additionalProperties: true decisions: type: array items: type: object required: [kind, id, label, state, reason, node_ids, causes] properties: kind: type: string id: oneOf: - type: integer - type: string nullable: true label: type: string state: type: string reason: type: string node_ids: type: array items: type: string causes: type: array items: type: object additionalProperties: true additionalProperties: true hardware: type: object additionalProperties: true graph_annotations: type: object properties: nodes: type: object additionalProperties: type: object additionalProperties: true edges: type: object additionalProperties: type: object additionalProperties: true recommendations: type: array items: type: object additionalProperties: true SelfserveStudioSimulationResponse: allOf: - $ref: '#/components/schemas/SelfserveVehicleAllowedResponse' - type: object properties: simulator_version: { type: integer } dry_run: { type: boolean, enum: [true] } mode: { type: string, enum: [full_dry_run] } config_source: { type: string, enum: [draft, published] } debug: $ref: '#/components/schemas/SelfserveStudioSimulationDebug' SelfserveStudioPathOutcomesRequest: type: object required: [department] properties: department: type: integer lane_id: type: integer nullable: true vehicle_type_id: type: integer nullable: true config_source: type: string enum: [draft, published] default: draft hardware_mode: type: string enum: [studio, real, none] default: studio include_hardware: type: boolean default: true max_states: type: integer minimum: 1 maximum: 2048 default: 2048 nullable: true description: Optional debug cap for explored states. Omitted and larger values are capped at 2048. path_sample_limit: type: integer minimum: 1 maximum: 200 default: 200 nullable: true description: Optional cap for returned path rows. Omitted and larger values are capped at 200. SelfserveStudioPathOutcomesResponse: type: object required: [scope, summary, outcomes, paths, warnings, truncated, progress] properties: scope: type: object additionalProperties: true summary: type: object required: [state_count, terminal_path_count, outcome_count, question_count, max_states, path_sample_count] properties: state_count: { type: integer } terminal_path_count: { type: integer } outcome_count: { type: integer } question_count: { type: integer } question_ids: type: array items: { type: integer } max_states: { type: integer } path_sample_count: { type: integer } confirmations: $ref: '#/components/schemas/SelfserveStudioPathConfirmationSummary' outcomes: type: array items: $ref: '#/components/schemas/SelfserveStudioPathOutcome' paths: type: array items: $ref: '#/components/schemas/SelfserveStudioPathResult' warnings: type: array items: { type: string } truncated: type: boolean progress: $ref: '#/components/schemas/SelfserveStudioPathProgress' confirmations: type: object properties: summary: $ref: '#/components/schemas/SelfserveStudioPathConfirmationSummary' removed: type: array items: $ref: '#/components/schemas/SelfserveStudioPathConfirmation' SelfserveStudioPathProgress: type: object required: [complete, percent, state_count, pending_state_count, terminal_path_count] properties: complete: { type: boolean } percent: type: integer minimum: 0 maximum: 100 state_count: { type: integer } pending_state_count: { type: integer } terminal_path_count: { type: integer } scenario_index: type: integer nullable: true scenario_count: type: integer nullable: true SelfserveStudioPathOutcome: type: object required: [id, summary, path_count, allowed, services, tasks, signals, sample_chains, node_ids] properties: id: { type: string } summary: { type: string } path_count: { type: integer } allowed: { type: boolean } services: type: array items: $ref: '#/components/schemas/SelfserveLaneService' tasks: type: array items: $ref: '#/components/schemas/SelfserveStudioPathTask' signals: type: array items: $ref: '#/components/schemas/SelfserveStudioPathSignal' sample_chains: type: array items: $ref: '#/components/schemas/SelfserveStudioPathSampleChain' scopes: type: array items: type: object additionalProperties: true node_ids: type: array items: { type: string } SelfserveStudioPathResult: type: object required: [id, result, summary, allowed, services, tasks, signals, task_count, signal_count, answers, scope, node_ids] properties: id: { type: string } result: { type: string } summary: { type: string } allowed: { type: boolean } services: type: array items: $ref: '#/components/schemas/SelfserveLaneService' tasks: type: array items: $ref: '#/components/schemas/SelfserveStudioPathTask' signals: type: array items: $ref: '#/components/schemas/SelfserveStudioPathSignal' task_count: { type: integer } signal_count: { type: integer } answers: type: array items: $ref: '#/components/schemas/SelfserveStudioPathAnswer' scope: type: object additionalProperties: true node_ids: type: array items: { type: string } path_signature: { type: string } result_signature: { type: string } confirmation_status: type: string enum: [unconfirmed, confirmed, stale] confirmed_at: type: string nullable: true confirmed_by: type: integer nullable: true stale_reason: type: string nullable: true SelfserveStudioPathConfirmationSummary: type: object properties: confirmed: { type: integer } unconfirmed: { type: integer } stale: { type: integer } removed: { type: integer } total: { type: integer } SelfserveStudioPathConfirmationRequest: type: object required: [department, path_signature] properties: department: { type: integer } action: type: string enum: [confirm, reset, delete, clear] default: confirm path_signature: { type: string } result_signature: type: string description: Required when action is confirm. scope: type: object additionalProperties: true answers: type: array items: $ref: '#/components/schemas/SelfserveStudioPathAnswer' result: type: object additionalProperties: true SelfserveStudioPathConfirmation: type: object properties: id: type: integer nullable: true department_id: { type: integer } lane_id: type: integer nullable: true vehicle_type_id: type: integer nullable: true config_version_id: type: integer nullable: true config_source: { type: string } path_signature: { type: string } result_signature: { type: string } confirmation_status: type: string enum: [unconfirmed, confirmed, stale] answers: type: array items: $ref: '#/components/schemas/SelfserveStudioPathAnswer' result: type: object additionalProperties: true scope: type: object additionalProperties: true confirmed_at: type: string nullable: true confirmed_by: type: integer nullable: true stale_reason: type: string nullable: true SelfserveStudioPathTask: type: object properties: id: { type: integer } node_id: { type: string } label: { type: string } services: type: array items: $ref: '#/components/schemas/SelfserveLaneService' buttons: type: array items: {} order_priority: { type: integer } SelfserveStudioPathSignal: type: object properties: sequence: { type: integer } runtime_stage: { type: string } signal_type: { type: string } relay_role: { type: string } relay_id: type: string nullable: true target_gateway_label: type: string nullable: true target_binding: type: string nullable: true source: { type: string } virtual: { type: boolean } predicted_status: { type: string } payload: type: object additionalProperties: true skip_block_reason: type: string nullable: true SelfserveStudioPathSampleChain: type: object properties: scope: type: object additionalProperties: true answers: type: array items: $ref: '#/components/schemas/SelfserveStudioPathAnswer' SelfserveStudioPathAnswer: type: object properties: question_id: { type: integer } question: { type: string } node_id: { type: string } answer: { type: boolean } answer_label: { type: string } SelfserveVehicleAllowedResponse: type: object properties: lane: $ref: '#/components/schemas/DepartmentLane' machine_type: allOf: - $ref: '#/components/schemas/SelfserveMachineType' nullable: true vehicle: type: object additionalProperties: true nullable: true reg: type: string customer_number: type: integer nullable: true vehicle_type_id: type: integer nullable: true questions: type: array items: $ref: '#/components/schemas/SelfserveVisibleQuestion' tasks: type: array items: $ref: '#/components/schemas/SelfserveTaskDecision' allowed_services: type: array items: $ref: '#/components/schemas/SelfserveLaneService' machine_available: type: boolean all_visible_questions_answered: type: boolean allowed: type: boolean blocked_reason: type: string nullable: true session: allOf: - $ref: '#/components/schemas/SelfserveWashSession' nullable: true config_source: type: string nullable: true evaluation_trace: type: object nullable: true additionalProperties: true SelfserveWashSummary: type: object properties: session: $ref: '#/components/schemas/SelfserveWashSession' lane: allOf: - $ref: '#/components/schemas/DepartmentLane' nullable: true machine_type: allOf: - $ref: '#/components/schemas/SelfserveMachineType' nullable: true questions: type: array items: $ref: '#/components/schemas/SelfserveWashQuestionAnswer' tasks: type: array items: $ref: '#/components/schemas/SelfserveWashTaskSnapshot' allowed_services: type: array items: $ref: '#/components/schemas/SelfserveLaneService' machine_available: type: boolean all_visible_questions_answered: type: boolean allowed: type: boolean events: type: array items: $ref: '#/components/schemas/SelfserveWashEvent' SelfserveForceStopResponse: type: object properties: lane_id: type: integer forced: type: boolean bill: type: boolean order_id: type: integer nullable: true session: allOf: - $ref: '#/components/schemas/SelfserveWashSummary' nullable: true runtime_before_reset: type: object additionalProperties: true 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 selfserve_enabled: type: boolean DepartmentCreate: type: object required: - name - economic_department_id properties: name: type: string description: type: string economic_department_id: type: integer visible: type: boolean 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 selfserve_enabled: type: boolean default: true status: type: string created_at: type: string format: date-time updated_at: type: string format: date-time DepartmentLaneCreate: type: object required: - department - name properties: department: type: integer name: type: string relay_in_id: type: string relay_out_id: type: string relay_machine_id: type: string relay_machine_program_picker_id: type: string relay_machine_cleaner_id: type: string dynamic_image_id: type: integer nullable: true minimum: 1 machine_type_id: type: integer nullable: true selfserve_enabled: type: boolean default: true DepartmentLaneUpdate: type: object required: - id properties: id: type: integer department: type: integer name: type: string relay_in_id: type: string relay_out_id: type: string relay_machine_id: type: string relay_machine_program_picker_id: type: string relay_machine_cleaner_id: type: string dynamic_image_id: type: integer nullable: true minimum: 1 machine_type_id: type: integer nullable: true selfserve_enabled: type: boolean DepartmentGate: type: object properties: id: type: integer department: type: integer is_entrance: type: boolean is_exit: type: boolean name: type: string config: $ref: '#/components/schemas/DepartmentGateConfig' created_at: type: string format: date-time updated_at: type: string format: date-time DepartmentGateConfig: type: object required: - type properties: type: type: string example: PHONE_CALL phone_number: type: string nullable: true example: +4512345678 call_duration_threshold: type: integer nullable: true example: 10 description: | Configuration for the department gate. If type is 'PHONE_CALL', 'phone_number' and 'call_duration_threshold' are required. DepartmentGateCreate: type: object required: - department - is_entrance - is_exit - name - config properties: department: type: integer is_entrance: type: boolean is_exit: type: boolean name: type: string config: $ref: '#/components/schemas/DepartmentGateConfig' DepartmentGateUpdate: type: object required: - id properties: id: type: integer is_entrance: type: boolean is_exit: type: boolean name: type: string config: $ref: '#/components/schemas/DepartmentGateConfig' DepartmentRelay: type: object properties: id: type: integer department: type: integer relay_id: type: string name: type: string type: type: string enum: [SWITCH, TRIGGER] config: $ref: '#/components/schemas/DepartmentRelayConfig' created_at: type: string format: date-time updated_at: type: string format: date-time DepartmentRelayConfig: type: object properties: what_happens: type: string nullable: true example: open_gate webhook_token: type: string nullable: true example: secret_token description: | Configuration for the department relay. If the relay 'type' is 'TRIGGER', 'what_happens' and 'webhook_token' are required. DepartmentRelayCreate: type: object required: - department - relay_id - name - type - config properties: department: type: integer relay_id: type: string name: type: string type: type: string enum: [SWITCH, TRIGGER] config: $ref: '#/components/schemas/DepartmentRelayConfig' DepartmentRelayUpdate: type: object required: - id properties: id: type: integer relay_id: type: string name: type: string type: type: string enum: [SWITCH, TRIGGER] config: $ref: '#/components/schemas/DepartmentRelayConfig' Product: type: object properties: id: type: integer name: type: string description: type: string price: type: number format: float category_id: type: integer category_name: type: string visible: type: boolean created_at: type: string format: date-time updated_at: type: string format: date-time ProductCreate: type: object required: - name - price - category_id properties: name: type: string description: type: string price: type: number format: float category_id: type: integer visible: type: boolean ProductUpdate: type: object required: - id properties: id: type: integer name: type: string description: type: string price: type: number format: float category_id: type: integer visible: type: boolean Category: type: object properties: id: type: integer name: type: string description: type: string created_at: type: string format: date-time updated_at: type: string format: date-time CategoryCreate: type: object required: - name properties: name: type: string description: type: string CategoryUpdate: type: object required: - id properties: id: type: integer name: type: string description: type: string ModuleActionLog: type: object properties: id: type: integer description: Log ID module: type: string description: The module name action: type: string description: The action name status_code: type: integer description: HTTP status code data: type: object description: The action data (JSON decoded) created_at: type: string format: date-time description: Log creation timestamp Booking: type: object properties: id: type: integer customer_id: type: integer department_id: type: integer booking_time: type: string format: date-time status: type: string created_at: type: string format: date-time BookingUpdate: type: object required: - id properties: id: type: integer status: type: string booking_time: type: string format: date-time Vehicle: type: object properties: id: type: integer user_id: type: integer description: Internal user ID owning the customer account reg: type: string description: Vehicle registration number customer_id: type: integer customer_name: type: string type: type: integer description: Product ID representing the vehicle wash type reference: type: string nullable: true description: Optional external reference/label wash_subscription: type: boolean barred: type: boolean description: True if the associated customer is barred addons: type: object properties: enabled: { type: integer } available: { type: integer } list: type: array items: type: object last_order_id: type: integer nullable: true xlvask: type: object nullable: true description: XL Vask vehicle data when available vehicle_types: type: array items: type: object created_at: type: string format: date-time VehicleManagementSummary: type: object required: [total, wash_subscription, self_service] properties: total: type: integer minimum: 0 wash_subscription: type: integer minimum: 0 self_service: type: integer minimum: 0 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 example: "accepted" resumeData: type: object properties: action: type: string example: "continue" resumedAt: type: string format: date-time nullable: true suspendedAt: type: string format: date-time statusCode: type: integer enum: [202] statusText: type: string enum: [Accepted] 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 } target_percentage: { type: number, format: float, nullable: true } target_department_id: { type: integer, nullable: true } DepartmentDailyReportProductTargetRequest: type: object required: - department_id - product_id - target_percentage properties: department_id: { type: integer } product_id: { type: integer } target_percentage: type: number format: float nullable: true DepartmentDailyReportProductTarget: type: object properties: department_id: { type: integer } product_id: { type: integer } target_percentage: { type: number, format: float, nullable: true } DepartmentDailyReportProductTargetResponse: type: object properties: success: { type: boolean, example: true } data: $ref: '#/components/schemas/DepartmentDailyReportProductTarget' 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'