Searching, sorting and paging
GET /api/v1/contacts can search, sort and page — the same three things the Statey dashboard does, with the same options.
Paging
Every paginated collection uses page and page_size :
curl "https://app.statey.app/api/v1/contacts?page=2&page_size=100" \
-H "Authorization: Bearer stky_your_key_here"
{
"contacts": [ … ],
"meta": {
"page": 2,
"page_size": 100,
"total_pages": 6,
"total_count": 573,
"data_freshness": { "last_completed_at": "…", "rebuild_in_progress": false }
}
}
page_size defaults to 50 and is capped at 200. Ask for more and you get 200 rather than an error.
Searching
search matches part of a customer's name or account number, ignoring case:
curl "https://app.statey.app/api/v1/contacts?search=acme" \ -H "Authorization: Bearer stky_your_key_here"
It is a plain substring match, not a pattern — a % in your search term matches a literal % .
Sorting
sort takes one of six columns, and direction is asc (the default) or desc :
sort |
Orders by | In the response? |
|---|---|---|
xero_name |
Customer name. The default | as name |
balance_due |
What they owe in total | yes |
amount_outstanding |
What is outstanding | no |
amount_overdue |
What is overdue | yes |
most_days_overdue |
How late their oldest overdue item is | yes |
available_credit |
Credit available to them | yes |
curl "https://app.statey.app/api/v1/contacts?sort=most_days_overdue&direction=desc" \ -H "Authorization: Bearer stky_your_key_here"
amount_outstanding is the one to watch: you can order by it, but the listing does not return the value, so you cannot show or re-sort it yourself. It is there because the dashboard offers the same sort. If you need the number, balance_due is returned and is what most integrations actually want.
An unrecognised sort or direction is refused with 422 validation_failed rather than quietly ignored, so a typo cannot hand you correct-looking data in the wrong order.
The sharp edge, and how to avoid it
Read this before writing anything that pages through your whole customer list.
Statey recalculates every customer's balances in the background. When it does, a customer's summary row is replaced — the old one is deleted and a new one written. Partial syncs are queued every hour, on the fifth minute, and the recalculation trails a little behind that.
That matters when you are paging. If the numbers change while you are on page 3 of 12, rows can move between pages: a customer you have already seen can reappear, and one you have not seen can slip past you. Nothing errors. You simply end up with a slightly wrong list.
Three ways to avoid it, in order of how much they help.
1. Sort by name when you want everything
A customer's name does not change during a recalculation, so their position in a name-ordered list does not either. Paging is stable.
# Safe for a full export: position does not move underneath you. curl "https://app.statey.app/api/v1/contacts?sort=xero_name&page_size=200"
Sorting by an amount ranks correctly at the moment of each request, but those amounts are exactly what a recalculation changes. Use amount sorts to answer "who are my worst twenty debtors" — a single page — rather than to walk the whole list.
2. Check the sync status before you start
GET /api/v1/sync_status
If a rebuild is running, wait for it to finish before starting a long export. See Data freshness.
3. Watch rebuild_in_progress while you go
Every page you fetch carries it:
"meta": { "data_freshness": { "rebuild_in_progress": false } }
If it is false when you start and true on a later page, the data moved while you were reading. The safe response is to start again rather than trust the pages you already have.
A worked example
Exporting every customer who owes you anything, safely:
1. GET /api/v1/sync_status → if it is not idle, wait. 2. GET /api/v1/contacts?filter=outstanding&sort=xero_name&page_size=200 → note meta.total_pages, and meta.data_freshness.rebuild_in_progress 3. Repeat for page=2 … page=N → if rebuild_in_progress ever becomes true, stop and start again from step 1 4. Compare meta.total_count against how many you collected. → a mismatch means the set changed while you read it
Step 4 is the cheap safety net: total_count is recalculated on every request, so comparing it with what you actually collected catches a shifting list without any extra calls.
Filtering
filter narrows to one of the dashboard's groups, and combines with search and sort:
filter |
Customers who |
|---|---|
overdue |
Have something overdue |
outstanding |
Have an outstanding balance |
has_credit |
Have available credit |
missing_email |
Have nobody to email — see Who receives a statement |
has_issues |
Have something wrong with their record |
currency narrows to one currency code, for customers who trade in several.