Skip to content
Orders

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_admin scope. See ERP integration.
  • The order:update permission 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"
Response
{
  "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".

StatusMeaningThrough this route
noneNo return.No, responds 409
requestedRequested.Yes
inProgressIn progress.Yes
inspectedInspected.Yes
rejectedRejected.Yes
returnedReturned 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 /v1/orders/{orderId}/items/{itemId}/return. For the whole order, use POST /v1/orders/{orderId}/return 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"
  }'
Response
{
  "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
  }
}
FieldUse
idempotencyKeyRequired, up to 100 characters. Repeating it records no new return.
quantityUnits to return. If omitted, returns all pending units.
restocktrue adds the units to stock at restockLocationId.
restockLocationIdLocation that receives the units. Required with restock.
refundAmountAmount to refund in the same call.
paymentTransactionIdPayment to refund. Required with refundAmount.
commentNote, 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 /v1/orders/{orderId}/refunds 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
  }'
Response
{
  "id": "7204558912004325301",
  "hasRefund": true,
  "financialSummary": {
    "paidAmount": 178,
    "refundedAmount": 109,
    "refundableAmount": 69,
    "shippingRefundableAmount": 0
  },
  "pendingRefund": null
}
FieldUse
paymentTransactionIdPayment to refund, taken from payments[].
idempotencyKeyRequired, up to 100 characters.
amountTotal to refund, with up to two decimals. Includes shippingAmount.
shippingAmountPart of amount that refunds shipping. Defaults to 0.
reasonReason, up to 500 characters.
notifyCustomerNotifies the customer. Defaults to true.
linesItems and units refunded. Required with restock.
restockWith 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:

CallStock
Return without restockNo change.
Return with restockAdds the returned units at restockLocationId.
Refund without linesNo change.
Refund with lines, no restockUnits not yet dispatched leave preparation and release their reservation.
Refund with lines and restockRecords 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.

Order with a pending refund
{
  "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 /v1/orders/{orderId}/transactions 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"
Response
[
  {
    "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:

On this page