> ## 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.

# List return inventories with pagination

<Warning>
  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](/webhooks) — `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.
</Warning>

Returns a paginated list of return inventory records — the warehouse-side per-item state. Each record represents a single received item with its current handling decision, RMA, SKU mapping, and warehouse assignment.

## 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.

For ongoing state — knowing when a parcel is logged, when handling completes, when an RMA is reassigned — subscribe to webhooks. Polling this endpoint to discover state changes is unsupported and will produce stale data under load.

## 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](#window-semantics) below for the exact rule and what "62 days" really means.
* **`pageSize`** — between `1` and `50` inclusive.
* **`offset`** — non-negative integer. Combine with `pageSize` for offset-based pagination.

## Response notes

* `totalNumberOfRecords` (top-level field, sibling to `data`) is the row count for the current window — use it as the upper bound when paginating.
* `handlingCode` reflects the current decision on the item — call [Update return inventory handling](/api-reference/returninventory/update-return-inventory-handling) to change it.
* `handlingStatusCode` reflects the workflow state for that handling decision; translate via [Get all handling statuses](/api-reference/handlingstatus/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.

1. **Validator rule (calendar-day diff):**
   ```
   createTo.Date − createFrom.Date  ≤  62
   ```
   The time-of-day is stripped before comparison. So a request with `createFrom = 2024-03-13T15:00:00Z` and `createTo = 2024-05-14T09:00:00Z` is valid (`May 14 − Mar 13 = 62` days), even though wall-clock difference is less than 62 × 24h.

2. **Data filter (inclusive both ends, by full day):**
   ```
   createOn ≥ createFrom.BeginOfDay()   AND   createOn ≤ createTo.EndOfDay()
   ```
   That is, the server expands `createFrom` to that day's `00:00:00.000` and `createTo` to that day's `23:59:59.999` before 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).

The practical consequence for a sliding-window backfill: if you reuse `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

1. Pick a `historyStart` (e.g. the date your account was provisioned).
2. Start with `windowEnd = now()`.
3. Compute `windowStart = max(windowEnd − 62 days, historyStart)`.
4. Inside the window, page from `offset = 0` in `pageSize` chunks until `offset ≥ totalNumberOfRecords`. UPSERT each row keyed by `returnInventoryId`.
5. Set `windowEnd = windowStart` and repeat from step 3 until `windowEnd ≤ historyStart`.
6. From this point on, maintain your local store off the `newInventoryCreated` webhook (and the other inventory-lifecycle events). Re-running this backfill is unnecessary unless you suspect webhook data loss.

### Sample code

<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>

## Related

* [Get return inventory details](/api-reference/returninventory/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](/api-reference/returninventory/get-return-inventory-by-line-item-id) — look up by your `returnRequestLineItemId`.
* [Update return inventory handling](/api-reference/returninventory/update-return-inventory-handling) — write counterpart.
* [Get all handling statuses](/api-reference/handlingstatus/get-all-handling-statuses) and [Get all handling types](/api-reference/handling/get-all-handling-types) — code-to-label mappings.
* [Webhooks](/webhooks) — the canonical channel for inventory lifecycle events. Set up webhooks before relying on this endpoint.

See [Error codes](/reference/error-codes) for how to interpret and handle the API's error responses.


## 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

````