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,
"createOn": "2023-11-07T05:31:56Z"
}
],
"totalNumberOfRecords": 123
}{
"correlationId": "<string>",
"meta": {
"status": 123,
"data": {},
"errorCode": "<string>",
"error": {}
}
}退貨庫存
列出 Return Inventories(分頁)
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,
"createOn": "2023-11-07T05:31:56Z"
}
],
"totalNumberOfRecords": 123
}{
"correlationId": "<string>",
"meta": {
"status": 123,
"data": {},
"errorCode": "<string>",
"error": {}
}
}本端點不是追蹤退件庫存狀態的建議方式。Return Helper API 中庫存生命週期的真實資料來源是 Webhook 事件流——
markShipmentArrive、newInventoryCreated、vasUpdated、庫存處理完成事件、notifyUserRmaSwapped 等會在狀態變更時即時推送至您的端點。請圍繞 webhook 設計整合;本列表端點僅用於一次性回填與營運對帳。僅在以下情況呼叫
- 一次性回填:首次整合時,於訂閱 webhook 之前,將既有庫存寫入本地資料庫。
- 定期對帳:用以偵測遺漏或亂序的 webhook 傳遞——將本地快取與本端點結果比對。
必要參數
createFrom/createTo— 均必填,ISO 8601 時間戳。範圍上限為 62 天(SearchConfig.simpleRecordsMaxDays),超出會傳回軟錯誤。實際語義請見下方的 視窗語義。pageSize— 介於1與50之間。offset— 非負整數。搭配pageSize用於位移分頁。
回應備註
totalNumberOfRecords(頂層欄位,與data同層)為目前視窗的總筆數——分頁時以此作為上限。handlingCode反映目前的處理決定——可呼叫 更新退件庫存處理 變更。handlingStatusCode反映該處理決定的工作流狀態;可透過 取得所有處理狀態 轉譯。- 物品的 RMA(
rma)是倉庫指派的識別碼,並非您賣家端的參考號。
歷史庫存回填
可用本端點將每一筆既有的退件庫存載入您的本地資料庫,之後一律改用 webhook 接收後續事件。由於 API 對每次請求的時間範圍上限為 62 天,整體流程是「以 62 天為一格的滑動視窗,每格視窗內分頁、視窗依序往前推」,直至覆蓋帳號開通日。視窗語義
開始撰寫迴圈前,請先理解以下兩條規則——這是「乾淨回填」與「靜默漏一天」之間的分界線。-
驗證器規則(日曆日差):
比較前會先把兩端的時分秒歸零。所以
createTo.Date − createFrom.Date ≤ 62createFrom = 2024-03-13T15:00:00Z、createTo = 2024-05-14T09:00:00Z的請求仍然有效(May 14 − Mar 13 = 62天),即使實際牆鐘時長略短於 62 × 24 小時。 -
資料過濾(兩端整日皆包含):
也就是說,伺服器會把
createOn ≥ createFrom.BeginOfDay() AND createOn ≤ createTo.EndOfDay()createFrom擴張到當日00:00:00.000,createTo擴張到當日23:59:59.999,再進行過濾。因此一次合法請求實際覆蓋 63 個連續日曆日的資料(createFrom 當日整天、createTo 當日整天、以及之間所有天)。
createFrom 直接作為視窗 N+1 的 createTo,那一天會同時出現在兩個視窗的結果中。這是重複,不是漏單——演算法絕不會漏單,只是在每個視窗接縫處多抓約一天的資料。只要本地資料表以 returnInventoryId 作為主鍵並使用 UPSERT(或 INSERT IGNORE),重複自動合併,最終資料完全正確。
如要徹底避免重複抓取,每次迭代後改成 windowEnd = windowStart − 1 day(而非 windowEnd = windowStart),每個視窗就是無重疊的全新 63 天切片。兩種寫法都正確;下方範例採用「容許接縫重疊」版本,因為它對客戶端與伺服器之間的時鐘漂移更寬容,是建議的預設。
回填演算法
- 選定
historyStart(例如帳號開通日)。 - 以
windowEnd = now()起步。 - 計算
windowStart = max(windowEnd − 62 天, historyStart)。 - 在視窗內,從
offset = 0開始按pageSize翻頁,直到offset ≥ totalNumberOfRecords。每筆紀錄以returnInventoryId為主鍵 UPSERT 進入本地資料庫。 - 設
windowEnd = windowStart,回到第 3 步繼續,直至windowEnd ≤ historyStart。 - 之後改由
newInventoryCreatedwebhook(以及其他庫存生命週期事件)維護本地資料;除非懷疑 webhook 資料遺失,否則無須重跑本回填。
程式範例
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\'');
}
}
相關
- 取得退件庫存詳情 — 當 webhook 提及未快取的
returnInventoryId時,依 ID 取得單筆紀錄。 - 依行項目 ID 取得退件庫存 — 依您的
returnRequestLineItemId查詢。 - 更新退件庫存處理 — 寫入側端點。
- 取得所有處理狀態 與 取得所有處理類型 — 代碼至標籤對映。
- Webhooks — 庫存生命週期事件的標準通道。請先設定 webhook 再使用本端點。
授權
Your API key
Your API token — keep this private
查詢參數
Number of records per page (1–50)
必填範圍:
1 <= x <= 50⌘I