List return inventories with pagination
curl --request GET \
--url https://api.returnshelper.com/uat/user/api/ReturnInventory/list \
--header 'x-rr-apikey: <api-key>' \
--header 'x-rr-apitoken: <api-key>'import requests
url = "https://api.returnshelper.com/uat/user/api/ReturnInventory/list"
headers = {
"x-rr-apikey": "<api-key>",
"x-rr-apitoken": "<api-key>"
}
response = requests.get(url, headers=headers)
print(response.text)const options = {
method: 'GET',
headers: {'x-rr-apikey': '<api-key>', 'x-rr-apitoken': '<api-key>'}
};
fetch('https://api.returnshelper.com/uat/user/api/ReturnInventory/list', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.returnshelper.com/uat/user/api/ReturnInventory/list",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"x-rr-apikey: <api-key>",
"x-rr-apitoken: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.returnshelper.com/uat/user/api/ReturnInventory/list"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-rr-apikey", "<api-key>")
req.Header.Add("x-rr-apitoken", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.returnshelper.com/uat/user/api/ReturnInventory/list")
.header("x-rr-apikey", "<api-key>")
.header("x-rr-apitoken", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.returnshelper.com/uat/user/api/ReturnInventory/list")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-rr-apikey"] = '<api-key>'
request["x-rr-apitoken"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"data": [
{
"returnInventoryId": 123,
"returnRequestId": 123,
"returnRequestLineItemId": "<string>",
"rma": "<string>",
"itemRma": "<string>",
"sku": "<string>",
"handlingCode": "<string>",
"handlingStatusCode": "<string>",
"warehouseId": 123
}
],
"totalNumberOfRecords": 123
}{
"correlationId": "<string>",
"meta": {
"status": 123,
"data": {},
"errorCode": "<string>",
"error": {}
}
}ReturnInventory
List return inventories with pagination
GET
/
api
/
ReturnInventory
/
list
List return inventories with pagination
curl --request GET \
--url https://api.returnshelper.com/uat/user/api/ReturnInventory/list \
--header 'x-rr-apikey: <api-key>' \
--header 'x-rr-apitoken: <api-key>'import requests
url = "https://api.returnshelper.com/uat/user/api/ReturnInventory/list"
headers = {
"x-rr-apikey": "<api-key>",
"x-rr-apitoken": "<api-key>"
}
response = requests.get(url, headers=headers)
print(response.text)const options = {
method: 'GET',
headers: {'x-rr-apikey': '<api-key>', 'x-rr-apitoken': '<api-key>'}
};
fetch('https://api.returnshelper.com/uat/user/api/ReturnInventory/list', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.returnshelper.com/uat/user/api/ReturnInventory/list",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"x-rr-apikey: <api-key>",
"x-rr-apitoken: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.returnshelper.com/uat/user/api/ReturnInventory/list"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-rr-apikey", "<api-key>")
req.Header.Add("x-rr-apitoken", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.returnshelper.com/uat/user/api/ReturnInventory/list")
.header("x-rr-apikey", "<api-key>")
.header("x-rr-apitoken", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.returnshelper.com/uat/user/api/ReturnInventory/list")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-rr-apikey"] = '<api-key>'
request["x-rr-apitoken"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"data": [
{
"returnInventoryId": 123,
"returnRequestId": 123,
"returnRequestLineItemId": "<string>",
"rma": "<string>",
"itemRma": "<string>",
"sku": "<string>",
"handlingCode": "<string>",
"handlingStatusCode": "<string>",
"warehouseId": 123
}
],
"totalNumberOfRecords": 123
}{
"correlationId": "<string>",
"meta": {
"status": 123,
"data": {},
"errorCode": "<string>",
"error": {}
}
}This is not the recommended way to track return inventory state. The Return Helper API’s source of truth for inventory lifecycle is the webhook event stream —
markShipmentArrive, newInventoryCreated, vasUpdated, the inventory-handling-complete event, and notifyUserRmaSwapped deliver every state change to your endpoint as it happens. Build your integration around webhooks; this list endpoint exists for one-time backfill and operational reconciliation only.When (and only when) to call
- One-time backfill when first integrating, to populate a local database with existing inventory before subscribing to webhooks.
- Periodic reconciliation to detect dropped or out-of-order webhook deliveries — diff your local cache against this endpoint’s results.
Required parameters
createFrom/createTo— both required, ISO 8601 timestamps. The window is capped at 62 days (SearchConfig.simpleRecordsMaxDays); wider ranges are rejected with a soft-error. See Window semantics below for the exact rule and what “62 days” really means.pageSize— between1and50inclusive.offset— non-negative integer. Combine withpageSizefor offset-based pagination.
Response notes
totalNumberOfRecords(top-level field, sibling todata) is the row count for the current window — use it as the upper bound when paginating.handlingCodereflects the current decision on the item — call Update return inventory handling to change it.handlingStatusCodereflects the workflow state for that handling decision; translate via Get all handling statuses.- An item’s RMA (
rma) is the warehouse-assigned identifier, not your seller-side reference numbers.
Backfilling historical inventory
Use this endpoint to seed your local database with every inventory record you’ve ever had, then switch to webhooks for everything afterwards. Because the API caps each request at 62 days, you walk the timeline in 62-day windows, paginate inside each window, and step backwards until you reach your account’s start date.Window semantics
Two rules to internalize before you build the loop — they’re the difference between a clean backfill and a backfill that silently drops a day.-
Validator rule (calendar-day diff):
The time-of-day is stripped before comparison. So a request with
createTo.Date − createFrom.Date ≤ 62createFrom = 2024-03-13T15:00:00ZandcreateTo = 2024-05-14T09:00:00Zis valid (May 14 − Mar 13 = 62days), even though wall-clock difference is less than 62 × 24h. -
Data filter (inclusive both ends, by full day):
That is, the server expands
Records created from the beginning of `createFrom` through the end of `createTo`createFromto that day’s00:00:00.000andcreateToto that day’s23:59:59.999before filtering. So a single legal request actually covers 63 consecutive calendar days of data (the full createFrom day, the full createTo day, and every day in between).
createFrom from window N as createTo for window N+1, the boundary day appears in both results. That’s a duplicate, not a gap — the algorithm never misses records, only over-fetches by ~1 day per window seam. As long as your local store uses returnInventoryId as the primary key with UPSERT (or INSERT IGNORE) semantics, the duplicates collapse and the final state is exact.
If you’d rather avoid the duplicate fetch entirely, step windowEnd = windowStart − 1 day between iterations instead of windowEnd = windowStart. Each window then covers a fresh 63-day slice with no overlap. Either approach is correct; the safe-overlap variant below is the recommended default because it tolerates clock skew between client and server.
Backfill algorithm
- Pick a
historyStart(e.g. the date your account was provisioned). - Start with
windowEnd = now(). - Compute
windowStart = max(windowEnd − 62 days, historyStart). - Inside the window, page from
offset = 0inpageSizechunks untiloffset ≥ totalNumberOfRecords. UPSERT each row keyed byreturnInventoryId. - Set
windowEnd = windowStartand repeat from step 3 untilwindowEnd ≤ historyStart. - From this point on, maintain your local store off the
newInventoryCreatedwebhook (and the other inventory-lifecycle events). Re-running this backfill is unnecessary unless you suspect webhook data loss.
Sample code
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
// Requires a JSON library on the classpath. Below uses org.json for brevity:
// <dependency><groupId>org.json</groupId><artifactId>json</artifactId></dependency>
import org.json.JSONArray;
import org.json.JSONObject;
class Main {
static final String BASE_URL = "https://api.returnhelpercentre.com/v1/user"; // production
// sandbox: "https://api.returnshelper.com/uat/user"
static final String API_KEY = "<your api key>";
static final String API_TOKEN = "<your api token>";
static final int PAGE_SIZE = 50;
static final int WINDOW_DAYS = 62; // server cap: createTo.Date - createFrom.Date <= 62
public static void main(String[] args) throws Exception {
Instant historyStart = Instant.parse("2024-01-01T00:00:00Z"); // backfill anchor
Instant windowEnd = Instant.now(); // walk backwards from now
HttpClient http = HttpClient.newHttpClient();
while (windowEnd.isAfter(historyStart)) {
Instant windowStart = windowEnd.minus(WINDOW_DAYS, ChronoUnit.DAYS);
if (windowStart.isBefore(historyStart)) {
windowStart = historyStart;
}
int offset = 0;
int total = Integer.MAX_VALUE;
while (offset < total) {
String url = BASE_URL + "/api/ReturnInventory/list"
+ "?pageSize=" + PAGE_SIZE
+ "&offset=" + offset
+ "&createFrom=" + windowStart
+ "&createTo=" + windowEnd;
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("x-rr-apikey", API_KEY)
.header("x-rr-apitoken", API_TOKEN)
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> resp = http.send(req, HttpResponse.BodyHandlers.ofString());
JSONObject body = new JSONObject(resp.body());
// Standard envelope: business status is in meta.status, not HTTP status.
JSONObject meta = body.optJSONObject("meta");
if (meta == null || meta.optInt("status") != 200) {
System.err.println("list call failed: " + (meta == null ? "no meta" : meta.toString()));
return;
}
total = body.optInt("totalNumberOfRecords", 0);
JSONArray rows = body.optJSONArray("data");
if (rows != null) {
for (int i = 0; i < rows.length(); i++) {
JSONObject inv = rows.getJSONObject(i);
// TODO: UPSERT into your DB with returnInventoryId as primary key.
// Adjacent windows share a seam day, so the same row may be
// returned twice; UPSERT absorbs the duplicate.
// inv.getLong("returnInventoryId")
// inv.getString("handlingCode")
// inv.getString("handlingStatusCode")
// inv.getInt("warehouseId")
}
}
offset += PAGE_SIZE;
Thread.sleep(200); // light throttle
}
windowEnd = windowStart;
}
}
}
// Requires Node.js 18+ for built-in fetch.
const BASE_URL = 'https://api.returnhelpercentre.com/v1/user'; // production
// sandbox: 'https://api.returnshelper.com/uat/user'
const API_KEY = '<your api key>';
const API_TOKEN = '<your api token>';
const PAGE_SIZE = 50;
const WINDOW_DAYS = 62; // server cap: createTo.Date - createFrom.Date <= 62
const MS_PER_DAY = 24 * 60 * 60 * 1000;
async function main() {
const historyStart = new Date('2024-01-01T00:00:00Z'); // backfill anchor
let windowEnd = new Date(); // walk backwards from now
while (windowEnd > historyStart) {
let windowStart = new Date(windowEnd.getTime() - WINDOW_DAYS * MS_PER_DAY);
if (windowStart < historyStart) {
windowStart = historyStart;
}
let offset = 0;
let total = Infinity;
while (offset < total) {
const params = new URLSearchParams({
pageSize: String(PAGE_SIZE),
offset: String(offset),
createFrom: windowStart.toISOString(),
createTo: windowEnd.toISOString(),
});
const resp = await fetch(`${BASE_URL}/api/ReturnInventory/list?${params}`, {
headers: {
'x-rr-apikey': API_KEY,
'x-rr-apitoken': API_TOKEN,
'Accept': 'application/json',
},
});
const body = await resp.json();
// Standard envelope: business status is in meta.status, not HTTP status.
if (body?.meta?.status !== 200) {
console.error('list call failed:', body?.meta);
return;
}
total = body.totalNumberOfRecords ?? 0;
const rows = body.data ?? [];
for (const inv of rows) {
// TODO: UPSERT into your DB with returnInventoryId as primary key.
// Adjacent windows share a seam day, so the same row may be
// returned twice; UPSERT absorbs the duplicate.
// inv.returnInventoryId
// inv.handlingCode
// inv.handlingStatusCode
// inv.warehouseId
}
offset += PAGE_SIZE;
await new Promise((r) => setTimeout(r, 200)); // light throttle
}
windowEnd = windowStart;
}
}
main().catch(console.error);
// Requires Node.js 18+ for built-in fetch (or a fetch polyfill in older runtimes).
const BASE_URL = 'https://api.returnhelpercentre.com/v1/user'; // production
// sandbox: 'https://api.returnshelper.com/uat/user'
const API_KEY = '<your api key>';
const API_TOKEN = '<your api token>';
const PAGE_SIZE = 50;
const WINDOW_DAYS = 62; // server cap: createTo.Date - createFrom.Date <= 62
const MS_PER_DAY = 24 * 60 * 60 * 1000;
interface ApiEnvelope<T> {
correlationId: string;
meta: {
status: number;
errorCode?: string | null;
error?: Record<string, unknown>;
};
data?: T[];
totalNumberOfRecords?: number;
}
interface ReturnInventoryRow {
returnInventoryId: number;
handlingCode: string;
handlingStatusCode: string;
warehouseId: number;
// ...other fields documented in the API reference
}
async function main(): Promise<void> {
const historyStart = new Date('2024-01-01T00:00:00Z'); // backfill anchor
let windowEnd = new Date(); // walk backwards from now
while (windowEnd > historyStart) {
let windowStart = new Date(windowEnd.getTime() - WINDOW_DAYS * MS_PER_DAY);
if (windowStart < historyStart) {
windowStart = historyStart;
}
let offset = 0;
let total = Infinity;
while (offset < total) {
const params = new URLSearchParams({
pageSize: String(PAGE_SIZE),
offset: String(offset),
createFrom: windowStart.toISOString(),
createTo: windowEnd.toISOString(),
});
const resp = await fetch(`${BASE_URL}/api/ReturnInventory/list?${params}`, {
headers: {
'x-rr-apikey': API_KEY,
'x-rr-apitoken': API_TOKEN,
'Accept': 'application/json',
},
});
const body = (await resp.json()) as ApiEnvelope<ReturnInventoryRow>;
// Standard envelope: business status is in meta.status, not HTTP status.
if (body.meta?.status !== 200) {
console.error('list call failed:', body.meta);
return;
}
total = body.totalNumberOfRecords ?? 0;
const rows = body.data ?? [];
for (const inv of rows) {
// TODO: UPSERT into your DB with returnInventoryId as primary key.
// Adjacent windows share a seam day, so the same row may be
// returned twice; UPSERT absorbs the duplicate.
// inv.returnInventoryId
// inv.handlingCode
// inv.handlingStatusCode
// inv.warehouseId
}
offset += PAGE_SIZE;
await new Promise((r) => setTimeout(r, 200)); // light throttle
}
windowEnd = windowStart;
}
}
main().catch(console.error);
// Salesforce Apex — callable from an execute-anonymous window or wrapped in a
// Queueable for large backfills (single-transaction callout limit is 100).
//
// Add the API host to "Remote Site Settings" before running.
public class Main {
private static final String BASE_URL = 'https://api.returnhelpercentre.com/v1/user'; // production
// sandbox: 'https://api.returnshelper.com/uat/user'
private static final String API_KEY = '<your api key>';
private static final String API_TOKEN = '<your api token>';
private static final Integer PAGE_SIZE = 50;
private static final Integer WINDOW_DAYS = 62; // server cap: createTo.Date - createFrom.Date <= 62
public static void main() {
Datetime historyStart = Datetime.newInstanceGmt(2024, 1, 1, 0, 0, 0); // backfill anchor
Datetime windowEnd = Datetime.now(); // walk backwards from now
Http http = new Http();
while (windowEnd > historyStart) {
Datetime windowStart = windowEnd.addDays(-WINDOW_DAYS);
if (windowStart < historyStart) {
windowStart = historyStart;
}
Integer offset = 0;
Integer total = Integer.MAX_VALUE;
while (offset < total) {
String url = BASE_URL + '/api/ReturnInventory/list'
+ '?pageSize=' + PAGE_SIZE
+ '&offset=' + offset
+ '&createFrom=' + EncodingUtil.urlEncode(formatGmt(windowStart), 'UTF-8')
+ '&createTo=' + EncodingUtil.urlEncode(formatGmt(windowEnd), 'UTF-8');
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setEndpoint(url);
req.setHeader('x-rr-apikey', API_KEY);
req.setHeader('x-rr-apitoken', API_TOKEN);
req.setHeader('Accept', 'application/json');
HttpResponse resp = http.send(req);
Map<String, Object> body = (Map<String, Object>) JSON.deserializeUntyped(resp.getBody());
Map<String, Object> meta = (Map<String, Object>) body.get('meta');
// Standard envelope: business status is in meta.status, not HTTP status.
if (meta == null || (Integer) meta.get('status') != 200) {
System.debug('list call failed: ' + meta);
return;
}
Object totalObj = body.get('totalNumberOfRecords');
total = (totalObj == null) ? 0 : (Integer) totalObj;
List<Object> rows = (List<Object>) body.get('data');
if (rows != null) {
for (Object row : rows) {
Map<String, Object> inv = (Map<String, Object>) row;
// TODO: UPSERT into your custom object with returnInventoryId__c
// as External Id. Adjacent windows share a seam day, so the
// same row may be returned twice; UPSERT absorbs the duplicate.
// inv.get('returnInventoryId')
// inv.get('handlingCode')
// inv.get('handlingStatusCode')
// inv.get('warehouseId')
}
}
offset += PAGE_SIZE;
}
windowEnd = windowStart;
}
}
private static String formatGmt(Datetime dt) {
return dt.formatGmt('yyyy-MM-dd\'T\'HH:mm:ss\'Z\'');
}
}
Related
- Get return inventory details — fetch a single record by
returnInventoryId(only when a webhook references one you don’t have cached). - Get return inventory by line item ID — look up by your
returnRequestLineItemId. - Update return inventory handling — write counterpart.
- Get all handling statuses and Get all handling types — code-to-label mappings.
- Webhooks — the canonical channel for inventory lifecycle events. Set up webhooks before relying on this endpoint.
Authorizations
Your API key
Your API token — keep this private
Query Parameters
Number of records per page (1–50)
Required range:
1 <= x <= 50