> ## Documentation Index
> Fetch the complete documentation index at: https://apidocs.returnhelper.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 列出 Return Inventories（分頁）

<Warning>
  本端點**不**是追蹤退件庫存狀態的建議方式。Return Helper API 中庫存生命週期的真實資料來源是 [Webhook 事件流](/zh-Hant/webhooks)——`markShipmentArrive`、`newInventoryCreated`、`vasUpdated`、庫存處理完成事件、`notifyUserRmaSwapped` 等會在狀態變更時即時推送至您的端點。請圍繞 webhook 設計整合；本列表端點僅用於一次性回填與營運對帳。
</Warning>

回傳退件庫存紀錄的分頁列表——即倉庫側的逐項狀態。每筆紀錄代表一件已收到的物品，包含目前的處理決定、RMA、SKU 對映與所屬倉庫。

<h2 id="when-and-only-when-to-call">
  僅在以下情況呼叫
</h2>

* **一次性回填**：首次整合時，於訂閱 webhook 之前，將既有庫存寫入本地資料庫。
* **定期對帳**：用以偵測遺漏或亂序的 webhook 傳遞——將本地快取與本端點結果比對。

至於持續性狀態——例如包裹被登記、處理完成、RMA 被重派——請訂閱 webhook。輪詢本端點以察覺狀態變更不受支援，且在高負載下會產生過期資料。

<h2 id="required-parameters">
  必要參數
</h2>

* **`createFrom` / `createTo`** — 均必填，ISO 8601 時間戳。範圍上限為 **62 天**（`SearchConfig.simpleRecordsMaxDays`），超出會傳回軟錯誤。實際語義請見下方的 [視窗語義](#window-semantics)。
* **`pageSize`** — 介於 `1` 與 `50` 之間。
* **`offset`** — 非負整數。搭配 `pageSize` 用於位移分頁。

<h2 id="response-notes">
  回應備註
</h2>

* `totalNumberOfRecords`（頂層欄位，與 `data` 同層）為目前視窗的總筆數——分頁時以此作為上限。
* `handlingCode` 反映目前的處理決定——可呼叫 [更新退件庫存處理](/zh-Hant/api-reference/returninventory/update-return-inventory-handling) 變更。
* `handlingStatusCode` 反映該處理決定的工作流狀態；可透過 [取得所有處理狀態](/zh-Hant/api-reference/handlingstatus/get-all-handling-statuses) 轉譯。
* 物品的 RMA（`rma`）是倉庫指派的識別碼，並非您賣家端的參考號。

<h2 id="backfilling-historical-inventory">
  歷史庫存回填
</h2>

可用本端點將每一筆既有的退件庫存載入您的本地資料庫，之後一律改用 webhook 接收後續事件。由於 API 對每次請求的時間範圍上限為 62 天，整體流程是「以 62 天為一格的滑動視窗，每格視窗內分頁、視窗依序往前推」，直至覆蓋帳號開通日。

<h3 id="window-semantics">
  視窗語義
</h3>

開始撰寫迴圈前，請先理解以下兩條規則——這是「乾淨回填」與「靜默漏一天」之間的分界線。

1. **驗證器規則（日曆日差）：**
   ```
   createTo.Date − createFrom.Date  ≤  62
   ```
   比較前會先把兩端的時分秒歸零。所以 `createFrom = 2024-03-13T15:00:00Z`、`createTo = 2024-05-14T09:00:00Z` 的請求仍然有效（`May 14 − Mar 13 = 62` 天），即使實際牆鐘時長略短於 62 × 24 小時。

2. **資料過濾（兩端整日皆包含）：**
   ```
   createOn ≥ createFrom.BeginOfDay()   AND   createOn ≤ createTo.EndOfDay()
   ```
   也就是說，伺服器會把 `createFrom` 擴張到當日 `00:00:00.000`，`createTo` 擴張到當日 `23:59:59.999`，再進行過濾。因此**一次合法請求實際覆蓋 63 個連續日曆日的資料**（createFrom 當日整天、createTo 當日整天、以及之間所有天）。

對滑動視窗回填的實際影響：若把視窗 N 的 `createFrom` 直接作為視窗 N+1 的 `createTo`，那一天會同時出現在兩個視窗的結果中。這是**重複**，不是**漏單**——演算法絕不會漏單，只是在每個視窗接縫處多抓約一天的資料。只要本地資料表以 `returnInventoryId` 作為主鍵並使用 UPSERT（或 INSERT IGNORE），重複自動合併，最終資料完全正確。

如要徹底避免重複抓取，每次迭代後改成 `windowEnd = windowStart − 1 day`（而非 `windowEnd = windowStart`），每個視窗就是無重疊的全新 63 天切片。兩種寫法都正確；下方範例採用「容許接縫重疊」版本，因為它對客戶端與伺服器之間的時鐘漂移更寬容，是建議的預設。

<h3 id="backfill-algorithm">
  回填演算法
</h3>

1. 選定 `historyStart`（例如帳號開通日）。
2. 以 `windowEnd = now()` 起步。
3. 計算 `windowStart = max(windowEnd − 62 天, historyStart)`。
4. 在視窗內，從 `offset = 0` 開始按 `pageSize` 翻頁，直到 `offset ≥ totalNumberOfRecords`。每筆紀錄以 `returnInventoryId` 為主鍵 UPSERT 進入本地資料庫。
5. 設 `windowEnd = windowStart`，回到第 3 步繼續，直至 `windowEnd ≤ historyStart`。
6. 之後改由 `newInventoryCreated` webhook（以及其他庫存生命週期事件）維護本地資料；除非懷疑 webhook 資料遺失，否則無須重跑本回填。

<h3 id="sample-code">
  程式範例
</h3>

<CodeGroup>
  ```java Java theme={null}
  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;
      }
    }
  }
  ```

  ```js Node.js theme={null}
  // 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);
  ```

  ```ts TypeScript theme={null}
  // 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);
  ```

  ```apex Apex theme={null}
  // 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\'');
      }
  }
  ```
</CodeGroup>

<h2 id="related">
  相關
</h2>

* [取得退件庫存詳情](/zh-Hant/api-reference/returninventory/get-return-inventory-details) — 當 webhook 提及未快取的 `returnInventoryId` 時，依 ID 取得單筆紀錄。
* [依行項目 ID 取得退件庫存](/zh-Hant/api-reference/returninventory/get-return-inventory-by-line-item-id) — 依您的 `returnRequestLineItemId` 查詢。
* [更新退件庫存處理](/zh-Hant/api-reference/returninventory/update-return-inventory-handling) — 寫入側端點。
* [取得所有處理狀態](/zh-Hant/api-reference/handlingstatus/get-all-handling-statuses) 與 [取得所有處理類型](/zh-Hant/api-reference/handling/get-all-handling-types) — 代碼至標籤對映。
* [Webhooks](/zh-Hant/webhooks) — 庫存生命週期事件的標準通道。請先設定 webhook 再使用本端點。

如需了解錯誤回應的解讀與處理方式，請參閱 [Error codes](/zh-Hant/reference/error-codes)。


## OpenAPI

````yaml get /api/ReturnInventory/list
openapi: 3.1.0
info:
  title: Return Helper API
  description: API documentation for Return Helper — covering User and Public endpoints.
  version: 1.0.0
servers:
  - url: https://api.returnshelper.com/uat/user
    description: Sandbox — User API
  - url: https://api.returnshelper.com/uat/public
    description: Sandbox — Public API
  - url: https://api.returnhelpercentre.com/v1/user
    description: Production — User API
  - url: https://api.returnhelpercentre.com/v1/public
    description: Production — Public API
  - url: https://api.returnhelperchina.com/user
    description: Production — User API (China)
security:
  - ApiKey: []
    ApiToken: []
paths:
  /api/ReturnInventory/list:
    get:
      tags:
        - ReturnInventory
      summary: List return inventories with pagination
      operationId: ReturnUserApi_ReturnInventory_List
      parameters:
        - name: pageSize
          in: query
          required: true
          schema:
            type: integer
            maximum: 50
            minimum: 1
          description: Number of records per page (1–50)
        - name: offset
          in: query
          required: true
          schema:
            type: integer
        - name: createFrom
          in: query
          required: true
          schema:
            type: string
            format: date-time
        - name: createTo
          in: query
          required: true
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: >-
                  #/components/schemas/PaginationResponse_List_UserListReturnInventoryReply
        '401':
          description: >-
            Authentication failed. Returned when the `x-rr-apikey` or
            `x-rr-apitoken` header is missing or invalid. The body uses the
            standard `ApiResponse` envelope with `meta.error.message` describing
            the auth failure.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiResponse'
      security:
        - ApiKey: []
          ApiToken: []
      servers:
        - url: https://api.returnshelper.com/uat/user
          description: Sandbox — User API
components:
  schemas:
    PaginationResponse_List_UserListReturnInventoryReply:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/UserListReturnInventoryReply'
          description: List of return inventories
        totalNumberOfRecords:
          type: integer
          format: int32
          description: Total count of return inventories
    ApiResponse:
      type: object
      description: >-
        Universal response envelope. Successful responses include the business
        payload as additional top-level fields alongside `correlationId` and
        `meta`. Failed responses (auth errors, validation errors) only populate
        `correlationId` and `meta`, with `meta.errorCode` and `meta.error`
        describing the failure.
      properties:
        correlationId:
          type:
            - string
            - 'null'
          description: >-
            Unique correlation ID for tracing the request through Return Helper
            systems.
        meta:
          $ref: '#/components/schemas/ApiResponseMeta'
    UserListReturnInventoryReply:
      type: object
      properties:
        returnInventoryId:
          type: integer
          format: int32
        returnRequestId:
          type: integer
          format: int32
        returnRequestLineItemId:
          type:
            - string
            - 'null'
        rma:
          type:
            - string
            - 'null'
        itemRma:
          type:
            - string
            - 'null'
        sku:
          type:
            - string
            - 'null'
        handlingCode:
          type:
            - string
            - 'null'
        handlingStatusCode:
          type:
            - string
            - 'null'
        warehouseId:
          type: integer
          format: int32
        createOn:
          type: string
          format: date-time
    ApiResponseMeta:
      type: object
      description: >-
        Application-level metadata for every API response. Inspect `status` and
        `errorCode` to detect soft-error responses (validation failures arrive
        as HTTP 200 with `meta.status: 400`).
      properties:
        status:
          type: integer
          description: >-
            Application-level status code. For successful operations this
            mirrors the HTTP status (e.g. 200). For validation failures it
            reports the logical status (e.g. 400) even though the wire HTTP
            status is 200.
        data:
          type: object
          additionalProperties:
            type: string
          description: Reserved free-form metadata key/value pairs. Usually empty.
        errorCode:
          type:
            - string
            - 'null'
          description: >-
            Machine-readable error code (e.g. `VALIDATION_FAILED`). Non-null
            only when the operation failed.
        error:
          type: object
          additionalProperties: true
          description: >-
            Field-level or message-level error detail keyed by request property
            name. Empty object on success.
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: x-rr-apikey
      description: Your API key
    ApiToken:
      type: apiKey
      in: header
      name: x-rr-apitoken
      description: Your API token — keep this private

````