Overview

All list endpoints in the OpenFX v1 API use cursor-based pagination. Unlike offset-based pagination, cursors provide stable, consistent results even when new resources are created or existing resources are modified between page requests.
Cursor-based pagination is the standard for financial APIs because it guarantees you will never skip or duplicate records when paginating through results — critical when reconciling transactions or payments.

Query Parameters

Every list endpoint accepts these pagination parameters:
ParameterTypeDefaultDescription
limitinteger50Number of items to return per page. Minimum 1, maximum 200.
starting_afterstringReturn items created after the resource with this ID. For forward pagination.
ending_beforestringReturn items created before the resource with this ID. For backward pagination.
starting_after and ending_before are mutually exclusive. Providing both in the same request will return a 400 invalid_request_error.
Pagination parameters use snake_case (starting_after, ending_before). This is an intentional exception to the camelCase convention used elsewhere in the API, following industry-standard pagination naming from Stripe and similar APIs.

Response Envelope

All list endpoints return a consistent response shape:
{
  "items": [
    { "id": "pmt_01953e1a5f4b7001", "..." : "..." },
    { "id": "pmt_01953e1a5f4b7005", "..." : "..." },
    { "id": "pmt_01953e1a5f4b7012", "..." : "..." }
  ],
  "pagination": {
    "hasMore": true,
    "nextCursor": "pmt_01953e1a5f4b7012"
  }
}
FieldTypeDescription
itemsarrayThe page of results. May be empty if no results match.
pagination.hasMorebooleantrue if more results exist beyond this page.
pagination.nextCursorstring or nullThe cursor to pass as starting_after to fetch the next page. null when hasMore is false.

Paginating Through All Results

To retrieve all results, repeatedly fetch pages using the nextCursor value until hasMore is false.
import requests

BASE_URL = "https://sandbox.api.openfx.com/v1"

def list_all_payments(headers: dict, **filters) -> list:
    """Fetch all payments, handling pagination automatically."""
    all_payments = []
    params = {"limit": 200, **filters}

    while True:
        response = requests.get(
            f"{BASE_URL}/payments",
            headers=headers,
            params=params,
        )
        response.raise_for_status()
        data = response.json()

        all_payments.extend(data["items"])

        if not data["pagination"]["hasMore"]:
            break

        # Use nextCursor to fetch the next page
        params["starting_after"] = data["pagination"]["nextCursor"]

    return all_payments

# Usage
payments = list_all_payments(
    headers=auth_headers,
    status="completed",
)
print(f"Found {len(payments)} completed payments")

Date Range Filtering

Most list endpoints support date range filtering using bracket notation on the createdAt field:
ParameterTypeDescription
createdAt[gte]ISO 8601 datetimeReturn items created at or after this timestamp.
createdAt[lte]ISO 8601 datetimeReturn items created at or before this timestamp.
# Payments created in January 2026
GET /v1/payments?createdAt[gte]=2026-01-01T00:00:00Z&createdAt[lte]=2026-01-31T23:59:59Z&limit=100
Date range filters work alongside pagination. The cursors will respect the date boundaries:
# Fetch all transactions for a specific account in Q1 2026
transactions = list_all(
    endpoint="/transactions",
    params={
        "accountId": "acc_01953e1a5f4b7002",
        "createdAt[gte]": "2026-01-01T00:00:00Z",
        "createdAt[lte]": "2026-03-31T23:59:59Z",
    },
)

Filter Parameters

List endpoints support resource-specific filters alongside pagination. Filter parameters use camelCase:
ParameterAvailable OnDescription
customerIdpayments, accounts, counterparties, transactionsFilter by customer.
accountIdtransactions, transfersFilter by account.
statuspayments, customers, accountsFilter by lifecycle status.
typetransactions, accounts, entitiesFilter by resource type.
railpayments, inbound paymentsFilter by payment rail (e.g., ach, swift).
directiontransactionsFilter by debit or credit.
# List all pending ACH payments for a specific customer
GET /v1/payments?customerId=cus_01953e1a5f4b7000&rail=ach&status=processing&limit=50

Why Cursors, Not Offsets

OpenFX does not support offset-based pagination (page=2 or offset=50). This is a deliberate design decision for financial data integrity.
Offset-based pagination has serious problems with financial data:
ProblemOffset PaginationCursor Pagination
New records insertedItems shift — you may see duplicates or skip itemsStable — cursor position is fixed
Records deleted/modifiedItems shift unpredictablyUnaffected — cursor references a specific record
Deep pagesSlow — database must skip N rowsFast — database seeks directly to cursor position
Concurrent requestsResults may be inconsistent across pagesResults are always consistent
For reconciliation and audit workflows, cursor-based pagination guarantees that iterating through all pages produces a complete, non-overlapping set of records.

Best Practices

Always use the nextCursor value. Do not attempt to construct cursor values or extract IDs from the items array for pagination. Always use the nextCursor returned in the pagination object.
Use the maximum limit for bulk operations. When fetching all records (e.g., for reconciliation), set limit=200 to minimize the number of API calls.
Combine date filters with pagination. For large datasets, narrow your query with createdAt[gte] and createdAt[lte] before paginating. This reduces total pages and improves response times.
Handle empty pages gracefully. A response with "items": [] and "hasMore": false is valid — it means no results match your query.
  • Resource IDs — how UUIDv7 IDs enable time-ordered cursor pagination
  • Rate Limiting — manage request volume when paginating large datasets
  • API Reference — see pagination parameters on every list endpoint