> ## Documentation Index
> Fetch the complete documentation index at: https://docs.haulstow.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Advance a sandbox delivery

> Advances a simulated delivery through an allowed forward transition and
emits the same webhook contract used for live lifecycle changes. This
endpoint is available only on the sandbox server and accepts only test keys.




## OpenAPI

````yaml /openapi.yaml post /test-helpers/deliveries/{delivery_id}/transition
openapi: 3.1.0
info:
  title: HaulStow Developer Delivery API
  version: 1.0.0
  summary: Create, quote, track, and test HaulStow deliveries.
  description: >
    The HaulStow Developer Delivery API is a server-to-server API for existing

    HaulStow business customers. Authenticate every request with a
    business-bound

    `hsg_live_key_...` API key. Use a test key with the sandbox base URL for

    deterministic, side-effect-free integration testing.


    All JSON responses use the same envelope. Every response includes

    `X-Request-ID` and rate-limit headers. Keep the request ID when contacting

    HaulStow support.
  contact:
    name: HaulStow Developer Support
    email: support@haulstow.co
servers:
  - url: https://developer.haulstow.co/v1
    description: Live
security:
  - DeveloperApiKey: []
tags:
  - name: Quotes
    description: Calculate a delivery quote without creating a delivery.
  - name: Recipients
    description: Manage business-scoped recipients and reusable addresses.
  - name: Deliveries
    description: Create and inspect deliveries created by the authenticated application.
  - name: Sandbox
    description: Test-only helpers that never affect live operations.
  - name: Webhooks
    description: Signed delivery lifecycle callbacks sent by HaulStow.
paths:
  /test-helpers/deliveries/{delivery_id}/transition:
    post:
      tags:
        - Sandbox
      summary: Advance a sandbox delivery
      description: >
        Advances a simulated delivery through an allowed forward transition and

        emits the same webhook contract used for live lifecycle changes. This

        endpoint is available only on the sandbox server and accepts only test
        keys.
      operationId: transitionSandboxDelivery
      parameters:
        - $ref: '#/components/parameters/XRequestID'
        - $ref: '#/components/parameters/DeliveryID'
        - $ref: '#/components/parameters/RequiredIdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxTransitionRequest'
            example:
              status: assigned
      responses:
        '200':
          description: Sandbox delivery advanced.
          headers:
            X-Request-ID:
              $ref: '#/components/headers/XRequestID'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
            Idempotent-Replayed:
              $ref: '#/components/headers/IdempotentReplayed'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliverySuccessResponse'
        '400':
          $ref: '#/components/responses/IdempotencyKeyRequired'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/DeliveryNotFound'
        '409':
          $ref: '#/components/responses/SandboxTransitionInvalid'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
      servers:
        - url: https://sandbox.developer.haulstow.co/v1
          description: Sandbox only
components:
  parameters:
    XRequestID:
      name: X-Request-ID
      in: header
      required: false
      description: >
        Optional caller correlation ID. Use 1–128 ASCII letters, digits, `.`,
        `_`,

        `-`, or `:`. Invalid values are replaced. The accepted/generated value
        is

        returned in the response.
      schema:
        type: string
        minLength: 1
        maxLength: 128
        pattern: ^[A-Za-z0-9._:-]+$
      example: req_shop_1842_create
    DeliveryID:
      name: delivery_id
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/DeliveryID'
    RequiredIdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: A unique key for this logical mutation. Retained for 24 hours.
      schema:
        type: string
        minLength: 8
        maxLength: 255
      example: shop-order-1842-create-v1
  schemas:
    SandboxTransitionRequest:
      type: object
      additionalProperties: false
      required:
        - status
      properties:
        status:
          type: string
          enum:
            - assigned
            - picked_up
            - in_transit
            - delivered
            - failed
            - cancelled
    DeliverySuccessResponse:
      $ref: '#/components/schemas/SuccessEnvelopeDelivery'
    DeliveryID:
      type: string
      pattern: ^dlv_[a-f0-9]{32}$
      example: dlv_3f1a821cdb58498cbb52f4706545a089
    SuccessEnvelopeDelivery:
      type: object
      additionalProperties: false
      required:
        - success
        - statusCode
        - message
        - data
      properties:
        success:
          type: boolean
          const: true
        statusCode:
          type: integer
          enum:
            - 200
            - 201
        message:
          type: string
        data:
          $ref: '#/components/schemas/Delivery'
    ErrorResponse:
      type: object
      additionalProperties: false
      required:
        - success
        - statusCode
        - message
        - error
      properties:
        success:
          type: boolean
          const: false
        statusCode:
          type: integer
          minimum: 400
          maximum: 599
        message:
          type: string
        error:
          $ref: '#/components/schemas/ErrorPayload'
    Delivery:
      type: object
      additionalProperties: false
      required:
        - id
        - environment
        - simulated
        - status
        - recipient
        - pickup
        - dropoff
        - payment
        - vehicle_type
        - currency
        - requires_custom_quote
        - created_at
        - updated_at
      properties:
        id:
          $ref: '#/components/schemas/DeliveryID'
        external_id:
          type:
            - string
            - 'null'
          maxLength: 128
        reference:
          type:
            - string
            - 'null'
          description: HaulStow's human-readable delivery reference.
          example: H-RS83223
        environment:
          $ref: '#/components/schemas/Environment'
        simulated:
          type: boolean
        status:
          $ref: '#/components/schemas/DeliveryStatus'
        recipient:
          $ref: '#/components/schemas/DeliveryRecipient'
        pickup:
          $ref: '#/components/schemas/Pickup'
        dropoff:
          $ref: '#/components/schemas/DeliveryDropoff'
        payment:
          $ref: '#/components/schemas/DeliveryPayment'
        vehicle_type:
          $ref: '#/components/schemas/VehicleType'
        delivery_instructions:
          type:
            - string
            - 'null'
        currency:
          type: string
          const: GHS
        delivery_fee:
          type:
            - number
            - 'null'
          format: double
          minimum: 0
          description: '`null` when a manual quote is required.'
        requires_custom_quote:
          type: boolean
        tracking_url:
          type:
            - string
            - 'null'
          format: uri
          description: Always `null` for sandbox deliveries.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ErrorPayload:
      type: object
      additionalProperties: false
      required:
        - code
      properties:
        code:
          type: string
          description: Stable machine-readable error code.
          example: VALIDATION_ERROR
        details:
          type: object
          additionalProperties: true
          description: >-
            Optional structured context. Do not parse `message` for control
            flow.
    Environment:
      type: string
      enum:
        - test
        - live
      example: live
    DeliveryStatus:
      type: string
      enum:
        - booked
        - assigned
        - picked_up
        - in_transit
        - delivered
        - failed
        - cancelled
        - outsourced
      example: in_transit
    DeliveryRecipient:
      type: object
      additionalProperties: false
      required:
        - id
        - phone_number
      properties:
        id:
          $ref: '#/components/schemas/RecipientID'
        name:
          type:
            - string
            - 'null'
        phone_number:
          type: string
    Pickup:
      type: object
      additionalProperties: false
      required:
        - name
        - contact
        - address
        - latitude
        - longitude
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 255
        contact:
          type: string
          minLength: 5
          maxLength: 64
        address:
          type: string
          minLength: 2
          maxLength: 500
        latitude:
          type: number
          format: double
          minimum: -90
          maximum: 90
        longitude:
          type: number
          format: double
          minimum: -180
          maximum: 180
    DeliveryDropoff:
      type: object
      additionalProperties: false
      required:
        - id
        - address
        - latitude
        - longitude
      properties:
        id:
          $ref: '#/components/schemas/AddressID'
        label:
          type:
            - string
            - 'null'
        address:
          type: string
        latitude:
          type: number
          format: double
        longitude:
          type: number
          format: double
        municipality:
          type:
            - string
            - 'null'
        delivery_notes:
          type:
            - string
            - 'null'
    DeliveryPayment:
      type: object
      additionalProperties: false
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - cod
            - delivery_only
            - client_billed
        goods_value:
          type: number
          format: double
          exclusiveMinimum: 0
          description: Present only for COD.
    VehicleType:
      type: string
      enum:
        - bike
        - minivan
      example: bike
    RecipientID:
      type: string
      pattern: ^rcp_[a-f0-9]{32}$
      example: rcp_3f1a821cdb58498cbb52f4706545a089
    AddressID:
      type: string
      pattern: ^adr_[a-f0-9]{32}$
      example: adr_78de60204f574a64be88ba9bd94a049f
  headers:
    XRequestID:
      description: Correlation ID for the request.
      schema:
        type: string
      example: 01J5T33T75A8S2JCB37Y6Z5Y4N
    RateLimitLimit:
      description: Maximum requests available in the current window.
      schema:
        type: integer
        minimum: 0
      example: 120
    RateLimitRemaining:
      description: Requests remaining in the current window.
      schema:
        type: integer
        minimum: 0
      example: 119
    RateLimitReset:
      description: Unix timestamp in seconds when the current window resets.
      schema:
        type: integer
        format: int64
      example: 1787236260
    IdempotentReplayed:
      description: >-
        Present and `true` when the response was replayed from an earlier
        completed request.
      schema:
        type: boolean
      example: true
    RetryAfter:
      description: Seconds to wait before retrying.
      schema:
        type: integer
        minimum: 1
      example: 30
  responses:
    IdempotencyKeyRequired:
      description: A required `Idempotency-Key` header was not supplied.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/XRequestID'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            statusCode: 400
            message: Idempotency-Key is required
            error:
              code: IDEMPOTENCY_KEY_REQUIRED
    Unauthorized:
      description: >-
        API key is missing, malformed, revoked, expired, or belongs to another
        environment.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/XRequestID'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            invalid:
              value:
                success: false
                statusCode: 401
                message: API key is invalid
                error:
                  code: INVALID_API_KEY
            revoked:
              value:
                success: false
                statusCode: 401
                message: API key has been revoked
                error:
                  code: API_KEY_REVOKED
            expired:
              value:
                success: false
                statusCode: 401
                message: API key has expired
                error:
                  code: API_KEY_EXPIRED
            environment:
              value:
                success: false
                statusCode: 401
                message: API key does not match this environment
                error:
                  code: ENVIRONMENT_MISMATCH
    Forbidden:
      description: The authenticated key lacks the operation's required scope.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/XRequestID'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            statusCode: 403
            message: API key lacks the required scope
            error:
              code: INSUFFICIENT_SCOPE
              details:
                required_scope: deliveries:write
    DeliveryNotFound:
      description: Delivery was not found for the authenticated application.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/XRequestID'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            statusCode: 404
            message: Delivery not found
            error:
              code: DELIVERY_NOT_FOUND
    SandboxTransitionInvalid:
      description: Requested sandbox transition is not a valid next state.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/XRequestID'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            statusCode: 409
            message: Sandbox transition is invalid
            error:
              code: SANDBOX_TRANSITION_INVALID
              details:
                current_status: booked
                requested_status: delivered
    ValidationError:
      description: Request parameters or body are invalid.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/XRequestID'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            validation:
              value:
                success: false
                statusCode: 422
                message: Delivery request is invalid
                error:
                  code: VALIDATION_ERROR
                  details:
                    fields:
                      - field: pickup.latitude
                        message: Latitude must be between -90 and 90
            codGoodsValue:
              value:
                success: false
                statusCode: 422
                message: Goods value is required for COD deliveries
                error:
                  code: COD_GOODS_VALUE_REQUIRED
    RateLimited:
      description: Rate limit exceeded.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/XRequestID'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            statusCode: 429
            message: Too many requests; please try again later
            error:
              code: RATE_LIMIT_EXCEEDED
    InternalError:
      description: Unexpected server error. Quote `X-Request-ID` when contacting support.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/XRequestID'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            statusCode: 500
            message: Internal server error
            error:
              code: INTERNAL_SERVER_ERROR
  securitySchemes:
    DeveloperApiKey:
      type: http
      scheme: bearer
      bearerFormat: hsg_live_key_... or hsg_test_key_...
      description: >
        A business-bound HaulStow API key. Send

        `Authorization: Bearer hsg_live_key_...` to the live server or a

        `hsg_test_key_...` key to the sandbox server. Never expose this key in a
        browser.

````