GetFulfillmentOrder fetches one fulfillment order, whatever queue it is or is not in. For troubleshooting and for reconciling after a crash.
You call thissales:orders:fulfillment:self
const url = 'https://api.zentail.com/v2/shipping/fulfillment_order';const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url https://api.zentail.com/v2/shipping/fulfillment_order \ --header 'Authorization: Bearer <token>'Pass exactly one of fulfillment_order_id or external_order_id. This is a point lookup, not a queue: an order it returns may still owe a shipment, and only ListNeedToShip says so. Polling it in place of the queues tells you what exists, never what Zentail wants done.
Authorizations
Section titled “Authorizations”Parameters
Section titled “ Parameters ”Query Parameters
Section titled “Query Parameters”Zentail’s id, as it appears on any fulfillment order you have been given.
The id you recorded with AcknowledgeFulfillmentOrders. Unique within your integration, so it resolves to exactly one fulfillment order — which is what makes it usable for reconciling after a crash.
Responses
Section titled “ Responses ”A successful response.
FulfillmentOrder is a lean view of the work: only the lines routed to one of the caller’s warehouses, with the quantities routed there.
object
Zentail’s identifier for this unit of work, and the handle for every write in this API. Stable for the life of the fulfillment order.
The customer order this came from. For display and correlation only — it is not unique to you, and is not accepted as an identifier on any write.
Set once you have acknowledged it.
Status is informational. Do not drive behaviour from it — the queues already say what Zentail wants done, and a status left over from a previous poll is how an integration ends up shipping work it no longer owes.
- FULFILLMENT_ORDER_STATUS_UNSPECIFIED: Not a state Zentail sends. Read it as a status you cannot interpret.
- FULFILLMENT_ORDER_STATUS_NEW: Routed to you, not yet acknowledged.
- FULFILLMENT_ORDER_STATUS_ACCEPTED: Acknowledged and awaiting shipment.
- FULFILLMENT_ORDER_STATUS_PARTIALLY_SHIPPED: Some units shipped and some are still owed; the remainder stays in ListNeedToShip.
- FULFILLMENT_ORDER_STATUS_SHIPPED: Every routed unit shipped. Nothing further is owed.
- FULFILLMENT_ORDER_STATUS_CANCELLED: Zentail pulled the whole thing back.
- FULFILLMENT_ORDER_STATUS_PARTIALLY_CANCELLED: Part was pulled back. Ship whatever ListNeedToShip still shows.
- FULFILLMENT_ORDER_STATUS_REJECTED: You declined it. Zentail reroutes or surfaces it.
The warehouse these lines are routed to, in your own namespace.
When the buyer placed the customer order — not when the work reached you. Age your own queue on assigned_ts instead; an order can be routed to a warehouse days after it was placed.
When this work was routed to your warehouse.
When Zentail last changed anything here, a re-route included. It tells you
a cached copy is stale; it does not say what changed, so re-read lines
rather than diffing on it.
Deadline for handing the package to the carrier. Not populated: Zentail has no ship-by column to read, and will not approximate one from another timestamp because a fabricated deadline is worse than none to a fulfiller that prioritises on it. Always unset today — measured against sales-orders on 2026-09-21. Prioritise on assigned_ts.
Address is the buyer’s shipping address exactly as the sales channel supplied it. Zentail copies it through without normalising, validating or completing any part of it, so treat every field as free text — in particular country and region are not guaranteed to be ISO codes.
Validate it against your own carrier’s rules and reject with UNDELIVERABLE_ADDRESS rather than shipping to a guess. An address that was never captured, or that has been redacted under the PII retention policy, is absent from the fulfillment order entirely rather than arriving half-filled.
object
Who the package is addressed to. Not always the buyer — see FulfillmentOrder.buyer_name.
Business name at the delivery address, when the channel captured one.
Street address. The line that is present whenever there is an address at all.
Apartment, suite, unit or similar. Commonly empty.
Town or city, as supplied. Free text, and empty when the channel sent none.
State, province or county, as supplied. Sometimes a code and sometimes a full name, because it is whatever the channel sent — do not key on it.
ZIP or postal code, as supplied. Not validated, and not checked against the country.
Country, as supplied. Usually a two-letter ISO code but not guaranteed to be one, so anything parsing it needs a fallback.
Contact number for the carrier, when the channel captured one. Not normalised to E.164 and may carry extensions or punctuation.
The buyer’s email address, for carrier notifications about this delivery. It hangs off the address rather than the order because the two are PII under one retention policy: a redacted order carries neither.
The shipping speed the buyer bought, as Zentail’s standardised service level for the order. Empty when the channel named none. Map it onto one of your own carrier services; it does not name a carrier.
The buyer, for packing slips and support. Not the addressee: ship_to.name is who the label goes to and legitimately differs on a gift or a business delivery. Address the package from ship_to.
Not populated. Zentail has no gift-message column to read one from, so this is always empty and is not evidence that the order carries no gift message. Measured against sales-orders on 2026-09-21.
Authoritative on every read. Quantities change when Zentail re-routes work in or out of this warehouse, so never cache them across polls.
object
Stable per line; echo it back on shipment, cancellation and rejection.
Zentail’s SKU for the unit to pick. Report work by line_item_id rather than by this: one SKU can appear on two lines of the same order.
The product name, for packing slips and pick lists. Display only — it can change between polls and identifies nothing.
Routed to your warehouse, not the customer order’s total.
Always zero, deliberately. quantity is already only what you still owe,
so subtracting this would double-count what has shipped. Your own
ConfirmShipments calls are the record of shipments, not this field.
Measured against sales-orders on 2026-09-21.
Always zero, on the same terms as shipped_quantity.
Money is an amount and the currency it is in.
The amount is a decimal string, not a float or units+nanos. Money in this contract crosses into Shopify’s MoneyBagInput, which is itself a decimal string, so a string maps across untouched — no scaling and no rounding at the edge, which is where money bugs come from. Follows the Money already in api-proto’s listing contract rather than google.type.Money, for that reason.
currency_code travels with every amount rather than sitting once on the order. Multi-currency is real (etp has purchase_orders.non_standard_currencies), and a silently wrong currency is worse than a wrong amount: the number still looks plausible.
object
Decimal string, e.g. “13.00”. Negative for a credit.
ISO 4217, e.g. “USD”.
Money is an amount and the currency it is in.
The amount is a decimal string, not a float or units+nanos. Money in this contract crosses into Shopify’s MoneyBagInput, which is itself a decimal string, so a string maps across untouched — no scaling and no rounding at the edge, which is where money bugs come from. Follows the Money already in api-proto’s listing contract rather than google.type.Money, for that reason.
currency_code travels with every amount rather than sitting once on the order. Multi-currency is real (etp has purchase_orders.non_standard_currencies), and a silently wrong currency is worse than a wrong amount: the number still looks plausible.
object
Decimal string, e.g. “13.00”. Negative for a credit.
ISO 4217, e.g. “USD”.
Alerts you currently have open on this fulfillment order. Returned so a poller can see what it has already raised without keeping its own record — the same reason Zentail holds external_order_id.
object
The fulfillment order the problem is on. An alert is always about one piece of work; there is no integration-wide alert — IntegrationStatus is where that belongs.
AlertType maps to Zentail’s customer-order alert types. Only the types a fulfillment integration can legitimately raise are exposed.
- ALERT_TYPE_UNSPECIFIED: Refused. Every alert names a type.
- ALERT_TYPE_FULFILL: Something is blocking fulfilment but you have not given up on it. If you have, reject instead.
- ALERT_TYPE_LATE_SHIPMENT: Will miss, or has missed, the ship-by deadline.
- ALERT_TYPE_LOST: Shipped but the package is lost in transit.
- ALERT_TYPE_OTHER: Anything else worth telling a user about. Put the specifics in
message— it is the only thing that reaches a person.
Shown to the user. Say what is wrong and what would fix it.
Optional: scope the alert to one line.
Money is an amount and the currency it is in.
The amount is a decimal string, not a float or units+nanos. Money in this contract crosses into Shopify’s MoneyBagInput, which is itself a decimal string, so a string maps across untouched — no scaling and no rounding at the edge, which is where money bugs come from. Follows the Money already in api-proto’s listing contract rather than google.type.Money, for that reason.
currency_code travels with every amount rather than sitting once on the order. Multi-currency is real (etp has purchase_orders.non_standard_currencies), and a silently wrong currency is worse than a wrong amount: the number still looks plausible.
object
Decimal string, e.g. “13.00”. Negative for a credit.
ISO 4217, e.g. “USD”.
Money is an amount and the currency it is in.
The amount is a decimal string, not a float or units+nanos. Money in this contract crosses into Shopify’s MoneyBagInput, which is itself a decimal string, so a string maps across untouched — no scaling and no rounding at the edge, which is where money bugs come from. Follows the Money already in api-proto’s listing contract rather than google.type.Money, for that reason.
currency_code travels with every amount rather than sitting once on the order. Multi-currency is real (etp has purchase_orders.non_standard_currencies), and a silently wrong currency is worse than a wrong amount: the number still looks plausible.
object
Decimal string, e.g. “13.00”. Negative for a credit.
ISO 4217, e.g. “USD”.
Money is an amount and the currency it is in.
The amount is a decimal string, not a float or units+nanos. Money in this contract crosses into Shopify’s MoneyBagInput, which is itself a decimal string, so a string maps across untouched — no scaling and no rounding at the edge, which is where money bugs come from. Follows the Money already in api-proto’s listing contract rather than google.type.Money, for that reason.
currency_code travels with every amount rather than sitting once on the order. Multi-currency is real (etp has purchase_orders.non_standard_currencies), and a silently wrong currency is worse than a wrong amount: the number still looks plausible.
object
Decimal string, e.g. “13.00”. Negative for a credit.
ISO 4217, e.g. “USD”.
Money is an amount and the currency it is in.
The amount is a decimal string, not a float or units+nanos. Money in this contract crosses into Shopify’s MoneyBagInput, which is itself a decimal string, so a string maps across untouched — no scaling and no rounding at the edge, which is where money bugs come from. Follows the Money already in api-proto’s listing contract rather than google.type.Money, for that reason.
currency_code travels with every amount rather than sitting once on the order. Multi-currency is real (etp has purchase_orders.non_standard_currencies), and a silently wrong currency is worse than a wrong amount: the number still looks plausible.
object
Decimal string, e.g. “13.00”. Negative for a credit.
ISO 4217, e.g. “USD”.
Example
{ "status": "FULFILLMENT_ORDER_STATUS_UNSPECIFIED", "openAlerts": [ { "type": "ALERT_TYPE_UNSPECIFIED" } ]}default
Section titled “default”An unexpected error response.
object
object
Examplegenerated
{ "code": 1, "message": "example", "details": [ { "@type": "example" } ]}