Skip to content

Migrating to v2 ​

v2 is not a compatibility layer over v1. It is a separate API registered under api/v2 with its own authentication, its own envelope and its own identifiers, so migrating is a rewrite of the transport layer rather than a base-URL swap. This page maps what you have onto what replaces it.

The v2 side of every claim here is documented in full in the v2 reference; this page only says which v2 route replaces which v1 route, and what changes around it.


The five changes that touch every call ​

1. Credentials move from the payload to headers ​

v1 authenticates with license_key and domain inside the request body or query string. v2 authenticates the integration with two headers and selects the carrier account separately:

X-Client-Id: {client_id}
X-Client-Secret: {client_secret}

domain disappears entirely. license_key survives but changes job — it is no longer a credential, just the account selector, and it becomes optional when the account owns exactly one active license. See v2 Authentication and Licenses & license_key.

2. The envelope changes shape ​

v1v2
Success{data, message, success, warning}{data}
Failurethe same four keys, success: false{error: {code, message, status}}

v2 errors carry a machine-readable code, which v1 has no equivalent of — v1 callers have to match on message text. See v2 Errors.

3. The HTTP status becomes trustworthy ​

This is the change most likely to break a port done carelessly. In v1 the success flag is authoritative and the status often is not: several failure paths return success: false with HTTP 200, and one returns a validation failure as HTTP 500. Code written against v1 therefore tends to ignore the status and branch on success.

In v2 the status is always the real one, and there is no success flag to read. A port that keeps checking body.success will treat every v2 response as a failure, because the key is absent.

4. Reads become GET, and resources get URLs ​

v1 is POST-for-everything with no path parameters: you identify a shipment by putting its shipping_code in a body. v2 addresses resources by URL — GET /shipments/{uuid} — which also makes responses cacheable, and v2 serves ETags to support that.

5. Identifiers change ​

v1 addresses a shipment by shipping_code. v2 addresses it by uuid and exposes the carrier tracking code as a separate field. Any stored shipping_code values need to be resolved to uuids, or looked up through the order.


Route mapping ​

Shipments ​

v1v2Notes
POST /shipping/open_new_orderPOST /shipmentsSee idempotency below — the retry semantics differ materially.
POST /shipping/get_order_detailsGET /shipments/{uuid}
POST /shipping/get_shipping_statusGET /shipments/{uuid}/status
POST /shipping/{shipment}/statusGET /shipments/{uuid}/statusThe two v1 status routes collapse into one v2 route.
POST /shipping/change_order_statusPOST /shipments/{uuid}/cancelClosest equivalent. v1's endpoint is a general status change; v2 models cancellation explicitly rather than as a status write.
POST /shipping/orderGET /orders/{order}/shipments

Labels ​

v1v2Notes
POST /shipping/labelGET /shipments/{uuid}/label
POST /shipping/bulk-labelPOST /shipments/labels
POST /shipping/order-labelGET /orders/{order}/shipments, then a label call per shipmentNo single-call equivalent.

Pickup points ​

v1v2Notes
POST /shipping/pickup_locationsGET /pickup-pointsThe v2 response is a flat list. v1 nests the array under data.locations.spots.spot_detail; v2 does not. v2 also adds city filtering, limit, and nearest-first ranking by coordinates or a geocoded address.

Account and licenses ​

v1v2Notes
GET /shipping/user-by-licenseGET /account
GET /license/check_licenseGET /licensesv2 lists the licenses the credentials can act under; validating a key becomes "is it in that list".
GET /license/activate_license—No equivalent. Binding a license to a domain is an account-provisioning step, not an API operation, in v2.

No v2 equivalent ​

v1Why
GET /shipping/same_day_delivery_quoteThe express/same-day endpoints were never carried across. If you depend on them, stay on v1 for that call and raise it with the ShipOS team.
GET /shipping/create_shipping_promiseas above
GET /shipping/get_express_delivery_enabledas above
GET /shipping/get_express_delivery_statusas above
GET /get_app_userFirst-party recipient-app surface, not a merchant integration point.
POST /create_app_useras above
GET /shipping/phone_numberas above

Retry safety changes meaning ​

v1 protects open_new_order implicitly. A successful creation is cached for an hour under license_key + order_id, so a retry inside that window replays the first response instead of creating a second shipment. You get that whether you asked for it or not — and you cannot opt out, which is why creating two different shipment types for the same order inside the hour returns the first one again. That sharp edge is documented on Shipments.

v2 makes it explicit: you send an Idempotency-Key header of your own choosing, scoped to the request you actually made. Retries are safe because you said what counts as the same operation, not because the server guessed from order_id. See v2 Idempotency.

Practically: if your v1 code relies on the implicit cache to deduplicate, you must start sending Idempotency-Key in v2, or you will create duplicates — real shipments, with real carrier charges.


What you gain ​

Capabilities with no v1 counterpart at all:

  • Batches — queue up to 2,000 shipments, poll progress, inspect per-item failures, without holding a connection open. v1's only bulk primitive is a combined label URL.
  • Webhooks — HMAC-signed lifecycle events with a delivery log and test pings, instead of polling status endpoints.
  • Exports — asynchronous CSV extracts.
  • Notifications — SMS settings, templates and logs.
  • Reference data — the canonical shipping-status and service-type lists, rather than matching on strings.
  • Tracking — a public tracking lookup needing no credentials.
  • Health — GET /ping, so you can check reachability without credentials.

Suggested order of work ​

  1. Get credentials and confirm them against GET /account.
  2. Port creation first — POST /shipments with an Idempotency-Key — and run it alongside v1 on a small share of traffic.
  3. Port reads: status, shipment fetch, order shipments.
  4. Port labels.
  5. Replace status polling with webhooks, which is usually the largest single simplification.
  6. Keep any express/same-day calls on v1 until an equivalent exists.

A worked end-to-end walkthrough is in Create your first shipment.