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.
or multipart/form-data
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:
| Parameter | Type | Required | Description |
|---|---|---|---|
Auth-Token | string | Required | Long-lived per-shop API token (see Get an API Token). Sent as an HTTP header. Example: a1b2c3... |
shop | string | Required | Shopify 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:
- The ILS app is installed for the shop (
app.app_status = 'installed'). - Payment status is active (
freeoraccepted). - The shop's plan includes the Return Request feature.
- The supplied
Auth-Tokenmatches the token stored for the shop.
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
- Email support@shopiapps.in with the subject "Mobile API token request".
- Include the shop domain (e.g.
examplestore.myshopify.com), the mobile app name/platform (e.g. Acme Shop iOS), and a contact email. - ILS support verifies the shop has an active plan that includes
return_requestand provisions the token. - The token is delivered out-of-band (encrypted email or shared secret store).
Token properties
| Property | Value |
|---|---|
Format | Hex string, ~96 characters |
Lifetime | Long-lived (no expiry); rotated on demand |
Scope | Single Shopify shop, both Tracking & Return APIs |
Server storage | settings.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.
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."
}| Parameter | Type | Required | Description |
|---|---|---|---|
success | boolean | Required | true for any 2xx response, false otherwise. Use this for branching. |
code | string | Required | Stable machine code: OK, INVALID_INPUT, UNAUTHORIZED, FORBIDDEN, PLAN_LIMIT, NOT_FOUND, CONFLICT, SERVER_ERROR. Safe to switch on. |
message | string | Required | Human-readable message. Safe to display in UI. |
data | object | Optional | Endpoint-specific payload. Empty array on most error responses. |
result | enum | Optional | Legacy v1 alias of success — values: success / fail. |
msg | string | Optional | Legacy v1 alias of message. |
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
| HTTP | Code | When you'll see it |
|---|---|---|
| 200 | OK | Successful response |
| 400 | INVALID_INPUT | Missing or malformed parameters; unknown action; field length exceeds limit |
| 401 | UNAUTHORIZED | Missing / invalid Auth-Token |
| 403 | FORBIDDEN | App not installed for this shop, or payment status inactive |
| 403 | PLAN_LIMIT | Return-request feature not in the shop's plan |
| 404 | NOT_FOUND | Order / customer / product / address not found |
| 409 | CONFLICT | State conflict — order already cancelled, item already returned, fulfilment not started yet |
| 500 | SERVER_ERROR | Unexpected 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.
| Priority | Rule | HTTP | code | message |
|---|---|---|---|---|
| 1 | Order doesn't exist | 404 | NOT_FOUND | "Sorry we can't find your order." |
| 2 | Cancelled in Shopify | 409 | CONFLICT | "Order has been cancelled." (or "…already been cancelled." on cancel_order) |
| 3 | Closed / archived | 409 | CONFLICT | "Order has been closed." |
| 4 | Shipped (AWB on file) | 409 | CONFLICT | "Order has been shipped — this action is no longer available." (wording is action-specific) |
| 5 | Not fulfilled yet | 409 | CONFLICT | "Order not fulfilled yet!" (only on return_requests_submit) |
| 6 | Action-specific conflict | 409 | CONFLICT | e.g. "This item has already returned!" |
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.
| Identifier | Returned by | Sent back to |
|---|---|---|
order_id | Keys in get_order_list; data.order_id in order_details | order_details, cancel_order, edit_customer_*, return_requests_submit, get_variant, get_products |
customer_id | order_details | get_customer_shipping_addresses, edit_customer_shipping_address |
location_id | get_customer_shipping_addresses | edit_customer_shipping_address |
item_id (lineitem) | order_details | return_requests_submit.product_data[].item_id |
product_id | order_details | get_variant, get_products, return_requests_submit |
variant_id | get_variant, get_products | return_requests_submit.product_data[].replace_variant_id |
id (return request) | order_details.line_items.<k>.return_data[].id | reschedule_pickup.id |
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
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
| Parameter | Type | Required | Description |
|---|---|---|---|
Auth-Token | string | Required | Per-shop API token. Example: a1b2c3d4... |
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
shop | string | Required | Shopify shop domain. Example: examplestore.myshopify.com |
action | enum | Required | Constant. 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)
| Parameter | Type | Required | Description |
|---|---|---|---|
data.login_page_heading | string | Required | Heading copy for the order-lookup screen. |
data.login_page_description | string | Required | Sub-heading copy. |
data.show_login_banner | enum | Required | "0"/"1". When "1", render the banner at login_banner_image_url. |
data.login_banner_image_url | string | Optional | Absolute URL of the login banner (empty when disabled). |
data.login_banner_position | enum | Optional | "0" = left, "1" = right. |
data.heading_font_color | string | Required | Hex colour for headings. |
data.text_color | string | Required | Body text hex. |
data.button_font_color | string | Required | CTA text hex. |
data.button_color | string | Required | CTA background hex. |
data.custom_css | string | Optional | CSS injected on the web tracker. Mobile clients should ignore. |
data.contact_mail | string | Required | Support email shown on the return screen. |
data.return_page_heading | string | Required | Title displayed on the return-request screen. |
data.return_button_text | string | Required | Label for the per-item Return button. |
data.return_reasons | array | Required | Reason list to populate the dropdown. |
data.enable_return_option | enum | Required | "1" = surface the return-option picker (refund / exchange / store credit). |
data.return_options_list | string | Optional | Comma-separated list of enabled return options. |
data.return_options_text | object | Optional | Display labels keyed by return-option code. |
data.refund_modes | array | Required | Refund payout methods (UPI, bank transfer, ...). |
data.selected_replace_option | enum | Required | "0": variant only · "1": any product (search via get_products). |
data.exchange_summary | string | Optional | Optional copy shown when the customer selects exchange. |
data.enable_customer_note | enum | Required | "1" = show the customer-note textarea. |
data.enable_return_proof_image | enum | Required | "1" = allow attaching a proof image with the return. |
data.allow_partial_return | enum | Required | "1" = allow returning fewer items than were ordered. |
Errors
| HTTP | Code | When you'll see it |
|---|---|---|
| 401 | UNAUTHORIZED | Missing / invalid Auth-Token |
| 403 | FORBIDDEN | App not installed |
| 403 | PLAN_LIMIT | Return Request not in plan |
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
| Parameter | Type | Required | Description |
|---|---|---|---|
shop | string | Required | Shopify shop domain. Example: examplestore.myshopify.com |
action | enum | Required | Constant. Example: get_order_list |
email | string | Required | Customer email (max 254 chars). Example: jane@example.com |
next_data | string | Optional | Cursor 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
| Parameter | Type | Required | Description |
|---|---|---|---|
data.<encrypted_o_id> | object | Required | Map keyed by encrypted order id. Pass that key as order_id to subsequent endpoints. |
data.<k>.name | string | Required | Shopify order display name (e.g. #1042). |
data.<k>.total_price | string | Required | Pre-formatted total with shop currency symbol. |
data.<k>.line_item | object | Optional | First line item as a cover preview — title, src, vendor, variant, sku. |
data.<k>.created_date | string | Required | Pre-formatted created date. Example: 21 May, 2026 14:03:22 |
data.<k>.status_name | string | Required | Human-readable order/shipment status (e.g. "Fulfilled", "Cancelled", "Manifested", "Completed", "Failed to delivered"). |
data.<k>.payment_mode | enum | Required | COD or Pre-paid. |
next_data | string | Optional | Cursor to pass back as next_data for the next page. Empty when there is no further page. |
Errors
| HTTP | Code | When you'll see it |
|---|---|---|
| 400 | INVALID_INPUT | email missing or longer than 254 characters |
| 404 | NOT_FOUND | No orders in the last 60 days for this email |
Get 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.
order_detail, approve_package, fulfillment_detail, reverse_pickup, reverse_pickup_settings). Cache the response client-side for 30–60 seconds.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
shop | string | Required | Shopify shop domain. Example: examplestore.myshopify.com |
action | enum | Required | Constant. Example: order_details |
order_name | string | Required | Order number (max 128 chars). Shopify display name (#1042) or plain numeric.Example: #1042 |
email | string | Required | Email 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
| Parameter | Type | Required | Description |
|---|---|---|---|
data.order_id | string | Required | Encrypted order id. |
data.order_name | string | Required | Shopify order display name. |
data.order_date | string | Required | Pre-formatted date. Example: 21 May, 2026 |
data.customer_id | string | Required | Encrypted customer id. Pass to get_customer_shipping_addresses. |
data.delivery_address | object | Required | Shipping address (falls back through shipping → billing → customer default). |
data.order_shipment_data.courier_service | string | Optional | Display name of the courier (e.g. "Delhivery"). |
data.order_shipment_data.awb | string | Optional | AWB / tracking number (empty until manifested). |
data.order_shipment_data.show_shipment | enum | Required | 0: order ships via another app (no progress bar). 1: shipped via ILS — render order_status_list. |
data.order_shipment_data.track_status | integer | Required | 1 ordered · 2 packed · 3 shipped · 4 delivered · 5 RTO · 6 cancelled · 7 NDR. |
data.order_shipment_data.order_status_list | array | Optional | 4-cell timeline labels (e.g. ["Ordered","Packed","Shipped","Delivered"]) — only when show_shipment = 1. |
data.show_return_btn | boolean | Required | true if at least one line item is currently returnable. |
data.allow_edit_phone | boolean | Required | Whether the customer can call edit_customer_phone_number for this order. |
data.allow_edit_address | boolean | Required | Whether the customer can call edit_customer_shipping_address. |
data.allow_cancel_order | boolean | Required | Whether the customer can call cancel_order. |
data.line_items.<encrypted_l_id> | object | Required | Map keyed by encrypted lineitem id. |
data.line_items.<k>.title | string | Required | Product title. |
data.line_items.<k>.handle | string | Optional | Shopify product handle. |
data.line_items.<k>.price | string | Required | Line total. |
data.line_items.<k>.unit_price | string | Required | Per-unit price. |
data.line_items.<k>.total_qty | integer | Required | Quantity ordered. |
data.line_items.<k>.fulfill_qty | integer | Optional | Quantity fulfilled so far. |
data.line_items.<k>.remain_days | integer | Optional | Days remaining in the return window. |
data.line_items.<k>.return | enum | Required | "true" if the customer can submit a return for this line. |
data.line_items.<k>.msg | string | Optional | Human-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.<k>.remain_return_qty | string | Optional | Quantity still returnable. "N/A" when zero. |
data.line_items.<k>.return_data[] | array | Optional | Existing return rows (if any) — id (encrypted), status, quantity, return_reason, exchange_product_details. |
data.line_items.<k>.re_schedule_pickup | boolean | Required | true if the customer can call reschedule_pickup against this line's return. |
data.line_items.<k>.item_tracking_url | string | Optional | Public courier tracking URL for an approved return (empty otherwise). |
data.line_items.<k>.variant_id | string | Required | Encrypted variant id. |
data.line_items.<k>.product_id | string | Required | Encrypted product id — pass to get_variant / get_products. |
data.line_items.<k>.item_id | string | Required | Encrypted lineitem id — pass as item_id in return_requests_submit. |
Errors
| HTTP | Code | When you'll see it |
|---|---|---|
| 400 | INVALID_INPUT | order_name or email missing |
| 404 | NOT_FOUND | No order matches order_name + email/phone for this shop |
| 409 | CONFLICT | Order has been cancelled |
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
| Parameter | Type | Required | Description |
|---|---|---|---|
shop | string | Required | Shopify shop domain. Example: examplestore.myshopify.com |
action | enum | Required | Constant. Example: get_customer_shipping_addresses |
customer_id | string | Required | Encrypted 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
| Parameter | Type | Required | Description |
|---|---|---|---|
data[] | array | Required | List of saved addresses. |
data[].location_id | string | Required | Encrypted address id — pass to edit_customer_shipping_address. |
data[].first_name | string | Optional | Recipient first name. |
data[].last_name | string | Optional | Recipient last name. |
data[].company | string | Optional | Company name. |
data[].address1 | string | Required | Street / building. |
data[].address2 | string | Optional | Apt / suite / extra line. |
data[].city | string | Required | City. |
data[].province | string | Required | State / province (display name). |
data[].province_code | string | Required | ISO province code (e.g. KA). |
data[].country | string | Required | Country (display name). |
data[].country_code | string | Required | ISO country code (e.g. IN). |
data[].country_name | string | Required | Alias of country. |
data[].zip | string | Required | Postal code. |
data[].phone | string | Optional | Contact phone. |
data[].name | string | Optional | Recipient full name (Shopify-computed). |
Errors
| HTTP | Code | When you'll see it |
|---|---|---|
| 400 | INVALID_INPUT | customer_id missing or undecryptable |
| 404 | NOT_FOUND | Customer not found, or no saved addresses |
Edit Shipping Address
Replaces the order's shipping address with one of the customer's saved addresses (selected by location_id).
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
| Parameter | Type | Required | Description |
|---|---|---|---|
shop | string | Required | Shopify shop domain. Example: examplestore.myshopify.com |
action | enum | Required | Constant. Example: edit_customer_shipping_address |
order_id | string | Required | Encrypted order id. |
customer_id | string | Required | Encrypted customer id. |
location_id | string | Required | Encrypted 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
| Parameter | Type | Required | Description |
|---|---|---|---|
data | object | Required | The address object that was applied (mirrors Shopify's saved-address shape). Useful for refreshing the UI without an extra round-trip. |
Errors
| HTTP | Code | When you'll see it |
|---|---|---|
| 400 | INVALID_INPUT | Any of order_id, customer_id, location_id missing or undecryptable |
| 404 | NOT_FOUND | Order not found, customer not found, or the chosen address id is not in the customer's book |
| 409 | CONFLICT | Priority-ordered: cancelled → "Order has been cancelled." · shipped → "Order has been shipped — the address can no longer be edited." |
| 500 | SERVER_ERROR | Shopify rejected the order-update mutation |
Edit Phone Number
Updates the phone number on the order's shipping address. Same eligibility window as Edit Address.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
shop | string | Required | Shopify shop domain. Example: examplestore.myshopify.com |
action | enum | Required | Constant. Example: edit_customer_phone_number |
order_id | string | Required | Encrypted order id. |
phone_number | string | Required | New 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
| Parameter | Type | Required | Description |
|---|---|---|---|
data | string | Required | The phone string that was applied (echoed back for UI confirmation). |
Errors
| HTTP | Code | When you'll see it |
|---|---|---|
| 400 | INVALID_INPUT | order_id or phone_number missing |
| 404 | NOT_FOUND | Order not found for this shop |
| 409 | CONFLICT | Priority-ordered: cancelled → "Order has been cancelled." · shipped → "Order has been shipped — the phone number can no longer be edited." |
| 500 | SERVER_ERROR | Shopify rejected the order-update mutation |
Cancel Order
Cancels the Shopify order on the customer's behalf with reason = CUSTOMER, refund = true, restock = true, notifyCustomer = true.
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
| Parameter | Type | Required | Description |
|---|---|---|---|
shop | string | Required | Shopify shop domain. Example: examplestore.myshopify.com |
action | enum | Required | Constant. Example: cancel_order |
order_id | string | Required | Encrypted 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
| HTTP | Code | When you'll see it |
|---|---|---|
| 400 | INVALID_INPUT | order_id missing or undecryptable |
| 404 | NOT_FOUND | Order not found for this shop |
| 409 | CONFLICT | Priority-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
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
| Parameter | Type | Required | Description |
|---|---|---|---|
shop | string | Required | Shopify shop domain. Example: examplestore.myshopify.com |
action | enum | Required | Constant. Example: get_variant |
order_id | string | Required | Encrypted order id (used for the same-price filter against order_items). |
product_id | string | Required | Encrypted 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
| Parameter | Type | Required | Description |
|---|---|---|---|
data[] | array | Required | Variants that pass the visibility + price filters. |
data[].id | string | Required | Encrypted variant id — pass as replace_variant_id in return_requests_submit. |
data[].title | string | Required | Variant title (e.g. "M / Blue"). |
data[].price | string | Required | Per-unit price. |
data[].compare_at_price | string | Optional | Compare-at (strike-through) price, or null. |
Errors
| HTTP | Code | When you'll see it |
|---|---|---|
| 400 | INVALID_INPUT | Missing/undecryptable order_id or product_id |
| 404 | NOT_FOUND | Product 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
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
| Parameter | Type | Required | Description |
|---|---|---|---|
shop | string | Required | Shopify shop domain. Example: examplestore.myshopify.com |
action | enum | Required | Constant. Example: get_products |
order_id | string | Required | Encrypted order id. |
product_id | string | Required | Encrypted product id of the line item being exchanged (used for same-price filtering). |
search | string | Optional | JSON: {"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
| Parameter | Type | Required | Description |
|---|---|---|---|
data.products[] | array | Required | Page of products. |
data.products[].cursor | string | Required | GraphQL cursor for this entry. Pass cursor of the last entry as search.cursor with type:"next" for the next page. |
data.products[].id | string | Required | Encrypted product id. |
data.products[].title | string | Required | Product title. |
data.products[].handle | string | Required | Shopify product handle. |
data.products[].product_img | string | Required | Cover image URL. |
data.products[].options[] | array | Optional | Variant options (e.g. Size, Colour). Each: id (encrypted), name, values[]. |
data.products[].variants | object | Required | Map keyed by variant title. |
data.products[].variants.<title>.id | string | Required | Encrypted variant id. |
data.products[].variants.<title>.price | string | Required | Per-unit price. |
data.products[].variants.<title>.compare_price | string | Optional | Compare-at price (or "0"). |
data.products[].variants.<title>.qty | string | Required | Inventory qty (or "100" for inventoryPolicy = CONTINUE). |
data.products[].variants.<title>.image | string | Optional | Variant image (or product image for the Default Title variant). |
data.products[].variants.<title>.price_symbol | string | Required | Shop currency symbol (e.g. ₹, $). |
data.hasNextPage | boolean | Required | true if there's another page after this one. |
data.hasPreviousPage | boolean | Required | true if a previous page exists. |
Errors
| HTTP | Code | When you'll see it |
|---|---|---|
| 400 | INVALID_INPUT | Missing/undecryptable order_id or product_id |
| 404 | NOT_FOUND | No products match the search (page is empty) |
Submit Return Request
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.
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
| Parameter | Type | Required | Description |
|---|---|---|---|
shop | string | Required | Shopify shop domain. Example: examplestore.myshopify.com |
action | enum | Required | Constant. Example: return_requests_submit |
order_id | string | Required | Encrypted order id. |
product_data | string | Required | JSON array of items. Schema below. |
return_address | string | Optional | JSON object of the customer's pickup address. Each field is base64-encoded server-side before storage. |
img_<decrypted_lineitem_id> | file | Optional | Optional 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
| HTTP | Code | When you'll see it |
|---|---|---|
| 400 | INVALID_INPUT | order_id missing/undecryptable, or empty product_data |
| 404 | NOT_FOUND | Order not found for this shop |
| 409 | CONFLICT | Priority-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
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
| Parameter | Type | Required | Description |
|---|---|---|---|
shop | string | Required | Shopify shop domain. Example: examplestore.myshopify.com |
action | enum | Required | Constant. Example: reschedule_pickup |
id | string | Required | Encrypted 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
| HTTP | Code | When you'll see it |
|---|---|---|
| 400 | INVALID_INPUT | id missing or undecryptable |
| 404 | NOT_FOUND | Return request id not found for this shop |
| 409 | CONFLICT | Order on the return request has been cancelled — pickup cannot be rescheduled. |
| 500 | SERVER_ERROR | Underlying SQL update or mailer failed (logged server-side) |
Recommended Client Flow
A typical mobile-app implementation:
- 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. - Show the email input. On submit, call
get_order_listand render the order picker. Page on scroll usingnext_data. - When the user picks an order, call
order_detailsto render the line items and the eligibility flags:allow_cancel_order→ show "Cancel order" CTAallow_edit_phone→ show "Edit phone" CTAallow_edit_address→ show "Change address" CTA, then callget_customer_shipping_addresses+edit_customer_shipping_address- Per line item,
return == "true"→ enable the per-item "Return" button re_schedule_pickup == true→ enable the per-item "Reschedule" button →reschedule_pickup
- For exchange flows: either
get_variant(same product, different size/colour) orget_products(different product, paginated search). - On final submit, call
return_requests_submitwith the assembledproduct_dataand any proof images. - Re-fetch
order_detailsto refresh the UI —return_data[]will now contain the new rows.
Caching guidance
| Parameter | Type | Required | Description |
|---|---|---|---|
get_return_settings | string | Optional | Cache per shop. TTL: 1 hour. |
get_order_list | string | Optional | Cache per shop + email + cursor. TTL: 60 seconds. |
order_details | string | Optional | Cache per shop + order. TTL: 30–60 seconds. Invalidate on any write action. |
get_customer_shipping_addresses | string | Optional | Cache per customer. TTL: 5 minutes. |
get_variant, get_products | string | Optional | Cache per shop + product + page. TTL: 10 minutes. |
all write actions | string | Optional | Do not cache. Invalidate order_details after success. |
Best Practices
- Branch on
code, displaymessage.codeis stable;messagemay be merchant-customised. - Always check
successbefore readingdata. - Round-trip encrypted IDs verbatim. Don't trim, URL-decode, hash, or otherwise mutate them.
- Render
line_items.<k>.msgas HTML — it can contain<strong>for the days-remaining badge. - Disable the submit button until the user has entered a valid
reasonfor 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 theAuth-Token), the response JSON, and a timestamp with timezone
Resources
- Download Postman collection (.json)
- View this guide as Markdown
- Tracking API developer guide — the sibling product