{
  "openapi": "3.1.0",
  "info": {
    "title": "SwapBox Partner API",
    "version": "2026-10-07",
    "description": "Order acceptance API for SwapBox logistics partners.\n\nFlow: POST an order -> instant answer `provisionally_accepted` or `rejected` (with alternative dates) -> SwapBox reviews and sends `order.confirmed` (with a 2-hour arrival window) or `order.declined` by webhook -> `order.completed` / `order.failed` after the ride.\n\nAuthentication: `Authorization: Bearer sbx_live_...` (production) or `sbx_test_...` (sandbox: orders are never scheduled and never use capacity).\n\nChanges: orders can be modified or cancelled until 24 hours after SwapBox confirmed them (`change_deadline`). After that, contact operations@swap-box.com.\n\nWebhook events: order.provisionally_accepted, order.rejected, order.modified, order.confirmed, order.declined, order.cancelled, order.completed, order.failed. Each request carries `SwapBox-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, \"<t>.<raw body>\")>`, `SwapBox-Event` and `SwapBox-Delivery`. Respond 2xx within 10 seconds; failures are retried for 24+ hours with backoff. Deduplicate on the event `id`.",
    "contact": {
      "name": "SwapBox operations",
      "email": "operations@swap-box.com"
    }
  },
  "servers": [
    {
      "url": "https://api.swap-box.com",
      "description": "Production and sandbox (mode follows the API key)"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/v1/health": {
      "get": {
        "summary": "Health check",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/orders": {
      "post": {
        "summary": "Create an order request",
        "description": "Idempotent on order_number: re-sending the identical payload returns the existing order (200, header Idempotent-Replayed). A different payload for an existing order_number returns 409; use PATCH instead.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrderRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Idempotent replay of an existing identical order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "201": {
            "description": "Created. Check `status`: provisionally_accepted or rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "order_exists",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "validation_error (see error.field)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "summary": "List orders",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated statuses."
          },
          {
            "name": "updated_since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Incremental sync: only orders updated after this time (sorted by updated_at ascending)."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of orders",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Order"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders/{order_number}": {
      "get": {
        "summary": "Get an order",
        "parameters": [
          {
            "name": "order_number",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Your order number (URL-encoded)."
          }
        ],
        "responses": {
          "200": {
            "description": "The order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Modify an order",
        "description": "Send only the fields you want to change. Top-level fields replace the stored value; `service` is merged per field. The order is re-checked and goes back to provisionally_accepted (or rejected); a confirmed order loses its time window and must be re-confirmed by SwapBox. Allowed until change_deadline.",
        "parameters": [
          {
            "name": "order_number",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Your order number (URL-encoded)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "example": {
                  "service": {
                    "requested_date": "2026-10-22"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "409": {
            "description": "change_window_closed | invalid_state | conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders/{order_number}/cancel": {
      "post": {
        "summary": "Cancel an order",
        "parameters": [
          {
            "name": "order_number",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Your order number (URL-encoded)."
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cancelled order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "409": {
            "description": "change_window_closed | invalid_state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/availability": {
      "get": {
        "summary": "Bookable dates for a postal code",
        "description": "Indicative: dates on which the location is served and capacity is left. For exchanges pass both postal codes.",
        "parameters": [
          {
            "name": "postal_code",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dropoff_postal_code",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 30,
              "maximum": 90
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Availability"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "sbx_live_... or sbx_test_..."
      }
    },
    "schemas": {
      "OrderRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "order_number",
          "service",
          "items"
        ],
        "properties": {
          "order_number": {
            "type": "string",
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._\\-/#]{0,99}$",
            "description": "YOUR order number. Used as the identifier in every call, email and webhook. Unique per environment (live/test)."
          },
          "service": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "type",
              "requested_date"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "pickup",
                  "delivery",
                  "exchange"
                ]
              },
              "requested_date": {
                "type": "string",
                "format": "date",
                "description": "At least 4 working days ahead (weekends and Dutch public holidays excluded)."
              },
              "crew_size": {
                "type": "integer",
                "enum": [
                  1,
                  2
                ],
                "default": 1,
                "description": "1 = one-person ride (consumer helps carry), 2 = two-person ride."
              }
            }
          },
          "retailer_name": {
            "type": "string",
            "maxLength": 200
          },
          "pickup": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "address_line1",
              "postal_code",
              "city"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "residential",
                  "business"
                ],
                "default": "residential",
                "description": "residential = consumer home (contact_name + contact_phone required); business = shop/kringloop (name required)."
              },
              "name": {
                "type": "string",
                "maxLength": 200,
                "description": "Business name (required for business addresses)."
              },
              "address_line1": {
                "type": "string",
                "maxLength": 200,
                "example": "Stationsstraat 12"
              },
              "address_line2": {
                "type": "string",
                "maxLength": 200
              },
              "postal_code": {
                "type": "string",
                "example": "3511 AB",
                "description": "Dutch postal code; with or without space."
              },
              "city": {
                "type": "string",
                "maxLength": 100,
                "example": "Utrecht"
              },
              "country": {
                "type": "string",
                "enum": [
                  "NL"
                ],
                "default": "NL"
              },
              "contact_name": {
                "type": "string",
                "maxLength": 200
              },
              "contact_phone": {
                "type": "string",
                "maxLength": 30,
                "example": "+31612345678",
                "description": "Our driver calls ahead."
              },
              "contact_email": {
                "type": "string",
                "format": "email"
              },
              "floor": {
                "type": "integer",
                "minimum": -2,
                "maximum": 40,
                "description": "0 = ground floor."
              },
              "elevator": {
                "type": "boolean"
              },
              "access_notes": {
                "type": "string",
                "maxLength": 1000,
                "example": "Bel bij 2-hoog, parkeren achter het pand"
              }
            },
            "description": "Required for pickup and exchange."
          },
          "dropoff": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "address_line1",
              "postal_code",
              "city"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "residential",
                  "business"
                ],
                "default": "residential",
                "description": "residential = consumer home (contact_name + contact_phone required); business = shop/kringloop (name required)."
              },
              "name": {
                "type": "string",
                "maxLength": 200,
                "description": "Business name (required for business addresses)."
              },
              "address_line1": {
                "type": "string",
                "maxLength": 200,
                "example": "Stationsstraat 12"
              },
              "address_line2": {
                "type": "string",
                "maxLength": 200
              },
              "postal_code": {
                "type": "string",
                "example": "3511 AB",
                "description": "Dutch postal code; with or without space."
              },
              "city": {
                "type": "string",
                "maxLength": 100,
                "example": "Utrecht"
              },
              "country": {
                "type": "string",
                "enum": [
                  "NL"
                ],
                "default": "NL"
              },
              "contact_name": {
                "type": "string",
                "maxLength": 200
              },
              "contact_phone": {
                "type": "string",
                "maxLength": 30,
                "example": "+31612345678",
                "description": "Our driver calls ahead."
              },
              "contact_email": {
                "type": "string",
                "format": "email"
              },
              "floor": {
                "type": "integer",
                "minimum": -2,
                "maximum": 40,
                "description": "0 = ground floor."
              },
              "elevator": {
                "type": "boolean"
              },
              "access_notes": {
                "type": "string",
                "maxLength": 1000,
                "example": "Bel bij 2-hoog, parkeren achter het pand"
              }
            },
            "description": "Required for delivery and exchange."
          },
          "items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 30,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "type",
                "quantity",
                "dimensions_cm"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "couch",
                    "loveseat",
                    "armchair",
                    "chair",
                    "table",
                    "cabinet",
                    "bed",
                    "mattress",
                    "other"
                  ]
                },
                "quantity": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 50
                },
                "dimensions_cm": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "length",
                    "width",
                    "height"
                  ],
                  "properties": {
                    "length": {
                      "type": "number"
                    },
                    "width": {
                      "type": "number"
                    },
                    "height": {
                      "type": "number"
                    }
                  }
                },
                "weight_kg": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 500
                },
                "direction": {
                  "type": "string",
                  "enum": [
                    "pickup",
                    "dropoff"
                  ],
                  "description": "Required for exchange orders."
                },
                "notes": {
                  "type": "string",
                  "maxLength": 1000,
                  "description": "Required when type is other."
                }
              }
            }
          },
          "notes": {
            "type": "string",
            "maxLength": 4000
          }
        }
      },
      "Order": {
        "type": "object",
        "properties": {
          "order_number": {
            "type": "string"
          },
          "reference": {
            "type": "string",
            "description": "SwapBox internal reference."
          },
          "status": {
            "type": "string",
            "enum": [
              "provisionally_accepted",
              "rejected",
              "confirmed",
              "declined",
              "cancelled",
              "completed",
              "failed"
            ]
          },
          "test": {
            "type": "boolean"
          },
          "source": {
            "type": "string",
            "enum": [
              "api",
              "portal",
              "admin"
            ]
          },
          "service": {
            "type": "object"
          },
          "retailer_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "pickup": {
            "type": [
              "object",
              "null"
            ]
          },
          "dropoff": {
            "type": [
              "object",
              "null"
            ]
          },
          "items": {
            "type": "array"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "scheduled": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "date": {
                "type": "string",
                "format": "date"
              },
              "time_window": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "start": {
                    "type": "string",
                    "example": "10:00"
                  },
                  "end": {
                    "type": "string",
                    "example": "12:00"
                  }
                }
              }
            }
          },
          "rejection": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "reason": {
                "type": "string",
                "enum": [
                  "location_not_serviced",
                  "date_not_available",
                  "lead_time_too_short",
                  "capacity_full",
                  "blackout_date",
                  "item_not_accepted",
                  "other"
                ]
              },
              "message": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "alternative_dates": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "date"
                }
              }
            }
          },
          "decision_note": {
            "type": [
              "string",
              "null"
            ]
          },
          "decided_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "change_deadline": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "After this moment the order can no longer be modified or cancelled online; contact operations@swap-box.com."
          },
          "actions": {
            "type": "object",
            "properties": {
              "can_modify": {
                "type": "boolean"
              },
              "can_cancel": {
                "type": "boolean"
              }
            }
          },
          "cancellation": {
            "type": [
              "object",
              "null"
            ]
          },
          "completion": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "status": {
                "type": "string"
              },
              "completed_at": {
                "type": "string"
              },
              "driver_note": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "proof_photos": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "version": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "field": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}