Process returns and exchanges

A return or exchange is created by the shopper (through the portal or a partner using the customer API) and then driven by the shop: optionally reviewed before the parcel ships, verified when it arrives, and finally processed, which issues the refund and closes it in your platform. This page covers the endpoints of that lifecycle. The shop-side flow of second-life offers and repairs is in Second life and repairs.

Find the returns

GET /returns lists the returns of the shop, fifteen per page, newest first:

GET /returns?page=0&statuses=Pending,PendingApproval&startDate=2026-08-01&endDate=2026-09-16&sortField=date&sortOrder=DESC
[
  {
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "1029-R1",
    "date": "2026-09-15T09:12:00.000Z",
    "state": "Pending",
    "orderId": "7f6e5d4c-...",
    "orderName": "#1029",
    "customer": "Jane Doe",
    "email": "[email protected]",
    "countryCode": "ES",
    "currency": "EUR",
    "items": 2,
    "totalItems": 3,
    "reasonTypes": ["Return", "Change"],
    "paymentMethod": "CreditCard",
    "isPaid": true,
    "shippingType": "DropOff",
    "carrierName": "GLS",
    "tracking": { "number": "12345678", "status": "InTransit", "url": "https://..." },
    "preProcessedStatus": "Pending"
  }
]

Filters, all optional: statuses (comma-separated or repeated), startDate and endDate with dateField (CreationReturn by default, or CreationOrder, InfoReceivedReturn, CollectedReturn, InTransitReturn, DeliveredReturn), customerName, customerEmail, orderName, name, trackingNumber, carrierName, regions (country codes), paymentMethods, reasonTypes (Return, Change), shippingTypes, trackingStatuses. Sorting honours sortField date, name, order and customer.

Three things to know:

  • The date window always applies. Without startDate and endDate the list covers the last two months of the chosen dateField; send startDate to look further back (endDate defaults to now). Days are taken in UTC, from 00:00:00 to 23:59:59.
  • Returns under review are hidden by default. Returns waiting for a pre-validation are only listed with forValidations=true (then statuses and state refer to the preValidationStatus: Pending, Accepted, Rejected). forApprovals=true lists the returns waiting to be processed after the parcel verification, and state becomes the verification result.
  • statuses=Pending means "not verified yet". When statuses includes Pending the list only shows returns whose parcel has not been verified; add preProcessed=true to list the verified ones waiting to be processed instead.
  • id is the identifier for every other call. name is the human reference (<order number>-R<n>). Users with the Shop role only see, in the list and the detail, returns dropped at a physical shop (shippingType Shop).

GET /returns/export?type=returns (or type=products, one row per unit) answers the URL of a CSV file with the same filters.

Read a return

GET /returns/1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d?lang=en

:id accepts the Drelife identifier or the identifier of the return in your platform (shopID) here and on every action except /pre-validate, which takes the Drelife identifier only. The response is the full return:

FieldMeaning
id, name, date, state, stateSetAtstate is Pending, PendingApproval, Approved, PartiallyApproved, Declined or Cancelled
order (id, name), cms, marketplaceId, currency, email, address, billingAddressorder.id is the identifier of the order in your platform (Shopify, PrestaShop, Logicommerce or Custom)
items[]One entry per unit, see below
includeChange, includeInstantExchange, exchange (id, name), exchangeTriggerExchange information; exchange is the exchange order once created
preValidationStatus, merchantPreValidationMessage, needUserApproval, userPreValidationStatusThe review before shipping; Pending, Accepted or Rejected
paymentMethod, allowedPaymentMethods, storeCreditExtraQuantity, isStoreCreditExtraQuantityPercentage, storeCreditExtraMoney, mockStoreCredit, mockGiftCardRefund method (CreditCard, StoreCredit, GiftCard) and the store credit bonus
isPaid, canSetAsPaid, paymentURLWhether the shopper has paid what they owe (exchanges with a price difference, shipping)
shippingType, shippingValue, isShippingRefunded, canShippingBeRefunded, methodName, carrierName, carrierLogo, labelURL, qrCodeURL, labelIsQR, needLabel, withoutLabelThe shipping and its documents
tracking (number, status, url, statuses[], expectedDeliveryDate, exceptionMessage)The reverse parcel; status is InfoReceived, Awaiting, InTransit, Delivered, Exception, Warning, Lost or Cancelled
totalReturnValue, discountValue, keepOrderDiscountAmounts of the return
allowPreApprovals, mustPreProcess, autoRestock, displayAutoRestock, showReturnPreApprovalSummary, showReturnPreValidationSummaryThe policy flags that decide which steps apply

Each item carries returnItemId (the identifier every action below expects; id is the variant of your platform), productId, variantId, sku, name, description, image, price, discount, status (OnHold, PreApproved, PreDeclined, Approved, Declined), reason (with type, content, allowRestock), customerInfo, customerImageUrls, answers[], changeItem (the variant wanted in exchange), validationStatus and validationReasonInfo (your review), userValidationStatus (the shopper's answer), preApprovalReason (id, content, type, needInfo), preApprovalReasonInfo, preApprovalImageUrls (the verification; its result per unit is status), refundItem (amount, motive) and isProcessable, which is false for units rejected during the review and therefore excluded from the next steps.

The lifecycle

Which steps apply depends on the policy (see Set up your shop):

  1. Pre-validation (allowPreValidation): the shop accepts or declines each unit before the shopper ships. When the policy asks for the shopper's confirmation (needUserApproval on the return), the return waits for it.
  2. Verification (allowPreApprovals): when the parcel arrives the shop checks each unit and records its condition; with mustPreProcess this step is mandatory before processing.
  3. Processing: the shop approves or declines each unit with its refund amount; Drelife issues the refund, restocks and closes the return in your platform.

The three steps answer 200: pre-validation and processing with the full return, verification with the return without order, address and email. Processing, pre-validation, the refund method change and the mark as paid take a short lock: a concurrent call answers 409 ReturnLocked; retry after a few seconds.

1. Pre-validate

POST /returns/{id}/pre-validate
Content-Type: application/json

{
  "items": [
    { "returnItemId": "d38fce81-37b6-419c-8fb7-ba2f55096396", "validationStatus": "Accepted" },
    { "returnItemId": "e49a0f22-8c1d-4e5f-9a6b-7c8d9e0f1a2b", "validationStatus": "Rejected", "validationReasonInfo": "Worn item, tags removed" }
  ],
  "message": "We cannot accept the trousers, see the note.",
  "withoutLabel": false
}

Roles: StoreOwner, Manager, Approver, PreValidator. Rules:

  • Every unit still Pending must be in items (400 Missing pre-validation data for item with id ...); units already decided may be left out. To defer a unit, send it with validationStatus Pending: the return stays Pending and a later call decides it. validationReasonInfo is required when rejecting.
  • The return becomes Accepted when no unit is Pending and at least one is accepted, Rejected when all are rejected. Once it leaves Pending the endpoint is closed (400 The return has been pre-validated already).
  • message (250 characters) is shown to the shopper as merchantPreValidationMessage.
  • withoutLabel: true on an acceptance tells Drelife the shop handles the shipping itself: no label, no tracking, no shipping cost.
  • On acceptance Drelife generates the label, creates the return (and the exchange order when the trigger allows it) in your platform and notifies the shopper; when the policy asks the shopper to confirm (needUserApproval), the return waits for their answer (userPreValidationStatus). Rejected units go back to the order and can be requested again.

Errors: 400 Pre-validations are not allowed in this marketplace, 400 Return item ... does not belong to this return, 400 Item ... was already rejected and cannot be accepted again, 400 ValidationFailed for an empty items or a rejection without note.

2. Verify the parcel

When the parcel arrives, record what you found for each processable unit with a pre-approval reason of the shop (the list is in the dashboard; each reason has a type, PreApproved, PreDeclined or OnHold, and may require a note; the detail exposes the one chosen as items[].preApprovalReason):

POST /returns/{id}/pre-process
Content-Type: application/json

{
  "items": [
    { "returnItemId": "d38fce81-...", "preApprovalReasonID": "6c7d8e9f-...", "preApprovalReasonInfo": "" },
    { "returnItemId": "f50b1c33-...", "preApprovalReasonID": "1a2b3c4d-...", "preApprovalReasonInfo": "Stain on the sleeve",
      "attachments": [ { "path": "temp/returns/1a2b.../f50b.../1789734400.jpg", "filename": "sleeve.jpg", "mimetype": "image/jpeg", "size": 348211 } ] }
  ]
}

Roles: StoreOwner, Manager, PreApprover, Shop (not Approver). The payload must list exactly the processable units, once each (400 Missing items, 400 The length of items is invalid, also for a duplicate, 400 PROCESS_REJECTED_ITEMS with the offending itemIds when a rejected unit is included). Each unit takes the status of its reason; the verification result of the return (Approved, Declined, PartiallyApproved or Pending) is shown as preProcessedStatus in the rows of GET /returns, and the shopper is notified when every unit is decided. With exchangeTrigger ReturnPreApproved, the exchange order is created in your platform at this point and released on Shopify.

Pictures of the verification are uploaded first: POST /returns/{id}/pre-approval/upload-url with { "returnItemId", "filename", "mimetype" } answers 201 with a signed form (url, fields, path, publicUrl); post the file to it, then reference path in attachments. POST /returns/{id}/pre-approval/delete-upload with { "path" } discards an upload.

Errors: 400 Return is not pending, 400 Pre-approvals are not allowed in this marketplace, 400 Return is not pre-validated, 400 Pre-process requires all items to have an on hold, pre approved or pre declined state (lines an approval draft left Approved/Declined are accepted while the return is still Pending and unverified), 400 One of the items needs a preApprovalReasonInfo, 404 Pre-approval reason ... not found.

3. Process

POST /returns/{id}/process
Content-Type: application/json

{
  "items": [
    { "returnItemId": "d38fce81-...", "status": "Approved", "amount": 29.90, "restock": true },
    { "returnItemId": "f50b1c33-...", "status": "Declined", "amount": 0, "restock": false, "motive": "Stain on the sleeve" }
  ],
  "storeCreditExtraMoney": 0,
  "manuallyRefunded": false
}

Roles: StoreOwner, Manager, Approver. The payload lists exactly the processable units, as in the verification. Per unit:

  • status Approved or Declined (OnHold saves the unit without processing).
  • amount is the refund of the unit; it is capped at the price paid for the unit and forced to 0 when declined.
  • restock puts the unit back in stock (Shopify and PrestaShop).
  • motive (250 characters) is required when declining. It may be omitted only when the same item also carries "preApprovalStatus": "PreDeclined", an optional field echoing the verification result.

Body-level fields: storeCreditExtraMoney, the bonus added to the refund, required when the refund method is a credit method (StoreCredit or GiftCard; send 0 for none) and rejected otherwise (400 Store credit extra money is only allowed for credit refund methods), both checked only when the call actually processes; manuallyRefunded: true records the refund without issuing it in your platform (Shopify and PrestaShop; passed on to Logicommerce); process: false saves the statuses and amounts without processing; forceProcessChild processes the return of an exchange order whose parent has not been processed.

When every unit is Approved or Declined, Drelife computes the refund (approved amounts minus the return shipping unless free, minus the order discount unless keepOrderDiscount; even swaps net to zero, other exchange units net the price difference), issues it in your platform through the refund method, restocks, approves the return there and, for exchanges not yet released, creates or releases the exchange order. The return becomes Approved, Declined or PartiallyApproved, and the shopper is notified. If any unit is OnHold the call only saves the decisions and state does not change.

Errors: the payload errors of the verification (400 No items, 400 Duplicate items, 400 Missing items, 400 The length of items is invalid, 400 PROCESS_REJECTED_ITEMS, 404 Return item ... not found), 400 Return is not pre-validated, 400 The return must be pre-processed before being processed, 400 The return must be paid by the customer before being processed, 400 This return has not been processed in the CMS (the return does not exist yet in your platform; wait for its creation), 400 Store credit extra money is required for credit refund methods, 400 ExistingRefund, 422 CannotGenerateRefund, 400 ParentOrderNotProcessed, 404 Return not found (also when the return is no longer Pending or PendingApproval).

On a Custom platform Drelife records the decision and notifies the shopper but issues no refund: the shop refunds on its side.

Other actions

ActionCallRolesAnswersNotes
Change the refund methodPOST /returns/{id}/payment-method with { "paymentMethod": "StoreCredit" }StoreOwner, Manager, ApproverThe full returnPending returns only (400 Return is not pending); must be in allowedPaymentMethods (400 Refund method not allowed for this marketplace policy); refused once a refund exists (400 Cannot change refund method: refund already issued); takes the lock. Switching to a credit method applies the policy bonus
Refund the return shippingPOST /returns/{id}/refund-shipping, no bodyStoreOwner, Manager, ApproverA summary of the return (estimatedRefund, refundedAmount, shippingAmount, state...)Shopify only, when the shipping was paid and not refunded yet; canShippingBeRefunded says whether it applies. Errors, including an unknown return, answer 422
Add a tracking by handPOST /returns/{id}/tracking with { "number", "status", "url" }StoreOwner, Manager, ApproverThe tracking (number, status, url, statuses[])For parcels shipped outside Drelife; status is a tracking status. Refused when the return already has a tracking or was accepted withoutLabel. Errors answer 422
Mark as paidPOST /returns/{id}/set-as-paid, no bodyStoreOwner, ManagertrueFor amounts the shopper owes and paid offline; the return must be Pending; not available on Shopify nor when MultiSafePay collects; takes the lock

There is no endpoint to cancel a return, to undo a processing or to resend a notification; those actions are done by Drelife on request.

Statuses at a glance

FieldValuesSet by
statePending, PendingApproval, Approved, PartiallyApproved, Declined, CancelledProcessing
preValidationStatus, items[].validationStatusPending, Accepted, RejectedPre-validation
userPreValidationStatus, items[].userValidationStatusPending, Accepted, RejectedThe shopper, when needUserApproval
items[].statusOnHold, PreApproved, PreDeclined, Approved, DeclinedVerification, then processing
preProcessedStatus (rows of GET /returns only)Pending, Approved, Declined, PartiallyApprovedVerification
tracking.statusInfoReceived, Awaiting, InTransit, Delivered, Exception, Warning, Lost, CancelledThe carrier

To be notified of these changes instead of polling, subscribe to the webhooks described in Integrations.


Did this page help you?