Added

Batch quotes: price up to 1,000 shipments at once

You can now send a list of shipments to the Shipper API and get a quote for each. A batch is accepted at once and priced in the background. Read it until it is done, then book any quoted item with its quote ID, exactly as you would a single quote.

How it works

  1. Create the batch. POST /v1/shipper/quotes/batch with 1 to 1,000 items. Each item is a regular quote request, the same shape you send to POST /v1/shipper/quote. The response is 201 Created with the batch in status PRICING, a Location header pointing at the batch, and a Retry-After header saying how long to wait before reading it.
  2. Read the batch. GET /v1/shipper/quotes/batch/{batchId} returns the same body as the create call, brought up to date. Results are in the order you sent the items, so match them by position. Keep reading until status is COMPLETED or FAILED; while it is PRICING, Retry-After tells you when to come back.
  3. Act on each item. Every item ends in one of three states:
    • QUOTED: quote carries the fields a single quote request answers with, including the id you book with and when the price expires.
    • NOT_QUOTED: problem says why, in the shape a single quote request would have used. A 400 with violations means the item was malformed; a 422 with a reason code means we cannot serve that lane or load as asked.
    • UNDER_REVIEW: no automatic price can be trusted for this item, so a person decides. problem.reason is under_review. The item later becomes QUOTED or NOT_QUOTED; read the batch again, or look it up by its customerReference.

While pricing, items show PENDING. Once a batch is COMPLETED, items change in one direction only, from UNDER_REVIEW to QUOTED or NOT_QUOTED; nothing else on the batch moves.

A refused or malformed item never fails the batch. The whole call is refused only when the envelope cannot be taken: no items, more than 1,000, or an empty item.

Example

POST /v1/shipper/quotes/batch
Idempotency-Key: plan-2026-10-14
Content-Type: application/json

{
  "customerReference": "plan-2026-10-14",
  "items": [
    {
      "customerReference": "PO-48213",
      "pickupAddress": { "zipCode": "91730" },
      "deliveryAddress": { "zipCode": "95206" },
      "orderComponents": [ { "palletCount": 6, "poundsWeight": 1200 } ],
      "requiredPickupDate": "2026-10-14T08:00:00-07:00"
    },
    {
      "pickupAddress": { "zipCode": "91730" },
      "deliveryAddress": { "zipCode": "97201" },
      "orderComponents": [ { "palletCount": 2, "poundsWeight": 900 } ]
    }
  ]
}
HTTP/1.1 201 Created
Location: /v1/shipper/quotes/batch/QB-8F3K29XA
Retry-After: 5

{
  "id": "QB-8F3K29XA",
  "status": "PRICING",
  "customerReference": "plan-2026-10-14",
  "createdAt": "2026-10-12T13:05:00Z",
  "counts": { "items": 2, "quoted": 0, "notQuoted": 0, "underReview": 0, "pending": 2 },
  "items": [
    { "customerReference": "PO-48213", "status": "PENDING" },
    { "status": "PENDING" }
  ]
}

A few seconds later:

GET /v1/shipper/quotes/batch/QB-8F3K29XA

{
  "id": "QB-8F3K29XA",
  "status": "COMPLETED",
  "customerReference": "plan-2026-10-14",
  "createdAt": "2026-10-12T13:05:00Z",
  "completedAt": "2026-10-12T13:05:09Z",
  "counts": { "items": 2, "quoted": 1, "notQuoted": 1, "underReview": 0, "pending": 0 },
  "items": [
    {
      "customerReference": "PO-48213",
      "status": "QUOTED",
      "quote": {
        "id": "68e9c2f1a4b7d3e2f0c91a77",
        "quotedPriceInCents": 123400,
        "quoteExpirationTime": "2026-10-14T13:05:09Z",
        "transitDays": 2,
        "estimatedDeliveryDate": "2026-10-16T13:05:09Z"
      }
    },
    {
      "status": "NOT_QUOTED",
      "problem": {
        "title": "Unprocessable Entity",
        "status": 422,
        "detail": "We can't cover this lane right now",
        "reason": "no_coverage"
      }
    }
  ]
}

References

  • The batch's customerReference is yours for the whole set, such as a plan or file name. It is stored on the batch only.
  • An item's customerReference (up to 64 characters) is stored on its quote, so a later shipment can name it and GET /v1/shipper/quotes/by-reference can find it. An item without one is matched by position only; no reference is invented for it.
  • An item whose reference is longer than 64 characters is NOT_QUOTED with a 400; the rest of the batch prices as usual.

Idempotency, limits and failure

  • Idempotency-Key works as it does on shipment creation: retrying the same request with the same key within 24 hours returns the original response; reusing the key with a different body returns 409 with reason idempotency_key_conflict.
  • Without a key, sending the same items for your account within 24 hours returns the batch that already ran, however far it has got. Quotes stay valid for two days, so a replay never returns an expired price.
  • One batch at a time per account. A different batch sent while one is still PRICING is refused with 409 and reason batch_in_progress; the running batch's ID is in detail.
  • Up to 1,000 items per batch. Results come back in the order you sent them.
  • FAILED means the run could not finish. A batch that stops making progress is marked FAILED after about 15 minutes, and its items may be submitted again at once; the same items are then priced afresh rather than answered with the failed batch.

Also in this release

  • customerReference on single quote requests. POST /v1/shipper/quote now accepts your own reference for the load, up to 64 characters. It is stored on the quote, so a later shipment can name it and GET /v1/shipper/quotes/by-reference can find it.
  • Quote lookups are scoped to your account. GET /v1/shipper/quote/{quoteId} answers 404 for a quote that does not belong to your company.
  • Pickup dates. requiredPickupDate is read as a day in the pickup location's time zone. Send it with that zone's offset, for example 2026-10-14T08:00:00-07:00, so the day is unambiguous.

The OpenAPI specification is version 1.3.0. SDK support for batches follows in the next SDK release.