Returns and refunds
Record returns, refund payments, and restock units with the Admin API.
Andrea Ramos returns one of the two T-shirts in her order. You record the return, restock the unit, and refund her payment with the Admin API.
Requirements
- An Admin token with the
api_adminscope. See ERP integration. - The
order:updatepermission and, to refund,payment:create. - An order with a captured payment.
- Node.js or Python, for the examples.
Read the order
Read the order before you return or refund. Store the id of each item and the
paymentTransactionId of each payment.
curl https://api.ecomiq.pe/api/v1/orders/7204558912004325301 \
-H "Authorization: Bearer $ECOMIQ_TOKEN"{
"id": "7204558912004325301",
"orderNumber": "ORD-2026-0001",
"status": "delivered",
"returnStatus": "none",
"currencyCode": "PEN",
"total": 178,
"items": [
{
"id": "7204558912004325302",
"title": "Polo de algodón",
"quantity": 2,
"quantityReturned": 0
}
],
"payments": [
{
"paymentTransactionId": "7204558912004325320",
"paymentMethod": "card",
"amount": 178,
"paymentStatus": "Captured",
"refundedAmount": 0,
"refundableAmount": 178
}
],
"financialSummary": {
"paidAmount": 178,
"refundedAmount": 0,
"refundableAmount": 178,
"shippingRefundableAmount": 0
},
"pendingRefund": null
}payments[].refundableAmount is what you can still refund from that payment.
It is 0 if the payment was not captured. financialSummary.refundableAmount
is the cap for the whole order.
The next steps reuse API, headers, and orderId.
Set the return status
returnStatus tracks the return before the units arrive. When Andrea Ramos
asks to return, set requested. To approve, move to inProgress with the
return address.
curl -X PUT https://api.ecomiq.pe/api/v1/orders/7204558912004325301/return-status \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "inProgress",
"comment": "Aprobamos tu devolución. Envía el polo con su etiqueta.",
"returnAddress": "Av. Larco 345, Miraflores, Lima"
}'The 200 response returns the order with returnStatus: "inProgress".
| Status | Meaning | Through this route |
|---|---|---|
none | No return. | No, responds 409 |
requested | Requested. | Yes |
inProgress | In progress. | Yes |
inspected | Inspected. | Yes |
rejected | Rejected. | Yes |
returned | Returned units are recorded. | No, responds 409 |
Moving from requested to inProgress sends the customer an approval notice
with comment and returnAddress. Moving to rejected sends a rejection
notice with comment.
none and returned respond 409 with Orders.InvalidReturnTransition:
returned is set when you record the return.
Record the return last
Any recorded return, even a partial one, sets returnStatus to returned.
From then on, this route responds 409 with Orders.ReturnCompleted.
Record the return
Record the units you received. For one item, use
POST /. For the whole order, use
POST / with the same body, without quantity.
This example returns one polo, restocks it, and refunds 89.
curl -X POST https://api.ecomiq.pe/api/v1/orders/7204558912004325301/items/7204558912004325302/return \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"idempotencyKey": "devolucion-ORD-2026-0001-1",
"quantity": 1,
"comment": "Talla incorrecta.",
"restock": true,
"restockLocationId": "7204558912004325378",
"refundAmount": 89,
"paymentTransactionId": "7204558912004325320"
}'{
"id": "7204558912004325301",
"status": "partialReturned",
"returnStatus": "returned",
"items": [
{
"id": "7204558912004325302",
"quantity": 2,
"quantityReturned": 1
}
],
"payments": [
{
"paymentTransactionId": "7204558912004325320",
"refundedAmount": 89,
"refundableAmount": 89
}
],
"financialSummary": {
"paidAmount": 178,
"refundedAmount": 89,
"refundableAmount": 89
}
}| Field | Use |
|---|---|
idempotencyKey | Required, up to 100 characters. Repeating it records no new return. |
quantity | Units to return. If omitted, returns all pending units. |
restock | true adds the units to stock at restockLocationId. |
restockLocationId | Location that receives the units. Required with restock. |
refundAmount | Amount to refund in the same call. |
paymentTransactionId | Payment to refund. Required with refundAmount. |
comment | Note, up to 500 characters. |
Location IDs are in
List fulfillment locations.
status moves to partialReturned, or to returned when all units are back.
refundAmount is not limited to the item's value. Its cap is what is left on
the payment: payments[].refundableAmount. This refund does not cover
shipping; for that, use Refund a payment.
If the refund fails, the return is not recorded. Retry with the same
idempotencyKey. A closed or cancelled order responds 409.
A whole-order return sends the order.returned webhook. An item return does
not send it.
Refund a payment
Use POST / to refund without a return or to refund
shipping. For a full refund, send the payment's refundableAmount in amount.
For a partial refund, send less.
curl -X POST https://api.ecomiq.pe/api/v1/orders/7204558912004325301/refunds \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"paymentTransactionId": "7204558912004325320",
"idempotencyKey": "reembolso-ORD-2026-0001-1",
"amount": 20,
"reason": "Compensación por demora en la entrega.",
"notifyCustomer": true
}'{
"id": "7204558912004325301",
"hasRefund": true,
"financialSummary": {
"paidAmount": 178,
"refundedAmount": 109,
"refundableAmount": 69,
"shippingRefundableAmount": 0
},
"pendingRefund": null
}| Field | Use |
|---|---|
paymentTransactionId | Payment to refund, taken from payments[]. |
idempotencyKey | Required, up to 100 characters. |
amount | Total to refund, with up to two decimals. Includes shippingAmount. |
shippingAmount | Part of amount that refunds shipping. Defaults to 0. |
reason | Reason, up to 500 characters. |
notifyCustomer | Notifies the customer. Defaults to true. |
lines | Items and units refunded. Required with restock. |
restock | With restockLocationId, puts the units in lines back in stock. |
amount cannot exceed payments[].refundableAmount or
financialSummary.refundableAmount. If it does, the API responds 400 with
Orders.RefundExceedsBalance. shippingAmount is capped at
financialSummary.shippingRefundableAmount.
Repeating the same idempotencyKey with the same body never refunds twice.
With a different body, the API responds 409 with Orders.RefundKeyConflict.
Stock changes by call:
| Call | Stock |
|---|---|
Return without restock | No change. |
Return with restock | Adds the returned units at restockLocationId. |
Refund without lines | No change. |
Refund with lines, no restock | Units not yet dispatched leave preparation and release their reservation. |
Refund with lines and restock | Records the units as returned and adds them at restockLocationId. |
restock on a refund requires an order in inTransit, delivered,
partialDelivered, or partialReturned. It only accepts dispatched units not
yet returned. Only items linked to a variant go back to stock, in whole units.
Retry a pending refund
The refund is saved before it goes to the payment provider. If the provider
fails or doesn't confirm, the API responds with an error and the order shows
pendingRefund.
{
"id": "7204558912004325301",
"pendingRefund": {
"id": "7204558912004325340",
"idempotencyKey": "reembolso-ORD-2026-0001-1",
"payments": [
{ "paymentTransactionId": "7204558912004325320", "amount": 20 }
],
"amount": 20,
"shippingAmount": 0,
"reason": "Compensación por demora en la entrega.",
"restockLocationId": null,
"notifyCustomer": true,
"lines": [],
"createdOn": "2026-10-04T15:30:00Z"
}
}While it exists, other refunds and returns respond 409 with
Orders.RefundPending. Retry with pendingRefund.id: the API reruns the
request with its same idempotencyKey.
curl -X POST https://api.ecomiq.pe/api/v1/orders/7204558912004325301/refunds/7204558912004325340/retry \
-H "Authorization: Bearer $ECOMIQ_TOKEN"If the refund already completed, the API returns the order without refunding again. If the provider confirms later, the pending refund completes on its own.
Review the money movements
GET / lists the money that came in and went
out of the order. Each refund is an out movement with a negative amount.
curl https://api.ecomiq.pe/api/v1/orders/7204558912004325301/transactions \
-H "Authorization: Bearer $ECOMIQ_TOKEN"[
{
"id": "7204558912004325350",
"type": "in",
"amount": 178,
"paymentMethod": "card",
"referenceId": "7204558912004325320",
"referenceType": "paymentTransaction",
"createdOn": "2026-10-01T10:12:00Z"
},
{
"id": "7204558912004325351",
"type": "out",
"amount": -89,
"paymentMethod": "card",
"referenceId": "7204558912004325320",
"referenceType": "paymentTransaction",
"content": "Talla incorrecta.",
"createdOn": "2026-10-04T15:10:00Z"
},
{
"id": "7204558912004325352",
"type": "out",
"amount": -20,
"paymentMethod": "card",
"referenceId": "7204558912004325320",
"referenceType": "paymentTransaction",
"content": "Compensación por demora en la entrega.",
"createdOn": "2026-10-04T15:30:00Z"
}
]referenceId is the payment behind each movement. content holds the refund
reason or the return comment.
Next steps
Last updated: