Buy a shipping label (charges your Shippo account)
AI agents: read /llms.txt; add .md to any page URL. This page as Markdown: /reference/shipping-labels/v2-shipment-label-create-goshippo.md. Index of every page and the rules for every call: /llms.txt. Guides and concepts in one file: /llms-full.txt. OpenAPI: /openapi.json.
POST/
This spends real money. Each successful call buys a label from the carrier and Shippo charges your team's Shippo account. The API can't undo a purchase: deleting the package later only asks Shippo for a refund, which Shippo and the carrier may refuse, and a refused refund is not reported back. Anyone whose role can view the order can buy: edit rights are not needed.
Buys the label for a rate from Get shipping rates and records it on a YoPrint shipment. Send the rate's object_id as rate_id: the addresses, parcels, service and options all come from the rate. The label file type is your Shippo integration's label size setting (4x6 PDF unless changed).
To bill the label's cost to the customer, send create_shipping_cost_line_item: true: after the purchase YoPrint adds one "Shipping Cost" line to the shipment, linked to the master package, for the label's cost (on an Auto Invoice order it then reaches the managed invoice as a shipping fee). Without it nothing is added, as before, and you can add the line yourself with Add a line to a shipment.
Returns the new packages: one per parcel of the rate, the first one the master, each with its tracking number, cost (charges, in thousandths, repeated on every package of a multi-parcel label) and a pdf entry whose url is Shippo's signed link to the label file: don't publish it. Next to data, shipping_cost_line_item says what happened to the shipping cost line.
Before you call: the shipment must belong to a published sales order (not a quote), and you should have shown the rate and its price to whoever approves the spend.
Retries: Never retry a label purchase automatically, whatever the error. Follow these steps instead:
- Check if the label was already bought. List the shipment's packages (
GET /v2/shipment/{shipment_id}/package). If a package with that carrier and service is present, the label was bought. Do not retry. - Check your Shippo account. If no package appears in YoPrint, a purchase might still have succeeded: when YoPrint buys a label in Shippo but encounters an error reading back or saving it, it returns HTTP 500 and queues a background refund request to Shippo. Check your Shippo dashboard to see whether a label or refund was created.
- When to stop (do not reuse the rate). Never repeat the purchase call with the same
rate_id. YoPrint holds only a temporary 10-second lock onrate_id. Once cleared or expired, repeating the call sends a new purchase to Shippo, risking a duplicate charge (or 422label_errorfor multi-parcel rates). - When to retry safely. If both YoPrint and Shippo confirm no label was purchased, request fresh shipping rates (
POST /v2/shipment/{shipment_id}/goshippo-rates), choose a rate, and buy using the newly issuedrate_id.
To avoid buying twice: send one purchase per shipment at a time and wait for its answer, however long it takes (give the call a client timeout of at least 120 seconds: a single-parcel purchase can take up to about 42 seconds and a multi-parcel one about 90); never resend while it is pending; after any error or timeout, list the shipment's packages and check your Shippo account before anything else; and if you do buy again, get new rates first.
Before you buy:
- Build the rate request from the shipment, its Ship To and its brand profile or company location (see Get shipping rates), and check the rate's
address_from,address_toandparcelsmatch the shipment. - Show the rate's carrier, service and
amountto a person and get their approval. AI agents must always ask. - Check the shipment has no package for this rate yet.
- Buy soon after rating. YoPrint doesn't record how long Shippo keeps a rate buyable; if the purchase is refused, rate again.
Money here: a rate's amount is a decimal string in currency ("12.40"); parse it as a decimal. After purchase, each package's charges is that amount cut to 3 decimal places, in thousandths, an older encoding (12400 is 12.40; usually a whole number of thousandths; a finer value comes back as a fractional number, or as an exact decimal string with X-Yoprint-Decimal-Format: exact-v1). On a multi-parcel label, every package repeats the rate's full amount: read the master package's value and don't add the packages up. See Money and quantities.
The order of calls:
- Optional: List Shippo carrier accounts, to limit rates to some of your accounts.
- Get shipping rates with the addresses and parcels. Note the top-level
object_id(the Shippo shipment) and the chosen rate'sobject_id(rates[].object_id). - Optional: Save a shipping estimate on the YoPrint shipment, to keep the quote.
- Buy a shipping label with the rate's
object_idasrate_id. This is the only step that costs money. - Print: Download labels as one PDF. Track: Get live tracking for a package.
Business rules
- Only for sales orders: a quote's shipment gets 422 "Shipping labels can't be bought for a quote. Convert it to a sales order first."
- The sales order must be published: otherwise 422 "No Permission - you cannot purchase goShippo label in an unpublished order/quote".
- FedEx allows at most 12 parcels per shipment: 422
goshippo_fedex_package_limit_exceeded. shipment_estimate_id, when sent, must be an estimate of this shipment: otherwise 422validation_error"The shipping estimate does not belong to this shipment.", checked before anything is bought.- The shipping cost line (with
create_shipping_cost_line_item: true) is added once per label: its cost is the master package'scharges(quantity 1, pricing mode none), and a second request for the same package returns the line already there. It is left out when the rate bills a third party (Shippoextra.billing.typeset to anything other thanSENDER), so the customer isn't charged twice. - A rate is not guaranteed to be bought only once. YoPrint holds a 10-second lock per
rate_idfrom the start of a purchase (cleared at once on success; after a failure it stays until 10 seconds after the start); a purchase can run longer than 10 seconds, because each of its Shippo calls may take up to 14. A repeat outside the lock goes to Shippo, which refuses a used multi-parcel rate (label_error); YoPrint has no record of what Shippo does with a reused single-parcel rate, so treat any repeat as a possible second charge. - On a shipment linked to Shopify: 422
shopify_managed_shipment_locked,shopify_fulfilled_shipment_package_locked("This shipment has already been fulfilled to Shopify. Void fulfillment first to edit packages or create labels.") orshopify_broken_link_shipment_locked; 422 "Shipment label cannot be created because shipment contains items fulfilled by a third-party."; and 422 when the carrier differs from the carrier of the shipment's other packages (Shopify needs one carrier per fulfillment).
Side effects
- Spends money SPENDS REAL MONEY: buys the label from Shippo, charged to your team's Shippo account. It can't be undone through the API.
- If YoPrint buys the label in Shippo but fails while reading back or saving it, the request returns HTTP 500 and YoPrint queues a background refund request to Shippo for that label (the refund's outcome is not reported).
- Creates one package per parcel on the shipment, with tracking number, carrier, service and cost.
- With
create_shipping_cost_line_item: true: adds the shipping cost line after packages are saved. Partial success: if saving the shipping cost line fails, the label and packages are still preserved and the call returns HTTP 200. Checkshipping_cost_line_item.status: iffailed, add the shipping cost line manually. - For an order from Shopify with tracking sync on, sends the tracking to Shopify, which may email the customer.
- YoPrint itself sends no email. Open Admin Portal pages refresh, and so do Customer Portal pages the order's customer has open.
Path parameters
Your team slug, shown as Team ID next to your API key in Profile.
- Example
your-team
- Example
The shipment's hash id (
id). Hash ids only: a unique key or a document number gives 404.- Example
MQyJUnd3oty75h
- Example
Request body
application/json, required.
The
rates[].object_idto buy, from Get shipping rates (or an estimate'sgoshippo_selected_rate_id).true to add a "Shipping Cost" line for the label to the shipment after the purchase. Default false: nothing is added.
- Default
false
- Default
shipment_estimate_idstring | nullOptional: the
idof one of this shipment's shipping estimates, to link the shipping cost line to it. It must belong to this shipment, even when you don't ask for the line.
Response
200Successful response.
One package per parcel of the rate: the master first, then the others (with
parent_id).Fields of each item (36)
chargesinteger | number | string | nullThe label cost. For a multi-parcel label every package repeats the rate's full amount: read the master's value and don't add packages up. Null for external labels. Thousandths, an older encoding:
12500is 12.50. Usually a whole number; an amount finer than a thousandth comes back as a number with a fraction (1234.5is 1.2345), or as a string ("1234.5") when you sendX-Yoprint-Decimal-Format: exact-v1. See Money and quantities.created_atstring (date-time)from_addressanyThe address record behind
from_address_id, or null when it is not set.from_address_idstring | nullOlder packages only: the ship-from address record; null otherwise.
goshippoobjectShippo labels only: the Shippo details (shipment, rate, carrier, addresses, parcels).
Child fields (13)
address_fromanyThe ship-from address sent to Shippo.
The return address sent to Shippo.
address_toanyThe ship-to address sent to Shippo.
carrier_idstring | nullThe Shippo carrier account.
parcel_idstring | nullThe Shippo parcel.
parcelsanyThe parcels of the Shippo shipment.
providerstring | nullprovider_image_200string | nullprovider_image_75string | nullprovider_tracking_numberstring | nullrate_idstring | nullThe Shippo rate bought.
The Shippo shipment the rate came from.
shipment_idstring | nullThe Shippo shipment.
heightnumber | string | nullParcel size and weight as stored, in
length_unitandweight_unit. Null for external labels.idstringThe package.
is_masterbooleanThe first package of a label (the others have
parent_id).lengthnumber | string | nulllength_unitstring | nullShippo labels only, upper case.
order_external_idsarray of objectsThe order's links to external systems.
order_external_typestring | nullshopifywhen the order came from Shopify.order_idstringThe order.
order_object_typestring | nullorder_quotebooleanorder_scoped_idstring | nullparent_idstring | nullThe master package, for the other parcels of a multi-parcel label.
pdfarray of objectsNot on external labels: the label file.
labelanddescriptionare fixed texts that always say 4 x 6, whatever your label size setting.Fields of each item (3)
descriptionstringlabelstringurlstringFor a Shippo label, Shippo's signed link to the label file: don't publish it.
provider_namestring | nullThe carrier.
provider_saturday_deliverybooleanShippo labels only (absent on external labels); always false today.
provider_service_codestring | nullFor external labels,
ups,fedexorother. For Shippo labels it may be null: read the service fromprovider_service_name.provider_service_namestring | nullprovider_shipment_idstring | nullThe Shippo transaction id (Shippo labels).
provider_tracking_numberstring | nullshipment_external_typestring | nullshopifywhen the shipment is linked to Shopify.shipment_idstringThe shipment.
shipment_scoped_idstringShopify sync state of the shipment.
statusstring- One of:
"pending","ready-to-ship","shipped","delivered"
- One of:
to_addressanyThe address record behind
to_address_id, or null when it is not set.to_address_idstring | nullOlder packages only: the ship-to address record; null otherwise.
typestringgoshippoa label bought through Shippo;external_labela label recorded from elsewhere.- One of:
"goshippo","external_label","single_label"
- One of:
weightnumber | string | nullweight_unitstring | nullShippo labels only, upper case.
widthnumber | string | null
What happened to the shipping cost line. Always present.
Child fields (2)
The shipment line's
idforcreatedandexisting; null otherwise.not_requestedwithoutcreate_shipping_cost_line_item: true;createdorexistingwhen the shipment has the line;skipped_third_party_billingwhen a third party pays for the label;failedwhen adding it failed (the label is still bought).- One of:
"not_requested","created","existing","skipped_third_party_billing","failed"
- One of:
Errors
401The API key is missing or wrong (
unauthenticated).403You are not allowed to do this.
api_access_not_in_plan: your team's plan doesn't include API access (Pro and above, with an active subscription or trial), so every request answers this until the team upgrades. When your role lacks the permission the operation needs, the body is only{"message": "Insufficient Permission"}. Other refusals use the error envelope, withunauthorizedoruser_is_not_authorized.- When
- Your role can't view this order (
user_is_not_authorized). It is checked before the quote, Shippo and publish checks, and nothing was bought. - Fix
- Ask the team owner for permission to view this order, or to be assigned to it.
404Not found.
page_not_found: the team slug is unknown, you are not a member of the team, or an id in the path is not one of this team's.code: null, with a message that starts "No query results for": there is no record with that id.- When
- The shipment isn't your team's.
- Fix
- Check the shipment id.
422The request was refused and changed nothing.
validation_failedlists what to fix inerror.fields; other codes name a business rule, explained inerror.message. A few checks answer with only{"message": "..."}. An operation that calls another service (a payment provider, a carrier, an accounting app) can answerconnect_timeout("Please retry again") when that service does not answer in time: the change may or may not have happened there, so read the record back before you retry.- When
rate_idis missing, orcreate_shipping_cost_line_itemisn't a boolean (validation_failed).- Fix
- Send the rate's
object_idasrate_id. - When
shipment_estimate_idisn't an estimate of this shipment (validation_error, "The shipping estimate does not belong to this shipment."). Nothing was bought.- Fix
- Send an estimate
idfrom List the shipping estimates of a shipment, or leave it out. - When
- Shippo already used this rate (
label_error: "Shippo has already used this rate for an earlier label attempt, so it can't be bought again. Get rates again and choose a rate..."). - Fix
- Check your Shippo account for a label from the earlier attempt before you buy another.
- When
- Shippo answered the purchase with a status other than
SUCCESS(label_error, with Shippo's message;erroralso carries Shippo'sobject_id,statusandmessages), for example an invalid address. - Fix
- Fix the cause, get rates again and buy the new rate.
- When
- A quote, an unpublished order, a Shopify or carrier conflict, or the FedEx parcel limit (see rules).
- Fix
- Resolve the named rule first.
- When
- Shippo isn't connected or is disabled (
goshippo_not_enabled). - Fix
- Connect Shippo in the web app first.
- When
- Another purchase of the same
rate_idstarted less than 10 seconds ago and hasn't succeeded: it is still running, or it failed (a failure keeps the lock until 10 seconds after that start).error.codeisvalidation_errorand the message is "Locking Token has Been Called: " followed by your request'slocking_tokenfield, if you sent one (nothing otherwise). - Fix
- Wait, then list the shipment's packages: the first attempt may have bought the label.
500A business rule refused the request (see the operation's errors), or something failed inside YoPrint. Refusals use the error envelope with a code such as
action_not_allowed; an unexpected failure answers{"message": "Server Error"}. Repeating a refusal gets the same answer; after an unexpected failure, read the record back before you retry a change.- When
- Shippo failed or timed out mid-purchase, Shippo refused the purchase call itself (a 4xx other than an already used multi-parcel rate), or YoPrint couldn't read back or save a bought label (it then asks Shippo for a refund in the background). A label may or may not have been bought.
- Fix
- Don't retry. List the shipment's packages and check your Shippo account first.
Notes
Never retry a label purchase automatically, whatever the error. Follow these steps instead:
- Check if the label was already bought. List the shipment's packages (
GET /v2/shipment/{shipment_id}/package). If a package with that carrier and service is present, the label was bought. Do not retry. - Check your Shippo account. If no package appears in YoPrint, a purchase might still have succeeded: when YoPrint buys a label in Shippo but encounters an error reading back or saving it, it returns HTTP 500 and queues a background refund request to Shippo. Check your Shippo dashboard to see whether a label or refund was created.
- When to stop (do not reuse the rate). Never repeat the purchase call with the same
rate_id. YoPrint holds only a temporary 10-second lock onrate_id. Once cleared or expired, repeating the call sends a new purchase to Shippo, risking a duplicate charge (or 422label_errorfor multi-parcel rates). - When to retry safely. If both YoPrint and Shippo confirm no label was purchased, request fresh shipping rates (
POST /v2/shipment/{shipment_id}/goshippo-rates), choose a rate, and buy using the newly issuedrate_id.
- Check if the label was already bought. List the shipment's packages (
- To avoid buying twice: send one purchase per shipment at a time and wait for its answer, however long it takes (give the call a client timeout of at least 120 seconds: a single-parcel purchase can take up to about 42 seconds and a multi-parcel one about 90); never resend while it is pending; after any error or timeout, list the shipment's packages and check your Shippo account before anything else; and if you do buy again, get new rates first.
- You can't buy only some parcels of a multi-parcel rate.
- Read
shipping_cost_line_item.status:created(the line was added;shipment_item_idis itsid),existing(the package already had one),not_requested,skipped_third_party_billing, orfailed. After a lost answer, adding the line again with Add a line to a shipment and the sameshipment_package_idreturns the line if it already exists. - To void a label, delete its package: YoPrint then asks Shippo for a refund in the background. Whether you get the money back is up to Shippo and the carrier, and a refused refund is not reported back.
- AI agents: always ask a person to confirm the rate and price before calling this.
Related
- POSTGet shipping rates
/v2/ api/ store/ {team_slug}/ v2/ shipment_rate/ goshippo - GETList the packages of a shipment
/v2/ api/ store/ {team_slug}/ v2/ shipment/ {hash_id}/ packages - POSTDownload labels as one PDF
/v2/ api/ store/ {team_slug}/ v2/ shipment_package/ labels/ compile - DELETEDelete a package (voids its label)
/v2/ api/ store/ {team_slug}/ v2/ shipment_package/ {hash_id} - POSTAdd a line to a shipment
/v2/ api/ store/ {team_slug}/ v2/ shipment/ {unique_key}/ item
