Set up your shop

Everything the dashboard lets a store owner configure is available through the API, so a shop can be provisioned or kept in sync from its own tools. This page walks the configuration in the order a new shop follows: where the parcels go, the rules, the reasons a shopper can pick, the carriers, the look of the portals, the access lists, the staff, the emails and the banners. Every call needs a Bearer token of a StoreOwner or a Manager (see Getting started).

Three things to know before the first call:

  • Unknown properties are errors. Every body is validated with a whitelist: a misspelled or extra property, including a read-only one copied from a GET, answers 400 ValidationFailed. Round-tripping a GET response into a PATCH does not work as is.
  • Configuration is per mode. Most resources exist once per mode (Returns, Circular for second life, Repair), selected with ?mode= or a mode field. When the mode is omitted, the API works on Returns.
  • Texts are localized maps. Fields such as content, name, label or message are objects keyed by language (ca, de, en, es, fr, it, pt); en is mandatory for the main texts (content, name, label, message, the terms) and unknown keys are always rejected. POST /customizations/translate gives a machine translation of a text into the seven languages when you want a starting point.

The shop-level switches (returnsEnabled, circularEnabled, repairEnabled, locale, currency) are managed by Drelife, not through this API; when returns are disabled the return-processing endpoints answer 503 Returns are not available while the configuration endpoints keep working.

1. The return address

The address where the parcels are sent back, one per mode; it is also the origin printed on the labels. Read it with the mode in the path and update it with the mode in the body:

GET /addresses/Returns
PATCH /addresses
Content-Type: application/json

{
  "mode": "Returns",
  "company": "Your Shop S.L.",
  "address1": "Calle Mayor 1",
  "city": "Madrid",
  "zip": "28013",
  "province": "Madrid",
  "provinceCode": "M",
  "country": "Spain",
  "countryCode": "ES",
  "phone": "+34910000000"
}

Either company or both firstName and lastName are required (400 Company or name must be provided). The response does not echo mode or an id. A mode without an address answers 404 Address not found on both calls: the addresses are created by Drelife when the mode is enabled.

2. The policy

GET /policies returns the list of policies of the shop: the base policy (region: null) and the regional policies (region like "ES" or "ES:B" for a province) that override it. PATCH /policies takes an array of policies and answers the whole list after the batch:

PATCH /policies
Content-Type: application/json

[
  {
    "id": "0d2b9a5e-...",
    "region": null,
    "maxReturnDaysEnabled": true,
    "maxReturnDays": 30,
    "exchangesEnabled": true,
    "normalExchangesEnabled": true,
    "instantExchangesEnabled": false,
    "allowedPaymentMethods": ["CreditCard", "StoreCredit"],
    "storeCreditExtraQuantity": 10,
    "isStoreCreditExtraQuantityPercentage": true,
    "allowPreValidation": false
  },
  { "region": "FR", "maxReturnDaysEnabled": true, "maxReturnDays": 14 },
  { "id": "7c1e...", "toDelete": true }
]

An element without id creates a regional policy (region required); with id it updates; with toDelete: true it deletes (the base policy cannot be deleted). The main groups of fields are:

GroupFields
WindowsmaxReturnDays and maxLabelReturnDays (7 to 180), maxExchangeDays (1 to 180), each with a ...Enabled flag
CapsmaxNumberOfItemsPerReturn, ...PerCircular, ...PerRepair, maxNumberOfReturnsPerOrder, maxNumberOfExchangesPerOrder (1 to 100, with ...Enabled)
SwitchesreturnsEnabled, exchangesEnabled, normalExchangesEnabled, instantExchangesEnabled (send the last two together; at least one must be true)
Refund methodsallowedPaymentMethods, allowedPaymentMethodsCircular, allowedPaymentMethodsRepair: CreditCard (Returns and Repair), StoreCredit, GiftCard; BankTransfer and DiscountCode (Shopify only) only for Circular. A method added to a list is checked against its mode and, on Shopify, against the access scopes the app holds (422 ShopifyScopeMissing, reason.field = the method); methods already stored are never rejected. GET /policies/refund-methods/permissions lists the missing scopes per method before you try
Store credit bonusstoreCreditExtraQuantity and isStoreCreditExtraQuantityPercentage, per mode; bankTransferReductionCircular. A percentage is an integer from 0 to 50, a fixed amount any positive amount with two decimals, validated on the modes the request changes
Discount codes (Circular, Shopify)discountCodeExtraQuantityCircular + isDiscountCodeExtraQuantityPercentageCircular (the bonus of the code, independent from the store-credit one), mockDiscountCodeCircular, and discountCodeSettingsCircular, a JSON replaced whole on every PATCH: { validityDays: 30 | null, minimumPurchasePercentage: 25, minimumPurchaseAmount: null, collections: [{ id: "gid://shopify/Collection/1", title: "Outlet" }], restrictToCustomer: true, combinesWith: { orderDiscounts, productDiscounts, shippingDiscounts } }. Only validityDays, minimumPurchasePercentage and collections are yours to set in this version: whatever you send, minimumPurchaseAmount is stored as null, restrictToCustomer as true and the three combinesWith flags as true (every code is bound to the customer of the request, combines with every other discount and has no fixed minimum). Adding DiscountCode to allowedPaymentMethodsCircular (activation) requires the Shopify app to hold write_discounts (422 ShopifyScopeMissing, 424 ShopifyUnavailable) and an enabled customer Approved email notification that applies to second lives and prints {{ refundMessage }} (422 PurchaseApprovedNotificationMissing); it also seeds the customer text of the method. The collections come from GET /purchase-items/circular/shopify/collections/all
Publishing (Circular, Shopify)defaultLocationIdCircular, defaultCollectionIdCircular (a manual collection) and defaultSalesChannelIdsCircular: the Shopify location, collection and sales channels the publish form starts with, as Shopify GIDs from GET /purchase-items/circular/shopify/locations, .../collections and .../publication-channels. Base-only and optional: null (or [] for the channels) means no default. They are only hints for the form (see Publish); a publish always sends its own values. skuPrefixCircular (20 characters of letters, digits, dots, dashes and underscores, starting and ending with a letter or digit) is the root of the SKUs generated for units published without one, <skuPrefixCircular>-000123; null or empty goes back to the default DL
Exchange rulesdifferentProductExchangeEnabled, allowExchangeDifferentProductHigherPrice / ...LowerPrice, equalize... flags, keepOrderDiscount, allow...PriceVariantExchange, exchangeTrigger
Review stepsallowPreValidation (the shop reviews before the shopper ships), mustDispute (the shopper confirms the review), allowPreApprovals, mustPreProcess, each with Circular and Repair variants; show...Summary flags
Shipping and amountsfreeShippingReturnAmount (returns) and freeShippingAmount (exchanges), the number of free shipments (0 to 1000), minAmountForReturn (10 to 1000), all with ...Enabled
Stale requestsshippingReminderDays (1 to 9), userValidationReminderDays (1 to 89), userValidationCancelDays (2 to 90), per mode, with ...Enabled; the reminder must be lower than the cancel window
Terms and paymentexplicitTermsAndConditions, termsAndConditions (localized), useMultiSafePay..., multiSafePayAppKey..., shopListUrl...

Rules worth knowing:

  • On the base policy null is an error (<field> should not be empty for a base policy.) except for the optional URLs, keys, terms and publication defaults; on a regional policy null means "inherit from the base".
  • About forty fields are base-only (payment methods, store credit bonus, pre-validation and pre-approval flags, MultiSafePay, terms, mocks, autoRestock, delayReturnCreation...): sending them on a regional policy answers 400 <field> is only allowed for a base policy., even with the same value as the base.
  • A numeric field is only validated when its ...Enabled flag is true in the same payload; enable the flag and set the value together.
  • Always send "region": null on the base policy element: the base-only fields are rejected at validation time (400 ValidationFailed, <field> is only allowed for base policy) when region is missing. region: null on a regional policy is 422 Is not a base policy, and a region on the base one is 422 A base policy should not be associated to a region.

3. Reasons

Reasons are what the shopper picks for each unit; they drive the flow (free shipping, required pictures or comments, exchange candidates) and, for second life and repairs, the money.

POST /reasons
Content-Type: application/json

{
  "type": "Return",
  "content": { "en": "Too small", "es": "Demasiado pequeño" },
  "tags": [],
  "categories": [],
  "products": [],
  "productsChange": [],
  "enabled": true,
  "order": 1,
  "isFreeShipping": false,
  "isExclusive": false,
  "isExclusiveCategories": false,
  "allowCustomerImage": true,
  "requireCustomerImage": false,
  "allowCustomerInfo": true,
  "requireCustomerInfo": false,
  "isAmountPercentage": false
}
FieldMeaning
typeReturn, Change (exchange), Purchase (second life), Repair, ValidationRejection and UserValidationRejection (the reasons offered when declining)
content, description, subQuestionLocalized texts; subQuestion is a follow-up question shown when the reason is picked
allowCustomerImage, requireCustomerImage, minPicsRequired, minVideosRequiredAttachments; require... needs the matching allow..., and at least one picture or video (0 to 5 each) must be required when images are
allowCustomerInfo, requireCustomerInfoFree-text comment of the shopper
isFreeShippingThe return shipping is free for this reason
products, productsChange, categories, orders with isExclusive, isExclusiveCategories, isExclusiveOrdersRestrict the reason to products, categories or orders; productsChange holds at most one product, the one offered as exchange
validityDaysEnabled, validityDaysDays from the delivery (0 to 3650) the reason stays selectable (Return and Change only)
amount, amountMax, isAmountPercentage, assessable, useParentPriceSecond life and repairs: the amount offered or charged, a range up to amountMax (assessable reasons only, at least amount), or an appraisal after receiving the item. isAmountPercentage is always sent and only kept for Purchase reasons
parentReasonIDBuilds a tree of reasons; children can inherit the price with useParentPrice

type, content, categories, products, productsChange, isExclusive, isExclusiveCategories, enabled and isAmountPercentage are mandatory in the body. GET /reasons filters by types, enabled, content, product, assessable and more, paged with page and pageSize. The single-reason responses (GET /reasons/{id}, POST, PATCH) include the linked categories, products, productsChange and orders; the list does not. PATCH /reasons/{id} needs the id in the body too. DELETE /reasons/{id} removes the reason and its children. The reason image and example image are uploaded as multipart/form-data with a field named image (PATCH /reasons/{id}/image, /example-image; 5 MB, png, jpeg, jpg or webp).

Questions

Extra questions asked to the shopper, per mode, optionally scoped to categories or products:

POST /questions
Content-Type: application/json

{
  "mode": "Returns",
  "type": "Select",
  "label": { "en": "How often did you wear it?" },
  "required": true,
  "options": [
    { "value": "never", "label": { "en": "Never" } },
    { "value": "few", "label": { "en": "A few times" } }
  ]
}

Types are String, Boolean, Number, Select and MultiSelect. options is required for Select and MultiSelect and rejected for the others; numberConfig (numberType Integer or Decimal, min, max) is required for Number and rejected otherwise. The value of an option is the stable key the API returns in the shopper answers.

Categories (second life and repairs)

The Circular and Repair catalogs can be organised as a tree of categories; the leaves carry the reasons and questions the shopper sees.

POST /categories
Content-Type: application/json

{ "mode": "Circular", "name": { "en": "Jackets" }, "parentCategoryID": "3f2c...", "enabled": true, "order": 1 }

GET /categories?mode=Circular&includeSubCategories=true lists them; GET /categories/{id}/purchases and /repairs list the requests received for a category. Images follow the same image multipart rule.

4. Carriers

A carrier is a provider (GET /carrierProviders: GLS, Correos, Correos Express, FedEx, UPS, InPost, SendCloud, Estafeta, Skydropx...) enabled for a mode, with its methods, prices and optionally the credentials of your own account. Creating it is a three-step process:

POST /carriers
Content-Type: application/json

{ "mode": "Returns", "providerId": "b1c2...", "displayName": "GLS" }

POST /carriers answers 201 with an empty carrier: mode, providerId and the optional displayName are taken (400 Carrier Provider not found for an unknown or disabled provider). Then configure the methods and prices:

PATCH /carriers/{id}
Content-Type: application/json

{
  "methods": [
    {
      "id": "9a8b...",
      "name": "BusinessParcel",
      "activated": true,
      "regions": [ { "country": "ES", "provinces": [] } ],
      "methodCustomPrices": [
        { "type": "DropOff", "enabled": true, "currency": "EUR", "returnPrice": 3.95, "exchangePrice": 0, "returnExchangePrice": 3.95, "rule": null },
        { "type": "PickUp", "enabled": true, "currency": "EUR", "returnPrice": 5.95, "exchangePrice": 2, "returnExchangePrice": 5.95,
          "rule": { "enabled": true, "subject": "OriginalOrderPrice", "condition": "GreaterThanOrEqualTo", "value": 100, "returnPrice": 0, "exchangePrice": 0, "returnExchangePrice": 0 } }
      ]
    }
  ]
}

The methods available for a provider come from GET /carrierProviders/{id} (methods[], with their name and type: DropOff, PickUp, Custom, Merchant, Shop); each method of the payload must carry that name besides its id (400 Method <displayName> not valid for this carrier otherwise). A price entry per shipping type sets what the shopper pays for a return, an exchange, a mixed return and exchange, a second-life shipment (circularPrice) or a repair (repairPrice); a rule makes it conditional on the order amount or the total to pay, optionally with a usage limit. Two prices with the same currency for one shipping type answer 400 The configuration of one of the methods is not valid.

To ship with your own contract instead of the Drelife one, send the credentials the provider lists in credentialAttributes, exactly those keys and no other:

PATCH /carriers/{id}/credentials
Content-Type: application/json

{ "credentialsEnabled": true, "credentials": { "username": "...", "password": "...", "contractId": "..." } }

With credentialsEnabled: true the credentials are verified against the carrier: invalid ones answer 406 Credentials are invalid, a wrong set of keys 400 Number of credentials provided is not correct, a provider without own credentials 400 Credentials cannot be configured for this carrier. The PATCH response and the carrier list omit the credentials, but GET /carriers/{id} returns them: treat that response as sensitive. credentialsValidated tells you they worked; DELETE /carriers/{id}/credentials goes back to the Drelife credentials.

5. The look of the portals

GET /customizations?mode=Returns and PATCH /customizations?mode=Returns manage colours, images and a few texts of the shopper portal of a mode. The PATCH is multipart/form-data, even when only colours change, because the images travel in it: fields logo, background, landingLogo and landingBackground (5 MB, png, jpeg, jpg or webp).

PATCH /customizations?mode=Returns
Content-Type: multipart/form-data; boundary=...

--...
Content-Disposition: form-data; name="buttonColor"

#1F6F43
--...
Content-Disposition: form-data; name="orderNameExample"

#1001
--...
Content-Disposition: form-data; name="logo"; filename="logo.png"
Content-Type: image/png

<binary>
--...--

Colours are hex (buttonColor, buttonTextColor, headerColor, headerTextColor, footerColor, footerTextColor, and the landing variants landingStartButtonColor, landingShopNowButtonColor, landingTitleColor, landingSubtitleColor with their text colours). orderNameExample is the placeholder of the login form, shopUrlLanding the target of the "shop now" button. skuPrefix is deprecated: the SKU prefix is the base policy's skuPrefixCircular (see The policy); sent with mode=Circular it is still accepted and also sets that policy value. Landing values left null fall back to the portal ones. blurBackground (true by default) blurs the background image behind the portal on large screens; send false to show it sharp. landingBlurBackground (false by default) does the same for the landing background image. showLanding is read-only: Drelife enables the landing page. The DELETE /customizations/logo, /background, /landingLogo, /landingBackground, /styleSettings, /landingStyleSettings and /landingTextColors calls reset each part.

Custom texts

The texts of the portal (Login, Instructions, Refund, SummaryInfo, SummaryAdditionalInfo, DoneTitle, DoneDescription, DoneHelpText, DoneStartButton, DoneShopButton, the landing and shop promo texts, the placeholders) are custom strings. GET /customizations/strings?mode=Returns lists them, creating the missing base strings of an enabled mode on the fly with their default text; PATCH /customizations/strings?mode=Returns takes an array whose items repeat the mode (the query only scopes the lookup and the returned list):

PATCH /customizations/strings?mode=Returns
Content-Type: application/json

[
  { "id": "5e1d...", "type": "Instructions", "mode": "Returns", "message": { "en": "Pack the items in their original box.", "es": "Empaqueta los artículos en su caja original." } },
  { "type": "Instructions", "mode": "Returns", "shippingType": "PickUp", "message": { "en": "The courier will collect the parcel at your address." } },
  { "id": "a0b1...", "type": "Instructions", "mode": "Returns", "shippingType": "DropOff", "toDelete": true }
]

The base string of a type is the one without shippingType and refundType; a specific string carries one of them and overrides the base one in that case. Deleting a base string makes the next GET re-create it with the default text.

Outgoing email account

Notifications leave from Drelife unless the shop configures its own SMTP account with PATCH /customizations/smtp (smtpEnabled, smtpHost, smtpPort and smtpUser always required, smtpPassword optional). When smtpEnabled is true the API connects to the server before saving, and answers 403 Invalid SMTP parameters when it cannot. The password is write-only: omit it to keep the stored one, send null to clear it; GET /customizations/smtp only tells hasSmtpPassword.

6. Access lists

Who can use the portals and what can be returned is controlled by blacklists and whitelists, all under /blacklist/... and /whitelist/... with ?mode=. Each list is { "enabled": true, "blacklist": [...] } (or "whitelist"), and a PATCH replaces the whole set and answers true.

ResourceListsEffect
/blacklist/access, /whitelist/accessemailsWho can log in to the portal of the mode
/blacklist/returns, /whitelist/returnsemails, orderNames, tags, orderTags (all four required)Which orders can be returned; Returns mode only
/blacklist/exchanges, /whitelist/exchangesemails, orderNames, tags, orderTags (optional)Which orders can be exchanged; Returns mode only
/blacklist/productsids, names, tags, customAttributesProducts that cannot be returned (no whitelist counterpart)
/whitelist/free-shippingemails, orderNames, tagsWho gets free return shipping
PATCH /blacklist/returns?mode=Returns
Content-Type: application/json

{
  "emails": { "enabled": true, "blacklist": ["[email protected]"] },
  "orderNames": { "enabled": true, "blacklist": ["^#B2B-.*"] },
  "tags": { "enabled": false, "blacklist": [] },
  "orderTags": { "enabled": true, "blacklist": ["final-sale"] }
}

orderNames and product names are regular expressions, not literals. The returns and exchanges lists answer 400 Returns and exchanges blacklist is only available for returns mode for any other mode.

7. Staff

GET /users lists the users of the shop the caller can see; POST /users creates one and returns its password once:

POST /users
Content-Type: application/json

{ "email": "[email protected]", "role": "Approver", "modes": ["Returns"] }
{
  "id": "c4d5...",
  "email": "[email protected]",
  "role": "Approver",
  "modes": ["Returns"],
  "password": "Xk9#..."
}

Roles: StoreOwner (everything except managing other owners), Manager (everything except managing owners and other managers), Approver (processes requests), PreApprover (verifies the parcels when they arrive and prepares second-life items), PreValidator (pre-validation only), Shop (a physical shop receiving parcels, with its own address at GET/PATCH /users/address) and Publisher (publishes second-life items; requires the shop to have Circular and exactly "modes": ["Circular"]). GET /users/role-descriptions/{language} returns a description of each role. The modes of a new user must be among the caller's modes (400 INVALID_MODES). Nobody can update or delete themselves; PATCH /users/{id} takes role, modes and a mandatory resetPassword boolean; true issues a new one-time password. POST /users/password (currentPassword, newPassword, confirmPassword) changes the caller's own password (8 to 20 characters with lower and upper case, a digit and a symbol; ten requests per minute).

8. Notifications

The emails and SMS sent to shoppers (and to the shop) are notifications, one per language, triggered by an action of the request:

POST /notifications
Content-Type: application/json

{
  "type": "Email",
  "name": "Return approved",
  "description": "Sent when the refund is issued",
  "locale": "en",
  "subject": "Your return #{{returnName}} has been approved",
  "template": "<html>...</html>",
  "blockList": "{...}",
  "action": "Approved",
  "recipientType": "Customer",
  "returnTypes": ["Return", "Exchange"],
  "carriers": [],
  "regions": [],
  "enabled": true
}

action is one of PendingPreValidation, PreValidated, PreValidationDeclined, PendingUserPreValidation, Payment, Preapproved, Predeclined, Process, Approved, Declined, Awaiting, InTransit, Delivered, Exception, Unprocess. recipientType is Customer, Merchant or Custom (then recipients lists the emails). Optional filters narrow when it fires: returnTypes (Return, Exchange, Purchase, Repair), carriers, shippingTypes, reasons, regions. template is the HTML actually sent and blockList the design of the email editor; update both together or the editor loses the design. POST /notifications/{id}/translate with { "locale": "fr" } creates a new notification in that language (400 SAME_LOCALE for the same one), POST /notifications/{id}/duplicate copies one, and POST /notifications/test sends a template to an email (204; 422 SMTP_NOT_CONFIGURED when neither the shop nor Drelife has a sending account).

9. Banners

Messages shown in the shopper portal at a given step:

POST /banners
Content-Type: application/json

{ "mode": "Returns", "type": "Warning", "location": "ConfirmationView", "title": { "en": "Sale items" }, "description": { "en": "Items bought on sale are refunded as store credit." }, "enabled": true }

type is Info, Success, Warning, Danger or Custom; location is Login, ConfirmationView, AdditionalInfoView, FilesStep or RefundDetails. A banner needs a title or a description.

Status codes and errors of this page

  • POST answers 200 for reasons, questions, categories, banners and notifications, and 201 for carriers and users; POST /notifications/test answers 204.
  • 404 <Resource> not found when the id does not belong to the shop; 404 Marketplace not found when the token is stale.
  • 400 ValidationFailed with the list of invalid fields in message for any body or query rule above.
  • 429 TooManyRequests when the per-minute quota is exceeded.

Once the shop is configured, the day-to-day work is described in Process returns and exchanges, Orders and Second life and repairs.


Did this page help you?