Shopify returns API: separate approval, inventory, and refunds
A state-first Shopify returns API playbook for requests, approval, reverse logistics, item disposition, refunds, webhooks, and human review.
The Shopify returns API is not one refund endpoint. It is a set of GraphQL objects, queries, mutations, and webhooks for a return’s business intent, reverse logistics, item disposition, exchange work, and financial outcome.
For a lean team, the safest design is a small state machine. Keep the customer’s request, the merchant’s decision, the warehouse result, the refund transaction, and the customer message separate. That structure fits the current Shopify return management workflow and makes failures easier to recover.
This is a source-reviewed playbook, not a KumoCart hands-on test. Confirm the current schema and permissions in the API version used by your app.
Pick the entry path before writing code
There are two useful starting paths.
| Situation | Mutation | First status | Human checkpoint |
|---|---|---|---|
| A customer asks to return an item and the merchant must review it | returnRequest | REQUESTED | Approve or decline against the current return policy |
| A merchant or external returns system has already approved the request | returnCreate | OPEN | Record who approved it and why before the mutation runs |
Shopify documents returnRequest for requests that need merchant approval. A requested return cannot simply be canceled. It must be approved or declined, and a decline is permanent.
Use returnCreate only when approval has already happened outside Shopify. Shopify says it creates an OPEN return, a reverse fulfillment order, and the basis for a reverse delivery. Do not use it as a shortcut around policy review.
Define a concrete input and output contract
Before calling the API, collect the minimum facts needed to make a valid decision.
Inputs
- Shopify order global ID;
- eligible fulfillment line item ID, not only the order line item or SKU;
- quantity requested;
- return reason and customer note;
- whether approval is still required;
- proposed shipping or restocking fees, when allowed by policy;
- intended refund method and amount, after a financial calculation;
- inspection and disposition decision when the item is received;
- whether Shopify should notify the customer.
Outputs to persist
- return ID, name, and status;
- mutation
userErrors, including field and code when available; - reverse fulfillment and delivery identifiers;
- returned and exchanged quantities;
- disposition and inventory location;
- refund ID plus associated transaction status;
- the last verified customer-facing status and message time.
The Return object needs read_returns or the marketplace equivalent. Write mutations need the corresponding write scope. Keep the app’s permissions as narrow as the workflow permits.
Run the lifecycle in six controlled steps
1. Query eligibility
Call returnableFulfillments with the order ID. Shopify returns delivered fulfillment records and the fulfillment line items available to return. This prevents a common integration mistake: sending an order line ID where the mutation needs a fulfillment line item ID.
Treat an empty result as a review state. The order might be unfulfilled, already refunded, already returned, or represented by a different fulfillment. Do not manufacture a returnable quantity.
2. Preview the financial effect
Use returnCalculate before creating the return when the workflow needs a financial preview. After a return exists, read suggestedFinancialOutcome. Keep shop currency, presentment currency, shipping, duties, discounts, fees, and payment method constraints visible.
This calculation is an input to a decision. It is not authorization to move money.
3. Request or open the return
Use returnRequest for merchant review. Use returnCreate for an externally approved return. Persist the returned ID and status, then consume userErrors as business outcomes rather than logging them as unstructured text.
The human decision record should include the policy version, approver, reason, and any exception. That record matters when a customer later asks why one item was accepted and another was not.
4. Track reverse logistics
Create or update the reverse delivery with the carrier, tracking number, label, and destination that the operation actually uses. Keep carrier state separate from return state. A label created does not mean a parcel was handed over, received, inspected, or restocked.
For broader automation boundaries, use the control model in How to automate a Shopify store.
5. Inspect, process, and reconcile
The current returnProcess mutation confirms returned and exchanged quantities, records dispositions, creates fulfillment orders for exchange items, and can optionally issue refunds. Shopify’s July 2025 changelog says this path replaced the deprecated returnRefund workflow from API version 2025-07.
Use the warehouse result to choose restock, disposal, or another supported disposition. Keep this as a human checkpoint when condition is ambiguous, the item is regulated, or an inventory override could make an unsellable item available.
Do not equate a refund record with completed money movement. Shopify’s Refund object documentation says the associated order transactions can still be pending, processing, successful, or failed. Verify transaction status before promising that funds have arrived.
6. Close only when the work is complete
Shopify can close a return automatically after all items have been processed and a disposition decision exists for each item. returnClose can also close it explicitly. An explicit close should not conceal an unresolved refund, exchange fulfillment, carrier exception, or inventory decision.
Store separate completion timestamps for the return, the refund transaction, the warehouse inspection, and the exchange fulfillment. A single completedAt field loses the information needed for reconciliation.
Example decision ledger
Suppose order #1042 contains two fulfilled variants and the customer asks to return one because the size is wrong.
| Event | Input | Recorded output | Next control |
|---|---|---|---|
| Customer submits request | Order ID, fulfillment line item ID, quantity 1, reason, note | Return ID and REQUESTED | Merchant reviews policy and item eligibility |
| Merchant approves | Return ID and approval | Status becomes OPEN | Create or attach reverse delivery details |
| Warehouse receives parcel | Return line item ID, received quantity, inspection note | Condition and proposed disposition | Human confirms restock or disposal |
| Finance processes return | Return ID, processed quantity, disposition, reviewed refund input | Return result, refund record, transaction state | Check transaction and exchange fulfillment |
| Customer gets update | Verified return and transaction state | Message template version and sent time | Escalate if states conflict |
This ledger is deliberately boring. It gives one operator enough context to repair the workflow without guessing what an earlier automation intended.
Failure modes to design before launch
Wrong identifier type: An order line item, fulfillment line item, return line item, and exchange line item are different resources. Validate the GraphQL global ID type at each boundary.
Approval skipped: returnCreate assumes approval. Require a recorded approval before an automation can call it.
Permanent decline used casually: A declined requested return cannot return to REQUESTED or APPROVED. Present the policy evidence and require confirmation.
Refund status inferred from return status: A return can describe operational state while the payment transaction has a different state. Reconcile both.
Inventory restored before inspection: Receiving a parcel does not prove an item is sellable. Keep disposition under warehouse control.
Deprecated mutation retained: Search the integration for returnRefund, suggestedReturnRefund, and suggestedRefund. Review the return processing migration before changing live traffic.
Partial work treated as complete: A return can contain multiple quantities and exchange items. Check processed quantities and all dispositions before closure.
Webhook coverage too narrow: Subscribe to the lifecycle events the integration uses, including request, approval, decline, process, cancel, close, reopen, update, refunds, and reverse delivery changes. Re-fetch the canonical object after an event instead of treating the webhook payload as the entire record.
Duplicate or out-of-order events: Make handlers safe to retry. Store event identifiers and observed object versions or timestamps, then ignore work already applied.
Test matrix for a development store
Do not use evidenceLevel: tested for this article. The cases below are a proposed test plan, not reported KumoCart results.
- one item from one fulfillment, approved and refunded;
- partial quantity from a multi-quantity line;
- items from two fulfillment locations;
- requested return approved after review;
- requested return declined with a policy reason;
- exchange with no immediate refund;
- damaged item sent to disposal rather than available inventory;
- refund transaction pending or failed after the return was processed;
- duplicate
returns/processwebhook delivery; - webhook delivery arriving after an operator already changed the return;
- cancellation before processing and attempted cancellation after a disposition;
- deprecated mutation removed while current API version behavior remains verified.
For each case, record the input IDs, GraphQL operation, response, user errors, webhook sequence, admin state, inventory effect, transaction state, customer message, and cleanup steps.
Keep the customer interface legible
Independent Baymard self-service research finds that returns are a high-friction part of account management. The API can expose detailed state, but a customer needs plain answers: whether the request is awaiting review, whether a parcel is in transit, whether the item passed inspection, what resolution was approved, and what happens next.
Do not expose internal enum names as the whole explanation. Map each verified state to a short message, expected next action, and escalation route. Never promise a refund date from an OPEN or CLOSED return alone.
Limits of this playbook
This article does not cover every exchange pricing case, carrier integration, duty refund, marketplace permission, or Customer Account API surface. It does not claim that a specific Shopify plan, payment provider, or returns app supports every path.
The current GraphQL Admin API reference showed version 2026-07 when reviewed on 2026-08-01. Pin an API version, monitor the Shopify developer changelog, and repeat the development-store matrix before upgrading. Keep humans in control of money movement, policy exceptions, inventory overrides, permissions, and customer promises.
Frequently asked questions
Is the Shopify returns API a REST API?
Shopify's current return management workflow is documented through the GraphQL Admin API, using return, fulfillment, processing, and refund objects and mutations.
What is the difference between returnRequest and returnCreate?
returnRequest creates a REQUESTED return for merchant review. returnCreate creates an OPEN return and assumes approval already happened outside Shopify.
Does a closed return prove the customer received a refund?
No. Return status, refund records, and payment transaction status are separate. Verify the associated transaction before making a customer promise.
Sources
- Build for return managementShopify Developers · official · Aug 1, 2026
- Return object in the GraphQL Admin APIShopify Developers · official · Aug 1, 2026
- returnRequest mutationShopify Developers · official · Aug 1, 2026
- returnProcess mutationShopify Developers · official · Aug 1, 2026
- Returns Processing APIs replaces Return Refund APIsShopify Developers · official · Aug 1, 2026
- Refund object in the GraphQL Admin APIShopify Developers · official · Aug 1, 2026
- Accounts and Self-Service UX ResearchBaymard Institute · research · Aug 1, 2026
Change log
- First edition reviewed against the current Shopify 2026-07 GraphQL Admin API and return processing migration guidance.