Skip to content

Changelog

All changes so far are within v1. See Versioning for what can change and how it is announced.

A write with an idempotency key never runs unprotected. Within v1, with one new refusal.

  • Past 1,000 writes a day, a key’s writes used to run without their answer being kept, so a retry after a timeout ran again (an upload stored a second copy). Each key now has 5,000 new idempotency keys per UTC day; past it, a write answers 429 Idempotency.DailyLimit with a Retry-After to midnight UTC, and nothing runs. Repeats of keys already answered are not counted. See Rate limits.
  • An answer over 256 KB, or past the key’s daily storage for answers, is kept by its status: a repeat does not run again and answers with X-Idempotency-Replayed: true and X-Idempotency-Body-Omitted: true, without a body. See Idempotency.

Cost totals count agreed costs only. Additive within v1, with one change in meaning.

  • On GET /deals/{id}/costs, additionalTotal and grandTotal count agreed additional costs only, as the app’s operation statement does since 2026-10-16. Declared costs not yet agreed are no longer in them.
  • New on each total: declaredTotal, the declared costs in that currency not yet agreed, never converted and never in the grand total. It is 0.00 when there are none, and in the sandbox.
  • A line counts as agreed when its status is Approved or the shipper booked it itself (recordedBy Shipper): the shipper pays it. A Declared line the carrier booked is in declaredTotal.
  • If your integration took declared costs from the totals, read declaredTotal, or sum the Declared lines the carrier booked.

Reference numbers. All additive within v1.

  • Every shipment request, quote, deal and operation has a reference number beside its id: REQ-048213, QTE-731905, DEAL-206447, SHPT-012618. Records created earlier have one too. See Reference numbers.
  • Reads carry the numbers of the records they name: the request status adds requestNumber, quoteNumber, dealNumber and shipmentNumber; a deal adds dealNumber, requestNumber, quoteNumber and shipmentNumber; the operation adds shipmentNumber and dealNumber; documents, costs and the upload answer add dealNumber; the create answer adds requestNumber.
  • New reads by number, under the id routes’ scopes and rules: GET /shipment-requests/by-number/{number}, GET /deals/by-number/{number} and GET /operations/by-number/{number}. Any case, prefix optional; another kind’s prefix answers 400 Integration.InvalidBusinessNumber, and a number your company may not read answers 404.
  • Webhooks: every delivery’s data adds requestNumber, quoteNumber, dealNumber and shipmentNumber, each null where the record does not exist yet. On request.bid_received, quoteNumber is the offer that arrived.
  • The sandbox numbers its test records the same way, never with a live record’s number.

The Integration API on every plan. No change to routes, fields or codes.

  • The API is open on every plan, Exchange included: 1 live and 1 test integration, 60 calls a minute per key, and 50 document uploads (250 MB) a day per company. Enterprise raises these to 5 live and 5 test integrations, 300 calls a minute per key, and 200 uploads (1 GiB) a day. See Getting access.
  • The rate limit and the upload allowance follow your company’s plan on every call.
  • The security features (OAuth, the IP allowlist, expiry and rotation, two-step verification, the call log and the audit trail) are the same on every plan.

Wave 3 follow-ups. All within v1.

  • X-Idempotency-Key compares a request by its content, not its spelling: a retry that rebuilds the same request in another member order, property case, number spelling or metadata key order replays the first answer instead of 422 Integration.IdempotencyKeyReused. Uploads compare the category in any case and the file type without its parameters.
  • A cancel expires the pending offers, and tells their carriers, only once the cancellation is saved. An update tells the carriers only once every part of it is written.
  • Every text of a create body (notes, addresses, contacts, the cargo’s words) refuses a control character such as U+0000 with 400 Integration.InvalidBody naming the field, as an update does.
  • DOCX and XLSX are recognised as Office reads them, through the package’s main part; a ZIP without root relationships, or with more than 5,000 entries, is not a document. A Word document sent as XLSX, or a workbook sent as DOCX, is kept as what it is: its contentType and its fileName extension follow the real type (list.xlsx becomes list.docx). A 415 says what the file is.
  • A broken surrogate escape (such as "\ud800") in an update answers 400 Integration.InvalidBody naming the field, or Integration.MetadataInvalid naming the metadata key.

Update, cancel, metadata and document upload. All additive within v1.

  • PATCH /shipment-requests/{id} updates a request where the app lets its owner (the listing while it is Published, your bookkeeping in any state), as a JSON merge patch of at most 64 KB, in one transaction. See Updating and cancelling.
  • requests:write now also covers update and cancel of API-created requests. A request opened in the app is read through the API, but changed and cancelled only in the app (409 Integration.RequestNotCreatedViaApi).
  • POST /shipment-requests/{id}/cancel cancels a request before its award; cancelling twice answers the cancelled request, and an awarded request answers 409 Integration.RequestAwarded with its dealId.
  • Reads of a request carry an ETag; update and cancel take an optional If-Match and answer 412 when the request changed in between. The check holds under the request’s lock, so two writers sending one tag cannot both win.
  • metadata on requests: up to 20 of your own fields, set on create and merged on update, visible only to your company. See Metadata.
  • POST /deals/{id}/documents uploads a document under the app’s upload rules and malware scan, with a daily allowance per company. The category follows what the app offers your side at the deal’s stage (400 Document.CategoryNotOffered); retired categories and AdditionalCostReceipt are not taken. A DOCX or XLSX must be a Word or Excel package. A new scope, documents:write. See Uploading documents.
  • The request status body adds metadata, revision and cancellationReason.
  • Webhooks: a new event, request.updated, one per update, naming everything it moved; request.cancelled carries the reason; events sent to a request’s owner carry its metadata.
  • X-Idempotency-Key is required on every write (create, update, cancel, upload). The same key on a different request answers 422 Integration.IdempotencyKeyReused. A 412 or a 429 is never replayed, so a retry with the same key really runs.
  • Text with control characters (such as U+0000) is refused by name, in fields and in metadata.
  • The sandbox takes updates, cancels and uploads; an uploaded file is checked and scanned, then only its record is kept.

Integration API v1.

  • Shipment requests: create (POST /shipment-requests, with a required X-Idempotency-Key and a unique externalReference), read by id, and find by externalReference.
  • Deals: list the deals made from a request, read a deal, its documents (with a 5-minute download link for clean files), its operation (milestones and delivery estimate) and its costs.
  • Authentication with the key itself or with a 15-minute OAuth 2.0 access token (client credentials), an “OAuth only” setting, scopes, 12-month expiry with reminders, rotation with 7 days of grace, and an IP allowlist.
  • Every key acts as its integration’s own service identity.
  • Signed webhooks for 11 event types, plus webhook.test, with retries, auto-disable, redelivery and secret rotation.
  • Test keys (qk_test_...) with a sandbox, and POST /sandbox/shipment-requests/{id}/advance to move a test request through its life.
  • Self-service: a company’s Owner and Admins create integrations, rotate and revoke keys and manage webhooks in Settings > Integrations.
  • The 409 for a repeated externalReference names the existing request’s status requestStatus, so status is only the HTTP status.