Shipping quotes and tracking
Sellers on United Pet Club price delivery themselves — from their own rate tables or from live carrier rates, with a markup and handling fee bounded by the platform. The shipping API exposes the parts of that a buyer-facing integration needs: quote a set of lines to a destination, confirm the option the buyer picked, and track a parcel. Everything a seller configures (carrier accounts, ship-from locations, shipping profiles, labels) is first-party only and is not available through this API.
All routes live under /api/fulfillment/v1 and are anonymous — no token is required.
Responses use the platform envelope; read the payload from data. Money is always
{ amountMinorUnits, currencyCode }-style minor units next to an ISO 4217 code; times are UTC
ISO 8601; enumerations travel as string codes.
Quote delivery options
Section titled “Quote delivery options”POST /api/fulfillment/v1/quotes
One request prices every seller group in a cart to one destination. Lines carry the offer,
the quantity, the line subtotal, the seller’s shipping-profile reference and the method being
quoted, plus the per-unit package facts of the offer. All money in a group is in the group’s
currencyCode.
{ "correlationId": "7f0e2f7e-5d0e-4d4b-9a3c-1b1e0c1d2e3f", "localeCode": "en-CA", "destination": { "countryCode": "CA", "subdivisionCode": "ON", "postalCode": "M5V", "latitude": null, "longitude": null, "isResidential": true }, "groups": [ { "storefrontId": "0f7a4c1e-1111-7000-8000-000000000001", "sellerProfileId": "0f7a4c1e-2222-7000-8000-000000000002", "currencyCode": "CAD", "lines": [ { "offerId": "0f7a4c1e-3333-7000-8000-000000000003", "quantity": 2, "lineSubtotalMinorUnits": 4500, "shippingProfileId": "0f7a4c1e-4444-7000-8000-000000000004", "methodCode": "GROUND_SHIP", "handlingClassCode": "STANDARD", "shipsSeparately": false, "package": { "weightGrams": 1200, "lengthMillimeters": 300, "widthMillimeters": 200, "heightMillimeters": 100, "declaredValue": null, "referenceText": null, "requiresAdultSignature": false, "insuredMinorUnits": null } } ] } ]}| Field | Notes |
|---|---|
destination.countryCode | required, ISO 3166-1 alpha-2. subdivisionCode and postalCode (a prefix is enough) sharpen manual lanes and are required by most carriers for live rates |
lines[].shippingProfileId | the seller’s profile reference carried on the offer; null uses the seller’s default profile |
lines[].methodCode | GROUND_SHIP, EXPRESS_SHIP, FREIGHT, COURIER or MAIL_ORDER_RX; null quotes every active method of the profile |
lines[].shipsSeparately | true quotes this line as its own package instead of combining it with the group |
lines[].package | per-unit weight (grams) and dimensions (millimetres) of the packed item — the parcel, not the product inside it. null leaves the line unmeasured: weight bands cannot price it and live carrier rating is skipped |
How lines become parcels
Section titled “How lines become parcels”Every line of a group that is not shipsSeparately is quoted as one parcel. Its weight is the
sum of each line’s package.weightGrams × quantity. Its size is the largest footprint in the
group, grown in height until the box holds the summed volume of its contents — so three
300 × 200 × 100 items quote as a 300 × 200 × 300 parcel, not as a 300 × 200 × 100 one. That matters
because carriers bill the greater of actual and dimensional weight.
If any line in the group has no dimensions, the group’s size is left as the largest of what is known rather than inflated: the content volume is unknown, and guessing it would overcharge.
A line with shipsSeparately: true gets one parcel per unit, exactly as measured.
Response data:
{ "quoteId": "0f7a4c1e-5555-7000-8000-000000000005", "expiresAtUtc": "2026-09-04T15:42:10Z", "groups": [ { "storefrontId": "0f7a4c1e-1111-7000-8000-000000000001", "sellerProfileId": "0f7a4c1e-2222-7000-8000-000000000002", "offerIds": ["0f7a4c1e-3333-7000-8000-000000000003"], "options": [ { "quoteOptionId": "0f7a4c1e-6666-7000-8000-000000000006", "methodCode": "GROUND_SHIP", "methodName": "Ground shipping", "rateSourceKind": "MANUAL", "carrierKind": null, "carrierServiceCode": null, "carrierServiceName": null, "currencyCode": "CAD", "baseMinorUnits": 1500, "markupMinorUnits": 150, "handlingMinorUnits": 200, "capApplied": false, "feeMinorUnits": 1850, "handlingDaysMin": 1, "handlingDaysMax": 2, "transitDaysMin": 3, "transitDaysMax": 6, "estimatedDeliveryDateMin": "2026-09-08", "estimatedDeliveryDateMax": "2026-09-11", "providerKindCode": "SELLER" } ], "unavailable": [ { "methodCode": "EXPRESS_SHIP", "methodName": "Express shipping", "reasonCode": "REGION_BLOCKED", "detail": null } ] } ]}What to do with it:
- Show
feeMinorUnitsas the delivery price. It already includes the seller’s markup and handling;baseMinorUnits,markupMinorUnitsandhandlingMinorUnitsare the breakdown andcapAppliedtells you the platform cap trimmed the surcharge. Do not recompute the fee. - Name the option
carrierServiceName ?? methodName— a live-rated option carries the carrier service (“UPS Ground”); a rate-table option carries the seller’s method name. - Keep
quoteOptionIdwith the buyer’s selection; it is what you confirm at checkout. unavailablelists methods the seller offers that could not be priced for this destination, with a reason code. Show them as unavailable, not as errors.providerKindCodeisSELLERorPLATFORM— who fulfils the label. Purely informational.
reasonCode | Meaning |
|---|---|
REGION_BLOCKED | the seller does not ship this method to the destination |
NO_LANE | no rate-table lane covers the destination |
CARRIER_UNAVAILABLE | the live carrier rate could not be obtained; rate-table options still quote |
ACCOUNT_INACTIVE | the seller’s carrier account (or platform authorization) is not active |
ORIGIN_NOT_SUPPORTED | the carrier does not ship from the seller’s origin country |
NOT_ALL_OFFERS | not every line in the group offers this method |
MIXED_CURRENCIES | lines in one group disagree on currency |
QUOTE_EXPIRED | returned by confirm when an option could not be re-priced |
Two detail values say the seller’s side is not set up, so no destination will do better:
NO_LANE with SHIP_FROM_MISSING (the seller has no ship-from location and no address on its
profile yet) and CARRIER_UNAVAILABLE with SHIP_FROM_INCOMPLETE (the seller’s address has no
postal code, so a live carrier rate cannot be asked for; its rate-table options still quote).
A seller without a ship-from location ships from its profile’s address. The quote never
returns that address or a distance to it.
Validity is set by the seller (rate-table options up to 30 minutes by default, live carrier
options up to 15) and the quote’s expiresAtUtc is the earliest of its options.
Errors: 400 for a malformed request (missing destination country, empty groups, unknown
method code); the envelope’s resultCode is Fulfillment_FulfillmentQuote_VL_Request_RJ.
Read a quote
Section titled “Read a quote”GET /api/fulfillment/v1/quotes/{quoteId}
Returns the persisted quote in exactly the shape above. 404
(Fulfillment_FulfillmentQuote_GT_Quote_FL) when the id is unknown. An expired quote still
reads for a while, so check expiresAtUtc before trusting the prices. Seven days after its last
option expired, a quote nobody confirmed is deleted and reads 404 too. A confirmed quote is kept.
Confirm at checkout
Section titled “Confirm at checkout”POST /api/fulfillment/v1/quotes/{quoteId}/confirm
{ "quoteOptionIds": ["0f7a4c1e-6666-7000-8000-000000000006"] }Confirm the options the buyer selected immediately before taking payment. An option that is
still valid comes back unchanged; an expired one is re-priced and returned with
priceChanged: true and replacedQuoteOptionId pointing at the option it supersedes —
surface the new price to the buyer and confirm again with the new id.
{ "quoteId": "0f7a4c1e-5555-7000-8000-000000000005", "expiresAtUtc": "2026-09-04T16:02:10Z", "options": [ { "quoteOptionId": "0f7a4c1e-7777-7000-8000-000000000007", "methodCode": "GROUND_SHIP", "methodName": "Ground shipping", "rateSourceKind": "MANUAL", "carrierKind": null, "carrierServiceCode": null, "carrierServiceName": null, "currencyCode": "CAD", "baseMinorUnits": 1500, "markupMinorUnits": 150, "handlingMinorUnits": 200, "capApplied": false, "feeMinorUnits": 1850, "handlingDaysMin": 1, "handlingDaysMax": 2, "transitDaysMin": 3, "transitDaysMax": 6, "estimatedDeliveryDateMin": "2026-09-08", "estimatedDeliveryDateMax": "2026-09-11", "providerKindCode": "SELLER", "priceChanged": true, "replacedQuoteOptionId": "0f7a4c1e-6666-7000-8000-000000000006" } ]}410 (Fulfillment_FulfillmentQuote_UP_Expired_RJ) when an option can no longer be
re-priced — start a new quote. A quote already deleted (seven days after its last option
expired) answers 404 (Fulfillment_FulfillmentQuote_GT_Quote_FL): start a new quote there too.
Track a parcel
Section titled “Track a parcel”GET /api/fulfillment/v1/tracking/{trackingNumber}
Returns the parcel’s normalized status and its scan events, oldest first, for any tracking
number issued or recorded on the platform. No seller or buyer data is included. 404 for a
number the platform does not know. This read is rate-limited per caller like every anonymous
route — poll a parcel no more often than the platform itself does (hourly before the first
scan, every few hours in transit, every 30 minutes out for delivery).
{ "trackingNumber": "1Z999AA10123456784", "carrierKind": "UPS", "status": "IN_TRANSIT", "events": [ { "occurredAtUtc": "2026-09-05T13:04:00Z", "status": "SHIPPED", "carrierStatusCode": "P", "description": "Pickup scan", "locationText": "Ottawa, ON, CA" }, { "occurredAtUtc": "2026-09-06T02:11:00Z", "status": "IN_TRANSIT", "carrierStatusCode": "I", "description": "Departed from facility", "locationText": "Mississauga, ON, CA" } ]}status | Meaning |
|---|---|
LABEL_CREATED | the seller has a label; the carrier has not received the parcel |
SHIPPED | first carrier scan |
IN_TRANSIT | moving between facilities |
OUT_FOR_DELIVERY | on the final vehicle |
DELIVERED | delivered |
EXCEPTION | delayed, address problem, held, or a delivery attempt failed |
RETURNED | on its way back to the seller |
LOST | declared lost |
carrierStatusCode is the carrier’s own raw code — informational; key your logic on status.
carrierKind is one of the carrier-kinds vocabulary below, or null for a parcel the seller
recorded with a carrier outside that set. Events for those parcels, and for carriers the
platform holds no account with, may stay empty while status still moves as the seller
updates it.
Vocabularies
Section titled “Vocabularies”GET /api/fulfillment/v1/lookups/{set}?locale=xx-XX
Localized names for the codes used above (the name follows the platform’s
locale fallback). Sets: carrier-kinds, rate-source-kinds,
amount-kinds, band-axis-kinds, region-scope-kinds, region-rule-kinds,
shipment-statuses, label-statuses, carrier-account-statuses, label-charge-statuses,
label-authorization-statuses, methods, environments.
[ { "id": 1, "code": "UPS", "name": "UPS", "description": null, "sortOrder": 10 }, { "id": 2, "code": "FEDEX", "name": "FedEx", "description": null, "sortOrder": 20 }, { "id": 3, "code": "DHL_EXPRESS", "name": "DHL Express", "description": null, "sortOrder": 30 }, { "id": 4, "code": "CANADA_POST", "name": "Canada Post", "description": null, "sortOrder": 40 }, { "id": 5, "code": "USPS", "name": "USPS", "description": null, "sortOrder": 50 }]Key on code; display name. An unknown set is a 404.
Not on this surface
Section titled “Not on this surface”Carrier accounts, ship-from locations, shipping profiles, rate tables, shipments and labels are managed by sellers in their United Pet Club dashboard and are first-party only — they are scoped to the signed-in seller and not exposed to partner applications. If you need to act on a seller’s behalf, the seller configures shipping in the dashboard and your integration reads the resulting prices through the quote route above.