openapi: 3.1.2
info:
  title: Bahn Customer API
  version: 2.0.0-draft
  summary: Create and follow vehicle transport orders.
  description: |
    Create vehicle transport orders and read their current state.
  contact:
    name: Bahn API support
    email: support@bahnexpress.com
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
  - url: https://sandbox.api.bahnexpress.fi
    description: Customer sandbox
  - url: https://api.bahnexpress.fi
    description: Production
tags:
  - name: Authentication
    description: Create an access token for the selected environment.
  - name: Prices
    description: Check the current price for a route.
  - name: Ordering channels
    description: Check which businesses an ordering channel can represent.
  - name: Orders
    description: Create, read, change, and cancel orders.
  - name: Files
    description: Upload and read order files.
  - name: Inspections
    description: Get vehicle inspection reports.
  - name: Tracking
    description: Get the current location for each order item.
  - name: Reports
    description: Get order report rows.
  - name: Sandbox
    description: Apply deterministic scenarios in the customer sandbox.
  - name: Webhooks
    description: Receive order change events from Bahn.
security:
  - oauth2:
      - orders:read
      - orders:write
paths:
  /oauth2/token:
    post:
      tags:
        - Authentication
      operationId: createAccessToken
      summary: Create an access token
      description: |
        Exchanges a client ID and client secret for a bearer token.
        The token is valid for 15 minutes and has no refresh token.
      security:
        - oauthClient: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/AccessTokenRequest"
            example:
              grant_type: client_credentials
      responses:
        "200":
          description: The access token is ready.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccessTokenResponse"
              example:
                access_token: eyJhbGciOiJSUzI1NiIsImtpZCI6ImV4YW1wbGUifQ.example
                expires_in: 900
                expires_at: 1785417831
                token_type: Bearer
                scope: orders:read orders:write
        "400":
          $ref: "#/components/responses/InvalidTokenRequest"
        "401":
          $ref: "#/components/responses/InvalidClient"
        "413":
          $ref: "#/components/responses/TokenRequestTooLarge"

  /v2/price-checks:
    post:
      tags:
        - Prices
      operationId: checkPrice
      summary: Check a price
      description: |
        Calculates the current price when automatic pricing supports the route.
        Otherwise, the response states that the route needs a custom quote.
        Bahn does not guarantee that it can provide a custom quote.
        Sandbox prices, distances, and times are demonstration data. They do not predict production results.
      security:
        - oauth2:
            - orders:write
      parameters:
        - $ref: "#/components/parameters/RequestId"
      x-agent-preconditions:
        - The pickup and delivery addresses must identify real places.
        - A trade-in route must stay inside Finland.
        - A private pickup cannot include a trade-in vehicle.
        - Only an ordering channel can supply ordered_for. It is optional.
      x-agent-side-effects:
        - Returns one price result for this request.
        - Does not create an order.
      x-agent-retry: Repeat the request after a timeout or a server error.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PriceCheckRequest"
            example:
              pickup:
                address:
                  formatted_address: Teollisuuskatu 1, 00510 Helsinki, Finland
                type: dealership
              delivery:
                address:
                  formatted_address: Hatanpään valtatie 24, 33100 Tampere, Finland
                type: dealership
              items:
                - role: primary
                  vin: WBA00000000000001
              service_options:
                transport_preference: flexible
                inspection: standard
      responses:
        "200":
          description: The price check is complete.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PriceCheck"
              example:
                status: available
                ordered_for: null
                price:
                  currency: EUR
                  total_cents: 45900
                  transport_cents: 43900
                  additional_fees_cents: 2000
                  trade_in_cents: 0
                distance_meters: 178000
                estimated_pickup_at: "2026-08-03T08:00:00+03:00"
                estimated_delivery_at: "2026-08-03T13:15:00+03:00"
        "400":
          description: "The request syntax or shape is invalid."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/validation-error"
                title: "The request body is not valid JSON."
                status: 400
                code: "validation_error"
                instance: "/v2/price-checks"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "401":
          description: "Authentication failed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/authentication-failed"
                title: "The access token is not valid."
                status: 401
                code: "authentication_failed"
                instance: "/v2/price-checks"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "403":
          description: "The access token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/permission-denied"
                title: "The access token does not have the required scope."
                status: 403
                code: "permission_denied"
                instance: "/v2/price-checks"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The orders:write scope is required."
        "413":
          description: "The request body exceeds the maximum size."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/request-too-large"
                title: "The request body is too large."
                status: 413
                code: "request_too_large"
                instance: "/v2/price-checks"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "422":
          description: "The request is valid, but the business rules reject it."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/business-rule-violation"
                title: "The price could not be checked."
                status: 422
                code: "business_rule_violation"
                instance: "/v2/price-checks"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "A trade-in price is available only for a route inside Finland."
        "500":
          description: "Bahn could not complete the request."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request."
                status: 500
                code: "internal_error"
                instance: "/v2/price-checks"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "503":
          description: "The request timed out before Bahn could return a result."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request in time."
                status: 503
                code: "internal_error"
                instance: "/v2/price-checks"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"

  /v2/represented-business-checks:
    post:
      tags:
        - Ordering channels
      operationId: checkRepresentedBusiness
      summary: Check a represented business
      description: |
        Checks whether the ordering channel can create orders for a business.
        The response does not disclose a customer identifier, name, or profile data.
        In production exactly one Bahn customer must match the business identity.
        In the sandbox a supported test identity is eligible when no customer matches yet.
      security:
        - oauth2:
            - orders:write
      parameters:
        - $ref: "#/components/parameters/RequestId"
      x-agent-preconditions:
        - The credential must belong to an ordering channel.
        - The business ID must be official and paired with the business country code.
        - Supported country codes are FI, DK, DE, and PL.
      x-agent-side-effects:
        - Does not create a customer or an order.
        - Does not disclose customer record data.
      x-agent-retry: Repeat the request after a timeout or a server error.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OrderedFor"
            example:
              business_id: FI12345678
              country_code: FI
      responses:
        "200":
          description: The ordering channel can create an order for the business.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RepresentedBusinessCheck"
              example:
                status: eligible
        "400":
          description: "The request syntax or shape is invalid."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/validation-error"
                title: "The request body is not valid JSON."
                status: 400
                code: "validation_error"
                instance: "/v2/represented-business-checks"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "401":
          description: "Authentication failed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/authentication-failed"
                title: "The access token is not valid."
                status: 401
                code: "authentication_failed"
                instance: "/v2/represented-business-checks"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "403":
          description: "The access token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/permission-denied"
                title: "The access token does not have the required scope."
                status: 403
                code: "permission_denied"
                instance: "/v2/represented-business-checks"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The orders:write scope is required."
        "413":
          description: "The request body exceeds the maximum size."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/request-too-large"
                title: "The request body is too large."
                status: 413
                code: "request_too_large"
                instance: "/v2/represented-business-checks"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "422":
          description: "The request is valid, but the business rules reject it."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/ordered-for-not-allowed"
                title: "The represented business could not be checked."
                status: 422
                code: "ordered_for_not_allowed"
                instance: "/v2/represented-business-checks"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "Only an ordering-channel credential can check a represented business."
        "500":
          description: "Bahn could not complete the request."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request."
                status: 500
                code: "internal_error"
                instance: "/v2/represented-business-checks"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"

  /v2/files:
    post:
      tags:
        - Files
      operationId: createFileUpload
      summary: Create a file upload
      description: |
        Creates a file identifier and a temporary direct-upload request.
        Send a multipart form POST to the returned URL.
        Add each returned field to the form.
        Add the file as the final form field with the name `file`.
        Use the file identifier in an order request after the upload succeeds.
      security:
        - oauth2:
            - orders:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/RequestId"
      x-agent-preconditions:
        - The file name, media type, and size must describe the upload.
        - The file size must not exceed 12582912 bytes.
      x-agent-side-effects:
        - Returns a file identifier and a temporary upload form.
        - Attaches the file only after the file identifier is added to an order.
      x-agent-retry: Retry with the same Idempotency-Key after a timeout or a server error.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FileUploadRequest"
            example:
              description: Pickup release document for this vehicle.
              name: pickup-release-document.pdf
              category: customer_document
              media_type: application/pdf
              size_bytes: 48211
      responses:
        "201":
          description: The upload request is ready.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileUpload"
              example:
                file_id: file_01k1a2b3c4d5e6f7g8h9j0k1m2
                upload:
                  method: POST
                  url: https://upload.example.invalid/customer-file
                  fields:
                    key: customer-api/example/file
                    Content-Type: application/pdf
                  expires_at: "2026-07-30T14:15:00Z"
        "400":
          description: "The request syntax or shape is invalid."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/validation-error"
                title: "The request body is not valid JSON."
                status: 400
                code: "validation_error"
                instance: "/v2/files"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "401":
          description: "Authentication failed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/authentication-failed"
                title: "The access token is not valid."
                status: 401
                code: "authentication_failed"
                instance: "/v2/files"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "403":
          description: "The access token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/permission-denied"
                title: "The access token does not have the required scope."
                status: 403
                code: "permission_denied"
                instance: "/v2/files"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The orders:write scope is required."
        "409":
          description: "The request conflicts with the current resource state."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/idempotency-conflict"
                title: "The file upload could not be created."
                status: 409
                code: "idempotency_conflict"
                instance: "/v2/files"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The idempotency key was used for a different operation."
        "413":
          description: "The file or request body is too large."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/file-too-large"
                title: "The file is too large."
                status: 413
                code: "file_too_large"
                instance: "/v2/files"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The file must not exceed 12582912 bytes."
        "500":
          description: "Bahn could not complete the request."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request."
                status: 500
                code: "internal_error"
                instance: "/v2/files"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "503":
          description: "The request timed out before Bahn could return a result."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request in time."
                status: 503
                code: "internal_error"
                instance: "/v2/files"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"

  /v2/orders:
    post:
      tags:
        - Orders
      operationId: createOrder
      summary: Create an order
      description: |
        Creates one order with one primary vehicle and, optionally, one trade-in vehicle.
        Pickup needs available_from, requested_windows, or both.
        The primary vehicle needs a VIN or registration number; cross-border orders require its VIN.
        A private pickup or end-customer delivery requires a contact name and phone number.
        For either type of private handover, pickup must start on the next Helsinki calendar day or later.
        Trade-in routes must stay within Finland and cannot start at a private pickup.
        A production order starts real operational work.
      x-mint:
        content: |
          Start with a complete example before you inspect every field.

          <CardGroup cols={2}>
            <Card title="Create your first order" icon="rocket" href="/quickstart">
              Get a token and create a complete sandbox order.
            </Card>
            <Card title="Review order rules" icon="book-open" href="/guides/orders">
              See the timing, vehicle, and service rules with focused examples.
            </Card>
          </CardGroup>
      security:
        - oauth2:
            - orders:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/RequestId"
      x-agent-preconditions:
        - The request must contain one primary vehicle.
        - Pickup must contain available_from or one requested window.
        - Requested windows for one stop must not overlap.
        - Each requested window must end in the future.
        - A pickup window must start at or after available_from.
        - An order with private pickup or end_customer delivery must have pickup start on the next Helsinki calendar day or later.
        - Private pickups and end_customer deliveries require the respective contact name and phone number.
        - A cross-border order must include the primary VIN.
        - A Finland order can use the primary registration number without a VIN.
        - Each file_id must belong to the authenticated customer.
        - Only an ordering channel can supply ordered_for. It is optional.
      x-agent-side-effects:
        - Creates the order and its order items.
        - Adds the requested pickup and delivery times to the order.
        - Attaches the supplied customer files.
        - Sends an order.created webhook event.
      x-agent-retry: Retry with the same Idempotency-Key after a timeout or a server error.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OrderCreateRequest"
            examples:
              "Minimal booking":
                summary: "Minimal"
                value:
                  pickup:
                    address:
                      formatted_address: "Teollisuuskatu 1, 00510 Helsinki, Finland"
                      country_code: "FI"
                    type: "dealership"
                    available_from: "2099-08-03T08:00:00+03:00"
                  delivery:
                    address:
                      formatted_address: "Hatanpään valtatie 24, 33100 Tampere, Finland"
                      country_code: "FI"
                    type: "dealership"
                  items:
                    -
                      role: "primary"
                      vin: "WBA00000000000001"
                  service_options:
                    transport_preference: "flexible"
                    inspection: "standard"
              "Time windows":
                summary: "Windows"
                value:
                  customer_reference: "PO-2026-1042"
                  pickup:
                    address:
                      formatted_address: "Teollisuuskatu 1, 00510 Helsinki, Finland"
                    type: "dealership"
                    contact:
                      name: "Aino Seller"
                      phone: "+358401234567"
                      email: "aino@example.com"
                    release_code: "GATE-17"
                    available_from: "2099-08-03T08:00:00+03:00"
                    requested_windows:
                      -
                        start_at: "2099-08-03T08:00:00+03:00"
                        end_at: "2099-08-03T12:00:00+03:00"
                        timezone: "Europe/Helsinki"
                  delivery:
                    address:
                      formatted_address: "Hatanpään valtatie 24, 33100 Tampere, Finland"
                    type: "dealership"
                    contact:
                      name: "Olli Receiver"
                      phone: "+358409876543"
                      email: "olli@example.com"
                    requested_windows: []
                  items:
                    -
                      role: "primary"
                      vin: "WBA00000000000001"
                      winter_tires:
                        mounted: true
                        in_vehicle: false
                  service_options:
                    transport_preference: "flexible"
                    inspection: "standard"
                  file_ids: []
                  customer_note: "Call the pickup contact before arrival."
              "Trade-in":
                summary: "Trade-in"
                value:
                  pickup:
                    address:
                      formatted_address: "Teollisuuskatu 1, 00510 Helsinki, Finland"
                      country_code: "FI"
                    type: "dealership"
                    available_from: "2099-08-03T08:00:00+03:00"
                  delivery:
                    address:
                      formatted_address: "Hatanpään valtatie 24, 33100 Tampere, Finland"
                      country_code: "FI"
                    type: "dealership"
                  items:
                    -
                      role: "primary"
                      vin: "WBA00000000000001"
                    -
                      role: "trade_in"
                      registration_number: "XYZ-987"
                  service_options:
                    transport_preference: "flexible"
                    inspection: "standard"
              "Custom CMR":
                summary: "Custom CMR"
                value:
                  pickup:
                    address:
                      formatted_address: "Teollisuuskatu 1, 00510 Helsinki, Finland"
                      country_code: "FI"
                    type: "dealership"
                    available_from: "2099-08-03T08:00:00+03:00"
                  delivery:
                    address:
                      formatted_address: "Hatanpään valtatie 24, 33100 Tampere, Finland"
                      country_code: "FI"
                    type: "dealership"
                  items:
                    -
                      role: "primary"
                      vin: "WBA00000000000001"
                      cmr_recipient:
                        kind: "other"
                        recipient:
                          name: "Example Motors Oy"
                          street_address: "Esimerkkikatu 5"
                          postal_code: "00100"
                          city: "Helsinki"
                          country_code: "FI"
                  service_options:
                    transport_preference: "flexible"
                    inspection: "standard"
      responses:
        "201":
          description: Bahn created the order.
          headers:
            ETag:
              description: The new order revision.
              schema:
                type: string
            Location:
              description: The new order resource.
              schema:
                type: string
              example: /v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
              example:
                id: ord_01k1a2b3c4d5e6f7g8h9j0k1m2
                ordered_for: null
                customer_reference: PO-2026-1042
                status: accepted
                pickup_readiness: preparing
                created_at: "2026-07-30T13:00:00Z"
                updated_at: "2026-07-30T13:00:00Z"
                pickup:
                  address:
                    formatted_address: Teollisuuskatu 1, 00510 Helsinki, Finland
                    name: null
                    address_line_1: Teollisuuskatu 1
                    address_line_2: null
                    postal_code: "00510"
                    city: Helsinki
                    country_code: FI
                    coordinates:
                      latitude: 60.188
                      longitude: 24.951
                  type: dealership
                  contact:
                    name: Aino Seller
                    phone: "+358401234567"
                    email: aino@example.com
                  release_code: GATE-17
                  available_from: "2099-08-03T08:00:00+03:00"
                  requested_windows:
                    - start_at: "2099-08-03T08:00:00+03:00"
                      end_at: "2099-08-03T12:00:00+03:00"
                      timezone: Europe/Helsinki
                  agreed_window: null
                  eta: null
                  eta_updated_at: null
                  actual_at: null
                delivery:
                  address:
                    formatted_address: Hatanpään valtatie 24, 33100 Tampere, Finland
                    name: null
                    address_line_1: Hatanpään valtatie 24
                    address_line_2: null
                    postal_code: "33100"
                    city: Tampere
                    country_code: FI
                    coordinates:
                      latitude: 61.489
                      longitude: 23.766
                  type: dealership
                  contact:
                    name: Olli Receiver
                    phone: "+358409876543"
                    email: olli@example.com
                  requested_windows: []
                  agreed_window: null
                  commitment_deadline_at: null
                  eta: null
                  eta_updated_at: null
                  actual_at: null
                items:
                  - id: item_01k1a2b3c4d5e6f7g8h9j0k1m2_primary
                    role: primary
                    status: awaiting_pickup
                    vin: WBA00000000000001
                    registration_number: null
                    make: null
                    model: null
                    note: null
                    fuel_type: unknown
                    transmission: unknown
                    engine_power_kw: null
                    first_registered_year: null
                    kerb_weight_kg: null
                    cmr: digital
                    plates: none
                    pickup_condition: null
                    delivery_condition: null
                    winter_tires:
                      mounted: true
                      in_vehicle: false
                service_options:
                  transport_preference: flexible
                  inspection: standard
                price:
                  currency: EUR
                  total_cents: 45900
                  transport_cents: 43900
                  additional_fees_cents: 2000
                  trade_in_cents: 0
                distance_meters: 178000
                customer_note: Call the pickup contact before arrival.
                allowed_actions:
                  - update
                  - cancel
                  - get_files
                  - get_tracking
                revision: 7qS4Jd1cKx9Nw2Yh6mVb8Pz0Rt3Fu5AeLgCiEoUaWQk
        "400":
          description: "The request syntax or shape is invalid."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/validation-error"
                title: "The request is not valid."
                status: 400
                code: "validation_error"
                instance: "/v2/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                errors:
                  - path: items.0.vin
                    code: too_small
                    message: "Too small: expected string to have exactly 17 characters"
        "401":
          description: "Authentication failed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/authentication-failed"
                title: "The access token is not valid."
                status: 401
                code: "authentication_failed"
                instance: "/v2/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "403":
          description: "The access token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/permission-denied"
                title: "The access token does not have the required scope."
                status: 403
                code: "permission_denied"
                instance: "/v2/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The orders:write scope is required."
        "409":
          description: "The request conflicts with the current resource state."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/idempotency-conflict"
                title: "The order could not be created."
                status: 409
                code: "idempotency_conflict"
                instance: "/v2/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The idempotency key was used for a different operation."
        "413":
          description: "The request body exceeds the maximum size."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/request-too-large"
                title: "The request body is too large."
                status: 413
                code: "request_too_large"
                instance: "/v2/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "422":
          description: "The request is valid, but the business rules reject it."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/business-rule-violation"
                title: "The order could not be created."
                status: 422
                code: "business_rule_violation"
                instance: "/v2/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "A cross-border order requires the primary vehicle VIN."
        "500":
          description: "Bahn could not complete the request."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request."
                status: 500
                code: "internal_error"
                instance: "/v2/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "503":
          description: "The request timed out before Bahn could return a result."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request in time."
                status: 503
                code: "internal_error"
                instance: "/v2/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
    get:
      tags:
        - Orders
      operationId: listOrders
      summary: List orders
      description: |
        Returns order summaries from newest to oldest, using cursor pagination.
        customer_reference is an exact filter and is not a unique order identifier.
        Read the individual order resource for its complete current state.
      security:
        - oauth2:
            - orders:read
      parameters:
        - $ref: "#/components/parameters/RequestId"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/PageSize"
        - name: status
          in: query
          description: Return only orders with this status.
          schema:
            $ref: "#/components/schemas/OrderStatus"
        - name: customer_reference
          in: query
          description: Return orders with this exact customer reference.
          schema:
            type: string
            minLength: 1
            maxLength: 255
      x-agent-preconditions: []
      x-agent-side-effects: []
      x-agent-retry: A GET request is safe to retry.
      responses:
        "200":
          description: The order page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderPage"
              example:
                data:
                  - id: ord_01k1a2b3c4d5e6f7g8h9j0k1m2
                    ordered_for: null
                    customer_reference: PO-2026-1042
                    status: accepted
                    pickup_readiness: preparing
                    created_at: "2026-07-30T13:00:00Z"
                    updated_at: "2026-07-30T13:00:00Z"
                    primary_vehicle:
                      vin: WBA00000000000001
                      registration_number: null
                    pickup_address: Teollisuuskatu 1, 00510 Helsinki, Finland
                    delivery_address: Hatanpään valtatie 24, 33100 Tampere, Finland
                    actual_pickup_at: null
                    actual_delivery_at: null
                next_cursor: null
                has_more: false
        "400":
          description: "The request syntax or shape is invalid."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/validation-error"
                title: "The cursor is not valid."
                status: 400
                code: "validation_error"
                instance: "/v2/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "401":
          description: "Authentication failed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/authentication-failed"
                title: "The access token is not valid."
                status: 401
                code: "authentication_failed"
                instance: "/v2/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "403":
          description: "The access token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/permission-denied"
                title: "The access token does not have the required scope."
                status: 403
                code: "permission_denied"
                instance: "/v2/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The orders:read scope is required."
        "500":
          description: "Bahn could not complete the request."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request."
                status: 500
                code: "internal_error"
                instance: "/v2/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "503":
          description: "The request timed out before Bahn could return a result."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request in time."
                status: 503
                code: "internal_error"
                instance: "/v2/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"

  /v2/orders/{order_id}:
    parameters:
      - $ref: "#/components/parameters/OrderId"
      - $ref: "#/components/parameters/RequestId"
    get:
      tags:
        - Orders
      operationId: getOrder
      summary: Get an order
      description: |
        Returns the complete current order, including timing, vehicle states, accepted price,
        allowed_actions, and revision. Use the revision inside double quotation marks in If-Match
        for an update or cancellation. Live ETA changes do not change the order revision.
      security:
        - oauth2:
            - orders:read
      x-agent-preconditions:
        - The order must belong to the authenticated customer.
      x-agent-side-effects: []
      x-agent-retry: A GET request is safe to retry.
      responses:
        "200":
          description: The current order.
          headers:
            ETag:
              description: |
                The current order revision. Live ETA changes do not change it.
                Send this quoted value in If-Match for an update or cancellation.
              schema:
                type: string
              example: '"N2f8Kp4Vx1Qa7Js5Dc9Lm3Rw6Ty0Bh8GeUiZoAqCdEs"'
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
              examples:
                order:
                  $ref: "#/components/examples/OrderExample"
        "401":
          description: "Authentication failed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/authentication-failed"
                title: "The access token is not valid."
                status: 401
                code: "authentication_failed"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "403":
          description: "The access token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/permission-denied"
                title: "The access token does not have the required scope."
                status: 403
                code: "permission_denied"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The orders:read scope is required."
        "404":
          description: "The resource does not exist or the customer cannot access it."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/resource-not-found"
                title: "The order does not exist."
                status: 404
                code: "resource_not_found"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "500":
          description: "Bahn could not complete the request."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request."
                status: 500
                code: "internal_error"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "503":
          description: "The request timed out before Bahn could return a result."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request in time."
                status: 503
                code: "internal_error"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
    patch:
      tags:
        - Orders
      operationId: updateOrder
      summary: Change an order
      description: |
        Changes supported order fields while allowed_actions contains update.
        Send the current revision inside double quotation marks in If-Match.
        Omitted top-level fields stay unchanged. Pickup and delivery merge their supplied fields.
        An items array replaces the primary vehicle input and must retain its identity and desired facts.
        Requested window lists replace the supplied stop's list; file_ids only adds files.
        CMR recipients cannot change after creation. Price inputs can change the accepted price.
        Retry a timed-out write with its original key, revision, and request body.
      security:
        - oauth2:
            - orders:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/IfMatch"
      x-agent-preconditions:
        - The order `allowed_actions` value must contain `update`.
        - The If-Match value must match the current order revision.
        - The request must contain at least one changed field.
        - Requested windows for one stop must not overlap.
        - Each supplied requested window must end in the future.
        - An order with private pickup or end_customer delivery must have pickup start on the next Helsinki calendar day or later.
      x-agent-side-effects:
        - Returns the changed order.
        - Can return new price and time values when their inputs change.
        - Sends the applicable order change webhook event.
        - Can send a file event when the file list changes.
      x-agent-retry: Retry with the same Idempotency-Key and If-Match values.
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: "#/components/schemas/OrderUpdateRequest"
            example:
              customer_reference: PO-2026-1042-REV-A
              delivery:
                requested_windows:
                  - start_at: "2099-08-03T15:00:00+03:00"
                    end_at: "2099-08-03T18:00:00+03:00"
                    timezone: Europe/Helsinki
      responses:
        "200":
          description: The updated order.
          headers:
            ETag:
              description: The new order revision.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
              examples:
                order:
                  $ref: "#/components/examples/UpdatedOrderExample"
        "400":
          description: "The request syntax or shape is invalid."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/validation-error"
                title: "The request body is not valid JSON."
                status: 400
                code: "validation_error"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "401":
          description: "Authentication failed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/authentication-failed"
                title: "The access token is not valid."
                status: 401
                code: "authentication_failed"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "403":
          description: "The access token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/permission-denied"
                title: "The access token does not have the required scope."
                status: 403
                code: "permission_denied"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The orders:write scope is required."
        "404":
          description: "The resource does not exist or the customer cannot access it."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/resource-not-found"
                title: "The order could not change."
                status: 404
                code: "resource_not_found"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The order does not exist."
        "409":
          description: "The request conflicts with the current resource state."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/order-not-updateable"
                title: "The order could not change."
                status: 409
                code: "order_not_updateable"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "This order can no longer change."
        "412":
          description: "The order revision changed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/revision-conflict"
                title: "The order could not change."
                status: 412
                code: "revision_conflict"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The order revision does not match If-Match."
        "413":
          description: "The request body exceeds the maximum size."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/request-too-large"
                title: "The request body is too large."
                status: 413
                code: "request_too_large"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "422":
          description: "The request is valid, but the business rules reject it."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/business-rule-violation"
                title: "The order could not change."
                status: 422
                code: "business_rule_violation"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "A requested window has already ended."
        "500":
          description: "Bahn could not complete the request."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request."
                status: 500
                code: "internal_error"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "503":
          description: "The request timed out before Bahn could return a result."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request in time."
                status: 503
                code: "internal_error"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
    delete:
      tags:
        - Orders
      operationId: cancelOrder
      summary: Cancel an order
      description: |
        Cancels an order while allowed_actions contains cancel.
        Send the current revision inside double quotation marks in If-Match.
        Confirm the customer's intent before cancelling a production order.
        The cancelled order remains readable. Retry with the original key and revision.
      security:
        - oauth2:
            - orders:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/IfMatch"
      x-agent-preconditions:
        - The order `allowed_actions` value must contain `cancel`.
        - The If-Match value must match the current order revision.
      x-agent-side-effects:
        - Sets the order status to cancelled.
        - Keeps the cancelled order available for reads.
        - Sends the order.cancelled webhook event.
      x-agent-retry: Retry with the same Idempotency-Key and If-Match values.
      responses:
        "200":
          description: The cancelled order.
          headers:
            ETag:
              description: The cancelled order revision.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
              examples:
                cancelled:
                  value:
                    id: ord_01k1a2b3c4d5e6f7g8h9j0k1m2
                    ordered_for: null
                    customer_reference: PO-2026-1042
                    status: cancelled
                    pickup_readiness: preparing
                    created_at: "2026-07-30T13:00:00Z"
                    updated_at: "2026-07-30T13:10:00Z"
                    pickup:
                      address:
                        formatted_address: Teollisuuskatu 1, 00510 Helsinki, Finland
                        name: null
                        address_line_1: Teollisuuskatu 1
                        address_line_2: null
                        postal_code: "00510"
                        city: Helsinki
                        country_code: FI
                        coordinates:
                          latitude: 60.188
                          longitude: 24.951
                      type: dealership
                      contact: null
                      release_code: null
                      available_from: "2026-08-03T08:00:00+03:00"
                      requested_windows: []
                      agreed_window: null
                      eta: null
                      eta_updated_at: null
                      actual_at: null
                    delivery:
                      address:
                        formatted_address: Hatanpään valtatie 24, 33100 Tampere, Finland
                        name: null
                        address_line_1: Hatanpään valtatie 24
                        address_line_2: null
                        postal_code: "33100"
                        city: Tampere
                        country_code: FI
                        coordinates:
                          latitude: 61.489
                          longitude: 23.766
                      type: dealership
                      contact: null
                      requested_windows: []
                      agreed_window: null
                      commitment_deadline_at: null
                      eta: null
                      eta_updated_at: null
                      actual_at: null
                    items:
                      - id: item_01k1a2b3c4d5e6f7g8h9j0k1m2_primary
                        role: primary
                        status: awaiting_pickup
                        vin: WBA00000000000001
                        registration_number: null
                        make: null
                        model: null
                        note: null
                        fuel_type: unknown
                        transmission: unknown
                        engine_power_kw: null
                        first_registered_year: null
                        kerb_weight_kg: null
                        cmr: digital
                        plates: none
                        pickup_condition: null
                        delivery_condition: null
                        winter_tires:
                          mounted: false
                          in_vehicle: false
                    service_options:
                      transport_preference: flexible
                      inspection: standard
                    price:
                      currency: EUR
                      total_cents: 45900
                      transport_cents: 43900
                      additional_fees_cents: 2000
                      trade_in_cents: 0
                    customer_note: null
                    allowed_actions:
                      - get_files
                    revision: N2f8Kp4Vx1Qa7Js5Dc9Lm3Rw6Ty0Bh8GeUiZoAqCdEs
        "401":
          description: "Authentication failed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/authentication-failed"
                title: "The access token is not valid."
                status: 401
                code: "authentication_failed"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "403":
          description: "The access token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/permission-denied"
                title: "The access token does not have the required scope."
                status: 403
                code: "permission_denied"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The orders:write scope is required."
        "404":
          description: "The resource does not exist or the customer cannot access it."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/resource-not-found"
                title: "The order could not be cancelled."
                status: 404
                code: "resource_not_found"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The order does not exist."
        "409":
          description: "The request conflicts with the current resource state."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/order-not-cancellable"
                title: "The order could not be cancelled."
                status: 409
                code: "order_not_cancellable"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "This order can no longer be cancelled."
        "412":
          description: "The order revision changed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/revision-conflict"
                title: "The order could not be cancelled."
                status: 412
                code: "revision_conflict"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The order revision does not match If-Match."
        "500":
          description: "Bahn could not complete the request."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request."
                status: 500
                code: "internal_error"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "503":
          description: "The request timed out before Bahn could return a result."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request in time."
                status: 503
                code: "internal_error"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"

  /v2/cmr-options:
    get:
      tags: [Orders]
      operationId: getCmrOptions
      summary: Get CMR recipient choices and defaults
      description: |
        Returns the authenticated account's registered company address, saved customer
        locations, and document defaults for future orders. Each option identifies
        missing recipient details. This read does not accept ordered_for and does
        not change an order or its physical delivery destination.
      security:
        - oauth2: [orders:read]
      responses:
        "200":
          description: Current options for this customer.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: "#/components/schemas/CustomerCmrOptions"
              example:
                data:
                  revision: "2026-07-30T12:00:00Z"
                  default_recipient:
                    kind: automatic
                  default_mode: null
                  options:
                    - selection:
                        kind: company
                      label: Example Motors Oy
                      recipient:
                        name: Example Motors Oy
                        street_address: Mannerheimintie 1
                        postal_code: "00100"
                        city: Helsinki
                        country_code: FI
                      missing_fields: []
                    - selection:
                        kind: place
                        place_id: place_01k1a2b3c4d5e6f7g8h9j0k1m2
                      label: Warehouse
                      recipient: null
                      missing_fields: [postal_code]
        "401":
          description: "Authentication failed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/authentication-failed"
                title: "The access token is not valid."
                status: 401
                code: "authentication_failed"
                instance: "/v2/cmr-options"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "403":
          description: "The access token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/permission-denied"
                title: "The access token does not have the required scope."
                status: 403
                code: "permission_denied"
                instance: "/v2/cmr-options"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The orders:read scope is required."

  /v2/orders/{order_id}/files:
    parameters:
      - $ref: "#/components/parameters/OrderId"
      - $ref: "#/components/parameters/RequestId"
    get:
      tags:
        - Files
      operationId: listOrderFiles
      summary: List order files
      description: Lists the customer files that are available for the order.
      security:
        - oauth2:
            - orders:read
      x-agent-preconditions:
        - The order must belong to the authenticated customer.
      x-agent-side-effects: []
      x-agent-retry: A GET request is safe to retry.
      responses:
        "200":
          description: The order file list.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileList"
              example:
                data:
                  - id: file_01k1a2b3c4d5e6f7g8h9j0k1m2
                    item_id: item_01k1a2b3c4d5e6f7g8h9j0k1m2_primary
                    kind: image
                    category: vehicle
                    stage: delivery
                    evidence_for: []
                    description: null
                    name: delivery-right-side.jpg
                    media_type: image/jpeg
                    size_bytes: 1842112
                    created_at: "2026-08-03T13:18:00+03:00"
                    content_url: https://download.example.invalid/delivery-right-side.jpg
                    content_url_expires_at: "2026-08-03T14:18:00+03:00"
                    can_delete: false
        "401":
          description: "Authentication failed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/authentication-failed"
                title: "The access token is not valid."
                status: 401
                code: "authentication_failed"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/files"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "403":
          description: "The access token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/permission-denied"
                title: "The access token does not have the required scope."
                status: 403
                code: "permission_denied"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/files"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The orders:read scope is required."
        "404":
          description: "The resource does not exist or the customer cannot access it."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/resource-not-found"
                title: "The order does not exist."
                status: 404
                code: "resource_not_found"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/files"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "500":
          description: "Bahn could not complete the request."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request."
                status: 500
                code: "internal_error"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/files"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "503":
          description: "The request timed out before Bahn could return a result."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request in time."
                status: 503
                code: "internal_error"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/files"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"

  /v2/orders/{order_id}/files/{file_id}:
    parameters:
      - $ref: "#/components/parameters/OrderId"
      - $ref: "#/components/parameters/RequestId"
      - name: file_id
        in: path
        required: true
        description: File to delete from this order.
        schema:
          $ref: "#/components/schemas/FileId"
    delete:
      tags:
        - Files
      operationId: deleteOrderFile
      summary: Delete a customer upload
      description: |
        Deletes a file that the authenticated customer uploaded.
        The order must still allow changes.
      security:
        - oauth2:
            - orders:write
      x-agent-preconditions:
        - The order must belong to the authenticated customer.
        - The file must be a customer upload for this order.
        - The order must still allow changes.
      x-agent-side-effects:
        - Removes the file from the order file list.
      x-agent-retry: A DELETE request is safe to retry.
      responses:
        "204":
          description: The file is deleted.
        "401":
          description: "Authentication failed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/authentication-failed"
                title: "The access token is not valid."
                status: 401
                code: "authentication_failed"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/files/file_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "403":
          description: "The access token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/permission-denied"
                title: "The access token does not have the required scope."
                status: 403
                code: "permission_denied"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/files/file_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The orders:write scope is required."
        "404":
          description: "The resource does not exist or the customer cannot access it."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/resource-not-found"
                title: "The file could not be deleted."
                status: 404
                code: "resource_not_found"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/files/file_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The file does not exist."
        "409":
          description: "The request conflicts with the current resource state."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/order-not-updateable"
                title: "The file could not be deleted."
                status: 409
                code: "order_not_updateable"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/files/file_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "This order can no longer change."
        "500":
          description: "Bahn could not complete the request."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request."
                status: 500
                code: "internal_error"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/files/file_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "503":
          description: "The request timed out before Bahn could return a result."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request in time."
                status: 503
                code: "internal_error"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/files/file_01k1a2b3c4d5e6f7g8h9j0k1m2"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"

  /v2/orders/{order_id}/inspection-report:
    parameters:
      - $ref: "#/components/parameters/OrderId"
      - $ref: "#/components/parameters/RequestId"
    get:
      tags:
        - Inspections
      operationId: getOrderInspectionReport
      summary: Get the vehicle inspection report
      security:
        - oauth2:
            - orders:read
      x-agent-preconditions:
        - The order must belong to the authenticated customer.
      x-agent-side-effects: []
      x-agent-retry: A GET request is safe to retry.
      responses:
        "200":
          description: The inspection state and report.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InspectionReport"
              example:
                order_id: ord_01k1a2b3c4d5e6f7g8h9j0k1m2
                item_id: item_01k1a2b3c4d5e6f7g8h9j0k1m2_primary
                status: available
                inspected_at: "2026-08-03T08:42:00+03:00"
                overall_findings: One small scratch on the right front door.
                items:
                  - category: body
                    status: issue
                    findings: Small scratch on the right front door.
                    evidence:
                      - id: file_01k1a2b3c4d5e6f7g8h9j0k1m2
                        item_id: item_01k1a2b3c4d5e6f7g8h9j0k1m2_primary
                        kind: image
                        category: vehicle
                        stage: pickup
                        evidence_for:
                          - inspection
                          - damage
                        description: null
                        name: right-front-door.jpg
                        media_type: image/jpeg
                        size_bytes: 1138201
                        created_at: "2026-08-03T08:41:00+03:00"
                        content_url: https://download.example.invalid/right-front-door.jpg
                        content_url_expires_at: "2026-08-03T09:41:00+03:00"
                        can_delete: false
                  - category: tires
                    status: ok
                    findings: null
                    evidence: []
        "401":
          description: "Authentication failed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/authentication-failed"
                title: "The access token is not valid."
                status: 401
                code: "authentication_failed"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/inspection-report"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "403":
          description: "The access token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/permission-denied"
                title: "The access token does not have the required scope."
                status: 403
                code: "permission_denied"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/inspection-report"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The orders:read scope is required."
        "404":
          description: "The resource does not exist or the customer cannot access it."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/resource-not-found"
                title: "The order does not exist."
                status: 404
                code: "resource_not_found"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/inspection-report"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "500":
          description: "Bahn could not complete the request."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request."
                status: 500
                code: "internal_error"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/inspection-report"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "503":
          description: "The request timed out before Bahn could return a result."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request in time."
                status: 503
                code: "internal_error"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/inspection-report"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"

  /v2/orders/{order_id}/tracking:
    parameters:
      - $ref: "#/components/parameters/OrderId"
      - $ref: "#/components/parameters/RequestId"
      - $ref: "#/components/parameters/IfNoneMatch"
    get:
      tags:
        - Tracking
      operationId: getOrderTracking
      summary: Get the current transport tracking snapshot
      description: >
        Returns one tracking entry for each order item. When position is present,
        use point and observed_at to show the item on a map.
      security:
        - oauth2:
            - orders:read
      x-agent-preconditions:
        - The order must belong to the authenticated customer.
      x-agent-side-effects: []
      x-agent-retry: A GET request is safe to retry.
      responses:
        "200":
          description: The current tracking snapshot.
          headers:
            ETag:
              description: The location snapshot revision.
              schema:
                type: string
              example: '"trk_01k1a2b3c4d5e6f7g8h9j0k1m2"'
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackingSnapshot"
              example:
                order_id: ord_01k1a2b3c4d5e6f7g8h9j0k1m2
                status: accepted
                items:
                  - item_id: item_01k1a2b3c4d5e6f7g8h9j0k1m2_primary
                    role: primary
                    tracking_state: active
                    phase: pickup
                    pickup_progress: nearby
                    driver:
                      name: Alex Driver
                    position:
                      point:
                        type: Point
                        coordinates: [23.76, 61.50]
                      accuracy_meters: 12
                      heading_degrees: 45
                      observed_at: "2026-08-03T11:59:00+03:00"
                revision: trk_01k1a2b3c4d5e6f7g8h9j0k1m2
        "304":
          description: The snapshot did not change.
        "401":
          description: "Authentication failed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/authentication-failed"
                title: "The access token is not valid."
                status: 401
                code: "authentication_failed"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/tracking"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "403":
          description: "The access token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/permission-denied"
                title: "The access token does not have the required scope."
                status: 403
                code: "permission_denied"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/tracking"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The orders:read scope is required."
        "404":
          description: "The resource does not exist or the customer cannot access it."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/resource-not-found"
                title: "The order does not exist."
                status: 404
                code: "resource_not_found"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/tracking"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "500":
          description: "Bahn could not complete the request."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request."
                status: 500
                code: "internal_error"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/tracking"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "503":
          description: "The request timed out before Bahn could return a result."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request in time."
                status: 503
                code: "internal_error"
                instance: "/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/tracking"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"

  /v2/reports/orders:
    get:
      tags:
        - Reports
      operationId: listOrderReportRows
      summary: List order report rows
      security:
        - oauth2:
            - reports:read
      parameters:
        - $ref: "#/components/parameters/RequestId"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/PageSize"
        - name: created_from
          in: query
          description: Include orders created at or after this RFC 3339 timestamp.
          schema:
            type: string
            format: date-time
        - name: created_to
          in: query
          description: Include orders created at or before this RFC 3339 timestamp.
          schema:
            type: string
            format: date-time
      x-agent-preconditions: []
      x-agent-side-effects: []
      x-agent-retry: A GET request is safe to retry.
      responses:
        "200":
          description: The report page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderReportPage"
              example:
                data:
                  - order_id: ord_01k1a2b3c4d5e6f7g8h9j0k1m2
                    ordered_for: null
                    customer_reference: PO-2026-1042
                    created_at: "2026-07-30T13:00:00Z"
                    status: delivered
                    primary_vin: WBA00000000000001
                    primary_registration_number: ABC-123
                    origin_country: FI
                    destination_country: FI
                    pickup_address: Teollisuuskatu 1, 00510 Helsinki, Finland
                    delivery_address: Hatanpään valtatie 24, 33100 Tampere, Finland
                    pickup_type: dealership
                    delivery_type: dealership
                    ordered_by:
                      name: API service account
                      email: api@example.com
                    available_for_pickup_at: "2026-08-03T08:00:00+03:00"
                    picked_up_at: "2026-08-03T08:40:00+03:00"
                    delivered_at: "2026-08-03T13:18:00+03:00"
                    distance_meters: 181200
                    driver_time_minutes: 144
                    total_transport_minutes: 278
                    cost:
                      currency: EUR
                      total_cents: 45900
                    refuel:
                      occurred: true
                      cost_cents: 6200
                next_cursor: null
                has_more: false
        "400":
          description: "The request syntax or shape is invalid."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/validation-error"
                title: "The cursor is not valid."
                status: 400
                code: "validation_error"
                instance: "/v2/reports/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "401":
          description: "Authentication failed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/authentication-failed"
                title: "The access token is not valid."
                status: 401
                code: "authentication_failed"
                instance: "/v2/reports/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "403":
          description: "The access token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/permission-denied"
                title: "The access token does not have the required scope."
                status: 403
                code: "permission_denied"
                instance: "/v2/reports/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The reports:read scope is required."
        "500":
          description: "Bahn could not complete the request."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request."
                status: 500
                code: "internal_error"
                instance: "/v2/reports/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "503":
          description: "The request timed out before Bahn could return a result."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request in time."
                status: 503
                code: "internal_error"
                instance: "/v2/reports/orders"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"

  /v2/test/orders/{order_id}/advance:
    servers:
      - url: https://sandbox.api.bahnexpress.fi
        description: Customer sandbox
    parameters:
      - $ref: "#/components/parameters/OrderId"
      - $ref: "#/components/parameters/RequestId"
    post:
      tags:
        - Sandbox
      operationId: advanceSandboxOrder
      summary: Apply a sandbox scenario
      description: |
        Applies one deterministic scenario to a sandbox order.
        Use this route only with the sandbox base URL.
        The scenario list defines each supported order change.
      security:
        - oauth2:
            - test:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      x-agent-preconditions:
        - The caller must use the sandbox base URL.
        - The scenario must be valid for the current order state.
      x-agent-side-effects:
        - Changes the sandbox order for the selected scenario.
        - Sends the related webhook events.
      x-agent-retry: Retry with the same Idempotency-Key after a timeout or a server error.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SandboxAdvanceRequest"
            example:
              scenario: pickup_readiness_confirmed
              occurred_at: "2026-08-03T08:40:00+03:00"
      responses:
        "200":
          description: The sandbox applied the scenario.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxAdvanceResult"
              example:
                scenario: pickup_readiness_confirmed
                order:
                  id: ord_01k1a2b3c4d5e6f7g8h9j0k1m2
                  ordered_for: null
                  customer_reference: PO-2026-1042
                  status: accepted
                  pickup_readiness: confirmed
                  created_at: "2026-07-30T13:00:00Z"
                  updated_at: "2026-08-03T08:40:00+03:00"
                  pickup:
                    address:
                      formatted_address: Teollisuuskatu 1, 00510 Helsinki, Finland
                      name: null
                      address_line_1: Teollisuuskatu 1
                      address_line_2: null
                      postal_code: "00510"
                      city: Helsinki
                      country_code: FI
                      coordinates:
                        latitude: 60.188
                        longitude: 24.951
                    type: dealership
                    contact: null
                    release_code: null
                    available_from: "2026-08-03T08:00:00+03:00"
                    requested_windows: []
                    agreed_window: null
                    eta: "2026-08-03T08:40:00+03:00"
                    eta_updated_at: "2026-08-03T08:35:00+03:00"
                    actual_at: null
                  delivery:
                    address:
                      formatted_address: Hatanpään valtatie 24, 33100 Tampere, Finland
                      name: null
                      address_line_1: Hatanpään valtatie 24
                      address_line_2: null
                      postal_code: "33100"
                      city: Tampere
                      country_code: FI
                      coordinates:
                        latitude: 61.489
                        longitude: 23.766
                    type: dealership
                    contact: null
                    requested_windows: []
                    agreed_window: null
                    commitment_deadline_at: null
                    eta: null
                    eta_updated_at: null
                    actual_at: null
                  items:
                    - id: item_01k1a2b3c4d5e6f7g8h9j0k1m2_primary
                      role: primary
                      status: awaiting_pickup
                      vin: WBA00000000000001
                      registration_number: null
                      make: null
                      model: null
                      note: null
                      fuel_type: unknown
                      transmission: unknown
                      engine_power_kw: null
                      first_registered_year: null
                      kerb_weight_kg: null
                      cmr: digital
                      plates: none
                      pickup_condition: null
                      delivery_condition: null
                      winter_tires:
                        mounted: false
                        in_vehicle: false
                  service_options:
                    transport_preference: flexible
                    inspection: standard
                  price:
                    currency: EUR
                    total_cents: 45900
                    transport_cents: 43900
                    additional_fees_cents: 2000
                    trade_in_cents: 0
                  customer_note: null
                  allowed_actions:
                    - update
                    - cancel
                    - get_files
                    - get_inspection_report
                    - get_tracking
                  revision: x5Bm9Qa2Kd7Rv1Nu4Le8Yc3Hj6Wp0Tg5FsZiAoUkEnM
        "400":
          description: "The request syntax or shape is invalid."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/validation-error"
                title: "The request body is not valid JSON."
                status: 400
                code: "validation_error"
                instance: "/v2/test/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/advance"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "401":
          description: "Authentication failed."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/authentication-failed"
                title: "The access token is not valid."
                status: 401
                code: "authentication_failed"
                instance: "/v2/test/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/advance"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "403":
          description: "The access token lacks the required scope."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/permission-denied"
                title: "The access token does not have the required scope."
                status: 403
                code: "permission_denied"
                instance: "/v2/test/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/advance"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The test:write scope is required."
        "404":
          description: "The resource does not exist or the customer cannot access it."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/resource-not-found"
                title: "The sandbox scenario could not be applied."
                status: 404
                code: "resource_not_found"
                instance: "/v2/test/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/advance"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The order does not exist."
        "409":
          description: "The request conflicts with the current resource state."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/scenario-not-allowed"
                title: "The sandbox scenario could not be applied."
                status: 409
                code: "scenario_not_allowed"
                instance: "/v2/test/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/advance"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
                detail: "The order pickup must be complete."
        "413":
          description: "The request body exceeds the maximum size."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/request-too-large"
                title: "The request body is too large."
                status: 413
                code: "request_too_large"
                instance: "/v2/test/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/advance"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "500":
          description: "Bahn could not complete the request."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request."
                status: 500
                code: "internal_error"
                instance: "/v2/test/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/advance"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
        "503":
          description: "The request timed out before Bahn could return a result."
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              example:
                type: "https://api.bahnexpress.fi/problems/internal-error"
                title: "The server could not complete the request in time."
                status: 503
                code: "internal_error"
                instance: "/v2/test/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2/advance"
                request_id: "req_01k1a2b3c4d5e6f7g8h9j0k1m2"
webhooks:
  orderEvent:
    post:
      tags:
        - Webhooks
      operationId: receiveOrderEvent
      summary: Receive an order event
      security: []
      description: |
        Bahn sends this request to the configured customer endpoint.
        The receiver must deduplicate requests by the `id` field.
      x-agent-preconditions:
        - Verify the webhook signature against the raw request body.
        - Reject a timestamp outside the allowed tolerance.
      x-agent-side-effects:
        - Store the event before a successful response.
      x-agent-retry: Bahn sends one first attempt and up to eight retries.
      parameters:
        - name: webhook-id
          in: header
          required: true
          description: Event identifier included in the signature input. It remains the same across retries.
          schema:
            type: string
        - name: webhook-timestamp
          in: header
          required: true
          description: Unix timestamp in seconds included in the signature input.
          schema:
            type: string
        - name: webhook-signature
          in: header
          required: true
          description: One or more Standard Webhooks v1 signatures for the raw request body.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/cloudevents+json:
            schema:
              $ref: "#/components/schemas/OrderEvent"
            example:
              specversion: "1.0"
              id: evt_01k1a2b3c4d5e6f7g8h9j0k1m2
              source: https://sandbox.api.bahnexpress.fi/v2
              type: com.bahn.order.eta_changed.v1
              subject: orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2
              time: "2026-08-03T08:40:00+03:00"
              datacontenttype: application/json
              data:
                order_id: ord_01k1a2b3c4d5e6f7g8h9j0k1m2
                ordered_for: null
                order_sequence: 3
                resource_url: /v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2
                revision: x5Bm9Qa2Kd7Rv1Nu4Le8Yc3Hj6Wp0Tg5FsZiAoUkEnM
                target: delivery
                eta: "2026-08-03T15:20:00+03:00"
                previous_eta: "2026-08-03T14:45:00+03:00"
                window:
                  start_at: "2026-08-03T14:00:00+03:00"
                  end_at: "2026-08-03T15:00:00+03:00"
                  timezone: Europe/Helsinki
                commitment_deadline_at: "2026-08-03T15:00:00+03:00"
                reason: outside_window
      responses:
        "204":
          description: The receiver stored the event.

components:
  securitySchemes:
    oauthClient:
      type: http
      scheme: basic
      description: Use the client ID as the username and the client secret as the password.
    oauth2:
      type: oauth2
      description: OAuth 2.0 client credentials.
      flows:
        clientCredentials:
          tokenUrl: /oauth2/token
          scopes:
            orders:read: Read customer orders.
            orders:write: Check prices, upload files, and change customer orders.
            reports:read: Read customer order reports.
            test:write: Apply customer sandbox scenarios.

  parameters:
    OrderId:
      name: order_id
      in: path
      required: true
      description: The order identifier.
      schema:
        $ref: "#/components/schemas/OrderId"
    Cursor:
      name: cursor
      in: query
      description: The opaque cursor from the previous response.
      schema:
        type: string
    PageSize:
      name: page_size
      in: query
      description: The number of resources to return.
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: A unique key for this logical write across the customer account. Reuse it only for retries of the same operation and request data.
      schema:
        type: string
        minLength: 8
        maxLength: 255
    RequestId:
      name: X-Request-Id
      in: header
      required: false
      description: A caller-supplied request identifier for support.
      schema:
        type: string
        minLength: 1
        maxLength: 255
    IfMatch:
      name: If-Match
      in: header
      required: true
      description: The current order revision inside double quotation marks. Preserve it when retrying the same logical write.
      example: '"N2f8Kp4Vx1Qa7Js5Dc9Lm3Rw6Ty0Bh8GeUiZoAqCdEs"'
      schema:
        type: string
    IfNoneMatch:
      name: If-None-Match
      in: header
      required: false
      description: The last location revision that the caller received.
      schema:
        type: string

  responses:
    InvalidTokenRequest:
      description: The token request is not valid.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/OAuthError"
          example:
            error: invalid_request
            error_description: Use the client_credentials grant and optional scope field.
    TokenRequestTooLarge:
      description: The token request body exceeds the 8 KiB limit.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/OAuthError"
          example:
            error: invalid_request
            error_description: The token request body is too large.
    InvalidClient:
      description: The client ID or client secret is not valid.
      headers:
        WWW-Authenticate:
          schema:
            type: string
          description: HTTP Basic authentication challenge.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/OAuthError"
          example:
            error: invalid_client
            error_description: Use the client ID and client secret with HTTP Basic.

  examples:
    OrderExample:
      value:
        id: ord_01k1a2b3c4d5e6f7g8h9j0k1m2
        ordered_for: null
        customer_reference: PO-2026-1042
        status: accepted
        pickup_readiness: confirmed
        created_at: "2026-07-30T13:00:00Z"
        updated_at: "2026-07-31T09:00:00Z"
        pickup:
          address:
            formatted_address: Teollisuuskatu 1, 00510 Helsinki, Finland
            name: null
            address_line_1: Teollisuuskatu 1
            address_line_2: null
            postal_code: "00510"
            city: Helsinki
            country_code: FI
            coordinates:
              latitude: 60.188
              longitude: 24.951
          type: dealership
          contact:
            name: Aino Seller
            phone: "+358401234567"
            email: aino@example.com
          release_code: GATE-17
          available_from: "2026-08-03T08:00:00+03:00"
          requested_windows:
            - start_at: "2026-08-03T08:00:00+03:00"
              end_at: "2026-08-03T12:00:00+03:00"
              timezone: Europe/Helsinki
          agreed_window:
            start_at: "2026-08-03T08:00:00+03:00"
            end_at: "2026-08-03T12:00:00+03:00"
            timezone: Europe/Helsinki
          eta: "2026-08-03T09:30:00+03:00"
          eta_updated_at: "2026-08-03T09:20:00+03:00"
          actual_at: null
        delivery:
          address:
            formatted_address: Hatanpään valtatie 24, 33100 Tampere, Finland
            name: null
            address_line_1: Hatanpään valtatie 24
            address_line_2: null
            postal_code: "33100"
            city: Tampere
            country_code: FI
            coordinates:
              latitude: 61.489
              longitude: 23.766
          type: dealership
          contact:
            name: Olli Receiver
            phone: "+358409876543"
            email: olli@example.com
          requested_windows: []
          agreed_window: null
          commitment_deadline_at: "2026-08-04T18:00:00+03:00"
          eta: "2026-08-03T14:30:00+03:00"
          eta_updated_at: "2026-08-03T09:20:00+03:00"
          actual_at: null
        items:
          - id: item_01k1a2b3c4d5e6f7g8h9j0k1m2_primary
            role: primary
            status: awaiting_pickup
            vin: WBA00000000000001
            registration_number: ABC-123
            make: BMW
            model: "330e"
            note: null
            fuel_type: hybrid_petrol
            transmission: automatic
            engine_power_kw: 215
            first_registered_year: 2023
            kerb_weight_kg: 1845
            cmr: digital
            plates: none
            pickup_condition: null
            delivery_condition: null
            winter_tires:
              mounted: true
              in_vehicle: false
        service_options:
          transport_preference: flexible
          inspection: standard
        price:
          currency: EUR
          total_cents: 45900
          transport_cents: 43900
          additional_fees_cents: 2000
          trade_in_cents: 0
        customer_note: Call the pickup contact before arrival.
        allowed_actions:
          - update
          - cancel
          - get_files
          - get_tracking
        revision: N2f8Kp4Vx1Qa7Js5Dc9Lm3Rw6Ty0Bh8GeUiZoAqCdEs

    UpdatedOrderExample:
      value:
        id: "ord_01k1a2b3c4d5e6f7g8h9j0k1m2"
        ordered_for: null
        customer_reference: "PO-2026-1042-REV-A"
        status: "accepted"
        pickup_readiness: "preparing"
        created_at: "2026-07-30T13:00:00Z"
        updated_at: "2026-07-30T14:00:00Z"
        pickup:
          address:
            formatted_address: "Teollisuuskatu 1, 00510 Helsinki, Finland"
            name: null
            address_line_1: "Teollisuuskatu 1"
            address_line_2: null
            postal_code: "00510"
            city: "Helsinki"
            country_code: "FI"
            coordinates:
              latitude: 60.188
              longitude: 24.951
          type: "dealership"
          contact:
            name: "Aino Seller"
            phone: "+358401234567"
            email: "aino@example.com"
          release_code: "GATE-17"
          available_from: "2099-08-03T08:00:00+03:00"
          requested_windows:
            - start_at: "2099-08-03T08:00:00+03:00"
              end_at: "2099-08-03T12:00:00+03:00"
              timezone: "Europe/Helsinki"
          agreed_window: null
          eta: null
          eta_updated_at: null
          actual_at: null
        delivery:
          address:
            formatted_address: "Hatanpään valtatie 24, 33100 Tampere, Finland"
            name: null
            address_line_1: "Hatanpään valtatie 24"
            address_line_2: null
            postal_code: "33100"
            city: "Tampere"
            country_code: "FI"
            coordinates:
              latitude: 61.489
              longitude: 23.766
          type: "dealership"
          contact:
            name: "Olli Receiver"
            phone: "+358409876543"
            email: "olli@example.com"
          requested_windows:
            - start_at: "2099-08-03T15:00:00+03:00"
              end_at: "2099-08-03T18:00:00+03:00"
              timezone: "Europe/Helsinki"
          agreed_window: null
          commitment_deadline_at: null
          eta: null
          eta_updated_at: null
          actual_at: null
        items:
          - id: "item_01k1a2b3c4d5e6f7g8h9j0k1m2_primary"
            role: "primary"
            status: "awaiting_pickup"
            vin: "WBA00000000000001"
            registration_number: null
            make: null
            model: null
            note: null
            fuel_type: "unknown"
            transmission: "unknown"
            engine_power_kw: null
            first_registered_year: null
            kerb_weight_kg: null
            cmr: "digital"
            plates: "none"
            pickup_condition: null
            delivery_condition: null
            winter_tires:
              mounted: true
              in_vehicle: false
        service_options:
          transport_preference: "flexible"
          inspection: "standard"
        price:
          currency: "EUR"
          total_cents: 45900
          transport_cents: 43900
          additional_fees_cents: 2000
          trade_in_cents: 0
        customer_note: "Call the pickup contact before arrival."
        allowed_actions:
          - "update"
          - "cancel"
          - "get_files"
          - "get_tracking"
        revision: "x5Bm9Qa2Kd7Rv1Nu4Le8Yc3Hj6Wp0Tg5FsZiAoUkEnM"

  schemas:
    AccessTokenRequest:
      description: OAuth 2.0 client credentials request.
      type: object
      additionalProperties: false
      required:
        - grant_type
      properties:
        grant_type:
          const: client_credentials
          description: The customer API supports only the client_credentials grant.
        scope:
          type: string
          minLength: 1
          description: Space-separated scopes to request. Omit this field to request all assigned scopes.

    AccessTokenResponse:
      description: Short-lived bearer token for customer API requests.
      type: object
      additionalProperties: false
      required:
        - access_token
        - expires_in
        - expires_at
        - token_type
        - scope
      properties:
        access_token:
          type: string
          description: Bearer token to send in the Authorization header.
        expires_in:
          type: integer
          const: 900
          description: Token lifetime in seconds.
        expires_at:
          type: integer
          description: Token expiry as Unix time in seconds.
        token_type:
          const: Bearer
          description: Token type for the Authorization header.
        scope:
          type: string
          description: Space-separated scopes granted to this token.

    OAuthError:
      description: OAuth 2.0 token endpoint error.
      type: object
      additionalProperties: false
      required:
        - error
        - error_description
      properties:
        error:
          type: string
          description: Stable OAuth error code.
        error_description:
          type: string
          description: Human-readable reason for the error.

    Problem:
      description: A machine-readable API error in RFC 9457 problem-details format.
      type: object
      additionalProperties: false
      required:
        - type
        - title
        - status
        - code
        - instance
        - request_id
      properties:
        type:
          type: string
          format: uri
          description: A stable URI for the problem type.
        title:
          type: string
          description: A short problem summary.
        status:
          type: integer
          minimum: 400
          maximum: 599
          description: The HTTP status code returned with this problem.
        code:
          description: Stable code for application logic and retry decisions.
          $ref: "#/components/schemas/ProblemCode"
        detail:
          type: string
          description: A human-readable explanation of this occurrence.
        instance:
          type: string
          description: The request path that produced the problem.
        request_id:
          type: string
          description: Request identifier to include when you contact Bahn support.
        errors:
          type: array
          description: Field-level validation errors, when the problem concerns request input.
          items:
            $ref: "#/components/schemas/FieldError"

    ProblemCode:
      type: string
      description: A stable machine code for client decisions.
      enum:
        - validation_error
        - authentication_failed
        - permission_denied
        - resource_not_found
        - idempotency_conflict
        - revision_conflict
        - order_not_cancellable
        - order_not_updateable
        - business_rule_violation
        - ordered_for_not_allowed
        - ordered_for_customer_not_found
        - file_too_large
        - request_too_large
        - scenario_not_allowed
        - internal_error

    FieldError:
      description: One invalid request field.
      type: object
      additionalProperties: false
      required:
        - path
        - code
        - message
      properties:
        path:
          type: string
          description: Dot-separated path to the invalid field.
        code:
          type: string
          description: Stable validation code for this field error.
        message:
          type: string
          description: Human-readable reason why the field is invalid.

    Contact:
      description: Contact details for a pickup or delivery location, or null when none are available.
      type:
        - object
        - "null"
      additionalProperties: false
      required:
        - name
        - phone
        - email
      properties:
        name:
          type:
            - string
            - "null"
          description: Full name of the handover contact.
        phone:
          type:
            - string
            - "null"
          description: Phone number that Bahn can use to coordinate the handover.
        email:
          type:
            - string
            - "null"
          format: email
          description: Email address for the handover contact.

    ContactInput:
      description: Person to contact about pickup or delivery at this location.
      type: object
      additionalProperties: false
      properties:
        name:
          type:
            - string
            - "null"
          description: Full name of the handover contact.
        phone:
          type:
            - string
            - "null"
          description: Phone number for handover coordination. Include the country code.
        email:
          type:
            - string
            - "null"
          format: email
          description: Email address for handover coordination.

    AddressInput:
      description: Customer-supplied pickup or delivery address.
      type: object
      additionalProperties: false
      required:
        - formatted_address
      properties:
        formatted_address:
          type: string
          minLength: 1
          maxLength: 500
          description: Complete address as it should appear to a person handling the transport.
        name:
          type: string
          minLength: 1
          maxLength: 500
          description: Business, dealership, auction, or site name at this address.
        address_line_1:
          type: string
          minLength: 1
          maxLength: 200
          description: Street name and building number.
        address_line_2:
          type: string
          minLength: 1
          maxLength: 200
          description: Unit, gate, building, or other secondary address detail.
        postal_code:
          type: string
          minLength: 1
          maxLength: 100
          description: Postal code for the location.
        city:
          type: string
          minLength: 1
          maxLength: 100
          description: City or municipality for the location.
        country_code:
          type: string
          pattern: ^[A-Z]{2}$
          description: Two-letter ISO 3166-1 country code, such as FI or DE.

    Address:
      description: Address stored on the order.
      type: object
      additionalProperties: false
      required:
        - formatted_address
        - name
        - address_line_1
        - address_line_2
        - postal_code
        - city
        - country_code
        - coordinates
      properties:
        formatted_address:
          type: string
          description: Complete human-readable address.
        name:
          type:
            - string
            - "null"
          description: Business or site name, when supplied.
        address_line_1:
          type:
            - string
            - "null"
          description: Street name and building number, when supplied.
        address_line_2:
          type:
            - string
            - "null"
          description: Secondary address detail, when supplied.
        postal_code:
          type:
            - string
            - "null"
          description: Postal code, when supplied.
        city:
          type:
            - string
            - "null"
          description: City or municipality, when supplied.
        country_code:
          type:
            - string
            - "null"
          pattern: ^[A-Z]{2}$
          description: Two-letter ISO 3166-1 country code, when supplied.
        coordinates:
          type:
            - object
            - "null"
          description: Map coordinates for the address, when available.
          additionalProperties: false
          required:
            - latitude
            - longitude
          properties:
            latitude:
              type: number
              minimum: -90
              maximum: 90
              description: Latitude in decimal degrees.
            longitude:
              type: number
              minimum: -180
              maximum: 180
              description: Longitude in decimal degrees.

    PickupInput:
      x-docs-visible-fields: [available_from, requested_windows]
      description: Where, when, and from whom Bahn can collect the vehicle.
      type: object
      additionalProperties: false
      required:
        - address
        - type
      properties:
        address:
          description: Location where Bahn collects the vehicle.
          $ref: "#/components/schemas/AddressInput"
        type:
          type: string
          description: Dealership for a business handover; private for a private-person handover.
          enum:
            - dealership
            - private
        contact:
          description: Person Bahn should contact about the pickup.
          $ref: "#/components/schemas/ContactInput"
        release_code:
          type:
            - string
            - "null"
          maxLength: 255
          description: Code or reference required to release the vehicle at pickup.
        available_from:
          type: string
          format: date-time
          description: The first time when the vehicle is available for pickup.
        requested_windows:
          type: array
          description: Time ranges the customer can accept for pickup. Bahn can agree to one later.
          items:
            $ref: "#/components/schemas/TimeWindowInput"
          default: []

    DeliveryInput:
      description: Where, when, and to whom Bahn should deliver the vehicle.
      type: object
      additionalProperties: false
      required:
        - address
        - type
      properties:
        address:
          description: Location where Bahn delivers the vehicle.
          $ref: "#/components/schemas/AddressInput"
        type:
          type: string
          description: Dealership for a business handover; end_customer for delivery to the buyer or user.
          enum:
            - dealership
            - end_customer
        contact:
          description: Person Bahn should contact about the delivery.
          $ref: "#/components/schemas/ContactInput"
        requested_windows:
          type: array
          description: Time ranges the customer can accept for delivery. Bahn can agree to one later.
          items:
            $ref: "#/components/schemas/TimeWindowInput"
          default: []

    PickupUpdateInput:
      description: Pickup fields to change. Omitted fields keep their current values.
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        address:
          description: Replacement pickup address.
          $ref: "#/components/schemas/AddressInput"
        type:
          type: string
          description: Replacement pickup handover type.
          enum:
            - dealership
            - private
        contact:
          description: Replacement pickup contact.
          $ref: "#/components/schemas/ContactInput"
        release_code:
          type:
            - string
            - "null"
          maxLength: 255
          description: Replacement release code, or null to clear it.
        available_from:
          type: string
          format: date-time
          description: Replacement earliest pickup time.
        requested_windows:
          type: array
          description: Replacement list of acceptable pickup windows.
          items:
            $ref: "#/components/schemas/TimeWindowInput"

    DeliveryUpdateInput:
      description: Delivery fields to change. Omitted fields keep their current values.
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        address:
          description: Replacement delivery address.
          $ref: "#/components/schemas/AddressInput"
        type:
          type: string
          description: Replacement delivery handover type.
          enum:
            - dealership
            - end_customer
        contact:
          description: Replacement delivery contact.
          $ref: "#/components/schemas/ContactInput"
        requested_windows:
          type: array
          description: Replacement list of acceptable delivery windows.
          items:
            $ref: "#/components/schemas/TimeWindowInput"

    Pickup:
      description: Current pickup details and timing for the order.
      type: object
      additionalProperties: false
      required:
        - address
        - type
        - contact
        - release_code
        - available_from
        - requested_windows
        - agreed_window
        - eta
        - eta_updated_at
        - actual_at
      properties:
        address:
          description: Pickup location stored on the order.
          $ref: "#/components/schemas/Address"
        type:
          type: string
          description: Type of pickup handover.
          enum:
            - dealership
            - private
        contact:
          description: Pickup contact, or null when none is available.
          $ref: "#/components/schemas/Contact"
        release_code:
          type:
            - string
            - "null"
          description: Code or reference needed to release the vehicle, when supplied.
        available_from:
          type: string
          format: date-time
          description: The first time when the vehicle is available for pickup.
        requested_windows:
          type: array
          description: Pickup windows requested by the customer. These are not commitments.
          items:
            $ref: "#/components/schemas/TimeWindowInput"
        agreed_window:
          oneOf:
            - $ref: "#/components/schemas/TimeWindowInput"
            - type: "null"
          description: The agreed pickup window. This value is not an ETA.
        eta:
          type:
            - string
            - "null"
          format: date-time
          description: The current pickup ETA. This value is null when no reliable ETA exists.
        eta_updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: The time when Bahn last calculated or updated the pickup ETA.
        actual_at:
          type:
            - string
            - "null"
          format: date-time
          description: The actual pickup time.

    Delivery:
      description: Current delivery details and timing for the order.
      type: object
      additionalProperties: false
      required:
        - address
        - type
        - contact
        - requested_windows
        - agreed_window
        - commitment_deadline_at
        - eta
        - eta_updated_at
        - actual_at
      properties:
        address:
          description: Delivery location stored on the order.
          $ref: "#/components/schemas/Address"
        type:
          type: string
          description: Type of delivery handover.
          enum:
            - dealership
            - end_customer
        contact:
          description: Delivery contact, or null when none is available.
          $ref: "#/components/schemas/Contact"
        requested_windows:
          type: array
          description: Delivery windows requested by the customer. These are not commitments.
          items:
            $ref: "#/components/schemas/TimeWindowInput"
        agreed_window:
          oneOf:
            - $ref: "#/components/schemas/TimeWindowInput"
            - type: "null"
          description: The agreed delivery window. This value is not an ETA.
        commitment_deadline_at:
          type:
            - string
            - "null"
          format: date-time
          description: The latest committed delivery time.
        eta:
          type:
            - string
            - "null"
          format: date-time
          description: The current delivery ETA. This value is null when no reliable ETA exists.
        eta_updated_at:
          type:
            - string
            - "null"
          format: date-time
          description: The time when Bahn last calculated or updated the delivery ETA.
        actual_at:
          type:
            - string
            - "null"
          format: date-time
          description: The actual delivery time.

    TimeWindowInput:
      description: One requested or agreed local time range.
      type: object
      additionalProperties: false
      required:
        - start_at
        - end_at
        - timezone
      properties:
        start_at:
          type: string
          format: date-time
          description: Start of the window as an RFC 3339 timestamp with an offset.
        end_at:
          type: string
          format: date-time
          description: End of the window as an RFC 3339 timestamp with an offset.
        timezone:
          type: string
          description: An IANA time-zone name.

    WinterTires:
      description: Winter-tire details for the primary vehicle.
      type: object
      additionalProperties: false
      required:
        - mounted
        - in_vehicle
      properties:
        mounted:
          type: boolean
          description: Whether winter tires are mounted on the vehicle.
        in_vehicle:
          type: boolean
          description: Whether an additional winter-tire set travels inside the vehicle.

    FuelType:
      type: string
      description: Vehicle fuel or powertrain type.
      enum:
        - petrol
        - diesel
        - electric
        - hybrid_petrol
        - hybrid_diesel
        - unknown

    Transmission:
      type: string
      description: Vehicle transmission type.
      enum:
        - manual
        - automatic
        - unknown

    PrimaryVehicleInput:
      x-docs-visible-fields: [vin, registration_number]
      title: Primary vehicle
      description: Vehicle that Bahn transports from pickup to delivery. Send a VIN or registration number; cross-border orders require the VIN.
      type: object
      additionalProperties: false
      required:
        - role
      properties:
        role:
          const: primary
          description: Identifies the main vehicle in the order.
        vin:
          type: string
          minLength: 17
          maxLength: 17
          description: 17-character vehicle identification number. Required for cross-border orders.
        registration_number:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 50
          description: Registration number, or null when the vehicle is not registered.
        make:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 100
          description: Vehicle manufacturer, when known.
        model:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 100
          description: Vehicle model, when known.
        fuel_type:
          description: Vehicle fuel or powertrain type, when known.
          $ref: "#/components/schemas/FuelType"
        transmission:
          description: Vehicle transmission type, when known.
          $ref: "#/components/schemas/Transmission"
        engine_power_kw:
          type: integer
          minimum: 1
          description: Engine or motor power in kilowatts, when known.
        first_registered_year:
          type: integer
          minimum: 1886
          maximum: 3000
          description: Calendar year of first registration, when known.
        kerb_weight_kg:
          type: integer
          minimum: 1
          description: Vehicle kerb weight in kilograms, when known.
        winter_tires:
          description: Winter-tire information, when relevant to the transport.
          $ref: "#/components/schemas/WinterTires"
        note:
          type:
            - string
            - "null"
          maxLength: 5000
          description: Vehicle-specific handling or identification note for Bahn.
        cmr_recipient:
          description: Optional CMR recipient for this vehicle. Omit to use your account defaults. It does not change the transport destination or handling mode.
          $ref: "#/components/schemas/CmrRecipientSelection"

    PriceCheckPrimaryVehicleInput:
      x-docs-visible-fields: [vin, registration_number]
      title: Primary vehicle
      description: Primary vehicle facts needed to calculate a price. A vehicle identifier is optional here.
      type: object
      additionalProperties: false
      required:
        - role
      properties:
        role:
          const: primary
          description: Identifies the main vehicle in the price check.
        vin:
          type: string
          minLength: 17
          maxLength: 17
          description: 17-character vehicle identification number, when already known.
        registration_number:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 50
          description: Registration number, or null when it is not known.
        make:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 100
          description: Vehicle manufacturer, when known.
        model:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 100
          description: Vehicle model, when known.
        fuel_type:
          description: Vehicle fuel or powertrain type, when known.
          $ref: "#/components/schemas/FuelType"
        transmission:
          description: Vehicle transmission type, when known.
          $ref: "#/components/schemas/Transmission"
        engine_power_kw:
          type: integer
          minimum: 1
          description: Engine or motor power in kilowatts, when known.
        first_registered_year:
          type: integer
          minimum: 1886
          maximum: 3000
          description: Calendar year of first registration, when known.
        kerb_weight_kg:
          type: integer
          minimum: 1
          description: Vehicle kerb weight in kilograms, when known.
        winter_tires:
          description: Winter-tire information, when relevant to pricing.
          $ref: "#/components/schemas/WinterTires"
        note:
          type:
            - string
            - "null"
          maxLength: 5000
          description: Vehicle-specific note that can affect the transport.

    TradeInVehicleInput:
      x-docs-visible-fields: [vin]
      title: Trade-in vehicle
      description: Optional vehicle collected from the delivery location and returned to the pickup location.
      type: object
      additionalProperties: false
      required:
        - role
        - registration_number
      properties:
        role:
          const: trade_in
          description: Identifies the trade-in vehicle in the order.
        vin:
          type: string
          minLength: 17
          maxLength: 17
          description: 17-character vehicle identification number, when known.
        registration_number:
          type: string
          minLength: 1
          maxLength: 50
          description: Registration number used to identify the trade-in vehicle.
        make:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 100
          description: Vehicle manufacturer, when known.
        model:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 100
          description: Vehicle model, when known.
        fuel_type:
          description: Vehicle fuel or powertrain type, when known.
          $ref: "#/components/schemas/FuelType"
        transmission:
          description: Vehicle transmission type, when known.
          $ref: "#/components/schemas/Transmission"
        engine_power_kw:
          type: integer
          minimum: 1
          description: Engine or motor power in kilowatts, when known.
        first_registered_year:
          type: integer
          minimum: 1886
          maximum: 3000
          description: Calendar year of first registration, when known.
        kerb_weight_kg:
          type: integer
          minimum: 1
          description: Vehicle kerb weight in kilograms, when known.
        note:
          type:
            - string
            - "null"
          maxLength: 5000
          description: Vehicle-specific handling or identification note for Bahn.
        cmr_recipient:
          description: Optional CMR recipient for this vehicle. Omit to use your account defaults. It does not change the transport destination or handling mode.
          $ref: "#/components/schemas/CmrRecipientSelection"

    VehicleInput:
      description: One vehicle in an order. Use primary for the transported vehicle and trade_in for the return vehicle.
      oneOf:
        - $ref: "#/components/schemas/PrimaryVehicleInput"
        - $ref: "#/components/schemas/TradeInVehicleInput"
      discriminator:
        propertyName: role
        mapping:
          primary: "#/components/schemas/PrimaryVehicleInput"
          trade_in: "#/components/schemas/TradeInVehicleInput"

    PriceCheckVehicleInput:
      description: One vehicle included in a price calculation.
      oneOf:
        - $ref: "#/components/schemas/PriceCheckPrimaryVehicleInput"
        - $ref: "#/components/schemas/TradeInVehicleInput"
      discriminator:
        propertyName: role
        mapping:
          primary: "#/components/schemas/PriceCheckPrimaryVehicleInput"
          trade_in: "#/components/schemas/TradeInVehicleInput"

    OrderItem:
      description: Current identity, transport state, and handover results for one vehicle.
      type: object
      additionalProperties: false
      required:
        - id
        - role
        - status
        - vin
        - registration_number
        - make
        - model
        - note
        - fuel_type
        - transmission
        - engine_power_kw
        - first_registered_year
        - kerb_weight_kg
        - cmr
        - plates
        - pickup_condition
        - delivery_condition
        - winter_tires
      properties:
        id:
          description: Stable ID for this vehicle within the order.
          $ref: "#/components/schemas/OrderItemId"
        role:
          type: string
          description: primary for the outbound vehicle; trade_in for the reverse-route vehicle.
          enum:
            - primary
            - trade_in
        status:
          description: Current physical transport state of this vehicle.
          $ref: "#/components/schemas/OrderItemStatus"
        vin:
          type:
            - string
            - "null"
          description: Vehicle identification number, when available.
        registration_number:
          type:
            - string
            - "null"
          description: Registration number, when available.
        make:
          type:
            - string
            - "null"
          description: Vehicle manufacturer, when available.
        model:
          type:
            - string
            - "null"
          description: Vehicle model, when available.
        note:
          type:
            - string
            - "null"
          description: Vehicle-specific note supplied with the order.
        fuel_type:
          description: Vehicle fuel or powertrain type.
          $ref: "#/components/schemas/FuelType"
        transmission:
          description: Vehicle transmission type.
          $ref: "#/components/schemas/Transmission"
        engine_power_kw:
          type:
            - integer
            - "null"
          minimum: 1
          description: Engine or motor power in kilowatts, when available.
        first_registered_year:
          type:
            - integer
            - "null"
          minimum: 1886
          description: Calendar year of first registration, when available.
        kerb_weight_kg:
          type:
            - integer
            - "null"
          minimum: 1
          description: Vehicle kerb weight in kilograms, when available.
        cmr:
          type: string
          description: none for no CMR, physical for a paper CMR, or digital for a Bahn-generated document.
          enum:
            - none
            - physical
            - digital
        plates:
          type: string
          description: Whether the transport requires registration plates for this vehicle.
          enum:
            - none
            - required
        pickup_condition:
          description: Condition recorded at pickup, or null until it is available.
          oneOf:
            - $ref: "#/components/schemas/PickupCondition"
            - type: "null"
        delivery_condition:
          description: Condition recorded at delivery, or null until it is available.
          oneOf:
            - $ref: "#/components/schemas/DeliveryCondition"
            - type: "null"
        winter_tires:
          description: Winter-tire details for the primary vehicle, or null when not supplied.
          oneOf:
            - $ref: "#/components/schemas/WinterTires"
            - type: "null"

    PickupCondition:
      description: Vehicle condition recorded when Bahn accepts the pickup handover.
      type: object
      additionalProperties: false
      required:
        - status
        - description
        - evidence_file_ids
      properties:
        status:
          type: string
          description: clear when no damage is recorded; damage_reported when damage is recorded.
          enum:
            - clear
            - damage_reported
        description:
          type:
            - string
            - "null"
          description: Human-readable condition or damage note, when available.
        evidence_file_ids:
          type: array
          description: IDs of order files that support this pickup condition.
          uniqueItems: true
          items:
            $ref: "#/components/schemas/FileId"

    DeliveryCondition:
      description: Vehicle condition recorded when Bahn accepts the delivery handover.
      type: object
      additionalProperties: false
      required:
        - status
        - description
        - evidence_file_ids
      properties:
        status:
          type: string
          description: clear when no new damage is recorded; new_damage_reported when new damage is recorded.
          enum:
            - clear
            - new_damage_reported
        description:
          type:
            - string
            - "null"
          description: Human-readable condition or new-damage note, when available.
        evidence_file_ids:
          type: array
          description: IDs of order files that support this delivery condition.
          uniqueItems: true
          items:
            $ref: "#/components/schemas/FileId"

    ServiceOptions:
      description: Transport and inspection service selected for the order.
      type: object
      additionalProperties: false
      required:
        - transport_preference
        - inspection
      properties:
        transport_preference:
          type: string
          description: |
            How Bahn may move the primary vehicle.
            flexible allows driven or truck transport; truck_preferred requests a truck when practical;
            truck_required means the vehicle must not be driven.
          enum:
            - flexible
            - truck_preferred
            - truck_required
        inspection:
          type: string
          description: standard for the normal handover check; full for a structured inspection report.
          enum:
            - standard
            - full

    PriceValue:
      description: Price breakdown in euro cents.
      type: object
      additionalProperties: false
      required:
        - currency
        - total_cents
        - transport_cents
        - additional_fees_cents
        - trade_in_cents
      properties:
        currency:
          const: EUR
          description: Currency for every amount in this price.
        total_cents:
          type: integer
          minimum: 0
          description: Total price, including transport, additional fees, and trade-in service.
        transport_cents:
          type: integer
          minimum: 0
          description: Price of the primary vehicle transport before additional fees and trade-in service.
        additional_fees_cents:
          type: integer
          description: >-
            Total additional service fees. The value is negative when agreed
            discounts or price corrections on the order outweigh its surcharges.
        trade_in_cents:
          type: integer
          minimum: 0
          description: Price of the reverse-route trade-in transport, or zero when none is included.

    Price:
      description: Accepted order price, or null when automatic pricing was not available.
      oneOf:
        - $ref: "#/components/schemas/PriceValue"
        - type: "null"

    PriceCheckRequest:
      description: Route, vehicles, and service choices used to calculate a price.
      type: object
      additionalProperties: false
      required:
        - pickup
        - delivery
        - items
        - service_options
      properties:
        ordered_for:
          description: Optional. Send it only when an ordering-channel credential orders for another business. Omit it to order for your own account.
          $ref: "#/components/schemas/OrderedFor"
        pickup:
          description: Proposed pickup details. A price check does not require pickup availability.
          $ref: "#/components/schemas/PickupInput"
        delivery:
          description: Proposed delivery details.
          $ref: "#/components/schemas/DeliveryInput"
        items:
          type: array
          minItems: 1
          maxItems: 2
          description: One primary vehicle and, optionally, one reverse-route trade-in vehicle.
          contains:
            type: object
            required:
              - role
            properties:
              role:
                const: primary
                description: Requires exactly one primary vehicle in the price check.
          minContains: 1
          maxContains: 1
          items:
            $ref: "#/components/schemas/PriceCheckVehicleInput"
        service_options:
          description: Proposed transport and inspection service.
          $ref: "#/components/schemas/ServiceOptions"

    PriceCheck:
      description: Automatic price result or a clear custom-quote result.
      oneOf:
        - $ref: "#/components/schemas/AvailablePriceCheck"
        - $ref: "#/components/schemas/CustomQuoteRequiredPriceCheck"
      discriminator:
        propertyName: status
        mapping:
          available: "#/components/schemas/AvailablePriceCheck"
          custom_quote_required: "#/components/schemas/CustomQuoteRequiredPriceCheck"

    AvailablePriceCheck:
      title: Automatic price available
      description: Automatic price with indicative pickup and delivery times.
      type: object
      additionalProperties: false
      required:
        - status
        - ordered_for
        - price
        - distance_meters
        - estimated_pickup_at
        - estimated_delivery_at
      properties:
        status:
          const: available
          description: available means the response contains an automatic price.
        ordered_for:
          description: Business the price was calculated for, or null when it is for your own account.
          $ref: "#/components/schemas/NullableOrderedFor"
        price:
          description: Automatic price for this request.
          $ref: "#/components/schemas/PriceValue"
        distance_meters:
          $ref: "#/components/schemas/DistanceMeters"
        estimated_pickup_at:
          type: string
          format: date-time
          description: Indicative pickup time for this price result. It is not an agreed window or ETA.
        estimated_delivery_at:
          type: string
          format: date-time
          description: Indicative delivery time for this price result. It is not an agreed window or ETA.

    CustomQuoteRequiredPriceCheck:
      title: Custom quote required
      description: Automatic pricing is not available. Bahn does not guarantee a custom quote.
      type: object
      additionalProperties: false
      required:
        - status
        - ordered_for
        - price
        - distance_meters
        - estimated_pickup_at
        - estimated_delivery_at
      properties:
        status:
          const: custom_quote_required
          description: custom_quote_required means automatic pricing is not available for this request.
        ordered_for:
          description: Business the price was calculated for, or null when it is for your own account.
          $ref: "#/components/schemas/NullableOrderedFor"
        price:
          type: "null"
          description: Always null because no automatic price is available.
        distance_meters:
          $ref: "#/components/schemas/DistanceMeters"
        estimated_pickup_at:
          type: string
          format: date-time
          description: Indicative pickup time when one can still be calculated.
        estimated_delivery_at:
          type: string
          format: date-time
          description: Indicative delivery time when one can still be calculated.

    DistanceMeters:
      type:
        - integer
        - "null"
      minimum: 0
      description: |
        The driving distance in meters that the price calculation uses for this route.
        Bahn's per-km prices use this distance rounded up to whole kilometers.
        Use it to calculate your own derived prices from exactly the same distance.
        The value is null when no driving distance is available for the route.

    OrderCreateRequest:
      description: Complete transport request that creates an order in the selected environment.
      type: object
      additionalProperties: false
      required:
        - pickup
        - delivery
        - items
        - service_options
      properties:
        ordered_for:
          description: Optional. Send it only when an ordering-channel credential orders for another business. Omit it to order for your own account.
          $ref: "#/components/schemas/OrderedFor"
        customer_reference:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 255
          description: Your reference for matching the Bahn order to your system. It does not need to be unique.
        pickup:
          description: Pickup location and handover details. Send available_from, one or more requested_windows, or both.
          $ref: "#/components/schemas/PickupInput"
        delivery:
          description: Delivery location, handover details, and requested windows.
          $ref: "#/components/schemas/DeliveryInput"
        items:
          type: array
          minItems: 1
          maxItems: 2
          description: Exactly one item must have the primary role.
          contains:
            type: object
            required:
              - role
            properties:
              role:
                const: primary
                description: Requires exactly one primary vehicle in the order.
          minContains: 1
          maxContains: 1
          items:
            $ref: "#/components/schemas/VehicleInput"
        service_options:
          description: Transport and inspection service for the order.
          $ref: "#/components/schemas/ServiceOptions"
        file_ids:
          type: array
          description: IDs returned by POST /v2/files. Use a customer_document file for a pickup release document.
          uniqueItems: true
          items:
            $ref: "#/components/schemas/FileId"
        customer_note:
          type:
            - string
            - "null"
          maxLength: 5000
          description: Order-level note for Bahn. Use item.note for vehicle-specific instructions.

    OrderUpdateRequest:
      description: Order fields to change. Omitted fields keep their current values.
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        customer_reference:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 255
          description: Replacement customer reference, or null to clear it.
        pickup:
          description: Changes only the supplied pickup fields.
          $ref: "#/components/schemas/PickupUpdateInput"
        delivery:
          description: Changes only the supplied delivery fields.
          $ref: "#/components/schemas/DeliveryUpdateInput"
        items:
          type: array
          minItems: 1
          maxItems: 1
          description: |
            Replaces the primary vehicle input. Include its identifier and every fact to retain.
            A trade-in item remains unchanged.
          items:
            allOf:
              - $ref: "#/components/schemas/PrimaryVehicleInput"
              - not:
                  required: [cmr_recipient]
        service_options:
          description: Replacement transport and inspection service.
          $ref: "#/components/schemas/ServiceOptions"
        file_ids:
          type: array
          description: |
            Adds uploaded files to the order.
            Existing order files remain attached.
          uniqueItems: true
          items:
            $ref: "#/components/schemas/FileId"
        customer_note:
          type:
            - string
            - "null"
          maxLength: 5000
          description: Replacement order-level note, or null to clear it.

    OrderStatus:
      type: string
      description: |
        Overall order state derived from all vehicles.
        accepted means no vehicle is in transit; in_transit means transport is underway;
        delivered means every required vehicle is delivered; cancelled means the order was cancelled.
      enum:
        - accepted
        - in_transit
        - delivered
        - cancelled

    OrderItemStatus:
      type: string
      description: awaiting_pickup before collection, in_transit after collection, and delivered after handover.
      enum:
        - awaiting_pickup
        - in_transit
        - delivered

    OrderId:
      type: string
      pattern: ^ord_[a-z0-9]+$
      description: Stable Bahn order identifier used in every order resource path.

    OrderItemId:
      type: string
      pattern: ^item_[a-z0-9]+_(primary|trade_in)$
      description: Stable identifier for the primary or trade-in vehicle within an order.

    FileId:
      type: string
      pattern: ^file_[a-z0-9-]+$
      description: Stable identifier for an uploaded or published order file.

    OrderedFor:
      description: Business that an ordering channel represents. It must already exist as a Bahn customer in production.
      type: object
      additionalProperties: false
      required:
        - country_code
        - business_id
      properties:
        country_code:
          type: string
          pattern: ^[A-Z]{2}$
          description: The ISO 3166-1 alpha-2 country code of the Bahn customer.
        business_id:
          type: string
          minLength: 1
          maxLength: 100
          description: The official business ID of the Bahn customer.

    RepresentedBusinessCheck:
      description: Eligibility result for a represented business.
      type: object
      additionalProperties: false
      required:
        - status
      properties:
        status:
          const: eligible
          description: The ordering channel can create orders for this business identity.

    NullableOrderedFor:
      description: Represented business, or null when the order belongs to the calling credential's own account.
      oneOf:
        - $ref: "#/components/schemas/OrderedFor"
        - type: "null"

    PickupReadiness:
      type: string
      description: pending before Bahn can confirm pickup availability, preparing while confirmation is possible, and confirmed after Bahn confirms it.
      enum:
        - pending
        - preparing
        - confirmed

    AllowedAction:
      type: string
      description: Operation the current credential can perform on the current order state.
      enum:
        - update
        - cancel
        - get_files
        - get_inspection_report
        - get_tracking

    Order:
      description: Current state of one transport order.
      type: object
      additionalProperties: false
      required:
        - id
        - ordered_for
        - customer_reference
        - status
        - pickup_readiness
        - created_at
        - updated_at
        - pickup
        - delivery
        - items
        - service_options
        - price
        - customer_note
        - allowed_actions
        - revision
      properties:
        id:
          description: Stable Bahn order ID. Store it after order creation.
          $ref: "#/components/schemas/OrderId"
        ordered_for:
          description: Business the order was placed for, or null when it belongs to your own account.
          $ref: "#/components/schemas/NullableOrderedFor"
        customer_reference:
          type:
            - string
            - "null"
          description: Reference supplied by your system, or null when none was supplied.
        status:
          description: Current overall order state.
          $ref: "#/components/schemas/OrderStatus"
        pickup_readiness:
          description: Current seller readiness for pickup.
          $ref: "#/components/schemas/PickupReadiness"
        created_at:
          type: string
          format: date-time
          description: Time when Bahn accepted the order.
        updated_at:
          type: string
          format: date-time
          description: Time when Bahn last updated the order.
        pickup:
          description: Current pickup location, contact, requested timing, agreed timing, ETA, and actual time.
          $ref: "#/components/schemas/Pickup"
        delivery:
          description: Current delivery location, contact, requested timing, agreed timing, ETA, and actual time.
          $ref: "#/components/schemas/Delivery"
        items:
          type: array
          minItems: 1
          maxItems: 2
          description: Primary vehicle and optional trade-in vehicle, each with its own state and handover condition.
          items:
            $ref: "#/components/schemas/OrderItem"
        service_options:
          description: Transport and inspection service selected for this order.
          $ref: "#/components/schemas/ServiceOptions"
        price:
          description: Price accepted when the order was created, or null when unavailable.
          $ref: "#/components/schemas/Price"
        distance_meters:
          description: |
            The driving distance in meters that the price calculation used.
            The field appears only in the creation response.
            Bahn does not store the value on the order.
          $ref: "#/components/schemas/DistanceMeters"
        customer_note:
          type:
            - string
            - "null"
          description: Order-level note supplied by the customer.
        allowed_actions:
          type: array
          uniqueItems: true
          description: Actions currently available to this credential. Use this list to enable order controls.
          items:
            $ref: "#/components/schemas/AllowedAction"
        revision:
          type: string
          description: |
            An opaque order revision.
            Use this value in the If-Match header for an order change.
            A live ETA update does not change this value.

    OrderListItem:
      description: Compact order summary for list and search views.
      type: object
      additionalProperties: false
      required:
        - id
        - ordered_for
        - customer_reference
        - status
        - pickup_readiness
        - created_at
        - updated_at
        - primary_vehicle
        - pickup_address
        - delivery_address
        - actual_pickup_at
        - actual_delivery_at
      properties:
        id:
          description: Stable Bahn order ID.
          $ref: "#/components/schemas/OrderId"
        ordered_for:
          description: Business the order was placed for, or null when it belongs to your own account.
          $ref: "#/components/schemas/NullableOrderedFor"
        customer_reference:
          type:
            - string
            - "null"
          description: Customer reference supplied with the order.
        status:
          description: Current overall order state.
          $ref: "#/components/schemas/OrderStatus"
        pickup_readiness:
          description: Current seller readiness for pickup.
          $ref: "#/components/schemas/PickupReadiness"
        created_at:
          type: string
          format: date-time
          description: Time when Bahn accepted the order.
        updated_at:
          type: string
          format: date-time
          description: Time when Bahn last updated the order.
        primary_vehicle:
          type: object
          description: Identifiers for the primary vehicle.
          additionalProperties: false
          required:
            - vin
            - registration_number
          properties:
            vin:
              type:
                - string
                - "null"
              description: Primary vehicle VIN, when available.
            registration_number:
              type:
                - string
                - "null"
              description: Primary vehicle registration number, when available.
        pickup_address:
          type: string
          description: Human-readable pickup address for list display.
        delivery_address:
          type: string
          description: Human-readable delivery address for list display.
        actual_pickup_at:
          type:
            - string
            - "null"
          format: date-time
          description: Actual primary-vehicle pickup time, or null before pickup.
        actual_delivery_at:
          type:
            - string
            - "null"
          format: date-time
          description: Actual primary-vehicle delivery time, or null before delivery.

    OrderPage:
      description: Cursor-paginated list of order summaries.
      type: object
      additionalProperties: false
      required:
        - data
        - next_cursor
        - has_more
      properties:
        data:
          type: array
          description: Orders in this page.
          items:
            $ref: "#/components/schemas/OrderListItem"
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor for the next page, or null after the final page.
        has_more:
          type: boolean
          description: Whether another page is available.

    FileUploadRequest:
      description: Metadata used to create a temporary direct-upload form.
      type: object
      additionalProperties: false
      required:
        - name
        - category
        - media_type
        - size_bytes
      properties:
        description:
          type: string
          minLength: 1
          description: Customer-facing explanation of what the uploaded document contains.
        name:
          type: string
          minLength: 1
          maxLength: 255
          description: Original file name shown with the uploaded document.
        category:
          type: string
          description: Use power_of_attorney only for a power of attorney. Use customer_document for other files, including pickup release documents.
          enum:
            - power_of_attorney
            - customer_document
        media_type:
          type: string
          description: MIME type of the file that will be uploaded.
          enum:
            - application/pdf
            - image/jpeg
            - image/png
        size_bytes:
          type: integer
          minimum: 1
          maximum: 12582912
          description: Exact file size in bytes. The maximum is 12 MiB.

    FileUpload:
      description: File ID and temporary multipart form for direct upload.
      type: object
      additionalProperties: false
      required:
        - file_id
        - upload
      properties:
        file_id:
          description: ID to add to an order after the upload succeeds.
          $ref: "#/components/schemas/FileId"
        upload:
          type: object
          description: Temporary form that accepts the file contents.
          additionalProperties: false
          required:
            - method
            - url
            - fields
            - expires_at
          properties:
            method:
              const: POST
              description: HTTP method required by the upload form.
            url:
              type: string
              format: uri
              description: Temporary URL that receives the multipart form.
            fields:
              type: object
              description: Form fields to send unchanged before the final file field.
              additionalProperties:
                type: string
            expires_at:
              type: string
              format: date-time
              description: Time after which this upload form no longer accepts the file.

    FileKind:
      type: string
      description: Whether the file is an image or a document.
      enum:
        - image
        - document

    FileCategory:
      type: string
      description: The file content category.
      enum:
        - vehicle
        - odometer
        - vin
        - accessories
        - cmr
        - power_of_attorney
        - customer_document

    FileEvidence:
      type: string
      description: The purpose for which the file is evidence.
      enum:
        - inspection
        - damage

    File:
      description: One customer upload, provider photo, transport document, generated CMR PDF, or inspection evidence file.
      type: object
      additionalProperties: false
      required:
        - id
        - item_id
        - kind
        - category
        - stage
        - evidence_for
        - description
        - name
        - media_type
        - size_bytes
        - created_at
        - content_url
        - content_url_expires_at
        - can_delete
      properties:
        id:
          description: Stable file ID. Use this as the file identity instead of content_url.
          $ref: "#/components/schemas/FileId"
        item_id:
          description: Vehicle associated with the file, or null for an order-level file.
          oneOf:
            - $ref: "#/components/schemas/OrderItemId"
            - type: "null"
        kind:
          description: Whether the UI should render this file as an image or document.
          $ref: "#/components/schemas/FileKind"
        category:
          description: What the file contains.
          $ref: "#/components/schemas/FileCategory"
        stage:
          description: The pickup or delivery stage, or null when the file has no handover stage.
          type:
            - string
            - "null"
          enum:
            - pickup
            - delivery
            - null
        evidence_for:
          description: The damage or inspection facts that use this file as evidence.
          type: array
          items:
            $ref: "#/components/schemas/FileEvidence"
        description:
          type:
            - string
            - "null"
          description: Customer-facing file description, when available.
        name:
          type: string
          description: Display file name.
        media_type:
          type:
            - string
            - "null"
          description: MIME type, when known.
        size_bytes:
          type:
            - integer
            - "null"
          minimum: 0
          description: File size in bytes, when known. This value is null for documents that the server creates on request.
        created_at:
          type: string
          format: date-time
          description: Time when the file became available on the order.
        content_url:
          type: string
          format: uri
          description: Short-lived URL for downloading the file. Do not store it as file identity.
        content_url_expires_at:
          type: string
          format: date-time
          description: Time when content_url expires. Read the file list again for a fresh URL.
        can_delete:
          type: boolean
          description: Whether the current credential can delete this file now.

    FileList:
      description: Files currently available for an order.
      type: object
      additionalProperties: false
      required:
        - data
      properties:
        data:
          type: array
          description: Customer uploads, photos, documents, and evidence files.
          items:
            $ref: "#/components/schemas/File"

    InspectionStatus:
      type: string
      description: not_required when the order has no full inspection, pending before completion, and available when the report can be read.
      enum:
        - not_required
        - pending
        - available

    InspectionCategory:
      type: string
      description: Vehicle area or function checked during a full inspection.
      enum:
        - body
        - tires
        - interior
        - starting
        - electrical
        - accessories
        - service_book
        - engine
        - gearbox
        - drivetrain
        - other

    InspectionItem:
      description: Result for one category in a full vehicle inspection.
      type: object
      additionalProperties: false
      required:
        - category
        - status
        - findings
        - evidence
      properties:
        category:
          description: Vehicle area or function that was checked.
          $ref: "#/components/schemas/InspectionCategory"
        status:
          type: string
          description: ok when no issue was recorded; issue when the report contains a finding.
          enum:
            - ok
            - issue
        findings:
          type:
            - string
            - "null"
          description: Inspector findings for this category, or null when none were recorded.
        evidence:
          type: array
          description: Photos or documents attached to this inspection category.
          items:
            $ref: "#/components/schemas/File"

    InspectionReport:
      description: Structured full-inspection result for the primary vehicle.
      type: object
      additionalProperties: false
      required:
        - order_id
        - item_id
        - status
        - inspected_at
        - overall_findings
        - items
      properties:
        order_id:
          description: Order that requested the inspection.
          $ref: "#/components/schemas/OrderId"
        item_id:
          oneOf:
            - $ref: "#/components/schemas/OrderItemId"
            - type: "null"
          description: |
            The primary order item.
            This value is null when the order needs no report.
        status:
          description: Current availability of the full inspection report.
          $ref: "#/components/schemas/InspectionStatus"
        inspected_at:
          type:
            - string
            - "null"
          format: date-time
          description: Time when the full inspection was completed, or null before completion.
        overall_findings:
          type:
            - string
            - "null"
          description: Overall inspector summary, or null when none was recorded.
        items:
          type: array
          description: Category-level inspection results. Empty until the report is available.
          items:
            $ref: "#/components/schemas/InspectionItem"

    GeoJsonPosition:
      type: array
      description: GeoJSON coordinate pair in longitude, latitude order.
      prefixItems:
        - type: number
          description: Longitude, in degrees.
          minimum: -180
          maximum: 180
        - type: number
          description: Latitude, in degrees.
          minimum: -90
          maximum: 90
      minItems: 2
      maxItems: 2

    GeoJsonPoint:
      type: object
      description: A GeoJSON position for one map marker.
      additionalProperties: false
      required:
        - type
        - coordinates
      properties:
        type:
          const: Point
          description: GeoJSON geometry type.
        coordinates:
          description: Longitude and latitude for the map marker.
          $ref: "#/components/schemas/GeoJsonPosition"

    TrackingPosition:
      description: Map-ready vehicle position with its observation time and optional movement details.
      type: object
      additionalProperties: false
      required:
        - point
        - observed_at
        - accuracy_meters
        - heading_degrees
      properties:
        point:
          description: GeoJSON point to render as the vehicle marker.
          $ref: "#/components/schemas/GeoJsonPoint"
        observed_at:
          type: string
          format: date-time
          description: Time when this position was observed. Show its age near the map.
        accuracy_meters:
          type:
            - number
            - "null"
          minimum: 0
          description: The position accuracy radius in meters.
        heading_degrees:
          type:
            - number
            - "null"
          minimum: 0
          maximum: 360
          description: The travel heading in degrees from north.

    TrackingState:
      type: string
      description: |
        not_started means no customer tracking has started.
        active means the current driver assignment has active tracking.
        paused means a durable transport phase has no driver GPS.
        ended means delivery or cancellation ended tracking.
        A short GPS gap does not change the state.
      enum:
        - not_started
        - active
        - paused
        - ended

    PickupProgress:
      type: string
      description: on_the_way while the driver approaches pickup; nearby after the pickup ETA reaches 30 minutes or less.
      enum:
        - on_the_way
        - nearby

    TrackingDriver:
      description: Known driver shown during pickup approach.
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        name:
          type: string
          minLength: 1
          description: Driver's full name.

    TrackingItem:
      description: Current tracking state for one primary or trade-in vehicle.
      type: object
      additionalProperties: false
      required:
        - item_id
        - role
        - tracking_state
        - phase
        - pickup_progress
        - driver
        - position
      properties:
        item_id:
          description: Vehicle that this tracking entry describes.
          $ref: "#/components/schemas/OrderItemId"
        role:
          type: string
          description: primary for the outbound vehicle; trade_in for the reverse-route vehicle.
          enum:
            - primary
            - trade_in
        tracking_state:
          description: |
            Current tracking lifecycle state.
            not_started means no customer tracking has started.
            active means the current driver assignment has active tracking.
            paused means a durable ferry or carrier phase has no driver GPS.
            ended means delivery or cancellation ended tracking.
            A null position during a short GPS gap does not change active to paused.
          $ref: "#/components/schemas/TrackingState"
        phase:
          type: string
          description: The current tracking phase.
          enum:
            - pickup
            - transport
        pickup_progress:
          description: Pickup approach state, or null outside pickup approach.
          oneOf:
            - $ref: "#/components/schemas/PickupProgress"
            - type: "null"
        driver:
          description: Known pickup driver, or null when no driver name is available.
          oneOf:
            - $ref: "#/components/schemas/TrackingDriver"
            - type: "null"
        position:
          description: The position to show for this item, or null when no position is available.
          oneOf:
            - $ref: "#/components/schemas/TrackingPosition"
            - type: "null"

    TrackingSnapshot:
      description: Current map and pickup-approach state for every vehicle in an order.
      type: object
      additionalProperties: false
      required:
        - order_id
        - status
        - items
        - revision
      properties:
        order_id:
          description: Order represented by this tracking snapshot.
          $ref: "#/components/schemas/OrderId"
        status:
          description: Current overall order status.
          $ref: "#/components/schemas/OrderStatus"
        items:
          type: array
          minItems: 1
          description: One tracking entry for each order item.
          items:
            $ref: "#/components/schemas/TrackingItem"
        revision:
          type: string
          description: Opaque snapshot revision used as the tracking ETag.

    OrderedBy:
      description: Person or API credential that created the order.
      type: object
      additionalProperties: false
      required:
        - name
        - email
      properties:
        name:
          type:
            - string
            - "null"
          description: Credential name or person name, when available.
        email:
          type:
            - string
            - "null"
          format: email
          description: Person email for portal orders, or null for API-credential orders.

    ReportCost:
      description: Billed order cost, or null when no cost is available.
      type:
        - object
        - "null"
      additionalProperties: false
      required:
        - currency
        - total_cents
      properties:
        currency:
          const: EUR
          description: Currency of the report cost.
        total_cents:
          type: integer
          minimum: 0
          description: Billing price plus additional billing rows, in euro cents.

    RefuelReport:
      description: Approved fuel or charging cost for an intra-Finland transport.
      type: object
      additionalProperties: false
      required:
        - occurred
        - cost_cents
      properties:
        occurred:
          type: boolean
          description: Whether Bahn recorded an approved fuel or charging expense.
        cost_cents:
          type: integer
          minimum: 0
          description: Total approved fuel or charging cost in euro cents.

    OrderReportRow:
      description: Flat order row for exports, reporting, and finance workflows.
      type: object
      additionalProperties: false
      required:
        - order_id
        - ordered_for
        - customer_reference
        - created_at
        - status
        - primary_vin
        - primary_registration_number
        - origin_country
        - destination_country
        - pickup_address
        - delivery_address
        - pickup_type
        - delivery_type
        - ordered_by
        - available_for_pickup_at
        - picked_up_at
        - delivered_at
        - distance_meters
        - driver_time_minutes
        - total_transport_minutes
        - cost
        - refuel
      properties:
        order_id:
          description: Stable Bahn order ID.
          $ref: "#/components/schemas/OrderId"
        ordered_for:
          description: Business the order was placed for, or null when it belongs to your own account.
          $ref: "#/components/schemas/NullableOrderedFor"
        customer_reference:
          type:
            - string
            - "null"
          description: Customer reference supplied with the order.
        created_at:
          type: string
          format: date-time
          description: Time when Bahn accepted the order.
        status:
          description: Current overall order state.
          $ref: "#/components/schemas/OrderStatus"
        primary_vin:
          type:
            - string
            - "null"
          description: Primary vehicle VIN, when available.
        primary_registration_number:
          type:
            - string
            - "null"
          description: Primary vehicle registration number, when available.
        origin_country:
          type:
            - string
            - "null"
          description: Pickup country as a two-letter ISO code, when available.
        destination_country:
          type:
            - string
            - "null"
          description: Delivery country as a two-letter ISO code, when available.
        pickup_address:
          type:
            - string
            - "null"
          description: Human-readable pickup address, when available.
        delivery_address:
          type:
            - string
            - "null"
          description: Human-readable delivery address, when available.
        pickup_type:
          type: string
          description: Pickup handover type.
          enum:
            - dealership
            - private
        delivery_type:
          type: string
          description: Delivery handover type.
          enum:
            - dealership
            - end_customer
        ordered_by:
          description: Person or API credential that created the order.
          $ref: "#/components/schemas/OrderedBy"
        available_for_pickup_at:
          type:
            - string
            - "null"
          format: date-time
          description: Earliest pickup availability supplied with the order, when available.
        picked_up_at:
          type:
            - string
            - "null"
          format: date-time
          description: Actual primary-vehicle pickup time, when available.
        delivered_at:
          type:
            - string
            - "null"
          format: date-time
          description: Actual primary-vehicle delivery time, when available.
        distance_meters:
          type:
            - integer
            - "null"
          minimum: 0
          description: Sum of recorded driving distances for completed transport work, in meters.
        driver_time_minutes:
          type:
            - integer
            - "null"
          minimum: 0
          description: Sum of recorded driving times for completed transport work, in minutes.
        total_transport_minutes:
          type:
            - integer
            - "null"
          minimum: 0
          description: Minutes from actual primary pickup to actual primary delivery.
        cost:
          description: Billed order cost, or null when unavailable.
          $ref: "#/components/schemas/ReportCost"
        refuel:
          description: Approved fuel or charging expense summary.
          $ref: "#/components/schemas/RefuelReport"

    OrderReportPage:
      description: Cursor-paginated page of flat report rows.
      type: object
      additionalProperties: false
      required:
        - data
        - next_cursor
        - has_more
      properties:
        data:
          type: array
          description: Report rows in this page.
          items:
            $ref: "#/components/schemas/OrderReportRow"
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor for the next page, or null after the final page.
        has_more:
          type: boolean
          description: Whether another report page is available.

    SandboxScenario:
      type: string
      description: Named deterministic change that advances a sandbox order.
      enum:
        - pickup_readiness_confirmed
        - pickup_approach_started
        - pickup_nearby
        - pickup_completed
        - delivery_eta_changed
        - inspection_completed
        - files_published
        - tracking_available
        - tracking_unavailable
        - tracking_paused
        - delivery_completed
        - cancelled

    SandboxAdvanceRequest:
      description: Scenario to apply to one sandbox order.
      type: object
      additionalProperties: false
      required:
        - scenario
      properties:
        scenario:
          description: Named state change to apply. Its current-state precondition must be satisfied.
          $ref: "#/components/schemas/SandboxScenario"
        occurred_at:
          type: string
          format: date-time
          description: Optional event time for deterministic tests. Defaults to the current time.

    SandboxAdvanceResult:
      description: Applied scenario and the resulting current order.
      type: object
      additionalProperties: false
      required:
        - scenario
        - order
      properties:
        scenario:
          description: Scenario that was applied.
          $ref: "#/components/schemas/SandboxScenario"
        order:
          description: Current order after the scenario.
          $ref: "#/components/schemas/Order"

    OrderEventDataBase:
      description: Fields shared by every Bahn order webhook.
      type: object
      required:
        - order_id
        - ordered_for
        - order_sequence
        - resource_url
      properties:
        order_id:
          description: Order affected by the event.
          $ref: "#/components/schemas/OrderId"
        ordered_for:
          description: Business the order was placed for, or null when it belongs to your own account.
          $ref: "#/components/schemas/NullableOrderedFor"
        order_sequence:
          type: integer
          minimum: 1
          description: Increasing sequence used to order events for this order.
        resource_url:
          type: string
          description: The path of the related current resource.
        related_resource_urls:
          type: array
          minItems: 1
          uniqueItems: true
          description: Paths of other resources that changed with the primary resource.
          items:
            type: string
        revision:
          type: string
          description: The order revision at the event time, when available.

    StandardOrderEventData:
      description: Change notice whose current values must be read from resource_url.
      allOf:
        - $ref: "#/components/schemas/OrderEventDataBase"
      unevaluatedProperties: false

    ActiveOrderStatus:
      type: string
      description: Non-cancelled order status used in status-transition events.
      enum:
        - accepted
        - in_transit
        - delivered

    OrderItemRole:
      type: string
      description: primary for the outbound vehicle; trade_in for the reverse-route vehicle.
      enum:
        - primary
        - trade_in

    OrderCreatedEventData:
      description: Details for an accepted order.
      allOf:
        - $ref: "#/components/schemas/OrderEventDataBase"
        - type: object
          required:
            - status
          properties:
            status:
              const: accepted
              description: Initial status of every newly accepted order.
      unevaluatedProperties: false

    OrderStatusChangedEventData:
      description: Overall order status transition that is not already represented by an item or cancellation event.
      allOf:
        - $ref: "#/components/schemas/OrderEventDataBase"
        - type: object
          required:
            - previous_status
            - status
          properties:
            previous_status:
              description: Overall order status before the change.
              $ref: "#/components/schemas/ActiveOrderStatus"
            status:
              description: Overall order status after the change.
              $ref: "#/components/schemas/ActiveOrderStatus"
      unevaluatedProperties: false

    OrderItemStatusChangedEventData:
      description: Physical transport status transition for one vehicle.
      allOf:
        - $ref: "#/components/schemas/OrderEventDataBase"
        - type: object
          required:
            - item_id
            - role
            - previous_status
            - status
          properties:
            item_id:
              description: Vehicle whose status changed.
              $ref: "#/components/schemas/OrderItemId"
            role:
              description: Role of the changed vehicle.
              $ref: "#/components/schemas/OrderItemRole"
            previous_status:
              description: Vehicle status before the change.
              $ref: "#/components/schemas/OrderItemStatus"
            status:
              description: Vehicle status after the change.
              $ref: "#/components/schemas/OrderItemStatus"
      unevaluatedProperties: false

    OrderCancelledEventData:
      description: Transition from any non-cancelled order status to cancelled.
      allOf:
        - $ref: "#/components/schemas/OrderEventDataBase"
        - type: object
          required:
            - previous_status
            - status
          properties:
            previous_status:
              description: Overall order status before cancellation.
              $ref: "#/components/schemas/ActiveOrderStatus"
            status:
              const: cancelled
              description: Final cancelled status.
      unevaluatedProperties: false

    EtaChangedEventData:
      description: Material pickup or delivery ETA change.
      type: object
      additionalProperties: false
      required:
        - order_id
        - ordered_for
        - order_sequence
        - resource_url
        - target
        - eta
        - previous_eta
        - window
        - commitment_deadline_at
        - reason
      properties:
        order_id:
          description: Order whose ETA changed.
          $ref: "#/components/schemas/OrderId"
        ordered_for:
          description: Business the order was placed for, or null when it belongs to your own account.
          $ref: "#/components/schemas/NullableOrderedFor"
        order_sequence:
          type: integer
          minimum: 1
          description: Increasing sequence used to order events for this order.
        resource_url:
          type: string
          description: Order path to read for the current ETA and timing values.
        revision:
          type: string
          description: Order revision at the event time, when available. ETA-only changes do not create a new revision.
        target:
          type: string
          description: Stop whose ETA changed.
          enum:
            - pickup
            - delivery
        eta:
          type:
            - string
            - "null"
          format: date-time
          description: New ETA, or null when the ETA became unavailable.
        previous_eta:
          type:
            - string
            - "null"
          format: date-time
          description: Previously notified ETA, or null when no ETA had been notified.
        window:
          description: Current agreed window for the target stop, or null when none exists.
          oneOf:
            - $ref: "#/components/schemas/TimeWindowInput"
            - type: "null"
        commitment_deadline_at:
          type:
            - string
            - "null"
          format: date-time
          description: Current delivery commitment deadline, or null for pickup and uncommitted delivery.
        reason:
          type: string
          description: |
            Why Bahn sent the event: available for a new ETA, material_change for a large change,
            outside_window or inside_window for a boundary crossing, and unavailable when an ETA disappears.
          enum:
            - available
            - material_change
            - outside_window
            - inside_window
            - unavailable

    TrackingStateChangedEventData:
      description: Tracking state transition for one vehicle.
      allOf:
        - $ref: "#/components/schemas/OrderEventDataBase"
        - type: object
          required:
            - item_id
            - role
            - previous_state
            - state
            - phase
          properties:
            item_id:
              description: Vehicle whose tracking state changed.
              $ref: "#/components/schemas/OrderItemId"
            role:
              description: Role of the changed vehicle.
              $ref: "#/components/schemas/OrderItemRole"
            previous_state:
              description: Tracking state before the change, or null when no previous state exists.
              oneOf:
                - $ref: "#/components/schemas/TrackingState"
                - type: "null"
            state:
              description: Tracking state after the change.
              $ref: "#/components/schemas/TrackingState"
            phase:
              type: string
              description: pickup before collection; transport after collection.
              enum:
                - pickup
                - transport
      unevaluatedProperties: false

    PickupProgressChangedEventData:
      description: Pickup-approach progress transition for one vehicle.
      allOf:
        - $ref: "#/components/schemas/OrderEventDataBase"
        - type: object
          required:
            - item_id
            - role
            - previous_progress
            - progress
          properties:
            item_id:
              description: Vehicle whose pickup progress changed.
              $ref: "#/components/schemas/OrderItemId"
            role:
              description: Role of the changed vehicle.
              $ref: "#/components/schemas/OrderItemRole"
            previous_progress:
              description: Previous pickup progress, or null when approach just started.
              oneOf:
                - $ref: "#/components/schemas/PickupProgress"
                - type: "null"
            progress:
              description: Current pickup progress.
              $ref: "#/components/schemas/PickupProgress"
      unevaluatedProperties: false

    OrderEventEnvelope:
      description: CloudEvents 1.0 metadata shared by every Bahn webhook.
      type: object
      required:
        - specversion
        - id
        - source
        - subject
        - time
        - datacontenttype
      properties:
        specversion:
          const: "1.0"
          description: CloudEvents specification version.
        id:
          type: string
          description: Stable event ID. Use it to deduplicate retries and replays.
        source:
          type: string
          format: uri
          description: Bahn API origin for the event environment. Resolve resource paths against this origin.
        subject:
          type: string
          description: Order subject in the form orders/{order_id}.
        time:
          type: string
          format: date-time
          description: Time when the event occurred.
        datacontenttype:
          const: application/json
          description: Media type of the event data.

    StandardOrderEvent:
      title: Order resource changed
      description: Order, schedule, readiness, file, or inspection change notice.
      allOf:
        - $ref: "#/components/schemas/OrderEventEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              type: string
              description: Stable event type that identifies the changed resource or order area.
              enum:
                - com.bahn.order.updated.v1
                - com.bahn.order.pickup_readiness.changed.v1
                - com.bahn.order.schedule.changed.v1
                - com.bahn.order.files.updated.v1
                - com.bahn.order.inspection_report.available.v1
            data:
              description: Order identity and URLs for reading current state.
              $ref: "#/components/schemas/StandardOrderEventData"
      unevaluatedProperties: false

    OrderCreatedEvent:
      title: Order created
      description: Sent after Bahn accepts a new order.
      allOf:
        - $ref: "#/components/schemas/OrderEventEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: com.bahn.order.created.v1
              description: Stable type for an accepted-order event.
            data:
              description: Accepted order identity and current-resource URL.
              $ref: "#/components/schemas/OrderCreatedEventData"
      unevaluatedProperties: false

    OrderStatusChangedEvent:
      title: Order status changed
      description: Sent for an overall status transition not covered by an item or cancellation event.
      allOf:
        - $ref: "#/components/schemas/OrderEventEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: com.bahn.order.status.changed.v1
              description: Stable type for an overall order-status event.
            data:
              description: Previous and current overall order status.
              $ref: "#/components/schemas/OrderStatusChangedEventData"
      unevaluatedProperties: false

    OrderItemStatusChangedEvent:
      title: Vehicle status changed
      description: Sent when one primary or trade-in vehicle changes physical transport state.
      allOf:
        - $ref: "#/components/schemas/OrderEventEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: com.bahn.order.item.status.changed.v1
              description: Stable type for a vehicle-status event.
            data:
              description: Vehicle identity and its previous and current status.
              $ref: "#/components/schemas/OrderItemStatusChangedEventData"
      unevaluatedProperties: false

    OrderCancelledEvent:
      title: Order cancelled
      description: Sent when an order becomes cancelled.
      allOf:
        - $ref: "#/components/schemas/OrderEventEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: com.bahn.order.cancelled.v1
              description: Stable type for an order cancellation event.
            data:
              description: Previous order status and final cancelled status.
              $ref: "#/components/schemas/OrderCancelledEventData"
      unevaluatedProperties: false

    EtaChangedOrderEvent:
      title: ETA changed
      description: Sent for a material pickup or delivery ETA change.
      allOf:
        - $ref: "#/components/schemas/OrderEventEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: com.bahn.order.eta_changed.v1
              description: Stable type for a material ETA event.
            data:
              description: Previous and current ETA with the reason for notification.
              $ref: "#/components/schemas/EtaChangedEventData"
      unevaluatedProperties: false

    TrackingStateChangedOrderEvent:
      title: Tracking state changed
      description: Sent when the tracking state changes for one vehicle.
      allOf:
        - $ref: "#/components/schemas/OrderEventEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: com.bahn.order.tracking.state_changed.v1
              description: Stable type for a tracking state event.
            data:
              description: Vehicle identity and its previous and current tracking state.
              $ref: "#/components/schemas/TrackingStateChangedEventData"
      unevaluatedProperties: false

    PickupProgressChangedOrderEvent:
      title: Pickup progress changed
      description: Sent when one vehicle becomes on_the_way or nearby for pickup.
      allOf:
        - $ref: "#/components/schemas/OrderEventEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: com.bahn.order.pickup_progress.changed.v1
              description: Stable type for a pickup-approach event.
            data:
              description: Vehicle identity and its previous and current pickup progress.
              $ref: "#/components/schemas/PickupProgressChangedEventData"
      unevaluatedProperties: false

    OrderEvent:
      description: Any signed Bahn order webhook event.
      oneOf:
        - $ref: "#/components/schemas/StandardOrderEvent"
        - $ref: "#/components/schemas/OrderCreatedEvent"
        - $ref: "#/components/schemas/OrderStatusChangedEvent"
        - $ref: "#/components/schemas/OrderItemStatusChangedEvent"
        - $ref: "#/components/schemas/OrderCancelledEvent"
        - $ref: "#/components/schemas/EtaChangedOrderEvent"
        - $ref: "#/components/schemas/TrackingStateChangedOrderEvent"
        - $ref: "#/components/schemas/PickupProgressChangedOrderEvent"
      discriminator:
        propertyName: type
        mapping:
          com.bahn.order.created.v1: "#/components/schemas/OrderCreatedEvent"
          com.bahn.order.updated.v1: "#/components/schemas/StandardOrderEvent"
          com.bahn.order.pickup_readiness.changed.v1: "#/components/schemas/StandardOrderEvent"
          com.bahn.order.schedule.changed.v1: "#/components/schemas/StandardOrderEvent"
          com.bahn.order.status.changed.v1: "#/components/schemas/OrderStatusChangedEvent"
          com.bahn.order.item.status.changed.v1: "#/components/schemas/OrderItemStatusChangedEvent"
          com.bahn.order.cancelled.v1: "#/components/schemas/OrderCancelledEvent"
          com.bahn.order.files.updated.v1: "#/components/schemas/StandardOrderEvent"
          com.bahn.order.inspection_report.available.v1: "#/components/schemas/StandardOrderEvent"
          com.bahn.order.eta_changed.v1: "#/components/schemas/EtaChangedOrderEvent"
          com.bahn.order.tracking.state_changed.v1: "#/components/schemas/TrackingStateChangedOrderEvent"
          com.bahn.order.pickup_progress.changed.v1: "#/components/schemas/PickupProgressChangedOrderEvent"

    CmrRecipient:
      title: CMR recipient address
      type: object
      additionalProperties: false
      required: [name, street_address, postal_code, city, country_code]
      properties:
        name: { type: string, minLength: 1 }
        street_address: { type: string, minLength: 1 }
        postal_code: { type: string, minLength: 1 }
        city: { type: string, minLength: 1 }
        country_code: { type: string, pattern: "^[A-Z]{2}$" }

    CmrRecipientSelection:
      description: Choose a recipient when creating the order; it stays fixed afterwards. For company or saved-location selections, send expected_recipient to reject stale address details.
      oneOf:
        - title: Registered company address
          description: Use the registered company address on the customer account.
          type: object
          additionalProperties: false
          required: [kind]
          properties:
            kind: { const: company }
            expected_recipient:
              description: Optional copy of the address returned by Get CMR options. Bahn rejects the selection if these details no longer match.
              $ref: "#/components/schemas/CmrRecipient"
        - title: Physical delivery destination
          description: Use the delivery destination for this vehicle.
          type: object
          additionalProperties: false
          required: [kind]
          properties:
            kind: { const: delivery }
        - title: Saved customer location
          description: Use an active saved location on the customer account. Send its place_id.
          type: object
          additionalProperties: false
          required: [kind, place_id]
          properties:
            kind: { const: place }
            place_id: { type: string, minLength: 1 }
            expected_recipient:
              description: Optional copy of the address returned by Get CMR options. Bahn rejects the selection if these details no longer match.
              $ref: "#/components/schemas/CmrRecipient"
        - title: One-off recipient
          description: Provide the complete recipient address for this vehicle.
          type: object
          additionalProperties: false
          required: [kind, recipient]
          properties:
            kind: { const: other }
            recipient:
              description: The complete address to print on this vehicle's CMR.
              $ref: "#/components/schemas/CmrRecipient"

    CustomerCmrOptions:
      type: object
      additionalProperties: false
      required: [revision, default_recipient, default_mode, options]
      properties:
        revision:
          type: string
          minLength: 1
          description: Version of the customer configuration. This is not an order revision for If-Match.
        default_recipient:
          description: Default for future orders. automatic uses the physical destination.
          oneOf:
            - type: object
              additionalProperties: false
              required: [kind]
              properties:
                kind: { enum: [automatic, company, delivery] }
            - type: object
              additionalProperties: false
              required: [kind, place_id]
              properties:
                kind: { const: place }
                place_id: { type: string, minLength: 1 }
        default_mode:
          enum: [PHYSICAL, DIGITAL, NONE, null]
          description: Customer document-handling override, or null when normal route rules apply. Independent of the recipient choice.
        options:
          description: Registered company and active saved locations on this account.
          type: array
          items:
            type: object
            additionalProperties: false
            required: [selection, label, recipient, missing_fields]
            properties:
              selection: { $ref: "#/components/schemas/CmrRecipientSelection" }
              label: { type: string, minLength: 1 }
              recipient:
                description: Complete recipient address, or null when required details are missing.
                oneOf:
                  - $ref: "#/components/schemas/CmrRecipient"
                  - type: "null"
              missing_fields:
                description: Recipient details to complete before this option can be used.
                type: array
                items: { enum: [name, street_address, postal_code, city, country_code] }
