Skip to main content

Purchase a shipping label

Apps can purchase Shopify Shipping labels for fulfillment orders on behalf of merchants. For example, an order management app might automate label purchases as soon as an order is ready to ship.

This guide shows how to use the GraphQL Admin API to purchase a shipping label for a fulfillment order, poll the asynchronous result, and retrieve the purchased label.

The shippingLabelPurchase mutation buys one shipping label for one fulfillment order. The mutation validates the fulfillment order, shipment, package, and available rates, and then starts an asynchronous purchase. You use the returned ShippingLabelPurchaseResult to track the purchase and retrieve the label.


  • Your app uses Admin API version 2026-07 or higher.
  • Your app can make authenticated requests to the GraphQL Admin API.
  • Your app has the write_orders access scope, along with a fulfillment order write scope that matches where the fulfillment order is assigned:
    • write_merchant_managed_fulfillment_orders for fulfillment orders assigned to merchant-managed locations.
    • write_assigned_fulfillment_orders for fulfillment orders assigned to a fulfillment service that your app manages.
    • write_third_party_fulfillment_orders for fulfillment orders assigned to another fulfillment service.
  • If a user is authenticated with the request, then the user has the buy_shipping_labels permission (a Shopify admin staff permission, separate from the access scopes above).
  • The store has accepted the Shopify Shipping terms of service.
  • You've met Shopify's protected customer data requirements.
Note

The shippingLabelPurchase mutation doesn't support FedEx label purchases.


Anchor to Step 1: Retrieve an eligible fulfillment orderStep 1: Retrieve an eligible fulfillment order

You can purchase a shipping label only for a fulfillment order that's fulfillable, requires shipping, has a shipping destination, and has an assigned location.

Query the order's fulfillmentOrders to find an eligible fulfillment order and its ID. The following example requests the fulfillment order's status, assigned location, and destination.

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

GraphQL query

query {
order(id: "gid://shopify/Order/1234") {
fulfillmentOrders(first: 1) {
nodes {
id
status
requestStatus
assignedLocation {
name
}
destination {
city
countryCode
}
}
}
}
}

JSON response

{
"data": {
"order": {
"fulfillmentOrders": {
"nodes": [
{
"id": "gid://shopify/FulfillmentOrder/5678",
"status": "OPEN",
"requestStatus": "UNSUBMITTED",
"assignedLocation": {
"name": "Main warehouse"
},
"destination": {
"city": "Ottawa",
"countryCode": "CA"
}
}
]
}
}
}
}

Anchor to Step 2: Purchase a shipping labelStep 2: Purchase a shipping label

Pass the shipment details to the shippingLabelPurchase mutation in a ShippingLabelPurchaseInput. Only fulfillmentOrderId and shippingDatetime are required. You can optionally provide package details, total weight, and a preferred rate.

The mutation returns errors in two places:

  • Synchronous validation errors appear in the mutation's userErrors field. Some errors include a code that identifies the failure, such as FULFILLMENT_ORDER_INVALID or RATES_NOT_FOUND, but code can be null. Use the message and field to handle errors that don't return a code.
  • Asynchronous purchase-processing errors appear in the ShippingLabelPurchaseResult errors field. You retrieve these when you poll the purchase result.

If userErrors is empty, then the purchase has started. The result begins with a status of PENDING_PURCHASE.

The following example purchases a label using a custom package. Because preferredRateSelection is omitted, the mutation uses Shopify Shipping's default rate selection.

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

GraphQL mutation

mutation {
shippingLabelPurchase(shippingLabelPurchase: {
fulfillmentOrderId: "gid://shopify/FulfillmentOrder/5678",
shippingDatetime: "2026-09-20T09:00:00Z",
totalWeight: {
value: 2.5,
unit: POUNDS
},
packageInfo: {
customPackage: {
weight: {
value: 3.0,
unit: OUNCES
},
dimensions: {
length: 12.0,
width: 9.0,
height: 3.0,
unit: INCHES
},
type: BOX
}
},
notifyCustomer: true
}) {
shippingLabelPurchaseResult {
id
status
}
userErrors {
field
code
message
}
}
}

JSON response

{
"data": {
"shippingLabelPurchase": {
"shippingLabelPurchaseResult": {
"id": "gid://shopify/ShippingLabelPurchaseResult/9012",
"status": "PENDING_PURCHASE"
},
"userErrors": []
}
}
}

If you provide packageInfo, then include exactly one of customPackage or carrierPackage. If you omit packageInfo, then Shopify uses the package assigned to the shipment. If you omit totalWeight, then Shopify calculates the total weight from the package and the fulfillable line items.

Set shippingDatetime to a date and time in the future. The mutation returns a SHIPPING_DATE_IN_THE_PAST error for a past value.

Note

For international shipments, the order must already include the required customs information, such as an HS code and a country of origin for each item. The mutation uses the customs information from the order, so you don't provide it in the input.

Anchor to Select a preferred rateSelect a preferred rate

To purchase a label with a specific carrier and service, include preferredRateSelection with a carrierCode and serviceCode. The serviceCode must exactly match a service code returned for the shipment's rates. If the selection doesn't match an available rate, then the mutation returns a RATES_NOT_FOUND error. The following values are examples.

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

GraphQL mutation

mutation {
shippingLabelPurchase(shippingLabelPurchase: {
fulfillmentOrderId: "gid://shopify/FulfillmentOrder/5678",
shippingDatetime: "2026-09-20T09:00:00Z",
preferredRateSelection: {
carrierCode: "ups_shipping",
serviceCode: "03"
}
}) {
shippingLabelPurchaseResult {
id
status
}
userErrors {
field
code
message
}
}
}

JSON response

{
"data": {
"shippingLabelPurchase": {
"shippingLabelPurchaseResult": {
"id": "gid://shopify/ShippingLabelPurchaseResult/9012",
"status": "PENDING_PURCHASE"
},
"userErrors": []
}
}
}

Anchor to Step 3: Poll the purchase resultStep 3: Poll the purchase result

Shopify purchases the label asynchronously, so the result doesn't contain the label right away. Use the ShippingLabelPurchaseResult ID from Step 2 to poll the result with the node query until the status field is PURCHASED or PURCHASE_FAILED.

Most purchases finish within a few seconds. Carrier processing and retries can occasionally take longer, so set an overall timeout, such as 60 seconds, instead of polling indefinitely.

If the purchase fails, then the errors field describes what went wrong.

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

GraphQL query

query {
node(id: "gid://shopify/ShippingLabelPurchaseResult/9012") {
... on ShippingLabelPurchaseResult {
status
errors {
code
message
}
}
}
}

JSON response

{
"data": {
"node": {
"status": "PURCHASE_FAILED",
"errors": [
{
"code": "CARRIER_NOT_AVAILABLE",
"message": "The carrier is not available for this label. Please try another carrier."
}
]
}
}
}

Anchor to Step 4: Retrieve the purchased labelStep 4: Retrieve the purchased label

When the status is PURCHASED, the shippingLabels field contains the purchased label. Query it to retrieve the label's tracking information and printable documents.

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

GraphQL query

query {
node(id: "gid://shopify/ShippingLabelPurchaseResult/9012") {
... on ShippingLabelPurchaseResult {
status
shippingLabels {
id
cancellable
printed
trackingInfo {
number
company
url
}
shippingDocuments {
documentType
format
url
}
}
}
}
}

JSON response

{
"data": {
"node": {
"status": "PURCHASED",
"shippingLabels": [
{
"id": "gid://shopify/ShippingLabel/3456",
"cancellable": true,
"printed": false,
"trackingInfo": {
"number": "1Z999AA10123456784",
"company": "UPS",
"url": "https://www.ups.com/track?tracknum=1Z999AA10123456784"
},
"shippingDocuments": [
{
"documentType": "LABEL",
"format": "PDF",
"url": "https://cdn.shopify.com/shipping-labels/3456.pdf"
}
]
}
]
}
}
}

Use the url on each ShippingDocument to print the label and any required customs forms.



Was this page helpful?