ILS Return-Request API v1.0
← Main Documentation Tracking API Postman Contact Support

ILS Return-Request API

Integrate the Indian Logistics Services return & exchange flow into your mobile application. Browse the customer's recent orders, request a return or exchange (with optional proof image, reason, refund mode, replacement variant), edit the shipping address before fulfilment, cancel an order, or reschedule a declined pickup — all through one clean JSON API.

Base URL
ils.shopiapps.in/prfiles/return-request/api/
Protocol
HTTPS only
Method
POST
Auth
Auth-Token header
Format
application/x-www-form-urlencoded
or multipart/form-data
Response
application/json

What this API gives you

  • Return-page configuration — theme, copy, reason list, refund modes — so your mobile UI matches the merchant's branding.
  • Order browsing & lookup — list orders by email (cursor-paginated, 60-day window) and load full details by order number + email/phone.
  • Self-service actions — cancel order, edit phone, edit shipping address (eligibility flags returned per-order).
  • Returns & exchanges — per-item return submission with reason, customer note, refund mode, exchange product / variant, and optional proof image (JPG/PNG, auto-resized).
  • Pickup management — reschedule a declined or cancelled pickup; customer + admin notifications dispatched automatically.

Pairs with the Tracking API

The same Auth-Token works for the Tracking API. One token gives access to both products, scoped to the same merchant.

Authentication

Every request must include:

ParameterTypeRequiredDescription
Auth-TokenstringRequiredLong-lived per-shop API token (see Get an API Token). Sent as an HTTP header.
Example: a1b2c3...
shopstringRequiredShopify shop domain, must end in .myshopify.com. Sent in the form body.
Example: examplestore.myshopify.com

Server-side checks

On every request, the server verifies the following in order:

  1. The ILS app is installed for the shop (app.app_status = 'installed').
  2. Payment status is active (free or accepted).
  3. The shop's plan includes the Return Request feature.
  4. The supplied Auth-Token matches the token stored for the shop.
CORS Access-Control-Allow-Origin: * is set, and OPTIONS preflight is handled. The endpoints can be called from a mobile WebView, React Native bridge, or web wrapper.

Get an API Token

The mobile-app access token is per-shop and issued by the ILS team. It is the same token used by the Tracking API — one token, two products.

How to request a token

  1. Email support@shopiapps.in with the subject "Mobile API token request".
  2. Include the shop domain (e.g. examplestore.myshopify.com), the mobile app name/platform (e.g. Acme Shop iOS), and a contact email.
  3. ILS support verifies the shop has an active plan that includes return_request and provisions the token.
  4. The token is delivered out-of-band (encrypted email or shared secret store).

Token properties

PropertyValue
FormatHex string, ~96 characters
LifetimeLong-lived (no expiry); rotated on demand
ScopeSingle Shopify shop, both Tracking & Return APIs
Server storagesettings.mobile_app_access_token

Rotation

If you suspect a token has been leaked, email support@shopiapps.in with [SECURITY] in the subject. The old token is revoked immediately on issue of a new one.

Storage guidance Never hard-code the token in your client binary or commit it to source control. Use platform secure storage — Keychain Services on iOS, EncryptedSharedPreferences / Keystore on Android.

Response Format

All responses — success and error — share the same envelope:

{
  "success": true,
  "code": "OK",
  "message": "Order found.",
  "data": { /* endpoint-specific payload */ },

  "result": "success",
  "msg": "Order found."
}
ParameterTypeRequiredDescription
successbooleanRequiredtrue for any 2xx response, false otherwise. Use this for branching.
codestringRequiredStable machine code: OK, INVALID_INPUT, UNAUTHORIZED, FORBIDDEN, PLAN_LIMIT, NOT_FOUND, CONFLICT, SERVER_ERROR. Safe to switch on.
messagestringRequiredHuman-readable message. Safe to display in UI.
dataobjectOptionalEndpoint-specific payload. Empty array on most error responses.
resultenumOptionalLegacy v1 alias of success — values: success / fail.
msgstringOptionalLegacy v1 alias of message.
Migration note New integrations should rely on success + code + data. The legacy result / msg keys (and the top-level data mirrors that some endpoints echo for v1 clients) are preserved indefinitely but will not be expanded with new fields.

Status Codes

HTTPCodeWhen you'll see it
200OKSuccessful response
400INVALID_INPUTMissing or malformed parameters; unknown action; field length exceeds limit
401UNAUTHORIZEDMissing / invalid Auth-Token
403FORBIDDENApp not installed for this shop, or payment status inactive
403PLAN_LIMITReturn-request feature not in the shop's plan
404NOT_FOUNDOrder / customer / product / address not found
409CONFLICTState conflict — order already cancelled, item already returned, fulfilment not started yet
500SERVER_ERRORUnexpected server error — retry, then contact support if persistent

Error response example

{
  "success": false,
  "code": "NOT_FOUND",
  "message": "Sorry we can't find your order.",
  "data": [],
  "result": "fail",
  "msg": "Sorry we can't find your order."
}

Order-state priority for write actions

Every write action (return_requests_submit, cancel_order, edit_customer_shipping_address, edit_customer_phone_number, reschedule_pickup) runs a priority-ordered precondition check before touching anything. The first failing rule wins — clients always get the most-actionable error.

PriorityRuleHTTPcodemessage
1Order doesn't exist 404NOT_FOUND"Sorry we can't find your order."
2Cancelled in Shopify 409CONFLICT"Order has been cancelled." (or "…already been cancelled." on cancel_order)
3Closed / archived 409CONFLICT"Order has been closed."
4Shipped (AWB on file) 409CONFLICT"Order has been shipped — this action is no longer available." (wording is action-specific)
5Not fulfilled yet 409CONFLICT"Order not fulfilled yet!" (only on return_requests_submit)
6Action-specific conflict 409CONFLICTe.g. "This item has already returned!"
Why this matters Without this chain, a cancelled order hitting return_requests_submit would report "Order not fulfilled yet!" because the cached fulfillments array is empty — misleading the user. The chain makes sure the cause is surfaced first.

Encrypted Identifiers

Every primary identifier exposed to the client is encrypted with the shop's key. Never treat these as opaque IDs you can substring, base64-decode, or pattern-match against — round-trip them: receive from the API, store, send back as-is.

IdentifierReturned bySent back to
order_idKeys in get_order_list; data.order_id in order_detailsorder_details, cancel_order, edit_customer_*, return_requests_submit, get_variant, get_products
customer_idorder_detailsget_customer_shipping_addresses, edit_customer_shipping_address
location_idget_customer_shipping_addressesedit_customer_shipping_address
item_id (lineitem)order_detailsreturn_requests_submit.product_data[].item_id
product_idorder_detailsget_variant, get_products, return_requests_submit
variant_idget_variant, get_productsreturn_requests_submit.product_data[].replace_variant_id
id (return request)order_details.line_items.<k>.return_data[].idreschedule_pickup.id
Multipart field-naming note For return_requests_submit proof images, the file field key is img_<decrypted_lineitem_id>. The server decrypts product_data[].item_id and matches against this raw numeric key.

Get Return Settings

POST https://ils.shopiapps.in/prfiles/return-request/api/ action = get_return_settings

Returns the merchant's per-shop return-page configuration: theme colours, copy, reason list, refund modes, and behaviour flags. Call on app launch or when the user enters the return flow and cache for the session.

Headers

ParameterTypeRequiredDescription
Auth-TokenstringRequiredPer-shop API token.
Example: a1b2c3d4...

Body parameters

ParameterTypeRequiredDescription
shopstringRequiredShopify shop domain.
Example: examplestore.myshopify.com
actionenumRequiredConstant.
Example: get_return_settings

Request example

curl --location 'https://ils.shopiapps.in/prfiles/return-request/api/' \
  --header 'Auth-Token: YOUR_TOKEN' \
  --data-urlencode 'shop=examplestore.myshopify.com' \
  --data-urlencode 'action=get_return_settings'
const body = new URLSearchParams({
  shop: 'examplestore.myshopify.com',
  action: 'get_return_settings',
});

const r = await fetch('https://ils.shopiapps.in/prfiles/return-request/api/', {
  method: 'POST',
  headers: { 'Auth-Token': 'YOUR_TOKEN' },
  body,
});
const json = await r.json();
if (!json.success) throw new Error(`${json.code}: ${json.message}`);
console.log(json.data);
val body = FormBody.Builder()
    .add("shop", "examplestore.myshopify.com")
    .add("action", "get_return_settings")
    .build()

val req = Request.Builder()
    .url("https://ils.shopiapps.in/prfiles/return-request/api/")
    .header("Auth-Token", "YOUR_TOKEN")
    .post(body)
    .build()

client.newCall(req).execute().use { response ->
    val json = JSONObject(response.body!!.string())
    if (!json.getBoolean("success")) {
        throw IOException("${json.getString("code")}: ${json.getString("message")}")
    }
}
var req = URLRequest(url: URL(string: "https://ils.shopiapps.in/prfiles/return-request/api/")!)
req.httpMethod = "POST"
req.setValue("YOUR_TOKEN", forHTTPHeaderField: "Auth-Token")
req.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
req.httpBody = "shop=examplestore.myshopify.com&action=get_return_settings".data(using: .utf8)

let (data, _) = try await URLSession.shared.data(for: req)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
<?php
$ch = curl_init('https://ils.shopiapps.in/prfiles/return-request/api/');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Auth-Token: YOUR_TOKEN'],
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'shop'   => 'examplestore.myshopify.com',
        'action' => 'get_return_settings',
    ]),
]);
$body = curl_exec($ch);
curl_close($ch);
$json = json_decode($body, true);

Response — 200 OK

{
  "success": true,
  "code": "OK",
  "message": "Return page settings loaded.",
  "data": {
    "login_page_heading": "RETURN & EXCHNAGE ORDERS",
    "login_page_description": "Let's start process! Please enter your details to show orders and customizations of it.",
    "show_login_banner": "1",
    "login_banner_image_url": "https://ils.shopiapps.in/return-images/examplestore_myshopify_com/banner.jpg?x=1717300000",
    "login_banner_position": "1",
    "heading_font_color": "#222222",
    "text_color": "#000000",
    "text_size": "13px",
    "button_font_color": "#ffffff",
    "button_color": "#0c4ca3",
    "custom_css": "",
    "contact_mail": "support@examplestore.com",
    "return_page_heading": "Return Order Request",
    "return_page_text_size": "12px",
    "return_button_text": "Return",
    "show_return_page_top_content": "0",
    "return_page_top_content": "",
    "show_return_page_bottom_content": "0",
    "return_page_bottom_content": "",
    "return_reasons": ["Wrong size", "Damaged on arrival", "Changed my mind"],
    "enable_return_option": "1",
    "return_options_list": "refund,exchange,store_credit",
    "return_options_text": { "refund": "Refund", "exchange": "Exchange", "store_credit": "Store credit" },
    "refund_modes": ["upi", "bank_transfer"],
    "selected_replace_option": "1",
    "exchange_summary": "Exchanges are dispatched within 3-5 working days.",
    "enable_customer_note": "1",
    "enable_return_proof_image": "1",
    "allow_partial_return": "1"
  },
  "result": "success",
  "msg": "Return page settings loaded."
}

Response fields (selected)

ParameterTypeRequiredDescription
data.login_page_headingstringRequiredHeading copy for the order-lookup screen.
data.login_page_descriptionstringRequiredSub-heading copy.
data.show_login_bannerenumRequired"0"/"1". When "1", render the banner at login_banner_image_url.
data.login_banner_image_urlstringOptionalAbsolute URL of the login banner (empty when disabled).
data.login_banner_positionenumOptional"0" = left, "1" = right.
data.heading_font_colorstringRequiredHex colour for headings.
data.text_colorstringRequiredBody text hex.
data.button_font_colorstringRequiredCTA text hex.
data.button_colorstringRequiredCTA background hex.
data.custom_cssstringOptionalCSS injected on the web tracker. Mobile clients should ignore.
data.contact_mailstringRequiredSupport email shown on the return screen.
data.return_page_headingstringRequiredTitle displayed on the return-request screen.
data.return_button_textstringRequiredLabel for the per-item Return button.
data.return_reasonsarrayRequiredReason list to populate the dropdown.
data.enable_return_optionenumRequired"1" = surface the return-option picker (refund / exchange / store credit).
data.return_options_liststringOptionalComma-separated list of enabled return options.
data.return_options_textobjectOptionalDisplay labels keyed by return-option code.
data.refund_modesarrayRequiredRefund payout methods (UPI, bank transfer, ...).
data.selected_replace_optionenumRequired"0": variant only · "1": any product (search via get_products).
data.exchange_summarystringOptionalOptional copy shown when the customer selects exchange.
data.enable_customer_noteenumRequired"1" = show the customer-note textarea.
data.enable_return_proof_imageenumRequired"1" = allow attaching a proof image with the return.
data.allow_partial_returnenumRequired"1" = allow returning fewer items than were ordered.

Errors

HTTPCodeWhen you'll see it
401UNAUTHORIZEDMissing / invalid Auth-Token
403FORBIDDENApp not installed
403PLAN_LIMITReturn Request not in plan

Get Order List

POST https://ils.shopiapps.in/prfiles/return-request/api/ action = get_order_list

Lists the customer's orders from the last 60 days. Cursor-paginated via Shopify's GraphQL after: parameter; the cursor is echoed as next_data.

Body parameters

ParameterTypeRequiredDescription
shopstringRequiredShopify shop domain.
Example: examplestore.myshopify.com
actionenumRequiredConstant.
Example: get_order_list
emailstringRequiredCustomer email (max 254 chars).
Example: jane@example.com
next_datastringOptionalCursor returned by a previous call. Empty / omitted on the first page.

Request example

curl --location 'https://ils.shopiapps.in/prfiles/return-request/api/' \
  --header 'Auth-Token: YOUR_TOKEN' \
  --data-urlencode 'shop=examplestore.myshopify.com' \
  --data-urlencode 'action=get_order_list' \
  --data-urlencode 'email=jane@example.com'
const body = new URLSearchParams({
  shop: 'examplestore.myshopify.com',
  action: 'get_order_list',
  email: 'jane@example.com',
});

const r = await fetch('https://ils.shopiapps.in/prfiles/return-request/api/', {
  method: 'POST',
  headers: { 'Auth-Token': 'YOUR_TOKEN' },
  body,
});
const json = await r.json();
if (!json.success) throw new Error(`${json.code}: ${json.message}`);
console.log(json.data);
val body = FormBody.Builder()
    .add("shop", "examplestore.myshopify.com")
    .add("action", "get_order_list")
    .add("email", "jane@example.com")
    .build()

val req = Request.Builder()
    .url("https://ils.shopiapps.in/prfiles/return-request/api/")
    .header("Auth-Token", "YOUR_TOKEN")
    .post(body)
    .build()

client.newCall(req).execute().use { response ->
    val json = JSONObject(response.body!!.string())
    if (!json.getBoolean("success")) {
        throw IOException("${json.getString("code")}: ${json.getString("message")}")
    }
}
var req = URLRequest(url: URL(string: "https://ils.shopiapps.in/prfiles/return-request/api/")!)
req.httpMethod = "POST"
req.setValue("YOUR_TOKEN", forHTTPHeaderField: "Auth-Token")
req.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
req.httpBody = "shop=examplestore.myshopify.com&action=get_order_list&email=jane%40example.com".data(using: .utf8)

let (data, _) = try await URLSession.shared.data(for: req)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
<?php
$ch = curl_init('https://ils.shopiapps.in/prfiles/return-request/api/');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Auth-Token: YOUR_TOKEN'],
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'shop'   => 'examplestore.myshopify.com',
        'action' => 'get_order_list',
        'email' => 'jane@example.com',
    ]),
]);
$body = curl_exec($ch);
curl_close($ch);
$json = json_decode($body, true);

Response — 200 OK

{
  "success": true,
  "code": "OK",
  "message": "Orders found.",
  "data": {
    "<encrypted_o_id>": {
      "name": "#1042",
      "total_price": "₹1499.00",
      "line_item": {
        "title": "Cotton T-Shirt",
        "src": "https://cdn.shopify.com/.../tshirt.jpg",
        "vendor": "Acme",
        "variant": { "title": "M / Blue" },
        "sku": "TS-M-BLUE"
      },
      "created_date": "21 May, 2026 14:03:22",
      "status_name": "Fulfilled",
      "payment_mode": "Pre-paid"
    }
  },
  "next_data": "eyJsYXN0X2lkIjoxMjM0fQ==",
  "result": "success",
  "msg": "Orders found."
}

Response fields

ParameterTypeRequiredDescription
data.&lt;encrypted_o_id&gt;objectRequiredMap keyed by encrypted order id. Pass that key as order_id to subsequent endpoints.
data.&lt;k&gt;.namestringRequiredShopify order display name (e.g. #1042).
data.&lt;k&gt;.total_pricestringRequiredPre-formatted total with shop currency symbol.
data.&lt;k&gt;.line_itemobjectOptionalFirst line item as a cover preview — title, src, vendor, variant, sku.
data.&lt;k&gt;.created_datestringRequiredPre-formatted created date.
Example: 21 May, 2026 14:03:22
data.&lt;k&gt;.status_namestringRequiredHuman-readable order/shipment status (e.g. "Fulfilled", "Cancelled", "Manifested", "Completed", "Failed to delivered").
data.&lt;k&gt;.payment_modeenumRequiredCOD or Pre-paid.
next_datastringOptionalCursor to pass back as next_data for the next page. Empty when there is no further page.

Errors

HTTPCodeWhen you'll see it
400INVALID_INPUTemail missing or longer than 254 characters
404NOT_FOUNDNo orders in the last 60 days for this email

Get Order Details

POST https://ils.shopiapps.in/prfiles/return-request/api/ action = order_details

Returns the full order payload for the return-request screen — delivery address, courier + AWB, per-line-item returnable flag, days remaining, existing returns/exchanges, plus flags telling the UI whether the customer can still edit the address / phone / cancel the order.

Heavy endpoint Combines Shopify GraphQL (order + line items + customer + media), a cached order JSON, and several SQL lookups (order_detail, approve_package, fulfillment_detail, reverse_pickup, reverse_pickup_settings). Cache the response client-side for 30–60 seconds.

Body parameters

ParameterTypeRequiredDescription
shopstringRequiredShopify shop domain.
Example: examplestore.myshopify.com
actionenumRequiredConstant.
Example: order_details
order_namestringRequiredOrder number (max 128 chars). Shopify display name (#1042) or plain numeric.
Example: #1042
emailstringRequiredEmail or 10-digit phone. Spaces, +, and - are stripped; only the trailing 10 digits are compared.
Example: jane@example.com

Request example

curl --location 'https://ils.shopiapps.in/prfiles/return-request/api/' \
  --header 'Auth-Token: YOUR_TOKEN' \
  --data-urlencode 'shop=examplestore.myshopify.com' \
  --data-urlencode 'action=order_details' \
  --data-urlencode 'order_name=#1042' \
  --data-urlencode 'email=jane@example.com'
const body = new URLSearchParams({
  shop: 'examplestore.myshopify.com',
  action: 'order_details',
  order_name: '#1042',
  email: 'jane@example.com',
});

const r = await fetch('https://ils.shopiapps.in/prfiles/return-request/api/', {
  method: 'POST',
  headers: { 'Auth-Token': 'YOUR_TOKEN' },
  body,
});
const json = await r.json();
if (!json.success) throw new Error(`${json.code}: ${json.message}`);
console.log(json.data);
val body = FormBody.Builder()
    .add("shop", "examplestore.myshopify.com")
    .add("action", "order_details")
    .add("order_name", "#1042")
    .add("email", "jane@example.com")
    .build()

val req = Request.Builder()
    .url("https://ils.shopiapps.in/prfiles/return-request/api/")
    .header("Auth-Token", "YOUR_TOKEN")
    .post(body)
    .build()

client.newCall(req).execute().use { response ->
    val json = JSONObject(response.body!!.string())
    if (!json.getBoolean("success")) {
        throw IOException("${json.getString("code")}: ${json.getString("message")}")
    }
}
var req = URLRequest(url: URL(string: "https://ils.shopiapps.in/prfiles/return-request/api/")!)
req.httpMethod = "POST"
req.setValue("YOUR_TOKEN", forHTTPHeaderField: "Auth-Token")
req.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
req.httpBody = "shop=examplestore.myshopify.com&action=order_details&order_name=%231042&email=jane%40example.com".data(using: .utf8)

let (data, _) = try await URLSession.shared.data(for: req)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
<?php
$ch = curl_init('https://ils.shopiapps.in/prfiles/return-request/api/');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Auth-Token: YOUR_TOKEN'],
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'shop'   => 'examplestore.myshopify.com',
        'action' => 'order_details',
        'order_name' => '#1042',
        'email' => 'jane@example.com',
    ]),
]);
$body = curl_exec($ch);
curl_close($ch);
$json = json_decode($body, true);

Response — 200 OK

{
  "success": true,
  "code": "OK",
  "message": "Order found.",
  "data": {
    "order_id": "<encrypted>",
    "order_name": "#1042",
    "order_date": "21 May, 2026",
    "customer_id": "<encrypted>",
    "delivery_address": {
      "name": "Jane Doe",
      "address1": "Flat 1, Main Street",
      "address2": "",
      "city": "Bengaluru",
      "province": "Karnataka",
      "provinceCode": "KA",
      "zip": "560001",
      "phone": "9876543210"
    },
    "order_shipment_data": {
      "courier_service": "Delhivery",
      "awb": "DLV1234567890",
      "show_shipment": 1,
      "track_status": 4,
      "order_status_list": ["Ordered", "Packed", "Shipped", "Delivered"]
    },
    "show_return_btn": true,
    "allow_edit_phone": false,
    "allow_edit_address": false,
    "allow_cancel_order": false,
    "line_items": {
      "<encrypted_l_id>": {
        "title": "Cotton T-Shirt",
        "handle": "cotton-tshirt",
        "vendor": "Acme",
        "price": "998.00",
        "unit_price": "499.00",
        "total_qty": 2,
        "variant_id": "<encrypted>",
        "src": "https://cdn.shopify.com/.../tshirt.jpg",
        "item_id": "<encrypted>",
        "product_id": "<encrypted>",
        "is_fulfilled": true,
        "fulfill_qty": 2,
        "remain_days": 7,
        "unfulfilled_qty": 0,
        "return": "true",
        "msg": "<strong>7</strong> Days remain to return",
        "remain_return_qty": 2,
        "item_tracking_url": "",
        "re_schedule_pickup": false
      }
    }
  }
}

Response fields

ParameterTypeRequiredDescription
data.order_idstringRequiredEncrypted order id.
data.order_namestringRequiredShopify order display name.
data.order_datestringRequiredPre-formatted date.
Example: 21 May, 2026
data.customer_idstringRequiredEncrypted customer id. Pass to get_customer_shipping_addresses.
data.delivery_addressobjectRequiredShipping address (falls back through shipping → billing → customer default).
data.order_shipment_data.courier_servicestringOptionalDisplay name of the courier (e.g. "Delhivery").
data.order_shipment_data.awbstringOptionalAWB / tracking number (empty until manifested).
data.order_shipment_data.show_shipmentenumRequired0: order ships via another app (no progress bar). 1: shipped via ILS — render order_status_list.
data.order_shipment_data.track_statusintegerRequired1 ordered · 2 packed · 3 shipped · 4 delivered · 5 RTO · 6 cancelled · 7 NDR.
data.order_shipment_data.order_status_listarrayOptional4-cell timeline labels (e.g. ["Ordered","Packed","Shipped","Delivered"]) — only when show_shipment = 1.
data.show_return_btnbooleanRequiredtrue if at least one line item is currently returnable.
data.allow_edit_phonebooleanRequiredWhether the customer can call edit_customer_phone_number for this order.
data.allow_edit_addressbooleanRequiredWhether the customer can call edit_customer_shipping_address.
data.allow_cancel_orderbooleanRequiredWhether the customer can call cancel_order.
data.line_items.&lt;encrypted_l_id&gt;objectRequiredMap keyed by encrypted lineitem id.
data.line_items.&lt;k&gt;.titlestringRequiredProduct title.
data.line_items.&lt;k&gt;.handlestringOptionalShopify product handle.
data.line_items.&lt;k&gt;.pricestringRequiredLine total.
data.line_items.&lt;k&gt;.unit_pricestringRequiredPer-unit price.
data.line_items.&lt;k&gt;.total_qtyintegerRequiredQuantity ordered.
data.line_items.&lt;k&gt;.fulfill_qtyintegerOptionalQuantity fulfilled so far.
data.line_items.&lt;k&gt;.remain_daysintegerOptionalDays remaining in the return window.
data.line_items.&lt;k&gt;.returnenumRequired"true" if the customer can submit a return for this line.
data.line_items.&lt;k&gt;.msgstringOptionalHuman-readable explanation when return = "false" (e.g. "Not Shipped yet", "Return days limit exceeded.", "Not Returnable"). May contain HTML <strong> for days remaining.
data.line_items.&lt;k&gt;.remain_return_qtystringOptionalQuantity still returnable. "N/A" when zero.
data.line_items.&lt;k&gt;.return_data[]arrayOptionalExisting return rows (if any) — id (encrypted), status, quantity, return_reason, exchange_product_details.
data.line_items.&lt;k&gt;.re_schedule_pickupbooleanRequiredtrue if the customer can call reschedule_pickup against this line's return.
data.line_items.&lt;k&gt;.item_tracking_urlstringOptionalPublic courier tracking URL for an approved return (empty otherwise).
data.line_items.&lt;k&gt;.variant_idstringRequiredEncrypted variant id.
data.line_items.&lt;k&gt;.product_idstringRequiredEncrypted product id — pass to get_variant / get_products.
data.line_items.&lt;k&gt;.item_idstringRequiredEncrypted lineitem id — pass as item_id in return_requests_submit.

Errors

HTTPCodeWhen you'll see it
400INVALID_INPUTorder_name or email missing
404NOT_FOUNDNo order matches order_name + email/phone for this shop
409CONFLICTOrder has been cancelled

Get Customer Shipping Addresses

POST https://ils.shopiapps.in/prfiles/return-request/api/ action = get_customer_shipping_addresses

Lists all addresses on file for the customer (Shopify customer.addresses[]). Used to populate the "change shipping address" picker.

Body parameters

ParameterTypeRequiredDescription
shopstringRequiredShopify shop domain.
Example: examplestore.myshopify.com
actionenumRequiredConstant.
Example: get_customer_shipping_addresses
customer_idstringRequiredEncrypted customer id from order_details.

Request example

curl --location 'https://ils.shopiapps.in/prfiles/return-request/api/' \
  --header 'Auth-Token: YOUR_TOKEN' \
  --data-urlencode 'shop=examplestore.myshopify.com' \
  --data-urlencode 'action=get_customer_shipping_addresses' \
  --data-urlencode 'customer_id=<encrypted_customer_id>'
const body = new URLSearchParams({
  shop: 'examplestore.myshopify.com',
  action: 'get_customer_shipping_addresses',
  customer_id: '<encrypted_customer_id>',
});

const r = await fetch('https://ils.shopiapps.in/prfiles/return-request/api/', {
  method: 'POST',
  headers: { 'Auth-Token': 'YOUR_TOKEN' },
  body,
});
const json = await r.json();
if (!json.success) throw new Error(`${json.code}: ${json.message}`);
console.log(json.data);
val body = FormBody.Builder()
    .add("shop", "examplestore.myshopify.com")
    .add("action", "get_customer_shipping_addresses")
    .add("customer_id", "<encrypted_customer_id>")
    .build()

val req = Request.Builder()
    .url("https://ils.shopiapps.in/prfiles/return-request/api/")
    .header("Auth-Token", "YOUR_TOKEN")
    .post(body)
    .build()

client.newCall(req).execute().use { response ->
    val json = JSONObject(response.body!!.string())
    if (!json.getBoolean("success")) {
        throw IOException("${json.getString("code")}: ${json.getString("message")}")
    }
}
var req = URLRequest(url: URL(string: "https://ils.shopiapps.in/prfiles/return-request/api/")!)
req.httpMethod = "POST"
req.setValue("YOUR_TOKEN", forHTTPHeaderField: "Auth-Token")
req.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
req.httpBody = "shop=examplestore.myshopify.com&action=get_customer_shipping_addresses&customer_id=%3Cencrypted_customer_id%3E".data(using: .utf8)

let (data, _) = try await URLSession.shared.data(for: req)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
<?php
$ch = curl_init('https://ils.shopiapps.in/prfiles/return-request/api/');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Auth-Token: YOUR_TOKEN'],
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'shop'   => 'examplestore.myshopify.com',
        'action' => 'get_customer_shipping_addresses',
        'customer_id' => '<encrypted_customer_id>',
    ]),
]);
$body = curl_exec($ch);
curl_close($ch);
$json = json_decode($body, true);

Response — 200 OK

{
  "success": true,
  "code": "OK",
  "message": "Shipping addresses found.",
  "data": [
    {
      "location_id": "<encrypted>",
      "first_name": "Jane",
      "last_name": "Doe",
      "company": "",
      "address1": "Flat 1, Main Street",
      "address2": "",
      "city": "Bengaluru",
      "province": "Karnataka",
      "country": "India",
      "zip": "560001",
      "phone": "9876543210",
      "name": "Jane Doe",
      "province_code": "KA",
      "country_code": "IN",
      "country_name": "India"
    }
  ]
}

Response fields

ParameterTypeRequiredDescription
data[]arrayRequiredList of saved addresses.
data[].location_idstringRequiredEncrypted address id — pass to edit_customer_shipping_address.
data[].first_namestringOptionalRecipient first name.
data[].last_namestringOptionalRecipient last name.
data[].companystringOptionalCompany name.
data[].address1stringRequiredStreet / building.
data[].address2stringOptionalApt / suite / extra line.
data[].citystringRequiredCity.
data[].provincestringRequiredState / province (display name).
data[].province_codestringRequiredISO province code (e.g. KA).
data[].countrystringRequiredCountry (display name).
data[].country_codestringRequiredISO country code (e.g. IN).
data[].country_namestringRequiredAlias of country.
data[].zipstringRequiredPostal code.
data[].phonestringOptionalContact phone.
data[].namestringOptionalRecipient full name (Shopify-computed).

Errors

HTTPCodeWhen you'll see it
400INVALID_INPUTcustomer_id missing or undecryptable
404NOT_FOUNDCustomer not found, or no saved addresses

Edit Shipping Address

POST https://ils.shopiapps.in/prfiles/return-request/api/ action = edit_customer_shipping_address

Replaces the order's shipping address with one of the customer's saved addresses (selected by location_id).

Eligibility Only callable while data.allow_edit_address == true from order_details. Once the order is fulfilled or has an AWB assigned, the merchant disables this and Shopify will reject the underlying mutation.

Body parameters

ParameterTypeRequiredDescription
shopstringRequiredShopify shop domain.
Example: examplestore.myshopify.com
actionenumRequiredConstant.
Example: edit_customer_shipping_address
order_idstringRequiredEncrypted order id.
customer_idstringRequiredEncrypted customer id.
location_idstringRequiredEncrypted address id from get_customer_shipping_addresses.

Request example

curl --location 'https://ils.shopiapps.in/prfiles/return-request/api/' \
  --header 'Auth-Token: YOUR_TOKEN' \
  --data-urlencode 'shop=examplestore.myshopify.com' \
  --data-urlencode 'action=edit_customer_shipping_address' \
  --data-urlencode 'order_id=<encrypted_order_id>' \
  --data-urlencode 'customer_id=<encrypted_customer_id>' \
  --data-urlencode 'location_id=<encrypted_location_id>'
const body = new URLSearchParams({
  shop: 'examplestore.myshopify.com',
  action: 'edit_customer_shipping_address',
  order_id: '<encrypted_order_id>',
  customer_id: '<encrypted_customer_id>',
  location_id: '<encrypted_location_id>',
});

const r = await fetch('https://ils.shopiapps.in/prfiles/return-request/api/', {
  method: 'POST',
  headers: { 'Auth-Token': 'YOUR_TOKEN' },
  body,
});
const json = await r.json();
if (!json.success) throw new Error(`${json.code}: ${json.message}`);
console.log(json.data);
val body = FormBody.Builder()
    .add("shop", "examplestore.myshopify.com")
    .add("action", "edit_customer_shipping_address")
    .add("order_id", "<encrypted_order_id>")
    .add("customer_id", "<encrypted_customer_id>")
    .add("location_id", "<encrypted_location_id>")
    .build()

val req = Request.Builder()
    .url("https://ils.shopiapps.in/prfiles/return-request/api/")
    .header("Auth-Token", "YOUR_TOKEN")
    .post(body)
    .build()

client.newCall(req).execute().use { response ->
    val json = JSONObject(response.body!!.string())
    if (!json.getBoolean("success")) {
        throw IOException("${json.getString("code")}: ${json.getString("message")}")
    }
}
var req = URLRequest(url: URL(string: "https://ils.shopiapps.in/prfiles/return-request/api/")!)
req.httpMethod = "POST"
req.setValue("YOUR_TOKEN", forHTTPHeaderField: "Auth-Token")
req.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
req.httpBody = "shop=examplestore.myshopify.com&action=edit_customer_shipping_address&order_id=%3Cencrypted_order_id%3E&customer_id=%3Cencrypted_customer_id%3E&location_id=%3Cencrypted_location_id%3E".data(using: .utf8)

let (data, _) = try await URLSession.shared.data(for: req)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
<?php
$ch = curl_init('https://ils.shopiapps.in/prfiles/return-request/api/');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Auth-Token: YOUR_TOKEN'],
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'shop'   => 'examplestore.myshopify.com',
        'action' => 'edit_customer_shipping_address',
        'order_id' => '<encrypted_order_id>',
        'customer_id' => '<encrypted_customer_id>',
        'location_id' => '<encrypted_location_id>',
    ]),
]);
$body = curl_exec($ch);
curl_close($ch);
$json = json_decode($body, true);

Response — 200 OK

{
  "success": true,
  "code": "OK",
  "message": "Shipping address changed successfully.",
  "data": {
    "firstName": "Jane",
    "lastName": "Doe",
    "address1": "Flat 1, Main Street",
    "address2": "",
    "city": "Bengaluru",
    "company": "",
    "countryCode": "IN",
    "provinceCode": "KA",
    "phone": "9876543210",
    "zip": "560001",
    "name": "Jane Doe"
  }
}

Response fields

ParameterTypeRequiredDescription
dataobjectRequiredThe address object that was applied (mirrors Shopify's saved-address shape). Useful for refreshing the UI without an extra round-trip.

Errors

HTTPCodeWhen you'll see it
400INVALID_INPUTAny of order_id, customer_id, location_id missing or undecryptable
404NOT_FOUNDOrder not found, customer not found, or the chosen address id is not in the customer's book
409CONFLICTPriority-ordered: cancelled → "Order has been cancelled." · shipped → "Order has been shipped — the address can no longer be edited."
500SERVER_ERRORShopify rejected the order-update mutation

Edit Phone Number

POST https://ils.shopiapps.in/prfiles/return-request/api/ action = edit_customer_phone_number

Updates the phone number on the order's shipping address. Same eligibility window as Edit Address.

Body parameters

ParameterTypeRequiredDescription
shopstringRequiredShopify shop domain.
Example: examplestore.myshopify.com
actionenumRequiredConstant.
Example: edit_customer_phone_number
order_idstringRequiredEncrypted order id.
phone_numberstringRequiredNew phone number (max 32 chars).
Example: 9876543210

Request example

curl --location 'https://ils.shopiapps.in/prfiles/return-request/api/' \
  --header 'Auth-Token: YOUR_TOKEN' \
  --data-urlencode 'shop=examplestore.myshopify.com' \
  --data-urlencode 'action=edit_customer_phone_number' \
  --data-urlencode 'order_id=<encrypted_order_id>' \
  --data-urlencode 'phone_number=9876543210'
const body = new URLSearchParams({
  shop: 'examplestore.myshopify.com',
  action: 'edit_customer_phone_number',
  order_id: '<encrypted_order_id>',
  phone_number: '9876543210',
});

const r = await fetch('https://ils.shopiapps.in/prfiles/return-request/api/', {
  method: 'POST',
  headers: { 'Auth-Token': 'YOUR_TOKEN' },
  body,
});
const json = await r.json();
if (!json.success) throw new Error(`${json.code}: ${json.message}`);
console.log(json.data);
val body = FormBody.Builder()
    .add("shop", "examplestore.myshopify.com")
    .add("action", "edit_customer_phone_number")
    .add("order_id", "<encrypted_order_id>")
    .add("phone_number", "9876543210")
    .build()

val req = Request.Builder()
    .url("https://ils.shopiapps.in/prfiles/return-request/api/")
    .header("Auth-Token", "YOUR_TOKEN")
    .post(body)
    .build()

client.newCall(req).execute().use { response ->
    val json = JSONObject(response.body!!.string())
    if (!json.getBoolean("success")) {
        throw IOException("${json.getString("code")}: ${json.getString("message")}")
    }
}
var req = URLRequest(url: URL(string: "https://ils.shopiapps.in/prfiles/return-request/api/")!)
req.httpMethod = "POST"
req.setValue("YOUR_TOKEN", forHTTPHeaderField: "Auth-Token")
req.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
req.httpBody = "shop=examplestore.myshopify.com&action=edit_customer_phone_number&order_id=%3Cencrypted_order_id%3E&phone_number=9876543210".data(using: .utf8)

let (data, _) = try await URLSession.shared.data(for: req)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
<?php
$ch = curl_init('https://ils.shopiapps.in/prfiles/return-request/api/');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Auth-Token: YOUR_TOKEN'],
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'shop'   => 'examplestore.myshopify.com',
        'action' => 'edit_customer_phone_number',
        'order_id' => '<encrypted_order_id>',
        'phone_number' => '9876543210',
    ]),
]);
$body = curl_exec($ch);
curl_close($ch);
$json = json_decode($body, true);

Response — 200 OK

{
  "success": true,
  "code": "OK",
  "message": "Phone number changed successfully.",
  "data": "9876543210"
}

Response fields

ParameterTypeRequiredDescription
datastringRequiredThe phone string that was applied (echoed back for UI confirmation).

Errors

HTTPCodeWhen you'll see it
400INVALID_INPUTorder_id or phone_number missing
404NOT_FOUNDOrder not found for this shop
409CONFLICTPriority-ordered: cancelled → "Order has been cancelled." · shipped → "Order has been shipped — the phone number can no longer be edited."
500SERVER_ERRORShopify rejected the order-update mutation

Cancel Order

POST https://ils.shopiapps.in/prfiles/return-request/api/ action = cancel_order

Cancels the Shopify order on the customer's behalf with reason = CUSTOMER, refund = true, restock = true, notifyCustomer = true.

Eligibility Only callable while data.allow_cancel_order == true from order_details. Once an AWB exists or the order is fulfilled, this returns 409 CONFLICT with the Shopify userErrors joined by <br />.

Body parameters

ParameterTypeRequiredDescription
shopstringRequiredShopify shop domain.
Example: examplestore.myshopify.com
actionenumRequiredConstant.
Example: cancel_order
order_idstringRequiredEncrypted order id.

Request example

curl --location 'https://ils.shopiapps.in/prfiles/return-request/api/' \
  --header 'Auth-Token: YOUR_TOKEN' \
  --data-urlencode 'shop=examplestore.myshopify.com' \
  --data-urlencode 'action=cancel_order' \
  --data-urlencode 'order_id=<encrypted_order_id>'
const body = new URLSearchParams({
  shop: 'examplestore.myshopify.com',
  action: 'cancel_order',
  order_id: '<encrypted_order_id>',
});

const r = await fetch('https://ils.shopiapps.in/prfiles/return-request/api/', {
  method: 'POST',
  headers: { 'Auth-Token': 'YOUR_TOKEN' },
  body,
});
const json = await r.json();
if (!json.success) throw new Error(`${json.code}: ${json.message}`);
console.log(json.data);
val body = FormBody.Builder()
    .add("shop", "examplestore.myshopify.com")
    .add("action", "cancel_order")
    .add("order_id", "<encrypted_order_id>")
    .build()

val req = Request.Builder()
    .url("https://ils.shopiapps.in/prfiles/return-request/api/")
    .header("Auth-Token", "YOUR_TOKEN")
    .post(body)
    .build()

client.newCall(req).execute().use { response ->
    val json = JSONObject(response.body!!.string())
    if (!json.getBoolean("success")) {
        throw IOException("${json.getString("code")}: ${json.getString("message")}")
    }
}
var req = URLRequest(url: URL(string: "https://ils.shopiapps.in/prfiles/return-request/api/")!)
req.httpMethod = "POST"
req.setValue("YOUR_TOKEN", forHTTPHeaderField: "Auth-Token")
req.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
req.httpBody = "shop=examplestore.myshopify.com&action=cancel_order&order_id=%3Cencrypted_order_id%3E".data(using: .utf8)

let (data, _) = try await URLSession.shared.data(for: req)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
<?php
$ch = curl_init('https://ils.shopiapps.in/prfiles/return-request/api/');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Auth-Token: YOUR_TOKEN'],
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'shop'   => 'examplestore.myshopify.com',
        'action' => 'cancel_order',
        'order_id' => '<encrypted_order_id>',
    ]),
]);
$body = curl_exec($ch);
curl_close($ch);
$json = json_decode($body, true);

Response — 200 OK

{
  "success": true,
  "code": "OK",
  "message": "Order cancelled successfully.",
  "data": []
}

Errors

HTTPCodeWhen you'll see it
400INVALID_INPUTorder_id missing or undecryptable
404NOT_FOUNDOrder not found for this shop
409CONFLICTPriority-ordered: already cancelled → "Order has already been cancelled." · shipped (AWB on file) → "Order has been shipped — it can no longer be cancelled." · otherwise Shopify userError text (<br />-joined).

Get Exchange Variants

POST https://ils.shopiapps.in/prfiles/return-request/api/ action = get_variant

Lists the variants of a single product the customer is exchanging into. Honours the shop's "hide out-of-stock variants" and "only same-price exchange" flags from reverse_pickup_settings.

Body parameters

ParameterTypeRequiredDescription
shopstringRequiredShopify shop domain.
Example: examplestore.myshopify.com
actionenumRequiredConstant.
Example: get_variant
order_idstringRequiredEncrypted order id (used for the same-price filter against order_items).
product_idstringRequiredEncrypted product id of the exchange target.

Request example

curl --location 'https://ils.shopiapps.in/prfiles/return-request/api/' \
  --header 'Auth-Token: YOUR_TOKEN' \
  --data-urlencode 'shop=examplestore.myshopify.com' \
  --data-urlencode 'action=get_variant' \
  --data-urlencode 'order_id=<encrypted_order_id>' \
  --data-urlencode 'product_id=<encrypted_product_id>'
const body = new URLSearchParams({
  shop: 'examplestore.myshopify.com',
  action: 'get_variant',
  order_id: '<encrypted_order_id>',
  product_id: '<encrypted_product_id>',
});

const r = await fetch('https://ils.shopiapps.in/prfiles/return-request/api/', {
  method: 'POST',
  headers: { 'Auth-Token': 'YOUR_TOKEN' },
  body,
});
const json = await r.json();
if (!json.success) throw new Error(`${json.code}: ${json.message}`);
console.log(json.data);
val body = FormBody.Builder()
    .add("shop", "examplestore.myshopify.com")
    .add("action", "get_variant")
    .add("order_id", "<encrypted_order_id>")
    .add("product_id", "<encrypted_product_id>")
    .build()

val req = Request.Builder()
    .url("https://ils.shopiapps.in/prfiles/return-request/api/")
    .header("Auth-Token", "YOUR_TOKEN")
    .post(body)
    .build()

client.newCall(req).execute().use { response ->
    val json = JSONObject(response.body!!.string())
    if (!json.getBoolean("success")) {
        throw IOException("${json.getString("code")}: ${json.getString("message")}")
    }
}
var req = URLRequest(url: URL(string: "https://ils.shopiapps.in/prfiles/return-request/api/")!)
req.httpMethod = "POST"
req.setValue("YOUR_TOKEN", forHTTPHeaderField: "Auth-Token")
req.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
req.httpBody = "shop=examplestore.myshopify.com&action=get_variant&order_id=%3Cencrypted_order_id%3E&product_id=%3Cencrypted_product_id%3E".data(using: .utf8)

let (data, _) = try await URLSession.shared.data(for: req)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
<?php
$ch = curl_init('https://ils.shopiapps.in/prfiles/return-request/api/');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Auth-Token: YOUR_TOKEN'],
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'shop'   => 'examplestore.myshopify.com',
        'action' => 'get_variant',
        'order_id' => '<encrypted_order_id>',
        'product_id' => '<encrypted_product_id>',
    ]),
]);
$body = curl_exec($ch);
curl_close($ch);
$json = json_decode($body, true);

Response — 200 OK

{
  "success": true,
  "code": "OK",
  "message": "Variants found.",
  "data": [
    { "id": "<encrypted>", "title": "M / Blue",  "price": "499.00", "compare_at_price": null },
    { "id": "<encrypted>", "title": "L / Blue",  "price": "499.00", "compare_at_price": null }
  ]
}

Response fields

ParameterTypeRequiredDescription
data[]arrayRequiredVariants that pass the visibility + price filters.
data[].idstringRequiredEncrypted variant id — pass as replace_variant_id in return_requests_submit.
data[].titlestringRequiredVariant title (e.g. "M / Blue").
data[].pricestringRequiredPer-unit price.
data[].compare_at_pricestringOptionalCompare-at (strike-through) price, or null.

Errors

HTTPCodeWhen you'll see it
400INVALID_INPUTMissing/undecryptable order_id or product_id
404NOT_FOUNDProduct not found, or no variants pass the filter (single-variant "Default Title", or all out-of-stock when hide flag is on)

Get Exchange Products

POST https://ils.shopiapps.in/prfiles/return-request/api/ action = get_products

Paginated product search for the "exchange with another product" flow. Title-prefix search via Shopify GraphQL; respects only_same_price_product_display. Page size 10.

Body parameters

ParameterTypeRequiredDescription
shopstringRequiredShopify shop domain.
Example: examplestore.myshopify.com
actionenumRequiredConstant.
Example: get_products
order_idstringRequiredEncrypted order id.
product_idstringRequiredEncrypted product id of the line item being exchanged (used for same-price filtering).
searchstringOptionalJSON: {"type":"next|previous","cursor":"...","search_pro":"shirt"}. Leave empty on first call.
Example: {"type":"next","cursor":"eyJsYXN0X2lkIjox...","search_pro":""}

Request example

curl --location 'https://ils.shopiapps.in/prfiles/return-request/api/' \
  --header 'Auth-Token: YOUR_TOKEN' \
  --data-urlencode 'shop=examplestore.myshopify.com' \
  --data-urlencode 'action=get_products' \
  --data-urlencode 'order_id=<encrypted_order_id>' \
  --data-urlencode 'product_id=<encrypted_product_id>' \
  --data-urlencode 'search={"type":"","cursor":"","search_pro":""}'
const body = new URLSearchParams({
  shop: 'examplestore.myshopify.com',
  action: 'get_products',
  order_id: '<encrypted_order_id>',
  product_id: '<encrypted_product_id>',
  search: '{"type":"","cursor":"","search_pro":""}',
});

const r = await fetch('https://ils.shopiapps.in/prfiles/return-request/api/', {
  method: 'POST',
  headers: { 'Auth-Token': 'YOUR_TOKEN' },
  body,
});
const json = await r.json();
if (!json.success) throw new Error(`${json.code}: ${json.message}`);
console.log(json.data);
val body = FormBody.Builder()
    .add("shop", "examplestore.myshopify.com")
    .add("action", "get_products")
    .add("order_id", "<encrypted_order_id>")
    .add("product_id", "<encrypted_product_id>")
    .add("search", "{\"type\":\"\",\"cursor\":\"\",\"search_pro\":\"\"}")
    .build()

val req = Request.Builder()
    .url("https://ils.shopiapps.in/prfiles/return-request/api/")
    .header("Auth-Token", "YOUR_TOKEN")
    .post(body)
    .build()

client.newCall(req).execute().use { response ->
    val json = JSONObject(response.body!!.string())
    if (!json.getBoolean("success")) {
        throw IOException("${json.getString("code")}: ${json.getString("message")}")
    }
}
var req = URLRequest(url: URL(string: "https://ils.shopiapps.in/prfiles/return-request/api/")!)
req.httpMethod = "POST"
req.setValue("YOUR_TOKEN", forHTTPHeaderField: "Auth-Token")
req.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
req.httpBody = "shop=examplestore.myshopify.com&action=get_products&order_id=%3Cencrypted_order_id%3E&product_id=%3Cencrypted_product_id%3E&search=%7B%22type%22%3A%22%22%2C%22cursor%22%3A%22%22%2C%22search_pro%22%3A%22%22%7D".data(using: .utf8)

let (data, _) = try await URLSession.shared.data(for: req)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
<?php
$ch = curl_init('https://ils.shopiapps.in/prfiles/return-request/api/');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Auth-Token: YOUR_TOKEN'],
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'shop'   => 'examplestore.myshopify.com',
        'action' => 'get_products',
        'order_id' => '<encrypted_order_id>',
        'product_id' => '<encrypted_product_id>',
        'search' => '{"type":"","cursor":"","search_pro":""}',
    ]),
]);
$body = curl_exec($ch);
curl_close($ch);
$json = json_decode($body, true);

Response — 200 OK

{
  "success": true,
  "code": "OK",
  "message": "Products have been fetched successfully.",
  "data": {
    "products": [
      {
        "cursor": "eyJsYXN0X2lkIjox...",
        "id": "<encrypted>",
        "title": "Cotton T-Shirt",
        "handle": "cotton-tshirt",
        "product_img": "https://cdn.shopify.com/.../tshirt.jpg",
        "options": [
          { "id": "<encrypted>", "name": "Size", "values": ["S","M","L"] }
        ],
        "variants": {
          "M / Blue": {
            "id": "<encrypted>",
            "title": "M / Blue",
            "price": "499.00",
            "compare_price": "0",
            "qty": "12",
            "inventoryPolicy": "DENY",
            "image": "https://cdn.shopify.com/.../tshirt-m-blue.jpg",
            "price_symbol": "₹"
          }
        }
      }
    ],
    "hasNextPage": true,
    "hasPreviousPage": false
  }
}

Response fields

ParameterTypeRequiredDescription
data.products[]arrayRequiredPage of products.
data.products[].cursorstringRequiredGraphQL cursor for this entry. Pass cursor of the last entry as search.cursor with type:"next" for the next page.
data.products[].idstringRequiredEncrypted product id.
data.products[].titlestringRequiredProduct title.
data.products[].handlestringRequiredShopify product handle.
data.products[].product_imgstringRequiredCover image URL.
data.products[].options[]arrayOptionalVariant options (e.g. Size, Colour). Each: id (encrypted), name, values[].
data.products[].variantsobjectRequiredMap keyed by variant title.
data.products[].variants.&lt;title&gt;.idstringRequiredEncrypted variant id.
data.products[].variants.&lt;title&gt;.pricestringRequiredPer-unit price.
data.products[].variants.&lt;title&gt;.compare_pricestringOptionalCompare-at price (or "0").
data.products[].variants.&lt;title&gt;.qtystringRequiredInventory qty (or "100" for inventoryPolicy = CONTINUE).
data.products[].variants.&lt;title&gt;.imagestringOptionalVariant image (or product image for the Default Title variant).
data.products[].variants.&lt;title&gt;.price_symbolstringRequiredShop currency symbol (e.g. , $).
data.hasNextPagebooleanRequiredtrue if there's another page after this one.
data.hasPreviousPagebooleanRequiredtrue if a previous page exists.

Errors

HTTPCodeWhen you'll see it
400INVALID_INPUTMissing/undecryptable order_id or product_id
404NOT_FOUNDNo products match the search (page is empty)

Submit Return Request

POST https://ils.shopiapps.in/prfiles/return-request/api/ action = return_requests_submit

Creates one reverse_pickup row per line item being returned or exchanged. Customer + admin "request received" mails are dispatched on the very first row in the request.

Content-Type Use multipart/form-data only when attaching proof images. Without files, a plain application/x-www-form-urlencoded body is fine. Images > 500 KB are server-side resized to 600 px wide; JPG / JPEG / PNG only.

Body parameters

ParameterTypeRequiredDescription
shopstringRequiredShopify shop domain.
Example: examplestore.myshopify.com
actionenumRequiredConstant.
Example: return_requests_submit
order_idstringRequiredEncrypted order id.
product_datastringRequiredJSON array of items. Schema below.
return_addressstringOptionalJSON object of the customer's pickup address. Each field is base64-encoded server-side before storage.
img_&lt;decrypted_lineitem_id&gt;fileOptionalOptional proof image, one per line item. JPG/JPEG/PNG; > 500 KB triggers resize to 600 px wide.

product_data[] schema

[
  {
    "item_id":             "<encrypted lineitem id>",
    "quantity":            1,
    "reason":              "Wrong size",
    "customer_note":       "Please send a size L instead",
    "return_option":       "exchange",
    "replace_variant_id":  "<encrypted variant id>",
    "replace_product_id":  "<encrypted product id>",
    "exchange_quantity":   1,
    "refund_mode":         "",
    "refund_mode_details": {}
  }
]

For a pure refund: set return_option = "refund", leave the replace_* fields empty, and populate refund_mode + refund_mode_details (e.g. {"upi_id":"abc@bank"}).

Request example

curl --location 'https://ils.shopiapps.in/prfiles/return-request/api/' \
  --header 'Auth-Token: YOUR_TOKEN' \
  --form 'shop=examplestore.myshopify.com' \
  --form 'action=return_requests_submit' \
  --form 'order_id=<encrypted_order_id>' \
  --form 'product_data=[{"item_id":"<encrypted_lineitem>","quantity":1,"reason":"Wrong size","customer_note":"Please send L","return_option":"exchange","replace_variant_id":"<encrypted_variant>","replace_product_id":"<encrypted_product>","exchange_quantity":1,"refund_mode":"","refund_mode_details":{}}]' \
  --form 'img_<decrypted_lineitem_id>=@/path/to/proof.jpg'
const form = new FormData();
form.append('shop', 'examplestore.myshopify.com');
form.append('action', 'return_requests_submit');
form.append('order_id', '<encrypted_order_id>');
form.append('product_data', JSON.stringify([{
  item_id: '<encrypted_lineitem>',
  quantity: 1,
  reason: 'Wrong size',
  customer_note: 'Please send L',
  return_option: 'exchange',
  replace_variant_id: '<encrypted_variant>',
  replace_product_id: '<encrypted_product>',
  exchange_quantity: 1,
  refund_mode: '',
  refund_mode_details: {}
}]));
form.append('img_<decrypted_lineitem_id>', fileInput.files[0]);

const r = await fetch('https://ils.shopiapps.in/prfiles/return-request/api/', {
  method: 'POST',
  headers: { 'Auth-Token': 'YOUR_TOKEN' },
  body: form,
});
const json = await r.json();
val body = MultipartBody.Builder()
    .setType(MultipartBody.FORM)
    .addFormDataPart("shop", "examplestore.myshopify.com")
    .addFormDataPart("action", "return_requests_submit")
    .addFormDataPart("order_id", "<encrypted_order_id>")
    .addFormDataPart("product_data", productDataJson)
    .addFormDataPart(
        "img_<decrypted_lineitem_id>",
        proofFile.name,
        proofFile.asRequestBody("image/jpeg".toMediaType())
    )
    .build()

val req = Request.Builder()
    .url("https://ils.shopiapps.in/prfiles/return-request/api/")
    .header("Auth-Token", "YOUR_TOKEN")
    .post(body)
    .build()

Response — 200 OK

{
  "success": true,
  "code": "OK",
  "message": "Return request submitted successfully.",
  "data": []
}

Errors

HTTPCodeWhen you'll see it
400INVALID_INPUTorder_id missing/undecryptable, or empty product_data
404NOT_FOUNDOrder not found for this shop
409CONFLICTPriority-ordered: cancelled → "Order has been cancelled." · closed → "Order has been closed." · not fulfilled → "Order not fulfilled yet!" · per-item conflicts (already fully returned, status not delivered/shipped) only surface after all order-level checks pass.

Reschedule Pickup

POST https://ils.shopiapps.in/prfiles/return-request/api/ action = reschedule_pickup

Resets a previously declined or cancelled return request back to "pending" (status = 0, reschedule_pickup = 1) and fires the customer + admin confirmation mails.

Body parameters

ParameterTypeRequiredDescription
shopstringRequiredShopify shop domain.
Example: examplestore.myshopify.com
actionenumRequiredConstant.
Example: reschedule_pickup
idstringRequiredEncrypted reverse_pickup.id — from data.line_items.<k>.return_data[].id in order_details.

Request example

curl --location 'https://ils.shopiapps.in/prfiles/return-request/api/' \
  --header 'Auth-Token: YOUR_TOKEN' \
  --data-urlencode 'shop=examplestore.myshopify.com' \
  --data-urlencode 'action=reschedule_pickup' \
  --data-urlencode 'id=<encrypted_return_id>'
const body = new URLSearchParams({
  shop: 'examplestore.myshopify.com',
  action: 'reschedule_pickup',
  id: '<encrypted_return_id>',
});

const r = await fetch('https://ils.shopiapps.in/prfiles/return-request/api/', {
  method: 'POST',
  headers: { 'Auth-Token': 'YOUR_TOKEN' },
  body,
});
const json = await r.json();
if (!json.success) throw new Error(`${json.code}: ${json.message}`);
console.log(json.data);
val body = FormBody.Builder()
    .add("shop", "examplestore.myshopify.com")
    .add("action", "reschedule_pickup")
    .add("id", "<encrypted_return_id>")
    .build()

val req = Request.Builder()
    .url("https://ils.shopiapps.in/prfiles/return-request/api/")
    .header("Auth-Token", "YOUR_TOKEN")
    .post(body)
    .build()

client.newCall(req).execute().use { response ->
    val json = JSONObject(response.body!!.string())
    if (!json.getBoolean("success")) {
        throw IOException("${json.getString("code")}: ${json.getString("message")}")
    }
}
var req = URLRequest(url: URL(string: "https://ils.shopiapps.in/prfiles/return-request/api/")!)
req.httpMethod = "POST"
req.setValue("YOUR_TOKEN", forHTTPHeaderField: "Auth-Token")
req.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
req.httpBody = "shop=examplestore.myshopify.com&action=reschedule_pickup&id=%3Cencrypted_return_id%3E".data(using: .utf8)

let (data, _) = try await URLSession.shared.data(for: req)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
<?php
$ch = curl_init('https://ils.shopiapps.in/prfiles/return-request/api/');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Auth-Token: YOUR_TOKEN'],
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'shop'   => 'examplestore.myshopify.com',
        'action' => 'reschedule_pickup',
        'id' => '<encrypted_return_id>',
    ]),
]);
$body = curl_exec($ch);
curl_close($ch);
$json = json_decode($body, true);

Response — 200 OK

{
  "success": true,
  "code": "OK",
  "message": "Order has been re-scheduled successfully.",
  "data": []
}

Errors

HTTPCodeWhen you'll see it
400INVALID_INPUTid missing or undecryptable
404NOT_FOUNDReturn request id not found for this shop
409CONFLICTOrder on the return request has been cancelled — pickup cannot be rescheduled.
500SERVER_ERRORUnderlying SQL update or mailer failed (logged server-side)

Recommended Client Flow

A typical mobile-app implementation:

  1. On launch / entering the return flow, call get_return_settings. Use the result to theme the screen and build reason / refund dropdowns. Cache for the session.
  2. Show the email input. On submit, call get_order_list and render the order picker. Page on scroll using next_data.
  3. When the user picks an order, call order_details to render the line items and the eligibility flags:
  4. For exchange flows: either get_variant (same product, different size/colour) or get_products (different product, paginated search).
  5. On final submit, call return_requests_submit with the assembled product_data and any proof images.
  6. Re-fetch order_details to refresh the UI — return_data[] will now contain the new rows.

Caching guidance

ParameterTypeRequiredDescription
get_return_settingsstringOptionalCache per shop. TTL: 1 hour.
get_order_liststringOptionalCache per shop + email + cursor. TTL: 60 seconds.
order_detailsstringOptionalCache per shop + order. TTL: 30–60 seconds. Invalidate on any write action.
get_customer_shipping_addressesstringOptionalCache per customer. TTL: 5 minutes.
get_variant, get_productsstringOptionalCache per shop + product + page. TTL: 10 minutes.
all write actionsstringOptionalDo not cache. Invalidate order_details after success.

Best Practices

  • Branch on code, display message. code is stable; message may be merchant-customised.
  • Always check success before reading data.
  • Round-trip encrypted IDs verbatim. Don't trim, URL-decode, hash, or otherwise mutate them.
  • Render line_items.<k>.msg as HTML — it can contain <strong> for the days-remaining badge.
  • Disable the submit button until the user has entered a valid reason for every item. The API will accept blank reasons but the merchant's downstream automation may not.
  • Compress proof images client-side when possible — the server resizes to 600px wide for files > 500 KB, but the upload still pays the round-trip cost.
  • Pin certificates at the chain root (Let's Encrypt) if your threat model warrants it.
  • Strip PII from logs. Email, mobile, address, customer name are personal data.
  • Show actionable errors: NOT_FOUND → "Couldn't find that order"; CONFLICT → "That item has already been returned"; PLAN_LIMIT → "Please contact the merchant".

FAQ & Troubleshooting

I get 403 FORBIDDEN — App not installed!

The shop domain doesn't match an installed ILS app. Common causes: typo in the shop domain (must end in .myshopify.com); the merchant uninstalled the app; payment status lapsed.

I get 403 PLAN_LIMIT

The merchant's plan does not include Return Request. Upgrade to Advanced or Gold, or contact ILS for a custom plan.

I get 401 UNAUTHORIZED with a valid-looking token

Confirm: (a) header name is exactly Auth-Token with the hyphen; (b) the token belongs to the same shop as in shop; (c) the token hasn't been rotated. Request a fresh token if unsure.

return_requests_submit returns 200 but the customer didn't get the email

Email/SMS is dispatched via the merchant's configured sender. Confirm the sender is verified under ILS admin → Templates and that the customer has a valid email on the order. The submit itself succeeds regardless of mailer outcome — the row is in reverse_pickup.

The lineitem-id in the multipart key — encrypted or decrypted?

Decrypted. The server takes the item_id from product_data[], decrypts it, then looks for img_<decrypted_id> in $_FILES. Encrypted IDs typically contain characters that aren't valid in HTTP field names.

The IDs I get from the API are very long and look random — is that ok?

Yes. All primary IDs are encrypted per-shop. Send them back verbatim; never URL-decode, trim, or hash them.

Can I call this from a browser (JS)?

Yes — CORS is wide-open (*). But never embed the Auth-Token in client-side JavaScript shipped to the public web. Use a server-side proxy.

Are there rate limits?

No hard per-token limits today, but please stay under 60 requests/min/shop. get_products hits Shopify's GraphQL cost limiter — if you see SERVER_ERROR after a burst, back off for 2–3 seconds and retry.

Is there a sandbox environment?

Not currently. You can request a test token against a development store (*-test.myshopify.com) the same way as a production token.

Support

  • Email: support@shopiapps.in
  • Subject prefix: [Mobile API] for faster triage, [SECURITY] for verified security issues
  • Include in your message: shop domain, request action, full request body (redact the Auth-Token), the response JSON, and a timestamp with timezone

Resources