Skip to content
LabelsDock

For whoever wires it up

An API written for the retry, not the happy path

Almost everything that goes wrong in shipping software goes wrong on the second attempt — a timeout, a retry, a duplicate charge that nobody notices until the invoice. These are the promises we make about that, and they are the same ones our own code is held to.

Buying a label needs a key

Every purchase carries an idempotency key, and it is required rather than offered. Send the same one again and you get back the label you already bought, not a second one. A caller who cannot send a key cannot retry safely, so we refuse the request rather than take that risk on their behalf.

Two credentials, and only one of them buys things

A key identifies your system; signing in identifies a person. Reading takes either. Anything that spends money takes the key alone, so the question "who did this?" always has one kind of answer.

One error shape, at every status

Every failure comes back with the same fields: the status, a stable label to branch on, a sentence written for a person, and the exact fields that were wrong when that is what happened. Your error handler gets written once.

Lists page by cursor, never by page number

Shipments are created while you are reading them, and each one arriving pushes a row off the page you already left — so a page number quietly skips it. A cursor names where you stopped instead of how far in you are. Keep going until there is no next cursor.

The contract is generated, not written

The API publishes its own OpenAPI document, built from the same definitions it validates requests against. It cannot describe a route that does not exist, and our build fails if a route is added without it. Point a client generator at it and start from working types.

Buying a label. Money is always whole cents and a currency — never a decimal.
POST /labels
Authorization: Bearer shpk_…
Idempotency-Key: order-1042

{ "rateId": "rate_…",
  "carrierCode": "ups",
  "serviceCode": "ups_ground",
  "shipment": { … } }

200 OK
{ "labelId": "0f9c…",
  "status": "purchased",
  "price": { "amountCents": 1899, "currency": "USD" },
  "trackingNumber": "1Z999AA10123456784" }

There is no public host to point a generator at yet — we are still pre-launch, and saying otherwise would be the first thing we got wrong. The document ships with the software and is served by the API itself.

Think this fits what you ship?

Tell us what you send, where it goes, and what your current tool gets wrong. That is the whole first conversation.