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

# Pagination

> Paginate DualEntry Public API list endpoints with limit and offset, and filter by update time using the inclusive updated_after and updated_before bounds.

List endpoints support limit/offset pagination to efficiently handle large datasets.

## Parameters

Every list endpoint accepts these two query parameters:

| Parameter | Type    | Default | Max | Description                                                 |
| --------- | ------- | ------- | --- | ----------------------------------------------------------- |
| `limit`   | integer | 100     | 100 | Number of records to return per page (1-100)                |
| `offset`  | integer | 0       | -   | Number of records to skip before starting to return results |

<Info>
  **Maximum Limit**: The `limit` parameter is capped at **100**. If you request more than 100 items, the API returns 100 items.
</Info>

## How it works

* **Page 1**: `offset=0, limit=100` → Returns records 1-100
* **Page 2**: `offset=100, limit=100` → Returns records 101-200
* **Page 3**: `offset=200, limit=100` → Returns records 201-300

## Example

```bash theme={null}
# First page (default)
curl "https://api.dualentry.com/public/v1/invoices" \
  -H "X-API-KEY: your_api_key_here"

# Custom page size
curl "https://api.dualentry.com/public/v1/invoices?limit=50&offset=100" \
  -H "X-API-KEY: your_api_key_here"
```

## Response format

List endpoints return an array of resources:

```json theme={null}
[
  {
    "id": "inv_001",
    "customer": "Customer A",
    "total": 1500.00
  },
  {
    "id": "inv_002",
    "customer": "Customer B",
    "total": 2300.00
  }
]
```

## Fetching all records

Continue fetching pages until you receive an empty array or fewer records than requested:

```python theme={null}
all_records = []
offset = 0
limit = 100

while True:
    response = requests.get(
        "https://api.dualentry.com/public/v1/invoices",
        headers=headers,
        params={"limit": limit, "offset": offset}
    )
    
    records = response.json()
    if not records:
        break
    
    all_records.extend(records)
    
    if len(records) < limit:
        break
    
    offset += limit
```

<Info>
  **Best Practice**: When fetching all records, add delays between requests to respect rate limits.
</Info>

## Filtering by updated date

Many list endpoints accept `updated_after` and `updated_before` query parameters to return only records whose `updated_at` timestamp falls in a given window. Both parameters accept an ISO 8601 `date-time` value and behave identically on v1 and v2. Support is per endpoint rather than universal: `invoices`, `bills`, `customer-payments`, and `vendor-payments` accept both, while some collections (including `companies`, `accounts`, `customers`, and `items`) accept neither. Check the endpoint's parameter list in the API reference before relying on them.

Both bounds are inclusive:

| Parameter        | Bound                           | Inclusive? |
| ---------------- | ------------------------------- | ---------- |
| `updated_after`  | Lower (maps to `updated_at >=`) | Yes        |
| `updated_before` | Upper (maps to `updated_at <=`) | Yes        |

A record whose `updated_at` matches either bound exactly appears in the response.

### Timestamp precision

DualEntry stores `updated_at` with microsecond precision, so a bound set to a whole second does not cover the fraction of a second that follows it. An upper bound of `updated_before=2026-07-31T23:59:59Z` excludes a record updated at `2026-07-31T23:59:59.500000Z`, silently dropping activity from the final second of the window.

Use the start of the next period as the upper bound rather than the last second of the current one:

| Upper bound            | Record at `23:59:59.000000Z` | Record at `23:59:59.500000Z` |
| ---------------------- | ---------------------------- | ---------------------------- |
| `2026-07-31T23:59:59Z` | Returned                     | Dropped                      |
| `2026-08-01T00:00:00Z` | Returned                     | Returned                     |

Because the upper bound is inclusive, a next-midnight bound also returns a record updated at exactly `00:00:00.000000` on August 1. The lower bound behaves the same way: passing `updated_after` equal to a record's exact `updated_at` returns that record again. Treat each bound as the start of the next period, resume from the greatest `updated_at` you have already processed, and deduplicate by record ID.

Fetch one calendar month of changes:

```bash theme={null}
curl "https://api.dualentry.com/public/v1/invoices?updated_after=2026-07-01T00:00:00Z&updated_before=2026-08-01T00:00:00Z" \
  -H "X-API-KEY: your_api_key_here"
```

## Validation errors

Invalid pagination parameters return a `400 Bad Request` error:

| Invalid Request | Error Message                              |
| --------------- | ------------------------------------------ |
| `limit=0`       | Input should be greater than or equal to 1 |
| `limit=-1`      | Input should be greater than or equal to 1 |
| `offset=-1`     | Input should be greater than or equal to 0 |

The date filters reject two further cases with the same status code:

| Invalid Request                             | Reason                                                             |
| ------------------------------------------- | ------------------------------------------------------------------ |
| `updated_after` later than `updated_before` | An inverted range is rejected rather than returning an empty array |
| `updated_after=2026-07-01T00:00:00`         | A value with no UTC offset is rejected when the request is parsed  |

Supply an explicit offset on every date filter value. Both `2026-07-01T00:00:00Z` and `2026-07-01T00:00:00-04:00` are accepted, and DualEntry normalizes the value to UTC before comparing it.

**Note:** Requesting `limit > 100` does not return an error. The limit is silently capped at 100.

**Example error response:**

```json theme={null}
{
  "success": false,
  "errors": {
    "__all__": ["greater_than_equal query.limit: Input should be greater than or equal to 1"]
  }
}
```

***

**Next:** [Learn about Errors →](/developers/guides/api/core-concepts/errors)
