ProxioDocs
API Reference

Pagination

The Proxio API uses cursor pagination everywhere. Pass limit and cursor, read meta.next_cursor, and loop until it's null. Includes a copy-paste pagination loop in Python and Node.js.

Every list in the Proxio API is cursor-paginated. There is no page number and no offset, you pass an opaque cursor forward and stop when the API says there's nothing left.

Parameters

ParameterValuesDefaultNotes
limit1 to 10020Maximum items per page.
cursoropaque string-The next_cursor from the previous page. Omit it for the first page.

Results are ordered newest first (created_at descending, then id descending), a stable order that never skips or repeats an item as you page.

Reading the cursor

Paginated responses put the next cursor in meta.next_cursor. When it's a string, there's another page; when it's null, you've reached the end.

{
  "data": [
    { "id": "clord_9f2a", "status": "PAID", "total": "112.50", "created_at": "2026-07-16T10:00:00.000Z" }
  ],
  "meta": {
    "next_cursor": "eyJpZCI6ImNsb3JkXzlmMmEiLCJ0cyI6IjIwMjYtMDctMTZUMTA6MDA6MDBaIn0",
    "has_more": true,
    "request_id": "req_8Ke2jP4mQ"
  }
}

meta.has_more says the same thing as next_cursor in boolean form, true exactly when next_cursor is a string and false exactly when it's null. Use whichever reads more naturally in your loop condition, they never disagree.

Cursors are opaque, pass them back verbatim

A cursor is an encoded pointer, not something you construct. Send back exactly the next_cursor string you received. Editing or truncating it fails with INVALID_CURSOR.

There is no total count: paginate until next_cursor is null (or has_more is false) rather than computing a page count up front.

Sorting and date filters

A cursor list's default order is newest first (created_at descending). Some lists accept more:

ParameterValuesNotes
sorta sort key, list-dependentDefaults to created_at. An endpoint that supports more names them on its own page, for example orders sorts by created_at or total and wallet transactions by created_at or amount. /services accepts only created_at, there's no second sort key to ask for. An unrecognized sort fails with VALIDATION_ERROR naming the allowed set, it's never silently ignored.
orderasc | descDefaults to desc. Same validation as sort.
created_after, created_beforeISO 8601 timestampsBoth inclusive, on the list's own created_at. An unparseable value, or a before earlier than after, fails with VALIDATION_ERROR naming the specific parameter.

Sort, order, and the cursor

A cursor is only valid under the ordering it was issued for

A cursor encodes a position, and a position only means something inside one specific ordering. Change sort or order partway through a walk and the API refuses the old cursor with INVALID_CURSOR rather than silently serving rows twice or skipping some, it does not transparently restart you. To change sort or order, drop cursor entirely and start a fresh walk from page one. A cursor from before a list supported sorting keeps working under the default created_at / desc order, only an actual change of sort or order mid-walk is refused.

Which lists paginate

Lists that can grow without bound are cursor-paginated: /services, /orders, /wallet/transactions, /wallet/topups, /webhooks, /api-keys, /webhooks/{id}/deliveries, and /events.

Small, bounded lists return a full array in data (still with meta, but no cursor): /locations, /products, a service's credentials (up to 20), a credential's whitelist (up to 50), and its sessions (up to 500).

A pagination loop

Keep calling with the returned cursor until it comes back null, collecting every page into one list:

import requests

BASE = "https://dashboard.proxio.net/api/v1"
API_KEY = "pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}


def paginate(path, params=None):
    params = dict(params or {})
    params.setdefault("limit", 100)
    cursor = None
    while True:
        if cursor:
            params["cursor"] = cursor
        resp = requests.get(f"{BASE}{path}", headers=HEADERS, params=params, timeout=15)
        resp.raise_for_status()
        body = resp.json()
        for item in body["data"]:
            yield item
        cursor = body["meta"].get("next_cursor")
        if not cursor:
            break


orders = list(paginate("/orders", {"status": "PAID"}))
print(f"Fetched {len(orders)} orders")
const BASE = "https://dashboard.proxio.net/api/v1"
const API_KEY = "pxo_9fJ2kQ7xR4mN8pL1dW6vB3cH5tZ0aYqS7dK2mN9x"
const HEADERS = { Authorization: `Bearer ${API_KEY}` }

async function paginate(path, params = {}) {
  const items = []
  const query = new URLSearchParams({ limit: "100", ...params })
  let cursor = null
  do {
    if (cursor) query.set("cursor", cursor)
    const res = await fetch(`${BASE}${path}?${query}`, { headers: HEADERS })
    if (!res.ok) throw new Error(`HTTP ${res.status}`)
    const body = await res.json()
    items.push(...body.data)
    cursor = body.meta.next_cursor
  } while (cursor)
  return items
}

const orders = await paginate("/orders", { status: "PAID" })
console.log(`Fetched ${orders.length} orders`)

Use the largest limit (100) to minimize round trips, and remember each page is one request against your rate-limit budget.

On this page