Programmatic Returns API: How to Handle Refunds (2026)
Automate returns in code instead of support tickets. Compare Zinc, Loop, Rye, and OpenReturn, then wire retailer-side refunds.
You can automate checkout and still get stuck the moment a shopper wants the item back. A programmatic returns API creates, tracks, and settles that return in code instead of a support ticket. Merchant tools such as Loop, Rye, and Shopify's Customer Account API do this for a store you own. They do not file returns at Amazon, Walmart, or Target for orders your app placed there.
- What a programmatic returns API is
- The return lifecycle every implementation follows
- How Zinc, Rye, Loop, Shopify, and OpenReturn compare
- Merchant APIs vs returns for retailer-placed orders
- How to add returns to your app
- FAQ
For a vendor shootout with pricing and decision rules, use Best Returns Management Software for Ecommerce. Below: the API lifecycle and how the major providers compare.
What is a programmatic returns API?
A programmatic returns API lets software create, track, and resolve product returns without a human filing a support ticket. It checks eligibility, opens a return authorization, issues a shipping label when the item has to come back, tracks the inbound package, and triggers a refund or exchange.
The bar is the same as a purchasing API: your system should create a return, read its status, and get a structured result, without anyone clicking through a portal.
Two different jobs get mixed under this phrase:
| Job | Who you are | What the API talks to |
|---|---|---|
| Merchant returns | You own the storefront | Your OMS, warehouse, and payment gateway |
| Retailer-placed returns | You bought the item at Amazon, Walmart, Target, or another retailer on a user's behalf | The retailer's return flow, through the same execution layer that placed the order |
Most public docs assume the first job. Zinc's Create Return endpoint is the second: it only applies to orders Zinc placed.
Return lifecycle (API states)
Almost every returns implementation follows the same sequence, with different endpoint names:
- Requested. The shopper or your app starts a return with an order ID, line items, and a reason.
- Eligibility check. Is the item within the return window, not final sale, and actually delivered.
- Approved or denied. Automatic for common cases, manual review for edge cases.
- Label issued (if required). A prepaid return label, only when the merchant needs the item back.
- Shipped and received. Carrier tracking as the package moves to a warehouse.
- Inspected. Condition check decides restock, refurbish, or discard.
- Resolved. Refund, exchange, or store credit, settled and reported back.
Rye's Returns API collapses that into a small set of states a developer has to branch on: requested, requires_action (hand the shopper a label), processing, then terminal refunded, denied, or failed. Rye also notes a merchant can approve and refund in one motion, so a return can jump from requested to refunded with nothing in between. Your state machine has to handle that skip, not only the happy path.
ResReturn's integration guide frames the same lifecycle as the API surface you should evaluate: create an RMA, webhook events for status changes, label generation, and refund vs exchange as distinct operations. It also calls out idempotency keys on mutating endpoints so a retried webhook cannot refund the same RMA twice.
The data model most APIs converge on
| Field | Type | Description |
|---|---|---|
return_id | string | Unique identifier for the return |
order_id | string | Foreign key to the original order |
status | enum | Requested, approved, shipped, received, refunded, denied, or the vendor's equivalent |
items | array | SKUs or line IDs, quantities, and per-item reasons |
refund_amount | decimal | Amount owed back, when the vendor exposes it |
label_url / label_urls | string or array | Prepaid return label, if one was issued |
timeline | object | Timestamps for state transitions, when the vendor provides them |
You do not need every verb Loop or AfterShip expose. You need a create call, a status read, and a way to hand the shopper a label when one exists.
How the major returns APIs compare
Guides on programmatic returns currently point at Rye, OpenReturn, ReturnLogic, and ResReturn. They split into merchant platforms (you own the store) and one execution layer (you placed the order at a retailer).
| Provider | Model | Platforms | Complexity |
|---|---|---|---|
| Zinc | Create + webhooks (poll for labels) | Amazon, Walmart, Target, Best Buy, and 50+ other retailers | Low. One POST /returns, then return.* webhooks or GET for status and labels |
| Rye | Single create + poll | Shopify merchants, whole-order returns in the first release | Low. One POST, one GET, typed states |
| Loop Returns | Multi-step draft return | Shopify | High. Five-phase workflow with a cart for exchanges |
| Shopify Customer Account API | Multi-step GraphQL | Shopify only | High. Eligibility, line items, calculated amount, then orderRequestReturn |
| ReturnLogic | REST + webhooks | Shopify and BigCommerce | Medium. Built to sync returns into ERPs |
| OpenReturn | Open spec + MCP | Shopify, WooCommerce, Magento, BigCommerce (self-hosted) | Self-hosted, labeled in development |
For the longer vendor list (Narvar, Happy Returns, ReturnGO, AfterShip, Redo, ChannelEngine), see Best Returns Management Software.
Zinc: returns for orders you placed at retailers
Zinc is the odd row on purpose. Every other platform assumes a merchant with a storefront. Zinc places orders at Amazon, Walmart, Target, Best Buy, and 50+ other retailers on a user's behalf. Returns follow the same execution model: your app does not talk to the retailer. Zinc does.
The order must already be in order_placed status. You create the return against Zinc's order UUID and the line-item UUIDs from that order:
POST https://api.zinc.com/returns
{
"order_id": "8c2d4e1a-7b90-4c11-9f22-0a1b2c3d4e5f",
"items": [
{ "order_item_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "quantity": 1 }
],
"reason": "defective",
"notes": "Power button does not respond."
}Reason codes are an enum: damaged, not_delivered, empty_box, wrong_item, defective, not_as_described, wrong_size, no_longer_needed, forced_cancellation, other. Use other only when none of the specific reasons fit, and put context in notes.
A successful create returns status: "open" and an empty label_urls array. Poll Get Return Request until status is approved, denied, or credited. Signed label_urls expire after 7 days; re-fetch if the shopper delayed the download. A second open return for the same order returns 409.
Order-side status flows through webhooks (order.placed, order.tracking_received, order.delivered). Return status does too: return.created, return.approved, return.denied, return.credited. Poll Get Return Request if you need the current label_urls after return.approved.
Rye: single call, Shopify only
Rye uses the same create-then-poll shape: POST /api/v1/returns with a Rye order ID and a reason, then GET /api/v1/returns/{returnId}. The response is typed (state, timeline, shopperRefundTotal). When the merchant needs the item back, the prepaid label sits at nextAction.shipItemsToMerchant.label.url. The first release is Shopify merchants and whole-order returns. Partial returns, list, webhooks, and extra platforms are on the roadmap. Closest public analog to Zinc's contract, pointed at one Shopify store instead of Amazon, Walmart, and Target.
Loop and Shopify native: the multi-step draft
Loop's Return Create API models a return as a draft that moves through five phases: initialize, add items, checkout (with a cart for exchanges), pick a return method, submit. Each phase is its own call.
Shopify's Customer Account API does the same job in GraphQL: eligibility, returnable line items, returnCalculate, then orderRequestReturn. The mutation submits a request the merchant still has to approve. Both are Shopify-only.
OpenReturn: an early open spec
OpenReturn is a spec, not a product: Apache 2.0, OpenAPI, a reference MCP server, discovery via /.well-known/openreturn. openreturn.io describes reason capture, exchanges, carrier handoff, tracking, and refund or store credit. It is self-labeled "in development," pre-1.0. Closest returns analog to MPP and ACP, without the backers those had. Watch it. Do not build on it today.
Merchant returns vs retailer-placed orders
Loop, Rye, Shopify native, ReturnLogic, and OpenReturn assume a merchant you control. That works when the order came from your store. It fails when your app placed the order at Amazon, Walmart, Target, or another retailer a purchasing API buys from. There is no public POST /returns on Amazon.com. There is no OpenReturn .well-known file at Target. Walmart Marketplace Returns APIs are for sellers managing their own marketplace orders, not for a shopper app that bought a Walmart.com item.
Same split as Web Scraping vs Ecommerce APIs: plenty of tools for a store you own, almost nothing for a retailer you do not. Zinc is the exception. Pair it with shipment tracking so the inbound label and outbound tracking live on one order.
How to handle returns programmatically
- Confirm the order is returnable before you create. For Zinc, that means
order_placedand line items that have not already been returned. A duplicate create against an open return returns409. For Shopify, query eligibility andnonReturnableReasonsfirst so you do not submit a request that cannot proceed. - Create with a reason enum, not free text. Rye, Loop, Shopify, and Zinc all use coded reasons because they drive routing. Put extra context in
noteswhen the reason isother. - Branch on the next action, not only the status. If
label_urlsis populated (Zinc) ornextActionasks you to ship the item (Rye), show the shopper the label. If the status is alreadycreditedorrefunded, skip the label UI and show the refund. - Subscribe to order and return webhooks. Zinc emits
order.placed,order.tracking_received,order.delivered, plusreturn.created,return.approved,return.denied, andreturn.credited. See webhooks. PollGET /returns/{id}when you need a freshlabel_urlsafterreturn.approved. Treat webhook delivery as at-least-once and make refund handlers idempotent. - Reconcile the refund against the original charge. If you collected payment yourself, refund against the original Stripe (or PayPal, Adyen) charge ID. If Zinc placed the retailer order, the retailer-side refund is reported back as
credited. Do not issue a second refund from your gateway for the same line.
Plan for one open Zinc return at a time (409 otherwise), Rye's whole-order-only first release, 7-day label URLs, and refund only after the retailer confirms. Exercise create and status reads in test mode before a live retailer RMA.
FAQ
How do you handle returns programmatically for online orders?
Create a return against the original order ID with line items and a reason code, then subscribe or poll until the status is terminal. On a store you own, that is Loop, Rye, ReturnLogic, or Shopify's Customer Account API. On an order your app placed at Amazon, Walmart, or Target, use the same execution API that placed the order. Zinc's flow is POST /returns, then return.approved / return.denied / return.credited webhooks or GET /returns/{id}.
Which ecommerce platforms have a programmatic returns API?
Shopify has a native GraphQL flow through the Customer Account API. Loop, ReturnLogic, AfterShip, and Redo offer REST APIs on Shopify and, in some cases, BigCommerce. OpenReturn is a pre-1.0 open-source spec. None of those cover orders placed at Amazon, Walmart, or Target through a purchasing API.
Can an AI shopping agent file a return?
Yes, if the original order went through an API the agent can call again. Rye's Returns API is built for that on Shopify: one create call, then poll. For Zinc-placed retailer orders, the agent calls Create Return with the order UUID and order_item_id values, then waits for return.approved, return.denied, or return.credited.
Do I need a payment gateway integration to refund programmatically?
Yes if you processed the original charge. Refund against that charge ID, never against a new charge. If Zinc placed the retailer order, settlement happens on the retailer side and the return moves to credited. You do not need a second gateway refund for that portion.
Is programmatic returns the same as return on ad spend (ROAS)?
No. This guide is reverse logistics: shipping an item back and settling the refund. In advertising, "programmatic" usually means automated ad buying. Some adjacent answers pull in Meta or Google conversion APIs so ad platforms stop optimizing for purchases that get refunded. That is a marketing-analytics problem, not a returns API.
Final recommendation
Loop and Shopify returns assume you own the store. Retailer-placed orders need the same execution API that placed them. Use Best Returns Management Software when you are picking a portal vendor. Use this lifecycle when you are writing the create, poll, and label path in code.
Install Universal Checkout, then paste this prompt so the agent finds the order, files the return with a reason code, and reports the label or credit status.
When you are ready to ship it, start with Create Return, Create Order, pricing, and the API docs.



