{
  "openapi": "3.1.0",
  "info": {
    "title": "Topping Courier API",
    "version": "1.0",
    "description": "Book, price and follow Topping Courier deliveries from your own software.\n\n**Keys.** Business accounts create keys under My account, API. Send the key as `Authorization: Bearer tc_...`\n(or the `X-Api-Key` header). Test keys (`tc_test_...`) book sandbox orders that are never dispatched, charged or messaged;\nlive keys (`tc_live_...`) book real deliveries. Each account has daily and monthly call limits (429 when reached).\n\n**Boxes.** Several boxes to one address are one delivery: send `quantity` (boxes of one size) or `packages`\n(several sizes). It is priced at the largest box plus a small fee for each extra box.\n\n**Sizes.** Give each box the smallest size it fits. A box heavier than its size allows goes in the next size up.\n\n| size | What it is | Weight up to |\n|---|---|---|\n| `envelope` | Envelope: documents or a mini parcel | 1 lb |\n| `small` | Small: a shoebox or backpack | 10 lb |\n| `medium` | Medium: a moving box | 35 lb |\n| `large` | Large: a TV, a chair or big boxes | 50 lb |\n| `xl` | Extra large: furniture or an appliance | 100 lb |\n\nAlso accepted: doc, document or mini parcel for envelope; extra_large for xl.\n\n**Quotes.** A box over 100 lb, more than 30 boxes to one address, or a trip over 80 km has no instant price: don't\nbook a box over 100 lb through the API, and the other two are answered with 400 and `\"code\": \"quote_needed\"`.\nContact us and we'll price them.\n\n**Webhooks.** Add an HTTPS address under My account, API to get a signed POST for each event. Check the\n`Topping-Signature: t=<unix time>,v1=<hex>` header: v1 is the HMAC-SHA256 of `\"<t>.<raw body>\"` with your endpoint\nsecret. Reply 2xx within 10 seconds; failed deliveries are retried with growing gaps.",
    "termsOfService": "https://toppingcourier.ca/legal/api-terms/",
    "contact": {
      "name": "Topping Courier",
      "url": "https://toppingcourier.ca/contact/?topic=api"
    }
  },
  "servers": [
    {
      "url": "https://toppingcourier.ca"
    }
  ],
  "tags": [
    {
      "name": "Prices"
    },
    {
      "name": "Orders"
    },
    {
      "name": "Testing"
    }
  ],
  "paths": {
    "/api/v1/quotes": {
      "post": {
        "operationId": "price_a_delivery_no_key",
        "summary": "A price for one delivery or a list of drops, without an account",
        "tags": [
          "Prices"
        ],
        "description": "The public price calculator. No API key and no account discounts.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicQuote"
              },
              "example": {
                "speed": "same_day",
                "size_class": "small",
                "distance_km": 18,
                "deliveries": 1,
                "boxes": 4
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "400": {
            "description": "The request needs fixing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over the daily or monthly limit; try again after the Retry-After seconds"
          }
        },
        "security": []
      }
    },
    "/api/v1/orders/quote": {
      "post": {
        "operationId": "quote_an_order",
        "summary": "The exact price and promise for an order, without booking it",
        "tags": [
          "Prices"
        ],
        "description": "Uses the account's own prices, discounts and rules, exactly as booking would.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrderRequest"
              },
              "example": {
                "speed": "same_day",
                "pickup": {
                  "contact_name": "Front desk",
                  "company": "Maple Print Shop",
                  "phone": "647 555 0100",
                  "line1": "100 Queen St W",
                  "city": "Toronto",
                  "postal_code": "M5H 2N2",
                  "instructions": "Side door"
                },
                "drops": [
                  {
                    "contact_name": "Sam Lee",
                    "company": "Lee Dental",
                    "phone": "647 555 0133",
                    "email": "sam@example.com",
                    "line1": "1 Dundas St W",
                    "unit": "Suite 200",
                    "city": "Toronto",
                    "postal_code": "M5G 1Z3",
                    "size": "small",
                    "quantity": 4,
                    "distance_km": 26,
                    "reference": "PO-1002"
                  },
                  {
                    "contact_name": "Alex Kim",
                    "phone": "647 555 0144",
                    "line1": "200 Bay St",
                    "city": "Toronto",
                    "postal_code": "M5J 2J2",
                    "packages": [
                      {
                        "size": "small",
                        "quantity": 3
                      },
                      {
                        "size": "large",
                        "quantity": 1
                      }
                    ],
                    "distance_km": 25
                  }
                ],
                "contact": {
                  "name": "Ana Diaz",
                  "email": "ap@maple.example",
                  "phone": "647 555 0199",
                  "texts_ok": false
                },
                "payment_method": "invoice",
                "reference": "Batch 42"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "400": {
            "description": "The request needs fixing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or wrong API key"
          },
          "429": {
            "description": "Over the daily or monthly limit; try again after the Retry-After seconds"
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/api/v1/orders": {
      "post": {
        "operationId": "book_an_order",
        "summary": "Book one pickup with one or more drop-offs",
        "tags": [
          "Orders"
        ],
        "description": "Test keys book sandbox orders: never dispatched, charged or messaged.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NewOrder"
              },
              "example": {
                "speed": "same_day",
                "pickup": {
                  "contact_name": "Front desk",
                  "company": "Maple Print Shop",
                  "phone": "647 555 0100",
                  "line1": "100 Queen St W",
                  "city": "Toronto",
                  "postal_code": "M5H 2N2",
                  "instructions": "Side door"
                },
                "drops": [
                  {
                    "contact_name": "Sam Lee",
                    "company": "Lee Dental",
                    "phone": "647 555 0133",
                    "email": "sam@example.com",
                    "line1": "1 Dundas St W",
                    "unit": "Suite 200",
                    "city": "Toronto",
                    "postal_code": "M5G 1Z3",
                    "size": "small",
                    "quantity": 4,
                    "distance_km": 26,
                    "reference": "PO-1002"
                  },
                  {
                    "contact_name": "Alex Kim",
                    "phone": "647 555 0144",
                    "line1": "200 Bay St",
                    "city": "Toronto",
                    "postal_code": "M5J 2J2",
                    "packages": [
                      {
                        "size": "small",
                        "quantity": 3
                      },
                      {
                        "size": "large",
                        "quantity": 1
                      }
                    ],
                    "distance_km": 25
                  }
                ],
                "contact": {
                  "name": "Ana Diaz",
                  "email": "ap@maple.example",
                  "phone": "647 555 0199",
                  "texts_ok": false
                },
                "payment_method": "invoice",
                "reference": "Batch 42"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "400": {
            "description": "The request needs fixing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or wrong API key"
          },
          "429": {
            "description": "Over the daily or monthly limit; try again after the Retry-After seconds"
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      },
      "get": {
        "operationId": "list_orders",
        "summary": "Your orders, newest first (25 a page)",
        "tags": [
          "Orders"
        ],
        "description": "Filter with status, reference, created_after and created_before (YYYY-MM-DD). Page with page and page_size (up to 100).",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "requested",
                "awaiting_payment",
                "confirmed",
                "in_progress",
                "completed",
                "cancelled"
              ]
            }
          },
          {
            "name": "reference",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "created_after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "created_before",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 25,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderList"
                }
              }
            }
          },
          "400": {
            "description": "The request needs fixing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or wrong API key"
          },
          "429": {
            "description": "Over the daily or monthly limit; try again after the Retry-After seconds"
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/api/v1/orders/{number}": {
      "get": {
        "operationId": "get_an_order",
        "summary": "One order with its deliveries",
        "tags": [
          "Orders"
        ],
        "parameters": [
          {
            "name": "number",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "TC-100123"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "400": {
            "description": "The request needs fixing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or wrong API key"
          },
          "429": {
            "description": "Over the daily or monthly limit; try again after the Retry-After seconds"
          },
          "404": {
            "description": "Not found for this account (test keys only see test orders)"
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/api/v1/orders/{number}/cancel": {
      "post": {
        "operationId": "cancel_an_order",
        "summary": "Cancel before pickup",
        "tags": [
          "Orders"
        ],
        "description": "After pickup it can't be cancelled through the API (409). Cancelling after a courier is assigned may cost a fee, as the Terms say.",
        "parameters": [
          {
            "name": "number",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "TC-100123"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CancelRequest"
              },
              "example": {
                "reason": "Customer changed the order"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "400": {
            "description": "The request needs fixing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or wrong API key"
          },
          "429": {
            "description": "Over the daily or monthly limit; try again after the Retry-After seconds"
          },
          "404": {
            "description": "Not found for this account (test keys only see test orders)"
          },
          "409": {
            "description": "Already picked up, or not cancellable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/api/v1/orders/{number}/labels.pdf": {
      "get": {
        "operationId": "print_labels",
        "summary": "Shipping labels as PDF: one per box",
        "tags": [
          "Orders"
        ],
        "description": "Format from the setting api.label_format: 4 x 6 inch labels, or two on a letter page.",
        "parameters": [
          {
            "name": "number",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "TC-100123"
          }
        ],
        "responses": {
          "200": {
            "description": "The labels",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "The request needs fixing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or wrong API key"
          },
          "429": {
            "description": "Over the daily or monthly limit; try again after the Retry-After seconds"
          },
          "404": {
            "description": "Not found for this account (test keys only see test orders)"
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/api/v1/orders/{number}/simulate": {
      "post": {
        "operationId": "move_a_test_order_along",
        "summary": "Test orders only: assigned, picked_up, delivered or failed",
        "tags": [
          "Testing"
        ],
        "description": "Sends the same webhooks a real order would, so you can test your integration.",
        "parameters": [
          {
            "name": "number",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "TC-100123"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SimulateRequest"
              },
              "example": {
                "to": "delivered"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "400": {
            "description": "The request needs fixing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or wrong API key"
          },
          "429": {
            "description": "Over the daily or monthly limit; try again after the Retry-After seconds"
          },
          "404": {
            "description": "Not found for this account (test keys only see test orders)"
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    },
    "/api/v1/deliveries/{tracking}": {
      "get": {
        "operationId": "get_a_delivery",
        "summary": "One delivery by tracking number, with its proof of delivery",
        "tags": [
          "Orders"
        ],
        "parameters": [
          {
            "name": "tracking",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "TC-100123-1"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Delivery"
                }
              }
            }
          },
          "400": {
            "description": "The request needs fixing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or wrong API key"
          },
          "429": {
            "description": "Over the daily or monthly limit; try again after the Retry-After seconds"
          },
          "404": {
            "description": "Not found for this account (test keys only see test orders)"
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      }
    }
  },
  "webhooks": {
    "assigned": {
      "post": {
        "summary": "Event: assigned",
        "description": "Sent to your webhook address. Check the Topping-Signature header before trusting it.",
        "parameters": [
          {
            "name": "Topping-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "t=1791230451,v1=5f3c..."
          },
          {
            "name": "Topping-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "assigned"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received"
          }
        }
      }
    },
    "cancelled": {
      "post": {
        "summary": "Event: cancelled",
        "description": "Sent to your webhook address. Check the Topping-Signature header before trusting it.",
        "parameters": [
          {
            "name": "Topping-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "t=1791230451,v1=5f3c..."
          },
          {
            "name": "Topping-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "cancelled"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received"
          }
        }
      }
    },
    "created": {
      "post": {
        "summary": "Event: created",
        "description": "Sent to your webhook address. Check the Topping-Signature header before trusting it.",
        "parameters": [
          {
            "name": "Topping-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "t=1791230451,v1=5f3c..."
          },
          {
            "name": "Topping-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "created"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received"
          }
        }
      }
    },
    "delivered": {
      "post": {
        "summary": "Event: delivered",
        "description": "Sent to your webhook address. Check the Topping-Signature header before trusting it.",
        "parameters": [
          {
            "name": "Topping-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "t=1791230451,v1=5f3c..."
          },
          {
            "name": "Topping-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "delivered"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received"
          }
        }
      }
    },
    "failed": {
      "post": {
        "summary": "Event: failed",
        "description": "Sent to your webhook address. Check the Topping-Signature header before trusting it.",
        "parameters": [
          {
            "name": "Topping-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "t=1791230451,v1=5f3c..."
          },
          {
            "name": "Topping-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "failed"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received"
          }
        }
      }
    },
    "invoice_issued": {
      "post": {
        "summary": "Event: invoice_issued",
        "description": "Sent to your webhook address. Check the Topping-Signature header before trusting it.",
        "parameters": [
          {
            "name": "Topping-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "t=1791230451,v1=5f3c..."
          },
          {
            "name": "Topping-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "invoice_issued"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received"
          }
        }
      }
    },
    "on_the_way": {
      "post": {
        "summary": "Event: on_the_way",
        "description": "Sent to your webhook address. Check the Topping-Signature header before trusting it.",
        "parameters": [
          {
            "name": "Topping-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "t=1791230451,v1=5f3c..."
          },
          {
            "name": "Topping-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "on_the_way"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received"
          }
        }
      }
    },
    "picked_up": {
      "post": {
        "summary": "Event: picked_up",
        "description": "Sent to your webhook address. Check the Topping-Signature header before trusting it.",
        "parameters": [
          {
            "name": "Topping-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "t=1791230451,v1=5f3c..."
          },
          {
            "name": "Topping-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "picked_up"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "description": "A problem with the request. Either {\"error\": \"...\"} or {\"field\": \"message\"}.",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "Address": {
        "type": "object",
        "required": [
          "contact_name",
          "line1",
          "city"
        ],
        "properties": {
          "contact_name": {
            "type": "string",
            "maxLength": 120
          },
          "company": {
            "type": "string"
          },
          "phone": {
            "type": "string",
            "description": "10 digits"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "line1": {
            "type": "string",
            "description": "Street address"
          },
          "unit": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "province": {
            "type": "string",
            "default": "ON"
          },
          "postal_code": {
            "type": "string",
            "examples": [
              "M5H 2N2"
            ]
          },
          "instructions": {
            "type": "string",
            "description": "For the courier"
          },
          "lat": {
            "type": "number"
          },
          "lng": {
            "type": "number"
          },
          "place_id": {
            "type": "string",
            "description": "A Google place ID, if you have one"
          }
        }
      },
      "Package": {
        "type": "object",
        "required": [
          "size",
          "quantity"
        ],
        "properties": {
          "size": {
            "type": "string",
            "enum": [
              "envelope",
              "small",
              "medium",
              "large",
              "xl"
            ],
            "description": "Also accepted: doc, document or mini parcel for envelope; extra_large for xl."
          },
          "quantity": {
            "type": "integer",
            "minimum": 1
          }
        }
      },
      "Drop": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Address"
          },
          {
            "type": "object",
            "properties": {
              "size": {
                "type": "string",
                "enum": [
                  "envelope",
                  "small",
                  "medium",
                  "large",
                  "xl"
                ],
                "default": "small",
                "description": "One size for all the boxes (also accepted as size_class). Also accepted: doc, document or mini parcel for envelope; extra_large for xl."
              },
              "quantity": {
                "type": "integer",
                "minimum": 1,
                "default": 1,
                "description": "How many boxes of that size go to this address"
              },
              "packages": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Package"
                },
                "description": "Boxes of several sizes to this address, instead of size and quantity. Priced as one delivery at the largest box, plus a fee for each extra box."
              },
              "distance_km": {
                "type": "number",
                "description": "Driving distance from the pickup. Needed only when address measuring is off."
              },
              "declared_value": {
                "type": "number",
                "default": 0,
                "description": "For extra coverage above what is included"
              },
              "heavy_pieces": {
                "type": "integer",
                "default": 0,
                "description": "Items over the one-person limit"
              },
              "return_trip": {
                "type": "boolean",
                "default": false
              },
              "reference": {
                "type": "string",
                "maxLength": 80
              }
            }
          }
        ]
      },
      "OrderRequest": {
        "type": "object",
        "required": [
          "speed",
          "pickup",
          "drops"
        ],
        "properties": {
          "speed": {
            "type": "string",
            "enum": [
              "rush",
              "same_day",
              "next_day",
              "scheduled"
            ],
            "description": "Rush is one drop-off per order"
          },
          "pickup": {
            "$ref": "#/components/schemas/Address"
          },
          "drops": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Drop"
            },
            "minItems": 1,
            "maxItems": 50
          },
          "date": {
            "type": "string",
            "format": "date",
            "description": "Scheduled only: the delivery day"
          },
          "window_hours": {
            "type": "number",
            "description": "Scheduled only: window length in hours (0 = any time that day)"
          },
          "window_start": {
            "type": "string",
            "format": "date-time",
            "description": "Scheduled only: when the window starts"
          },
          "promo_code": {
            "type": "string"
          }
        }
      },
      "NewOrder": {
        "allOf": [
          {
            "$ref": "#/components/schemas/OrderRequest"
          },
          {
            "type": "object",
            "properties": {
              "contact": {
                "type": "object",
                "description": "Who to tell about this order (default: the account's own details)",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "texts_ok": {
                    "type": "boolean"
                  }
                }
              },
              "payment_method": {
                "type": "string",
                "enum": [
                  "invoice",
                  "later",
                  "etransfer"
                ],
                "description": "invoice: approved accounts. later: we contact you. etransfer: Interac e-Transfer, when offered. Card payments need the customer and are not available through the API."
              },
              "reference": {
                "type": "string",
                "maxLength": 80
              },
              "notes": {
                "type": "string",
                "maxLength": 500
              }
            }
          }
        ]
      },
      "Line": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "amount": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "examples": [
              "24.50"
            ]
          }
        }
      },
      "Quote": {
        "type": "object",
        "properties": {
          "speed": {
            "type": "string",
            "enum": [
              "rush",
              "same_day",
              "next_day",
              "scheduled"
            ]
          },
          "deliveries": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer"
                },
                "size_class": {
                  "type": "string",
                  "enum": [
                    "envelope",
                    "small",
                    "medium",
                    "large",
                    "xl",
                    "doc"
                  ],
                  "description": "The largest box. The public price calculator (/api/v1/quotes) still calls the Envelope \"doc\"."
                },
                "distance_km": {
                  "type": "string"
                },
                "boxes": {
                  "type": "integer"
                },
                "packages": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "integer"
                  }
                },
                "lines": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Line"
                  }
                },
                "list_price": {
                  "type": "string",
                  "pattern": "^-?\\d+\\.\\d{2}$",
                  "examples": [
                    "24.50"
                  ]
                },
                "price": {
                  "type": "string",
                  "pattern": "^-?\\d+\\.\\d{2}$",
                  "examples": [
                    "24.50"
                  ]
                },
                "coverage": {
                  "type": "string",
                  "pattern": "^-?\\d+\\.\\d{2}$",
                  "examples": [
                    "24.50"
                  ]
                },
                "subtotal": {
                  "type": "string",
                  "pattern": "^-?\\d+\\.\\d{2}$",
                  "examples": [
                    "24.50"
                  ]
                }
              }
            }
          },
          "list_total": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "examples": [
              "24.50"
            ]
          },
          "discount_total": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "examples": [
              "24.50"
            ]
          },
          "subtotal": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "examples": [
              "24.50"
            ]
          },
          "taxes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Line"
            }
          },
          "total": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "examples": [
              "24.50"
            ],
            "description": "With tax"
          },
          "separate_pickups_subtotal": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^-?\\d+\\.\\d{2}$",
            "examples": [
              "24.50"
            ],
            "description": "What the same deliveries would cost booked separately"
          },
          "savings": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "examples": [
              "24.50"
            ]
          },
          "multi_drop_pct": {
            "type": "string"
          },
          "volume_pct": {
            "type": "string"
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "promise": {
            "type": [
              "string",
              "null"
            ],
            "description": "orders/quote only: when we will deliver, in words"
          }
        }
      },
      "Proof": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "received_by": {
            "type": "string"
          },
          "photo": {
            "type": "boolean"
          },
          "signature": {
            "type": "boolean"
          },
          "photo_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          }
        }
      },
      "Delivery": {
        "type": "object",
        "properties": {
          "tracking": {
            "type": "string",
            "examples": [
              "TC-100123-1"
            ]
          },
          "seq": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "booked",
              "assigned",
              "picked_up",
              "on_the_way",
              "delivered",
              "failed",
              "cancelled"
            ]
          },
          "status_label": {
            "type": "string"
          },
          "size": {
            "type": "string",
            "enum": [
              "envelope",
              "small",
              "medium",
              "large",
              "xl"
            ]
          },
          "boxes": {
            "type": "integer"
          },
          "packages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Package"
            }
          },
          "distance_km": {
            "type": "string"
          },
          "declared_value": {
            "type": "string"
          },
          "reference": {
            "type": "string"
          },
          "signature_required": {
            "type": "boolean"
          },
          "subtotal": {
            "type": "string",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "examples": [
              "24.50"
            ]
          },
          "dropoff": {
            "$ref": "#/components/schemas/Address"
          },
          "eta": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "picked_up_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "failed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "failure_reason": {
            "type": "string"
          },
          "tracking_url": {
            "type": "string",
            "format": "uri"
          },
          "proof": {
            "$ref": "#/components/schemas/Proof"
          }
        }
      },
      "Order": {
        "type": "object",
        "properties": {
          "number": {
            "type": "string",
            "examples": [
              "TC-100123"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "requested",
              "awaiting_payment",
              "confirmed",
              "in_progress",
              "completed",
              "cancelled"
            ]
          },
          "status_label": {
            "type": "string"
          },
          "speed": {
            "type": "string",
            "enum": [
              "rush",
              "same_day",
              "next_day",
              "scheduled"
            ]
          },
          "test": {
            "type": "boolean",
            "description": "Booked with a test key"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "promise": {
            "type": "string"
          },
          "deliver_from": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "deliver_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "pickup": {
            "$ref": "#/components/schemas/Address"
          },
          "pickup_from": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "pickup_to": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "reference": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "promo_code": {
            "type": "string"
          },
          "price": {
            "type": "object",
            "properties": {
              "subtotal": {
                "type": "string",
                "pattern": "^-?\\d+\\.\\d{2}$",
                "examples": [
                  "24.50"
                ]
              },
              "tax": {
                "type": "string",
                "pattern": "^-?\\d+\\.\\d{2}$",
                "examples": [
                  "24.50"
                ]
              },
              "total": {
                "type": "string",
                "pattern": "^-?\\d+\\.\\d{2}$",
                "examples": [
                  "24.50"
                ]
              },
              "credit_applied": {
                "type": "string",
                "pattern": "^-?\\d+\\.\\d{2}$",
                "examples": [
                  "24.50"
                ]
              },
              "amount_due": {
                "type": "string",
                "pattern": "^-?\\d+\\.\\d{2}$",
                "examples": [
                  "24.50"
                ]
              },
              "currency": {
                "type": "string",
                "const": "CAD"
              }
            }
          },
          "payment": {
            "type": "object",
            "properties": {
              "method": {
                "type": "string"
              },
              "status": {
                "type": "string"
              }
            }
          },
          "links": {
            "type": "object",
            "properties": {
              "order": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "deliveries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Delivery"
            }
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "evt_3f2a..."
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "assigned",
              "cancelled",
              "created",
              "delivered",
              "failed",
              "invoice_issued",
              "on_the_way",
              "picked_up"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "test": {
            "type": "boolean"
          },
          "data": {
            "type": "object",
            "description": "{\"order\": Order} for order events, plus \"delivery\" for a delivery's own events; {\"invoice\": ...} for invoice_issued",
            "properties": {
              "order": {
                "$ref": "#/components/schemas/Order"
              },
              "delivery": {
                "$ref": "#/components/schemas/Delivery"
              }
            }
          }
        }
      },
      "PublicQuote": {
        "type": "object",
        "required": [
          "speed"
        ],
        "properties": {
          "speed": {
            "type": "string",
            "enum": [
              "rush",
              "same_day",
              "next_day",
              "scheduled"
            ]
          },
          "size_class": {
            "type": "string",
            "enum": [
              "doc",
              "small",
              "medium",
              "large",
              "xl"
            ],
            "description": "doc is the Envelope size"
          },
          "distance_km": {
            "type": "number"
          },
          "deliveries": {
            "type": "integer",
            "default": 1,
            "description": "The same delivery to this many addresses"
          },
          "boxes": {
            "type": "integer",
            "default": 1,
            "description": "Boxes of size_class to each address"
          },
          "return_trip": {
            "type": "boolean"
          },
          "heavy_pieces": {
            "type": "integer"
          },
          "declared_value": {
            "type": "number"
          },
          "wait_minutes": {
            "type": "integer"
          },
          "scheduled_window": {
            "type": [
              "number",
              "null"
            ]
          },
          "drops": {
            "type": "array",
            "description": "Or a list of different drops",
            "items": {
              "type": "object",
              "properties": {
                "size_class": {
                  "type": "string",
                  "enum": [
                    "doc",
                    "small",
                    "medium",
                    "large",
                    "xl"
                  ]
                },
                "packages": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "size": {
                        "type": "string",
                        "enum": [
                          "doc",
                          "small",
                          "medium",
                          "large",
                          "xl"
                        ]
                      },
                      "quantity": {
                        "type": "integer",
                        "minimum": 1
                      }
                    }
                  }
                },
                "distance_km": {
                  "type": "number"
                },
                "return_trip": {
                  "type": "boolean"
                },
                "heavy_pieces": {
                  "type": "integer"
                },
                "declared_value": {
                  "type": "number"
                },
                "wait_minutes": {
                  "type": "integer"
                }
              }
            }
          },
          "welcome_preview": {
            "type": "boolean",
            "description": "Show the new-customer welcome offer"
          }
        }
      },
      "CancelRequest": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "maxLength": 200
          }
        }
      },
      "SimulateRequest": {
        "type": "object",
        "required": [
          "to"
        ],
        "properties": {
          "to": {
            "type": "string",
            "enum": [
              "assigned",
              "picked_up",
              "delivered",
              "failed"
            ]
          }
        }
      },
      "OrderList": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "page_size": {
            "type": "integer"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Order"
            }
          }
        }
      }
    },
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "tc_test_... or tc_live_..."
      }
    }
  }
}