API v1

ShipStack API reference

A JSON REST API for creating orders, quoting rates, buying labels, tracking shipments, managing inventory and returns. All requests use HTTPS against https://shipstack.app/api/v1. Amounts are integer cents; weights are ounces and dimensions inches. The machine-readable spec is at /api/v1/openapi.json (OpenAPI 3.1).

Authentication

Create keys in Settings → API (requires the API keys permission and a plan with API access). Send the key as a bearer token. The full key is shown once; ShipStack stores only its SHA-256 hash.

Request
curl https://shipstack.app/api/v1/orders \
  -H "Authorization: Bearer ss_live_…"
  • ss_live_… — full access within the key's scopes.
  • ss_test_… — same data, but POST /labels only works for test-mode (sandbox) shipments, so a test integration can never spend money.

Scopes

orders:readRead orders
orders:writeCreate & cancel orders
shipments:readRead shipments
rates:readGet rates (creates a shipment)
labels:writePurchase & void labels
tracking:readRead tracking
inventory:readRead inventory
inventory:writeAdjust inventory
warehouses:readRead warehouses
returns:readRead returns
returns:writeCreate returns

Errors

Every error uses the same envelope. Branch on type, show message, and include request_id (also in the X-Request-Id header) when contacting support.

422 Unprocessable Entity
{
  "error": {
    "type": "invalid_request_error",
    "code": "validation_error",
    "message": "parcel.weight_oz: Too small: expected number to be >0",
    "param": "parcel.weight_oz",
    "request_id": "req_4Zb1yS0a9vQhJx2m"
  }
}
typeHTTPMeaning
invalid_request_error400 / 422The request was malformed or failed validation. `param` names the offending field.
authentication_error401Missing, malformed, expired or revoked API key.
permission_error403The key is valid but lacks the scope required for this endpoint, or the organization is suspended.
plan_error402The organization's plan doesn't include API access (or the subscription is inactive).
not_found_error404The object doesn't exist in this organization.
conflict_error409The request conflicts with the object's current state (e.g. cancelling a shipped order).
idempotency_error409 / 400Idempotency-Key reused with a different body, still in progress, or missing where required.
rate_limit_error429Too many requests for this key. Retry after the X-RateLimit-Reset time.
provider_error409 / 502The shipping provider isn't configured or returned an error. Nothing was charged.
api_error500Something went wrong on our side. Safe to retry with the same Idempotency-Key.

Idempotency

Send an Idempotency-Key header (any unique string up to 255 characters, e.g. a UUID) on POST requests to retry safely after a timeout. It is required for POST /labels. Keys are scoped to your organization and endpoint.

  • • Same key + same body → the original status and body are replayed, with Idempotent-Replayed: true.
  • • Same key + different body → 409 idempotency_key_reused.
  • • Same key while the first request is still running → 409 idempotency_request_in_progress; retry shortly.
  • • 5xx responses aren't stored, so retrying with the same key re-attempts the request.
Buy a label
curl -X POST https://shipstack.app/api/v1/labels \
  -H "Authorization: Bearer ss_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f0b1c1e-2a47-4f7e-9d0e-6f1f6b0b7a21" \
  -d '{"shipment_id":"…","rate_id":"…","label_format":"PDF_4x6","confirmed_amount_cents":842}'

Pagination

List endpoints return newest first in a list envelope. Pass limit (1–100, default 25) and, for the next page, starting_after set to the previous page's next_cursor.

GET /orders?limit=2
{
  "object": "list",
  "data": [ { "id": "…", "object": "order", … }, { "id": "8c2e…", "object": "order", … } ],
  "has_more": true,
  "next_cursor": "8c2e…",
  "url": "/api/v1/orders"
}

Rate limits

120 requests per 60 seconds per API key. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds). Over the limit you get 429 rate_limit_error with Retry-After.

Webhooks

Add HTTPS endpoints in Settings → API. Each event is POSTed as JSON with a ShipStack-Signature header. Respond 2xx within 10 seconds; anything else is retried with exponential backoff (30 s, 2 m, 8 m, 32 m, ~2 h, then every ~6 h) for 8 attempts, after which the delivery is marked dead. You can retry from the dashboard. Deduplicate on the event id — retries reuse it.

order.createdAn order was created (dashboard, import, API or store sync).
shipment.label_purchasedA shipping label was purchased for a shipment.
shipment.tracking_updatedThe carrier reported a new tracking status.
shipment.deliveredThe carrier reported the shipment delivered.
return.createdA return (RMA) was created.
Headers
ShipStack-Signature: t=1760000000,v1=<hex HMAC-SHA256 of "1760000000.<raw body>">
ShipStack-Event-Id: evt_…
ShipStack-Event-Type: shipment.label_purchased
Event body
{
  "id": "evt_…",
  "object": "event",
  "type": "shipment.label_purchased",
  "created": 1760000000,
  "data": { "object": { "id": "…", "object": "shipment", … } }
}
Verify the signature (Node.js)
import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the exact request body string (don't re-serialize parsed JSON)
export function verifyShipStack(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1 ?? "", "hex");
  return given.length === expected.length && timingSafeEqual(given, expected);
}

Endpoints

get/api/v1/orders

List orders

Scope orders:readPaginated

Parameters

limitquery1–100, default 25
starting_afterquerynext_cursor from the previous page
statusqueryFilter by status. "pending" | "awaiting_fulfillment" | "processing" | "label_created" | "shipped" | "delivered" | "cancelled" | "exception"
sourcequeryFilter by source. "manual" | "csv" | "shopify" | "woocommerce" | "api"
external_idqueryExact external id. string

Responses

  • 200A page of orders, newest first.OrderList
  • Errors use the error envelope.
post/api/v1/orders

Create an order

Creates an order with source `api`, matches SKUs to your catalog, runs automation rules and reserves inventory at the assigned warehouse. Idempotent on `external_id`.

Scope orders:writeIdempotency-Key optional

Request body

external_idstring | nullmax 200 chars

Your id for the order. Re-sending the same external_id returns the existing order (200) instead of creating a duplicate.

order_numberstring | nullmax 64 chars

Defaults to the next SS-#### number.

ordered_atstring | null (date-time)
currencystring | nullmax 3 chars
customerrequiredobject
namerequiredstringmax 200 chars
emailstring | nullmax 254 chars
phonestring | nullmax 40 chars
ship_torequiredAddressInput
itemsrequiredobject[]min 1 items, max 500 items
skustring | nullmax 100 chars

Matched case-insensitively to your catalog; matched lines reserve inventory.

namerequiredstringmax 300 chars
quantityrequiredinteger≥ 1, ≤ 100000
unit_price_centsrequiredinteger≥ 0
weight_oznumber | null≥ 0
shipping_paid_centsinteger≥ 0
tax_centsinteger≥ 0
requested_servicestring | nullmax 120 chars
tagsstring[]max 30 items
notesstring | nullmax 5000 chars
warehouse_idstring | null (uuid)

Responses

  • 200An order with this external_id already existed; it is returned unchanged.Order
  • 201Created.Order
  • Errors use the error envelope.
get/api/v1/orders/{id}

Retrieve an order

Scope orders:read

Parameters

idpathOrder id

Responses

post/api/v1/orders/{id}/cancel

Cancel an order

Cancels the order and releases reserved inventory. Fails with 409 `label_exists` while a non-voided label exists.

Scope orders:writeIdempotency-Key optional

Parameters

idpathOrder id

Request body

reasonstring | nullmax 500 chars

Responses

get/api/v1/shipments

List shipments

Scope shipments:readPaginated

Parameters

limitquery1–100, default 25
starting_afterquerynext_cursor from the previous page
statusqueryFilter by shipment status. string
order_idqueryShipments for one order. string (uuid)

Responses

get/api/v1/shipments/{id}

Retrieve a shipment

Scope shipments:read

Parameters

idpathShipment id

Responses

  • 200The shipment, including its current label.Shipment
  • Errors use the error envelope.
post/api/v1/rates

Get rates

Creates a shipment and returns the shipping provider's live rates, exactly as quoted (valid for 20 minutes). Without a connected Shippo/EasyPost account the built-in sandbox answers and every rate has `test_mode: true`.

Scope rates:readIdempotency-Key optional

Request body

order_idstring | null (uuid)

Ship-to and warehouse default from the order.

warehouse_idstring | null (uuid)

Ship-from warehouse (defaults to the order's / org default).

from_addressAddressInput | null
to_addressAddressInput | null
parcelrequiredParcel
customsCustoms | null

Required when origin and destination countries differ.

insurance_amount_centsinteger | null≥ 0, ≤ 1000000
signature_confirmationboolean

Responses

  • 201The new shipment with its rates (cheapest first).ShipmentWithRates
  • Errors use the error envelope.
post/api/v1/labels

Purchase a label

Buys the label for a rate. Charges your carrier/provider account in live mode. Requires an `Idempotency-Key` and `confirmed_amount_cents` equal to the rate. `ss_test_` keys can only buy test-mode labels.

Scope labels:writeIdempotency-Key required

Request body

shipment_idrequiredstring (uuid)
rate_idrequiredstring (uuid)
label_format"PDF_4x6" | "PDF" | "ZPLII" | "PNG"default "PDF_4x6"
confirmed_amount_centsrequiredinteger≥ 0

Must equal the rate's amount_cents. Protects against paying a changed price.

Responses

  • 200This idempotent purchase already completed.Label
  • 201Purchased.Label
  • 202The provider hasn't confirmed yet; poll the shipment.PendingPurchase
  • Errors use the error envelope.
post/api/v1/labels/{id}/void

Void a label

Requests a refund for an unused label. Some carriers approve asynchronously (`void_requested`).

Scope labels:writeIdempotency-Key optional

Parameters

idpathLabel id

Responses

  • 200The label with the void result.VoidLabelResponse
  • Errors use the error envelope.
get/api/v1/tracking/{tracking_number}

Track a shipment

Scope tracking:read

Parameters

tracking_numberpathCarrier tracking number of one of your shipments

Responses

  • 200Status and carrier events, newest first.Tracking
  • Errors use the error envelope.
get/api/v1/inventory

List inventory levels

Scope inventory:readPaginated

Parameters

limitquery1–100, default 25
starting_afterquerynext_cursor from the previous page
skuqueryExact SKU (case-insensitive). string
warehouse_idqueryOne warehouse. string (uuid)

Responses

  • 200A page of levels (ordered by id).InventoryLevelList
  • Errors use the error envelope.
post/api/v1/inventory/adjustments

Adjust inventory

Receive stock, apply a signed adjustment, or record a cycle count. Send an Idempotency-Key so retries can't double-apply.

Scope inventory:writeIdempotency-Key optional

Request body

product_idstring (uuid)

Provide product_id or sku.

skustringmax 64 chars
warehouse_idrequiredstring (uuid)
moderequired"receive" | "adjust" | "count"

receive: add units · adjust: signed delta (note required) · count: set absolute on-hand

quantityrequiredinteger
notestring | nullmax 500 chars

Responses

get/api/v1/warehouses

List warehouses

Scope warehouses:readPaginated

Parameters

limitquery1–100, default 25
starting_afterquerynext_cursor from the previous page

Responses

get/api/v1/returns

List returns

Scope returns:readPaginated

Parameters

limitquery1–100, default 25
starting_afterquerynext_cursor from the previous page
statusqueryFilter by status. string

Responses

post/api/v1/returns

Create a return

Creates an RMA for shipped items. Your return window and reason policy apply.

Scope returns:writeIdempotency-Key optional

Request body

order_idrequiredstring (uuid)
reason"damaged" | "wrong_item" | "not_as_described" | "no_longer_needed" | "defective" | "other" | null
itemsrequiredobject[]min 1 items
order_item_idrequiredstring (uuid)
quantityrequiredinteger≥ 1
customer_notestring | nullmax 2000 chars
refund_requestedboolean

Responses

get/api/v1/returns/{id}

Retrieve a return

Scope returns:read

Parameters

idpathReturn id

Responses

Objects

Order

idstring (uuid)
object"order"
order_numberstring
status"pending" | "awaiting_fulfillment" | "processing" | "label_created" | "shipped" | "delivered" | "cancelled" | "exception"
source"manual" | "csv" | "shopify" | "woocommerce" | "api"
external_idstring | null
integration_idstring | null (uuid)
currencystring
subtotal_centsinteger
shipping_paid_centsinteger
tax_centsinteger
total_centsinteger
requested_servicestring | null
tagsstring[]
warehouse_idstring | null (uuid)
requires_approvalboolean
customerobject | null
ship_toAddress | null
itemsOrderItem[]
ordered_atstring | null (date-time)
shipped_atstring | null (date-time)
delivered_atstring | null (date-time)
cancelled_atstring | null (date-time)
created_atstring | null (date-time)
updated_atstring | null (date-time)

Shipment

idstring (uuid)
object"shipment"
order_idstring | null (uuid)
return_idstring | null (uuid)
direction"outbound" | "return"
status"draft" | "rated" | "purchasing" | "label_purchased" | "in_transit" | "out_for_delivery" | "delivered" | "exception" | "returned_to_sender" | "void_requested" | "voided" | "failed"
providerstring | null
test_modeboolean
from_addressAddress
to_addressAddress
parcelobject
length_innumber
width_innumber
height_innumber
weight_oznumber
carrierstring | null
service_codestring | null
service_namestring | null
rate_centsinteger | null
currencystring
tracking_numberstring | null
tracking_urlstring | null
tracking_statusstring | null
estimated_delivery_atstring | null (date-time)
shipped_atstring | null (date-time)
delivered_atstring | null (date-time)
labelLabel | null
created_atstring | null (date-time)
updated_atstring | null (date-time)

Rate

idstring (uuid)
object"rate"
shipment_idstring (uuid)
providerstring
carrierstring
service_codestring
service_namestring
amount_centsinteger
currencystring
estimated_daysinteger | null
duration_termsstring | null
attributesstring[]
test_modeboolean
retrieved_atstring | null (date-time)
expires_atstring | null (date-time)

Label

idstring (uuid)
object"label"
shipment_idstring (uuid)
status"purchased" | "void_requested" | "voided" | "void_rejected"
format"PDF" | "PDF_4x6" | "ZPLII" | "PNG"
label_urlstring | null
cost_centsinteger
currencystring
test_modeboolean
providerstring
tracking_numberstring | null
tracking_urlstring | null
carrierstring | null
service_codestring | null
purchased_atstring | null (date-time)
void_requested_atstring | null (date-time)
voided_atstring | null (date-time)
void_messagestring | null

Tracking

object"tracking"
tracking_numberstring
carrierstring | null
statusstring
shipment_idstring (uuid)
order_idstring | null (uuid)
test_modeboolean
estimated_delivery_atstring | null (date-time)
delivered_atstring | null (date-time)
last_updated_atstring | null (date-time)
eventsobject[]
statusstring
status_detailstring | null
descriptionstring | null
locationobject
citystring | null
statestring | null
countrystring | null
occurred_atstring | null (date-time)
sourcestring

InventoryLevel

idstring (uuid)
object"inventory_level"
product_idstring (uuid)
skustring
namestring
warehouse_idstring (uuid)
warehouse_codestring
on_handinteger
reservedinteger
availableinteger
bin_locationstring | null
updated_atstring | null (date-time)

Warehouse

idstring (uuid)
object"warehouse"
namestring
codestring
is_defaultboolean
activeboolean
addressAddress
created_atstring | null (date-time)

Return

idstring (uuid)
object"return"
rma_numberstring
order_idstring (uuid)
status"requested" | "authorized" | "label_created" | "in_transit" | "received" | "restocked" | "closed" | "rejected"
reasonstring
customer_notestring | null
refund_statusstring
restocking_fee_centsinteger
return_shipment_idstring | null (uuid)
itemsobject[]
idstring (uuid)
order_item_idstring | null (uuid)
product_idstring | null (uuid)
quantityinteger
conditionstring | null
restockedboolean
authorized_atstring | null (date-time)
received_atstring | null (date-time)
closed_atstring | null (date-time)
created_atstring | null (date-time)
updated_atstring | null (date-time)