{
  "openapi": "3.1.0",
  "info": {
    "title": "Nexroo partner API",
    "version": "1.2.0",
    "summary": "Quotes, bookings and tracking for Nexroo’s Sydney courier network.",
    "description": "Nexroo delivers parcels across Sydney. The API is provided by Panagia Group Pty Ltd (ABN 63 686 497 405), trading as Nexroo. Consignments booked through it are carried on our terms (https://nexroo.com.au/terms), including standard cover of $450 per consignment, and recipients' details are handled under our privacy policy (https://nexroo.com.au/privacy).\n\nPartners quote, book and track with this API using a key issued by Nexroo. Every request carries `Authorization: Bearer nxk_live_…` (or `nxk_test_…` for the sandbox). Prices are in Australian cents and include GST; times are ISO 8601 with the Sydney offset. Collection windows and delivery times are targets, not guarantees: if we miss the window through our fault, the delivery fee is refunded.\n\nErrors always have the shape `{ \"error\": { \"code\", \"message\", \"field\"?, \"requestId\" } }`. Each key may make about 120 requests a minute; every answer carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds), and over the limit the API answers 429 with Retry-After. Twenty requests a minute with a bad key from one address are also answered 429.\n\nMilestones are also pushed as signed webhooks: see the webhooks section and docs/partner-api.md. Test keys (nxk_test_…) work against a sandbox: real quotes, but bookings get NT- references, never become deliveries or appear on invoices, and are moved on with POST /v1/sandbox/consignments/{ref}/events, which fires your test webhook.\n\nChanges in 1.1.0: proof of delivery gives the recipient's first name and only the type of ID checked; photos and signatures are kept 12 months; keys can be made without proof-of-delivery access; RateLimit headers; POST /v1/webhooks/secret, and the old signing secret keeps signing for 24 hours after a rotation.",
    "contact": {
      "name": "Nexroo support",
      "email": "support@nexroo.com.au",
      "url": "https://nexroo.com.au/contact"
    },
    "termsOfService": "https://nexroo.com.au/terms"
  },
  "servers": [
    {
      "url": "https://nexroo.com.au/api",
      "description": "Production (live and test keys)"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/v1/account": {
      "get": {
        "operationId": "getAccount",
        "summary": "The account and key making the request",
        "description": "A quick way to check a key works: the account's name, the key's name and mode, and what the account may use.",
        "responses": {
          "200": {
            "description": "The account behind the key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/services": {
      "get": {
        "operationId": "listServices",
        "summary": "Service levels",
        "description": "The services this account can book, with the days they run, the booking cut-off, the collection and delivery windows and the delivery target (`promise`). Times are targets, not guarantees: if we miss the window through our fault, the delivery fee is refunded.",
        "responses": {
          "200": {
            "description": "The services.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "services",
                    "timezone"
                  ],
                  "properties": {
                    "services": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Service"
                      }
                    },
                    "timezone": {
                      "type": "string",
                      "examples": [
                        "Australia/Sydney"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/depots": {
      "get": {
        "operationId": "listDepots",
        "summary": "Depots for drop-offs",
        "description": "The Nexroo depots this account can hand parcels over at. A consignment that starts at a depot is delivered on Next Day rules: at the depot by the 6:30 pm cut-off, delivered 6–9 pm the next business day (a target, not a guarantee).",
        "responses": {
          "200": {
            "description": "The depots.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "depots"
                  ],
                  "properties": {
                    "depots": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Depot"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/quotes": {
      "post": {
        "operationId": "createQuote",
        "summary": "Price a consignment",
        "description": "Prices a consignment at the account's rates for each service (or the one asked for), on the date asked for or the first one open. The quote is held for 15 minutes: a booking that names its quoteId within that time gets the quoted price. Prices depend on the delivery postcode's zone and each parcel's size; a parcel without a size is sized by its weight and dimensions: the first size both fit (there is no cubic weight).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteRequest"
              },
              "examples": {
                "pickup": {
                  "summary": "A pickup from Parramatta to the CBD, all services",
                  "value": {
                    "pickup": {
                      "postcode": "2150"
                    },
                    "delivery": {
                      "postcode": "2000"
                    },
                    "parcels": [
                      {
                        "quantity": 2,
                        "weightKg": 3,
                        "lengthCm": 40,
                        "widthCm": 30,
                        "heightCm": 20
                      }
                    ]
                  }
                },
                "dropoff": {
                  "summary": "A drop-off at a depot, Next Day",
                  "value": {
                    "pickup": {
                      "depotId": "depot-ams"
                    },
                    "delivery": {
                      "postcode": "2170"
                    },
                    "parcels": [
                      {
                        "size": "S"
                      }
                    ],
                    "service": "nextday",
                    "date": "2026-10-22"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One option per service, available or with the reason it is not.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/consignments": {
      "post": {
        "operationId": "createConsignment",
        "summary": "Book a consignment",
        "description": "Books a consignment on your account, priced at your rates and invoiced with your other orders. It starts with a pickup from an address or as a drop-off at a depot (Next Day only). Addresses are checked on our map; one we cannot place is refused with address_not_found. The Idempotency-Key header is required: a retry with the same key and body returns the same consignment (with Idempotent-Replayed: true) instead of booking twice; the same key with a different body is refused. Partner barcodes are optional, one per parcel; a barcode already on an open consignment is refused.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "A unique value per consignment, e.g. a UUID. Up to 255 visible ASCII characters.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConsignmentRequest"
              },
              "examples": {
                "pickup": {
                  "summary": "A pickup from Parramatta to the CBD",
                  "value": {
                    "service": "nextday",
                    "reference": "PO-1001",
                    "channel": "shopify",
                    "pickup": {
                      "type": "address",
                      "address": {
                        "line1": "10 Church St",
                        "suburb": "Parramatta",
                        "state": "NSW",
                        "postcode": "2150"
                      },
                      "contact": {
                        "name": "Warehouse",
                        "phone": "02 9876 5432"
                      },
                      "instructions": "Dock 3"
                    },
                    "delivery": {
                      "address": {
                        "line1": "1 George St",
                        "line2": "Level 5",
                        "suburb": "Sydney",
                        "state": "NSW",
                        "postcode": "2000"
                      },
                      "contact": {
                        "name": "Jane Citizen",
                        "phone": "0412 345 678",
                        "email": "jane@example.com"
                      },
                      "instructions": "Reception"
                    },
                    "parcels": [
                      {
                        "quantity": 2,
                        "weightKg": 3,
                        "lengthCm": 40,
                        "widthCm": 30,
                        "heightCm": 20,
                        "barcodes": [
                          "PB0001",
                          "PB0002"
                        ]
                      }
                    ],
                    "goodsValueCents": 15000,
                    "containsAlcohol": false
                  }
                },
                "dropoff": {
                  "summary": "A drop-off at a depot, with a quote",
                  "value": {
                    "service": "nextday",
                    "quoteId": "q_pzlzLtMEL3hh41O2Ezsb",
                    "reference": "PO-2001",
                    "pickup": {
                      "type": "depot",
                      "depotId": "depot-ams"
                    },
                    "delivery": {
                      "address": {
                        "line1": "5 Macquarie St",
                        "suburb": "Liverpool",
                        "state": "NSW",
                        "postcode": "2170"
                      },
                      "contact": {
                        "name": "Sam Lee",
                        "phone": "0411 111 111"
                      }
                    },
                    "parcels": [
                      {
                        "size": "S",
                        "barcodes": [
                          "PB0100"
                        ]
                      }
                    ],
                    "signatureRequired": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Booked. The Location header points at the consignment.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                }
              },
              "Idempotent-Replayed": {
                "description": "true when this is the stored answer to an earlier request with the same key.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Consignment"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "A request with this key is still being processed (request_in_progress), or a barcode is already on an open consignment (barcode_in_use).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "get": {
        "operationId": "listConsignments",
        "summary": "Your consignments",
        "description": "Consignments booked through the API on your account, newest first, 50 a page (limit up to 100). Pass nextCursor back as cursor for the next page, or reference to find one by your own reference.",
        "parameters": [
          {
            "name": "reference",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of consignments.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "consignments",
                    "nextCursor"
                  ],
                  "properties": {
                    "consignments": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Consignment"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/consignments/{ref}": {
      "get": {
        "operationId": "getConsignment",
        "summary": "A consignment",
        "parameters": [
          {
            "name": "ref",
            "in": "path",
            "required": true,
            "description": "The consignment id, e.g. NX-AB4EKM.",
            "schema": {
              "type": "string",
              "pattern": "^NX-[23456789A-HJ-NP-Z]{6}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The consignment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Consignment"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No consignment of yours has that reference.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/consignments/{ref}/cancel": {
      "post": {
        "operationId": "cancelConsignment",
        "summary": "Cancel a consignment",
        "description": "Free until 6:30 pm the evening before collection (or, for a booking made after that, until a driver is assigned). After that it is refused with cancellation_closed: email support@nexroo.com.au to cancel, and a late-cancellation fee of $16.50 incl. GST ($15 + GST) applies; once the driver is at the pickup address the consignment is charged in full. Cancelling a cancelled consignment answers 200.",
        "parameters": [
          {
            "name": "ref",
            "in": "path",
            "required": true,
            "description": "The consignment id, e.g. NX-AB4EKM.",
            "schema": {
              "type": "string",
              "pattern": "^NX-[23456789A-HJ-NP-Z]{6}$"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "maxLength": 300
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Consignment"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Cancellation has closed (cancellation_closed).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/consignments/{ref}/labels": {
      "get": {
        "operationId": "getLabels",
        "summary": "Parcel labels (PDF)",
        "description": "A6 labels, one page per parcel, with our barcode and the 13-digit parcel number. Print them if you do not label parcels with your own barcodes; parcels with your barcode can be scanned by it.",
        "parameters": [
          {
            "name": "ref",
            "in": "path",
            "required": true,
            "description": "The consignment id, e.g. NX-AB4EKM.",
            "schema": {
              "type": "string",
              "pattern": "^NX-[23456789A-HJ-NP-Z]{6}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The labels.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/tracking/{code}": {
      "get": {
        "operationId": "trackConsignment",
        "summary": "Track a consignment",
        "description": "Finds one of your consignments by our id (NX-AB4EKM, or NX-AB4EKM/2 from a label), the 13-digit parcel number on our label (spaces allowed), or your own barcode, and gives its milestones and, once delivered, proof of delivery.",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "description": "NX- id, 13-digit parcel number, or your barcode (URL-encoded).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The consignment’s tracking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tracking"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No consignment of yours has that number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/consignments/{ref}/pod/photo": {
      "get": {
        "operationId": "getPodPhoto",
        "summary": "Delivery photo",
        "description": "The photo taken at delivery, as an image. Answers 404 until it has been uploaded by the driver’s phone (usually within minutes of delivery). Tracking says whether there is one.",
        "parameters": [
          {
            "name": "ref",
            "in": "path",
            "required": true,
            "description": "The consignment id, e.g. NX-AB4EKM.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The photo.",
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "This key has no proof-of-delivery access (forbidden), or API access is off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No photo yet (the driver's phone has not uploaded it), or it was deleted 12 months after delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/consignments/{ref}/pod/signature": {
      "get": {
        "operationId": "getPodSignature",
        "summary": "Delivery signature",
        "description": "The signature taken at delivery, as an image. Answers 404 until it has been uploaded by the driver’s phone (usually within minutes of delivery). Tracking says whether there is one.",
        "parameters": [
          {
            "name": "ref",
            "in": "path",
            "required": true,
            "description": "The consignment id, e.g. NX-AB4EKM.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The signature.",
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "This key has no proof-of-delivery access (forbidden), or API access is off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No signature yet (the driver's phone has not uploaded it), or it was deleted 12 months after delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/sandbox/consignments/{ref}/events": {
      "post": {
        "operationId": "advanceSandbox",
        "summary": "Move a test consignment on (sandbox)",
        "description": "Test keys only. Moves a test consignment (NT- reference) to its next milestone, as a driver or the depot would, and posts the matching event to your test webhook. A pickup goes picked_up, out_for_delivery, delivered; a drop-off received_at_depot, out_for_delivery, delivered. A failed attempt can follow out_for_delivery, then returned_to_depot and returned_to_sender. A booked one can be cancelled.",
        "parameters": [
          {
            "name": "ref",
            "in": "path",
            "required": true,
            "description": "The test consignment id, e.g. NT-DXY78T.",
            "schema": {
              "type": "string",
              "pattern": "^NT-[23456789A-HJ-NP-Z]{6}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "enum": [
                      "picked_up",
                      "received_at_depot",
                      "out_for_delivery",
                      "delivered",
                      "failed_attempt",
                      "returned_to_depot",
                      "returned_to_sender",
                      "cancelled"
                    ]
                  }
                }
              },
              "examples": {
                "collect": {
                  "value": {
                    "code": "picked_up"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The test consignment’s tracking after the step.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tracking"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No test consignment has that reference.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The step cannot happen now (conflict); the message says what must come first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/webhooks/secret": {
      "post": {
        "operationId": "rotateSigningSecret",
        "summary": "Change the webhook signing secret",
        "description": "Makes a new signing secret for the webhook of this key's mode (live or test) and returns it once. For 24 hours events carry two signatures, one with each secret, so you can switch over without losing any; then only the new one. Keys without proof-of-delivery access cannot do this.",
        "responses": {
          "200": {
            "description": "The new secret. Store it now: it is not shown again.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "secret",
                    "previousSecretValidUntil"
                  ],
                  "properties": {
                    "secret": {
                      "type": "string",
                      "examples": [
                        "whsec_…"
                      ]
                    },
                    "previousSecretValidUntil": {
                      "type": "string",
                      "format": "date-time",
                      "description": "Until when the old secret still signs."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "This key has no proof-of-delivery access (forbidden), or API access is off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "There is no webhook for this mode on your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/webhooks": {
      "post": {
        "operationId": "createWebhook",
        "summary": "Make a webhook",
        "description": "Adds a webhook of your own for this key's mode (live or test), beside any our staff set up for you: events are posted to its URL, signed with its own secret, which this answer shows once. Up to 10 per mode. Integrations such as Zapier and Make call this when a workflow is switched on and DELETE it when the workflow is switched off. A new webhook hears events within a minute. Keys without proof-of-delivery access cannot manage webhooks.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookRequest"
              },
              "examples": {
                "zapier": {
                  "summary": "Delivered and failed attempts to a Zapier catch hook",
                  "value": {
                    "url": "https://hooks.zapier.com/hooks/catch/123/abc/",
                    "events": [
                      "consignment.delivered",
                      "consignment.failed_attempt"
                    ],
                    "description": "Zap: delivery updates to Slack"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Made. The Location header is its URL.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "This key has no proof-of-delivery access (forbidden), or API access is off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A webhook of yours already posts to that URL (conflict); its id is in the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The URL cannot be called, an event type is unknown, or you already have 10 webhooks for this mode (validation_failed, with the field).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "get": {
        "operationId": "listWebhooks",
        "summary": "List your webhooks",
        "description": "The webhooks you made through the API for this key's mode, newest first. Webhooks our staff set up for you are not listed; ask support@nexroo.com.au about those.",
        "responses": {
          "200": {
            "description": "Your webhooks.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "webhooks"
                  ],
                  "properties": {
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "get": {
        "operationId": "getWebhook",
        "summary": "One of your webhooks",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^wh_[A-Za-z0-9]{16}$"
            },
            "description": "The webhook's id, from the answer that made it or GET /v1/webhooks."
          }
        ],
        "responses": {
          "200": {
            "description": "The webhook.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No webhook of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteWebhook",
        "summary": "Delete a webhook",
        "description": "Nothing more is posted to it. Deliveries already queued to it are dropped.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^wh_[A-Za-z0-9]{16}$"
            },
            "description": "The webhook's id, from the answer that made it or GET /v1/webhooks."
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No webhook of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/webhooks/{id}/test": {
      "post": {
        "operationId": "testWebhook",
        "summary": "Send a test event",
        "description": "Posts a ping event (type ping) to the webhook now, signed as every event is, and says what your endpoint answered.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^wh_[A-Za-z0-9]{16}$"
            },
            "description": "The webhook's id, from the answer that made it or GET /v1/webhooks."
          }
        ],
        "responses": {
          "200": {
            "description": "What happened.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookTestResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No webhook of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/events": {
      "get": {
        "operationId": "listEvents",
        "summary": "Recent events",
        "description": "The latest events on your newest consignments (live or sandbox, by the key's mode), newest first, in exactly the shape your webhook receives them: a sample for an integration you are setting up, available before any webhook has fired. Built from the last ten consignments.",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Only these event types, comma-separated, e.g. consignment.delivered,consignment.failed_attempt.",
            "examples": {
              "delivered": {
                "value": "consignment.delivered"
              }
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 3
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The events, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "events"
                  ],
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookEvent"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "description": "Unknown event type (validation_failed, field type).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "nxk_live_<keyId>_<secret> or nxk_test_<keyId>_<secret>"
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request could not be read (malformed JSON, wrong content type).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "No key, an unknown key or a revoked key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The account is not active or its API access is off.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "The request is valid JSON but cannot be done: a field is wrong (see field), a postcode is outside the service area (not_serviceable), or the date is not open.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Over about 120 requests a minute for this key (or 20 a minute with a bad key from one address). Wait for Retry-After seconds. RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset come on every answer.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "requestId"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_json",
                  "invalid_request",
                  "unauthorized",
                  "forbidden",
                  "not_found",
                  "method_not_allowed",
                  "conflict",
                  "request_in_progress",
                  "cancellation_closed",
                  "barcode_in_use",
                  "payload_too_large",
                  "unsupported_media_type",
                  "validation_failed",
                  "not_serviceable",
                  "address_not_found",
                  "date_unavailable",
                  "quote_expired",
                  "idempotency_key_reused",
                  "rate_limited",
                  "internal",
                  "unavailable"
                ]
              },
              "message": {
                "type": "string"
              },
              "field": {
                "type": "string",
                "description": "The request field at fault, e.g. parcels[0].weightKg."
              },
              "requestId": {
                "type": "string",
                "description": "Quote it to support@nexroo.com.au."
              }
            }
          }
        }
      },
      "Account": {
        "type": "object",
        "required": [
          "account",
          "key",
          "services",
          "pickupFee",
          "recipientEmails"
        ],
        "properties": {
          "account": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              }
            }
          },
          "key": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "prefix": {
                "type": "string"
              },
              "mode": {
                "type": "string",
                "enum": [
                  "live",
                  "test"
                ]
              },
              "access": {
                "type": "string",
                "enum": [
                  "full",
                  "no_pod"
                ],
                "description": "full, or no_pod: no delivery photos, signatures or recipient names, and it cannot change the webhook."
              },
              "createdAt": {
                "type": "string",
                "format": "date-time",
                "description": "When the key was made. Rotate keys by making a new one, switching over, then asking us to revoke the old one: an account may have 10 keys at once."
              }
            }
          },
          "services": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ServiceCode"
            }
          },
          "pickupFee": {
            "type": "string",
            "description": "Always none: a booked pickup is free.",
            "enum": [
              "none",
              "per_consignment"
            ]
          },
          "recipientEmails": {
            "type": "boolean",
            "description": "Whether Nexroo emails the delivery contact (collected, out for delivery, delivered) when you give their email."
          }
        }
      },
      "ServiceCode": {
        "type": "string",
        "enum": [
          "nextday",
          "sameday",
          "express"
        ]
      },
      "Window": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "from": {
            "type": "string",
            "examples": [
              "15:00"
            ]
          },
          "to": {
            "type": "string",
            "examples": [
              "17:00"
            ]
          }
        }
      },
      "Service": {
        "type": "object",
        "required": [
          "code",
          "label",
          "description",
          "days",
          "cutoff",
          "collectionWindow",
          "deliveryWindow",
          "promise",
          "depotDropOff"
        ],
        "properties": {
          "code": {
            "$ref": "#/components/schemas/ServiceCode"
          },
          "label": {
            "type": "string",
            "examples": [
              "Next Day"
            ]
          },
          "description": {
            "type": "string"
          },
          "days": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "Mon",
                "Tue",
                "Wed",
                "Thu",
                "Fri",
                "Sat",
                "Sun"
              ]
            }
          },
          "cutoff": {
            "type": [
              "string",
              "null"
            ],
            "description": "HH:mm (Sydney) bookings close on the collection day.",
            "examples": [
              "18:30"
            ]
          },
          "collectionWindow": {
            "$ref": "#/components/schemas/Window"
          },
          "deliveryWindow": {
            "$ref": "#/components/schemas/Window"
          },
          "promise": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "type": {
                    "const": "next_business_day"
                  },
                  "by": {
                    "type": "string",
                    "examples": [
                      "21:00"
                    ]
                  }
                }
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "const": "same_day"
                  },
                  "by": {
                    "type": "string",
                    "examples": [
                      "18:00"
                    ]
                  }
                }
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "const": "hours_after_ready"
                  },
                  "hours": {
                    "type": "number",
                    "examples": [
                      3
                    ]
                  }
                }
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "const": "end_of_day"
                  }
                }
              }
            ],
            "description": "When the service aims to deliver: a target, not a guarantee. Next Day is next_business_day by 21:00 (the delivery window is 18:00–21:00)."
          },
          "depotDropOff": {
            "type": "boolean",
            "description": "Whether a consignment on this service can start at a depot."
          }
        }
      },
      "Depot": {
        "type": "object",
        "required": [
          "id",
          "name",
          "address",
          "hours"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "address": {
            "type": "object",
            "properties": {
              "line1": {
                "type": "string"
              },
              "suburb": {
                "type": "string"
              },
              "state": {
                "type": "string"
              },
              "postcode": {
                "type": "string"
              },
              "formatted": {
                "type": "string"
              }
            }
          },
          "hours": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Parcel": {
        "type": "object",
        "description": "A line of identical parcels. Give a size, or the weight and/or all three dimensions and the size is worked out. A parcel is at most 25 kg and 80 × 60 × 50 cm; a consignment at most 30 parcels.",
        "properties": {
          "size": {
            "type": "string",
            "enum": [
              "S",
              "M",
              "L",
              "XL"
            ],
            "description": "S satchel (1 kg, 40×30×8), M small box (5 kg, 45×35×25), L carton (10 kg, 60×45×40), XL large carton (25 kg, 80×60×50)."
          },
          "quantity": {
            "type": "integer",
            "minimum": 1,
            "maximum": 30,
            "default": 1
          },
          "weightKg": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Per parcel."
          },
          "lengthCm": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "widthCm": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "heightCm": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "description": {
            "type": "string",
            "maxLength": 200
          },
          "barcodes": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 3,
              "maxLength": 48
            },
            "description": "Your own barcode for each parcel on the line, in order (bookings only). Scanning it finds the consignment."
          }
        }
      },
      "QuoteRequest": {
        "type": "object",
        "required": [
          "pickup",
          "delivery",
          "parcels"
        ],
        "properties": {
          "pickup": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "postcode"
                ],
                "properties": {
                  "postcode": {
                    "type": "string",
                    "pattern": "^\\d{4}$"
                  }
                }
              },
              {
                "type": "object",
                "required": [
                  "depotId"
                ],
                "properties": {
                  "depotId": {
                    "type": "string",
                    "description": "From GET /v1/depots."
                  }
                }
              }
            ]
          },
          "delivery": {
            "type": "object",
            "required": [
              "postcode"
            ],
            "properties": {
              "postcode": {
                "type": "string",
                "pattern": "^\\d{4}$"
              }
            }
          },
          "parcels": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/Parcel"
            }
          },
          "service": {
            "$ref": "#/components/schemas/ServiceCode"
          },
          "date": {
            "type": "string",
            "format": "date",
            "description": "The collection (or drop-off) date. Omitted: the first open date for each service."
          },
          "signatureRequired": {
            "type": "boolean",
            "default": false,
            "description": "Hand it only to someone who signs. Without it the driver may leave the parcel in a safe place and photograph it. It is required, whatever this says, when goodsValueCents is over 9900 or containsAlcohol is true. A signature adds the account's signature fee once per consignment (breakdown.signatureFeeIncGstCents)."
          },
          "goodsValueCents": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 10000000,
            "description": "The value of the goods in cents. Left out or null: $99 or less. Over 9900 ($99) a signature is required. Declaring a value sets the signature rule only: standard cover is $450 per consignment (the lesser of the declared value and $450); extra cover only by arrangement in writing before pickup.",
            "examples": [
              15000
            ]
          },
          "containsAlcohol": {
            "type": "boolean",
            "default": false,
            "description": "The consignment contains alcohol: it is handed only to someone 18 or over who shows photo ID and signs, never left. A signature (and its fee) is required."
          }
        }
      },
      "Price": {
        "type": "object",
        "required": [
          "currency",
          "incGstCents",
          "gstCents",
          "exGstCents",
          "accountRate",
          "breakdown"
        ],
        "properties": {
          "currency": {
            "const": "AUD"
          },
          "incGstCents": {
            "type": "integer"
          },
          "gstCents": {
            "type": "integer"
          },
          "exGstCents": {
            "type": "integer"
          },
          "accountRate": {
            "type": "boolean",
            "description": "Priced at your agreed rates."
          },
          "breakdown": {
            "type": "object",
            "properties": {
              "zone": {
                "type": "string"
              },
              "zoneLabel": {
                "type": "string"
              },
              "parcels": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "size": {
                      "type": "string"
                    },
                    "quantity": {
                      "type": "integer"
                    },
                    "unitIncGstCents": {
                      "type": "integer"
                    },
                    "chargeableKg": {
                      "type": "number"
                    }
                  }
                }
              },
              "parcelsIncGstCents": {
                "type": "integer"
              },
              "serviceMultiplier": {
                "type": "number"
              },
              "pickupFeeIncGstCents": {
                "type": "integer",
                "description": "Always 0: a booked pickup is free."
              },
              "signatureFeeIncGstCents": {
                "type": "integer",
                "description": "Signature on delivery, once per consignment; 0 when no signature is needed or the account's fee is waived."
              }
            }
          }
        }
      },
      "QuoteOption": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "service",
              "label",
              "available",
              "price",
              "collectionDate",
              "collectionWindow",
              "deliverBy",
              "bookBy"
            ],
            "properties": {
              "service": {
                "$ref": "#/components/schemas/ServiceCode"
              },
              "label": {
                "type": "string"
              },
              "available": {
                "const": true
              },
              "price": {
                "$ref": "#/components/schemas/Price"
              },
              "collectionDate": {
                "type": "string",
                "format": "date",
                "description": "The collection date, or for a drop-off the day the parcels must reach the depot."
              },
              "collectionWindow": {
                "$ref": "#/components/schemas/Window"
              },
              "deliverBy": {
                "type": "string",
                "format": "date-time",
                "examples": [
                  "2026-10-22T21:00:00+11:00"
                ],
                "description": "The target time for delivery: Next Day 6–9 pm the next business day, so 21:00. Not a guarantee: if it is missed through our fault, the delivery fee is refunded."
              },
              "bookBy": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "Bookings close (and a drop-off must be at the depot) by this time."
              }
            }
          },
          {
            "type": "object",
            "required": [
              "service",
              "label",
              "available",
              "reason"
            ],
            "properties": {
              "service": {
                "$ref": "#/components/schemas/ServiceCode"
              },
              "label": {
                "type": "string"
              },
              "available": {
                "const": false
              },
              "reason": {
                "type": "string"
              }
            }
          }
        ]
      },
      "Quote": {
        "type": "object",
        "required": [
          "quoteId",
          "expiresAt",
          "currency",
          "options"
        ],
        "properties": {
          "quoteId": {
            "type": "string",
            "examples": [
              "q_CPTUSegsBTjZlY31iBmK"
            ]
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "currency": {
            "const": "AUD"
          },
          "options": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/QuoteOption"
            }
          }
        }
      },
      "Address": {
        "type": "object",
        "required": [
          "line1",
          "suburb",
          "state",
          "postcode"
        ],
        "properties": {
          "line1": {
            "type": "string",
            "maxLength": 200
          },
          "line2": {
            "type": "string",
            "maxLength": 100,
            "description": "Unit, level or building; shown to the driver."
          },
          "suburb": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "enum": [
              "NSW",
              "ACT",
              "VIC",
              "QLD",
              "SA",
              "WA",
              "TAS",
              "NT"
            ]
          },
          "postcode": {
            "type": "string",
            "pattern": "^\\d{4}$"
          }
        }
      },
      "Contact": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "company": {
            "type": "string",
            "maxLength": 100
          },
          "phone": {
            "type": "string",
            "description": "An Australian number. Required for a pickup contact; strongly advised for delivery."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Delivery contact only: used for Nexroo’s status emails when your account has them switched on."
          }
        }
      },
      "ConsignmentRequest": {
        "type": "object",
        "required": [
          "service",
          "pickup",
          "delivery",
          "parcels"
        ],
        "properties": {
          "service": {
            "$ref": "#/components/schemas/ServiceCode"
          },
          "date": {
            "type": "string",
            "format": "date",
            "description": "The collection (or drop-off) date. Omitted: the first open date."
          },
          "quoteId": {
            "type": "string",
            "description": "From POST /v1/quotes within 15 minutes, to book at the quoted price. It must match the booking (service, postcodes, depot, parcels)."
          },
          "reference": {
            "type": "string",
            "maxLength": 60,
            "description": "Your own reference, e.g. an order number. Shown on our side and in GET /v1/consignments?reference=."
          },
          "channel": {
            "type": "string",
            "maxLength": 40,
            "pattern": "^[a-z0-9][a-z0-9_-]{0,39}$",
            "description": "Where the order came through, shown to our staff: shopify, woocommerce, ebay, zapier, make, or your own lowercase token. Optional.",
            "examples": [
              "shopify"
            ]
          },
          "signatureRequired": {
            "type": "boolean",
            "default": false,
            "description": "Hand it only to someone who signs. Without it the driver may leave the parcel in a safe place and photograph it. It is required, whatever this says, when goodsValueCents is over 9900 or containsAlcohol is true. A signature adds the account's signature fee once per consignment (breakdown.signatureFeeIncGstCents)."
          },
          "pickup": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "type",
                  "address",
                  "contact"
                ],
                "properties": {
                  "type": {
                    "const": "address"
                  },
                  "address": {
                    "$ref": "#/components/schemas/Address"
                  },
                  "contact": {
                    "$ref": "#/components/schemas/Contact"
                  },
                  "readyAt": {
                    "type": "string",
                    "pattern": "^\\d{2}:\\d{2}$",
                    "description": "When the parcels are ready (Express is due 3 hours after)."
                  },
                  "instructions": {
                    "type": "string",
                    "maxLength": 500
                  }
                }
              },
              {
                "type": "object",
                "required": [
                  "type",
                  "depotId"
                ],
                "properties": {
                  "type": {
                    "const": "depot"
                  },
                  "depotId": {
                    "type": "string",
                    "description": "From GET /v1/depots."
                  }
                }
              }
            ]
          },
          "delivery": {
            "type": "object",
            "required": [
              "address",
              "contact"
            ],
            "properties": {
              "address": {
                "$ref": "#/components/schemas/Address"
              },
              "contact": {
                "$ref": "#/components/schemas/Contact"
              },
              "instructions": {
                "type": "string",
                "maxLength": 500
              }
            }
          },
          "parcels": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/Parcel"
            }
          },
          "goodsValueCents": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 10000000,
            "description": "The value of the goods in cents. Left out or null: $99 or less. Over 9900 ($99) a signature is required. Declaring a value sets the signature rule only: standard cover is $450 per consignment (the lesser of the declared value and $450); extra cover only by arrangement in writing before pickup.",
            "examples": [
              15000
            ]
          },
          "containsAlcohol": {
            "type": "boolean",
            "default": false,
            "description": "The consignment contains alcohol: it is handed only to someone 18 or over who shows photo ID and signs, never left. A signature (and its fee) is required."
          }
        }
      },
      "Consignment": {
        "type": "object",
        "required": [
          "consignmentId",
          "reference",
          "mode",
          "status",
          "statusLabel",
          "service",
          "start",
          "depotId",
          "collectionDate",
          "deliverBy",
          "pickup",
          "delivery",
          "parcels",
          "signatureRequired",
          "idCheckRequired",
          "containsAlcohol",
          "goodsValueCents",
          "price",
          "trackingUrl",
          "labelsUrl",
          "createdAt"
        ],
        "properties": {
          "consignmentId": {
            "type": "string",
            "description": "NX- for a live consignment, NT- in the sandbox.",
            "examples": [
              "NX-AB4EKM"
            ]
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "channel": {
            "type": [
              "string",
              "null"
            ],
            "description": "The channel given when it was booked."
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "booked",
              "assigned",
              "picked_up",
              "at_depot",
              "out_for_delivery",
              "delivered",
              "failed",
              "cancelled"
            ]
          },
          "statusLabel": {
            "type": "string",
            "examples": [
              "Booking confirmed"
            ]
          },
          "service": {
            "$ref": "#/components/schemas/ServiceCode"
          },
          "start": {
            "type": "string",
            "enum": [
              "pickup",
              "depot"
            ]
          },
          "depotId": {
            "type": [
              "string",
              "null"
            ]
          },
          "collectionDate": {
            "type": "string",
            "format": "date"
          },
          "deliverBy": {
            "type": "string",
            "format": "date-time"
          },
          "pickup": {
            "type": "object",
            "properties": {
              "suburb": {
                "type": "string"
              },
              "postcode": {
                "type": "string"
              }
            }
          },
          "delivery": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "suburb": {
                "type": "string"
              },
              "postcode": {
                "type": "string"
              }
            }
          },
          "parcels": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "piece": {
                  "type": "integer"
                },
                "number": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The 13-digit parcel number on our label; null in the sandbox."
                },
                "barcode": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Your barcode for this parcel, if given."
                }
              }
            }
          },
          "signatureRequired": {
            "type": "boolean",
            "description": "Asked for, or required by the value of goods or alcohol."
          },
          "price": {
            "$ref": "#/components/schemas/Price"
          },
          "trackingUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "The public tracking page for the recipient; null in the sandbox."
          },
          "labelsUrl": {
            "type": "string",
            "format": "uri"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "goodsValueCents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The value declared, in cents; null when none was."
          },
          "containsAlcohol": {
            "type": "boolean"
          },
          "idCheckRequired": {
            "type": "boolean",
            "description": "The recipient must show photo ID proving they are 18 or over (alcohol)."
          }
        }
      },
      "MilestoneCode": {
        "type": "string",
        "enum": [
          "booked",
          "picked_up",
          "received_at_depot",
          "out_for_delivery",
          "delivered",
          "delivery_photo",
          "failed_attempt",
          "returned_to_depot",
          "returned_to_sender",
          "cancelled"
        ]
      },
      "Tracking": {
        "type": "object",
        "required": [
          "consignmentId",
          "reference",
          "mode",
          "status",
          "statusLabel",
          "service",
          "deliverBy",
          "origin",
          "destination",
          "parcels",
          "milestones",
          "proofOfDelivery",
          "trackingUrl"
        ],
        "properties": {
          "consignmentId": {
            "type": "string"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "status": {
            "type": "string"
          },
          "statusLabel": {
            "type": "string"
          },
          "service": {
            "$ref": "#/components/schemas/ServiceCode"
          },
          "deliverBy": {
            "type": "string",
            "format": "date-time"
          },
          "origin": {
            "type": "object",
            "properties": {
              "suburb": {
                "type": "string"
              },
              "postcode": {
                "type": "string"
              }
            }
          },
          "destination": {
            "type": "object",
            "properties": {
              "suburb": {
                "type": "string"
              },
              "postcode": {
                "type": "string"
              }
            }
          },
          "parcels": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "piece": {
                  "type": "integer"
                },
                "number": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "barcode": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "milestones": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "code",
                "at",
                "description"
              ],
              "properties": {
                "code": {
                  "$ref": "#/components/schemas/MilestoneCode"
                },
                "at": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": {
                  "type": "string"
                }
              }
            }
          },
          "proofOfDelivery": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "deliveredAt": {
                "type": "string",
                "format": "date-time"
              },
              "leftSafe": {
                "type": "boolean"
              },
              "location": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Where it was left or handed over, e.g. Front door."
              },
              "recipientName": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The first name of the person who took it, as the driver recorded it."
              },
              "photo": {
                "type": "boolean",
                "description": "GET /v1/consignments/{ref}/pod/photo has it: false once it is deleted, 12 months after delivery."
              },
              "signature": {
                "type": "boolean",
                "description": "GET /v1/consignments/{ref}/pod/signature has it: false once it is deleted, 12 months after delivery."
              },
              "idCheck": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "Alcohol: the type of photo ID the driver checked. Its number is never given.",
                "properties": {
                  "idType": {
                    "type": "string",
                    "enum": [
                      "drivers_licence",
                      "photo_card",
                      "proof_of_age",
                      "passport"
                    ]
                  }
                }
              }
            },
            "description": "Once delivered: how it was handed over. Photos and signatures are kept 12 months after delivery. A key without proof-of-delivery access gets recipientName null and photo and signature false."
          },
          "trackingUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Null in the sandbox."
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "description": "The body Nexroo posts to your webhook URL for each milestone (type consignment.<milestone>), or for a test (type ping). Check the Nexroo-Signature header before trusting it.",
        "required": [
          "id",
          "type",
          "createdAt",
          "mode",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The event id; the same on every retry. Use it to ignore repeats."
          },
          "type": {
            "type": "string",
            "enum": [
              "consignment.booked",
              "consignment.picked_up",
              "consignment.received_at_depot",
              "consignment.out_for_delivery",
              "consignment.delivered",
              "consignment.delivery_photo",
              "consignment.failed_attempt",
              "consignment.returned_to_depot",
              "consignment.returned_to_sender",
              "consignment.cancelled",
              "ping"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "data": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "consignment": {
                    "type": "object",
                    "properties": {
                      "consignmentId": {
                        "type": "string"
                      },
                      "reference": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "status": {
                        "type": "string"
                      },
                      "statusLabel": {
                        "type": "string"
                      },
                      "barcodes": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "milestone": {
                    "type": "object",
                    "properties": {
                      "code": {
                        "$ref": "#/components/schemas/MilestoneCode"
                      },
                      "at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "description": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string"
                  }
                }
              }
            ]
          }
        }
      },
      "WebhookRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 500,
            "description": "Where to post events: HTTPS on the standard port, a public host.",
            "examples": [
              "https://hooks.zapier.com/hooks/catch/123/abc/"
            ]
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "consignment.booked",
                "consignment.picked_up",
                "consignment.received_at_depot",
                "consignment.out_for_delivery",
                "consignment.delivered",
                "consignment.delivery_photo",
                "consignment.failed_attempt",
                "consignment.returned_to_depot",
                "consignment.returned_to_sender",
                "consignment.cancelled"
              ]
            },
            "description": "The event types to send. Left out or empty: all of them. The bare milestone code (delivered) is accepted too."
          },
          "description": {
            "type": "string",
            "maxLength": 100,
            "description": "Your own note, e.g. the workflow it serves. Shown to our staff."
          }
        }
      },
      "Webhook": {
        "type": "object",
        "description": "A webhook you made through the API. The signing secret is only in the answer that made it.",
        "required": [
          "id",
          "url",
          "description",
          "events",
          "enabled",
          "disabledReason",
          "failingSince",
          "secretHint",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "wh_AbCdEf0123456789"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "consignment.booked",
                "consignment.picked_up",
                "consignment.received_at_depot",
                "consignment.out_for_delivery",
                "consignment.delivered",
                "consignment.delivery_photo",
                "consignment.failed_attempt",
                "consignment.returned_to_depot",
                "consignment.returned_to_sender",
                "consignment.cancelled"
              ]
            },
            "description": "Empty means every event type."
          },
          "enabled": {
            "type": "boolean",
            "description": "False once the receiver answered 410 Gone or every delivery failed for three days; delete it and make a new one."
          },
          "disabledReason": {
            "type": [
              "string",
              "null"
            ]
          },
          "failingSince": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Since when every delivery has failed; null while it works."
          },
          "secretHint": {
            "type": "string",
            "description": "The last four characters of its signing secret."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookCreated": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Webhook"
          },
          {
            "type": "object",
            "required": [
              "secret"
            ],
            "properties": {
              "secret": {
                "type": "string",
                "description": "The signing secret for this webhook (whsec_…). Store it now: it is not shown again. To change it, delete the webhook and make a new one.",
                "examples": [
                  "whsec_…"
                ]
              }
            }
          }
        ]
      },
      "WebhookTestResult": {
        "type": "object",
        "required": [
          "delivered",
          "status",
          "error"
        ],
        "properties": {
          "delivered": {
            "type": "boolean"
          },
          "status": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The HTTP status your endpoint answered, or null when it could not be reached."
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      }
    },
    "headers": {
      "RateLimit-Limit": {
        "description": "Requests allowed per minute for this key.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimit-Remaining": {
        "description": "Requests left in the current minute.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimit-Reset": {
        "description": "Seconds until the minute resets.",
        "schema": {
          "type": "integer"
        }
      }
    }
  },
  "webhooks": {
    "consignmentMilestone": {
      "post": {
        "summary": "A milestone happened",
        "description": "Posted to the URL Nexroo has set up for your account. Headers: Nexroo-Signature (t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your signing secret>), Nexroo-Event-Id, Nexroo-Event-Type, Nexroo-Delivery-Attempt. Verify before trusting it: accept the event if any v1 matches (for 24 hours after the secret is changed there are two, one per secret), and reject it if t is more than 5 minutes from your clock, so a recorded request cannot be replayed. Each retry is signed afresh with a new t. Answer 2xx within 10 seconds. Anything else is retried after 1, 5 and 15 minutes, then 1, 3, 6 and 12 hours; answering 410 switches the webhook off. Events can arrive more than once or out of order: ignore an event id you have already handled, and order by the milestone time. Webhooks belong to the account, so every key's consignments are sent.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    }
  }
}
