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