Skip to content

Returns (RMA)

Returns — also called RMA (Return Merchandise Authorization) — let a logged-in customer ask the store to take back or cancel an item they ordered. A customer can raise a return for an eligible item, converse with the store about it through a message thread, and cancel, reopen or close the request. Every return is scoped to the customer who owns it; a customer can only ever see and act on their own returns.

Authentication

All return endpoints require an authenticated customer — provide the storefront key and a customer Bearer token. See the Authentication page for how to obtain and send them.

Returns must be switched on first

Returns are off out of the box. Nothing in the API errors when they are — returnableItems simply comes back empty, and a client sees an order with no returnable rows rather than a message explaining why. Three things have to line up, all configured on the admin side:

RequirementWhere it livesEffect when unset
Allowed product typesConfiguration → Sales → RMA → Allowed product typesAn empty setting makes nothing returnable — it does not mean "all types". A product whose type is absent from the list is never eligible.
Allow RMA on the productThe product's own allow_rma attribute, off by defaultThat product's order items are never returnable.
Return windowThe product's active RMA rule, otherwise Configuration → Sales → RMA → Default allow days (default 7)Falls back to the configured default.

The decision is made when the order is placed, not when the return is requested: the resolved window is written onto the order item and frozen there. Enabling RMA later therefore does not make existing orders returnable — only orders placed after the change qualify. A client testing the flow against pre-existing orders will see an empty list no matter how the settings are changed.

An item then stays returnable while the order date plus that window has not passed, and while some quantity remains unrefunded and uncanceled.

How a return works

  1. Find eligible orders. Query returnableOrders for the orders a return can still be raised against — an empty list means there is nothing to return, which is what the Returns area should reflect.
  2. Find eligible items. Query returnableItems for an order to see which items are still within their return window and how many units can be returned or canceled.
  3. Pick a reason. Query returnReasons for the resolution type (return or cancel_items) to get the reason ids to choose from.
  4. Collect the custom fields. Query returnCustomFields for the extra questions the store asks on its return form. The list is often empty; when it is not, every field marked isRequired must be answered.
  5. Raise the return. Call createCustomerReturn with the order, the item, a quantity, the resolution type, a reason id and the custom-field answers. The return starts in a Pending status.
  6. Converse. Read the thread with customerReturnMessages and add messages with createCustomerReturnMessage.
  7. Cancel, reopen or close. Use cancelCustomerReturn, reopenCustomerReturn or closeCustomerReturn to change the state of the request.

Status flags

Each return carries three action flags that tell a client which operations are currently allowed:

FlagMeaning
canCloseThe return can be closed (marked solved) by the customer.
canReopenThe return can be reopened back to pending. Only a canceled or declined return qualifies, and only while the matching admin setting allows re-requests for that state.
isExpiredThe return is past its window — the same order-date-plus-return-period window that made the item returnable, not a separate countdown from when the return was raised.

These flags are populated on every return the API returns — the listing included — so a client can decide which buttons to show without a query per row.

Quantity caps are enforced by the store

When raising a return, the quantity you send is capped server-side by the trusted quantity a customer is actually allowed to return or cancel for that item (forReturnQuantity / forCancelQuantity from returnableItems). You can never return more units than were ordered and are still eligible. Units held by a canceled or declined return are released back, so a shopper who withdrew a request can raise a new one for the same item.

Custom fields

A store can add its own questions to the return form — an invoice number, a preferred pickup slot, and so on. returnCustomFields lists the active ones with their types and allowed options; the answers go into customAttributes when raising the return, keyed by field id, and come back on the return as customAttributes. A field marked isRequired rejects the mutation when unanswered, so query it before rendering your form.

Attaching files

A JSON GraphQL request cannot carry a binary part, so every file travels over REST. There is no separate upload endpoint on either transport; the file rides along with the request that creates the record.

WhatEndpointField
Evidence photos on the returnPOST /api/shop/returnsimages[], several per return
An attachment on a conversation messagePOST /api/shop/return-messagesfile, one per message
bash
# Evidence photos while raising the return
curl -X POST "https://your-store.com/api/shop/returns" \
  -H "X-STOREFRONT-KEY: pk_storefront_PvlE42nWGsKRVIf8bDlJngTPAdWAZbIy" \
  -H "Authorization: Bearer 438|aSV6JyFn299xuoR6wr5KKOodyIlMA26h0IgHiqLW" \
  -F "order_id=45" \
  -F "order_item_id=78" \
  -F "rma_qty=1" \
  -F "resolution_type=return" \
  -F "rma_reason_id=2" \
  -F "agreement=true" \
  -F "images[]=@/home/john/Pictures/damage-front.jpg"

# A file on a message in the conversation
curl -X POST "https://your-store.com/api/shop/return-messages" \
  -H "X-STOREFRONT-KEY: pk_storefront_PvlE42nWGsKRVIf8bDlJngTPAdWAZbIy" \
  -H "Authorization: Bearer 438|aSV6JyFn299xuoR6wr5KKOodyIlMA26h0IgHiqLW" \
  -F "return_id=12" \
  -F "message=Photo of the broken zipper" \
  -F "file=@/home/john/Pictures/zipper.png"

A return the shopper attached photos to has to be raised over REST, since the photos can only go in at creation time. The conversation is otherwise unaffected: send messages through createCustomerReturnMessage here, and switch to the REST call only for the messages that carry a file.

Endpoints

OperationGraphQL fieldDescription
List own returnscustomerReturnsPaginated list of the customer's own returns.
View one returncustomerReturnA single return the customer owns.
List returnable ordersreturnableOrdersOrders a return can still be raised against.
List returnable itemsreturnableItemsReturn-eligible items of one of the customer's orders.
List return reasonsreturnReasonsActive reasons for a resolution type.
List return custom fieldsreturnCustomFieldsThe store's extra questions on the return form.
List return messagescustomerReturnMessagesThe conversation thread of a return.
Raise a returncreateCustomerReturnCreate a new return for one order item.
Cancel a returncancelCustomerReturnCancel the customer's own return.
Reopen a returnreopenCustomerReturnReopen a canceled/declined return.
Close a returncloseCustomerReturnMark a return solved.
Send a messagecreateCustomerReturnMessageAdd a message to the return conversation.

Released under the MIT License.