Reference Developers

Pagination and filters in the API

How API lists are paginated (page, per_page, meta) and which filters really work, including the merchant stage values: processing and awaiting_funds are ignored, while the company API rejects unknown values.

Last updated 22 October 2026 · Last verified 22 October 2026

Purpose#

This page explains how API lists are paginated and which filters really work as coded, especially the stage values the merchant API accepts.

Who can use it and what you need#

  • A merchant (platform merchant) with an API key on /api/v1, or a delivery company admin with the company key on /api/company/v1. Details are in the rate limits article.

Paginated lists#

Request Default per_page Maximum Order Filters
GET /api/v1/parcels 20 100 newest first (by id) reference, from, to, stage
GET /api/v1/statement 100 500 oldest first (date, then id) from, to, wallet, category
GET /api/company/v1/parcels 20 100 newest first (by id) merchant_id, deliveryman_id, reference, from, to, updated_since, stage
GET /api/company/v1/merchants 50 100 by id, ascending q

These lists are not paginated and return every record at once: in the merchant API /companies, /governorates, /pickup-locations, /warehouse/stock, /webhooks and /webhooks/events; in the company API /deliverymen.

Response shape#

json
{
  "success": true,
  "data": [ { "id": 1043, "tracking_number": "DS261013AB12CD", "reference": "ORDER-1043", "stage": "delivered", "status_code": 9 } ],
  "meta": { "page": 1, "per_page": 50, "total": 132, "last_page": 3 }
}

(Parcel objects are shortened here.) The four keys in meta are all there is: no links, no cursor. GET /statement is the exception for data: it is an object with period, currency, wallets, by_category and lines (the paginated array), and meta counts the lines. Rules:

  • page starts at 1. An invalid value, or one below 1, is treated as 1.
  • A per_page outside the allowed range is not rejected but clamped to the nearest limit (for example 500 on the parcel list becomes 100). The effective value comes back in meta.per_page.
  • A page beyond the last one returns an empty data, and meta still shows the same total and last_page. With no results last_page is 1.
  • total is the number of records after the filters.

Merchant parcel list filters#

  • reference: exact match with the reference you sent when creating the parcel.
  • from and to: YYYY-MM-DD, both days included, compared with the parcel's creation date (UTC). The format is not validated: use only this one.
  • stage: only six values work, and they match the stage field of the parcel object:
stage value Matches status_code
pending 1
out_for_delivery 7 and 8
delivered 9
partially_delivered 32
returning 24, 26, 27, 11 and 12
returned 30 and 13

Parcels in awaiting_funds (created with wait_for_balance or from connected-store orders) do not appear in the list, but you can fetch one by tracking number or reference with GET /parcels/{tracking}.

Differences in the company API#

  • An unknown stage is rejected with 422 validation_failed and a message listing the available values, instead of being ignored.
  • processing works here and matches only the codes 2, 3, 4, 5, 6 and 19. Any status code outside the documented buckets (for example a legacy or unusual code) is reported with stage processing but is not matched by this filter. awaiting_funds is not accepted (422).
  • updated_since (YYYY-MM-DD HH:MM:SS) returns parcels updated since that time, which suits periodic synchronisation.
  • merchant_id and deliveryman_id filter when sent; q on merchants searches the business name or mobile number.

Statement filters (/statement)#

  • from and to are accepted only as literal YYYY-MM-DD; otherwise they are ignored and the default is used: the start of the month to today. If from is after to they are swapped. A range longer than 366 days is shortened by moving from to 366 days before to, without an error.
  • wallet: one of recharge, held, return or payout_pending, otherwise ignored.
  • category: lowercase Latin letters and underscores (2 to 60 characters). It is not checked against known categories: an unknown value returns no lines and an empty by_category, while wallets still lists the wallets with their opening and closing balances and zero credits, debits and count.
  • The wallets and by_category sections cover the whole period regardless of the page; paging applies to lines only.

Reading every page#

  1. Request page 1 with a fixed `per_page`

    Send per_page=100 with your filters and keep them identical in later requests.

  2. Read `meta.last_page`

    This is the number of pages. Do not rely on the size of data to detect the end.

  3. Request the remaining pages

    Increase page up to last_page. On a 429 wait for Retry-After, then repeat the same request.

  4. Cope with the list changing while you read

    New parcels appear at the top and cancelled ones disappear, so a parcel can repeat or one can be missed. De-duplicate by id, fix the range with from and to, and for continuous synchronisation use webhooks or (companies) updated_since.

python
import time
import requests

BASE = "https://YOUR_PLATFORM_DOMAIN/api/v1"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY", "Accept": "application/json"}


def all_parcels(filters):
    page = 1
    while True:
        response = requests.get(
            f"{BASE}/parcels",
            headers=HEADERS,
            params={**filters, "per_page": 100, "page": page},
            timeout=30,
        )
        if response.status_code == 429:
            time.sleep(int(response.headers.get("Retry-After", 60)))
            continue
        response.raise_for_status()
        body = response.json()
        yield from body["data"]
        if page >= body["meta"]["last_page"]:
            break
        page += 1


for parcel in all_parcels({"stage": "delivered", "from": "2026-10-01", "to": "2026-10-31"}):
    print(parcel["tracking_number"], parcel["stage"])

Common mistakes#

  • Expecting stage=processing to filter in the merchant API: you get every parcel. Filter on stage yourself.
  • per_page=500 on parcels: no error, but you get only 100; read meta.per_page.
  • Other date formats (such as 13/10/2026): not validated on the parcel list (unexpected results), ignored on the statement.

Permissions and limits#

An active API key is required and, when plans apply to your account, the plan flag api (otherwise 403 plan_feature); the merchant API is for platform merchants, not company merchants. Each page request counts toward 60 requests per minute per IP address and toward your plan limit, so prefer a large per_page over many small pages.

API

  • GET /api/v1/parcels
  • GET /api/v1/statement
  • GET /api/company/v1/parcels
  • GET /api/company/v1/merchants