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#
{
"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:
pagestarts at 1. An invalid value, or one below 1, is treated as 1.- A
per_pageoutside 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 inmeta.per_page. - A page beyond the last one returns an empty
data, andmetastill shows the sametotalandlast_page. With no resultslast_pageis 1. totalis the number of records after the filters.
Merchant parcel list filters#
reference: exact match with the reference you sent when creating the parcel.fromandto: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 thestagefield 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
stageis rejected with422 validation_failedand a message listing the available values, instead of being ignored. processingworks 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 withstageprocessingbut is not matched by this filter.awaiting_fundsis not accepted (422).updated_since(YYYY-MM-DD HH:MM:SS) returns parcels updated since that time, which suits periodic synchronisation.merchant_idanddeliveryman_idfilter when sent;qon merchants searches the business name or mobile number.
Statement filters (/statement)#
fromandtoare accepted only as literalYYYY-MM-DD; otherwise they are ignored and the default is used: the start of the month to today. Iffromis aftertothey are swapped. A range longer than 366 days is shortened by movingfromto 366 days beforeto, without an error.wallet: one ofrecharge,held,returnorpayout_pending, otherwise ignored.category: lowercase Latin letters and underscores (2 to 60 characters). It is not checked against known categories: an unknown value returns nolinesand an emptyby_category, whilewalletsstill lists the wallets with their opening and closing balances and zerocredits,debitsandcount.- The
walletsandby_categorysections cover the whole period regardless of the page; paging applies tolinesonly.
Reading every page#
Request page 1 with a fixed `per_page`
Send
per_page=100with your filters and keep them identical in later requests.Read `meta.last_page`
This is the number of pages. Do not rely on the size of
datato detect the end.Request the remaining pages
Increase
pageup tolast_page. On a 429 wait forRetry-After, then repeat the same request.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 withfromandto, and for continuous synchronisation use webhooks or (companies)updated_since.
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=processingto filter in the merchant API: you get every parcel. Filter onstageyourself. per_page=500on parcels: no error, but you get only 100; readmeta.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/parcelsGET /api/v1/statementGET /api/company/v1/parcelsGET /api/company/v1/merchants