maesn
Product insight

Unified pagination and filtering, one shape for every result set

Systems paginate in incompatible ways, some by page number, some by cursor, each with its own quirks. Maesn puts one limit and page parameter in front of all of them, and adds the filters that let you pull only what changed.

Four ways to pageone result set
Page-basedcount pages first
Page-based, minus onelast page errors
Cursor-basedbookmark, no total
Max count in the URLparse it out
maesn pages it
One pagination, every system
GET /invoices?limit=50&page=1
Trusted by winning software teams
HubSpotTipaltiPaywiseRallyQredNordhealthFindityFintoClockinHEROHolviLanes & PlanesAgicapHubSpotTipaltiPaywiseRallyQredNordhealthFindityFintoClockinHEROHolviLanes & PlanesAgicap
The concept

What is unified pagination and filtering

Normalising the shape of the data is one problem. Controlling the shape of the result set is a separate one: which records come back, in what order, which ones are filtered out and how many arrive at a time. Two systems can return an identical invoice object and still disagree completely on how you walk through ten thousand of them.

Unified pagination and filtering is the layer that settles that. One paging scheme, one set of filter parameters and one way to order results, applied across every system, so the loop you write for the first integration is the loop that works for all of them. It sits alongside the common data model rather than inside it: the model settles what a record looks like, this settles which records you get.

The problem

Two paradigms, and a variant per system

Paging is not one problem with one solution. It is two incompatible models, each with per-system oddities on top, and you meet all of them the first time you try to read a full list.

Page-based

You count and request numbered pages.

  1. 1Ask how many pages there are
  2. 2Request page 1, 2, 3 in a loop
  3. 3Stop one page early, the last errors
  4. 4Some return the max count in the URL
Cursor-based

You follow a bookmark until it runs out.

  1. 1Ask for the first batch
  2. 2Read the cursor out of the response
  3. 3Send it back to get the next batch
  4. 4You never learn the total

Two incompatible paradigms, plus a variant per system. Build against them directly and every integration needs its own paging loop.

How Maesn handles it

Two parameters, predictable results

You send limit and page. Maesn translates that into whatever the target system actually needs and hands back a consistent page of normalised records.

limit
How many per page

Accepts 5, 10, 20, 50 or 100, so a page size is always a valid, predictable number.

page
Which page to return

Starts at 1 and counts up, with no pre-query and no off-by-one at the end of the set.

One pagination everywhere

Page-based schemes, cursor bookmarks and their per-system variants all sit behind the same two query parameters.

Delta with lastModified

Pass the timestamp of your last fetch and receive only the objects that are new or changed since then.

Filters where they matter

Beyond lastModified there is status on invoices, plus filters built for a specific system's gaps, on the same unified endpoints.

Ordering you control

Order invoices, bills and booking proposals by date, number or created and updated timestamps, ascending or descending.

fetch-invoices.ts
const res = await axios.get(url, {
params: { limit: 50, page: 1 },
headers: {
'X-API-KEY': apiKey,
'X-ACCOUNT-KEY': accountKey
}
});

You also learn the size

Every response carries a meta.pagination block. That is the thing a cursor cannot give you: you can show a count, plan the work and know when you are done, on systems that never expose a total themselves.

totalHow many records match in the first place
totalPagesHow many pages that is at your page size
currentPageWhich page this response represents
perPageThe page size the response was built with
The delta filter

Pull what changed, not everything again

The filter most integrations end up living on is lastModified. You store the timestamp of your last successful fetch, pass it on the next request, and receive only the objects that are new or changed since then. A nightly reconciliation over a customer's full ledger becomes a request that returns a handful of records.

It is built on nearly every schema and endpoint, which is what makes it usable as a general strategy rather than a special case. The practical rule that goes with it: poll at the lowest frequency that still keeps your data fresh enough. Polling every minute for something that changes once a day is wasted traffic and cost, and the budget you are spending is the target system's. Maesn adds no rate limit of its own on the way in, so on an ordinary synchronous call the system's limit is the one you can exhaust, and a refusal reaches you as its own standardised error rather than a generic failure. Asynchronous processing releases volume at a pace the system accepts on the endpoints that support it, which is a narrower set than every call you make. Where you need to react the moment something changes rather than on a schedule, unified webhooks push the event to you instead, and many integrations run both: webhooks for immediacy, a periodic delta poll as the safety net.

Your last fetch: Jan 15, 09:30
9714f3a2-5b8e-4d1b-…Jan 14, 08:12
b28c1f04-77ae-4c31-…Jan 14, 16:40
3f5d9a61-0c42-4e8b-…Jan 15, 10:05
eaa28f49-6028-4b6e-…Jan 15, 11:37

Illustrative. Grey objects were already in your database.

maesn returns the delta
Only what changed
GET /invoices?lastModified=2026-01-15T09:30:00

You get the two objects that are new or changed since your timestamp, not the whole ledger again.

Filtering and ordering

Filters built for what each system lacks

Some filters exist across the board, others exist because one system has a gap worth closing. Both live on the same unified endpoints, so using them costs you no per-system code.

sevdesk

·Contacts
number

Search contacts by their contact number, covering both customers and suppliers in the same filter.

sevdesk integration

Lexware Office

·Contacts
name, email

Filter contacts by name or by email address, so you can match a record you already hold.

Lexware Office integration

Sage Active

·Invoices
paymentStatus

Sage Active has no paid or partially paid state in its regular invoice status, so payment status is its own filter.

Sage Active integration

BuchhaltungsButler

·Bills
billDateFrom

Return only bills whose bill date falls after the value you pass, filtered in the request itself.

BuchhaltungsButler integration
orderFieldplusorderDirasASCorDESC
InvoicesinvoiceDate · invoiceNumber · createdDate · updatedDate
BillsbillDate · billNumber · createdDate · updatedDate
Booking proposalsbookingProposalDate · number · createdDate · updatedDate

Not every resource supports all three of filtering, ordering and pagination, and Maesn does not claim otherwise. The capability has to exist somewhere underneath, so support is documented per endpoint in the API reference. You can see what a given endpoint offers before you build against it, which is what a unified layer can be: consistent where consistency is possible, transparent about the edges.

Why it matters

One loop, every system

Building it yourself
  • Write a paging loop per system
  • Handle cursors and page counts
  • Work around the failing last page
  • Re-pull everything to find changes
With Maesn
  • One limit and page everywhere
  • Cursors and counts handled for you
  • No off-by-one to work around
  • Pull only what changed since

Reading a full list sounds like the simplest thing an integration does, and it is where a surprising amount of time disappears: a paging loop per system, a workaround for the page that errors, and a full re-read every night because there is no reliable way to ask what changed. Because you page and filter through Maesn rather than against each system, that work happens once. The same two parameters and the same delta filter carry over to every integration you add, including the ones Maesn adds after you have shipped, and the customer connects once for all of it. The paging loop nobody writes again is time product and engineering teams get back on every integration after the first.

Unified Pagination & Filtering FAQ

Common questions

How does pagination work in the Maesn API?

Through two query parameters: limit and page. The limit parameter sets how many resources come back per page and accepts 5, 10, 20, 50 or 100. The page parameter selects the page and starts at 1. That is the same on every system, whether the underlying API is page-based or cursor-based.

Do I have to know whether a system uses cursors or page numbers?

No. Maesn absorbs the difference. Cursor bookmarks, pre-queries for the number of pages, failing last pages and page counts returned inside the URL are all handled on Maesn's side, so your code sends limit and page and nothing else.

Do I get to know how many records there are?

Yes. Every response carries a meta.pagination block with the total number of matching records, the number of pages at your page size, the current page and the page size itself. That is worth calling out because a cursor-based system generally cannot tell you a total at all, so this is a case where going through Maesn gives you more than the underlying API does, not less.

How do I fetch only the records that changed?

Use the lastModified filter. Store the timestamp of your last successful fetch and pass it on the next request, for example lastModified=2026-01-15T09:30:00 on invoices, and you receive only the objects modified at that time or later. This is the filter most integrations rely on, because it turns a full re-read into a small delta.

Which filters are available besides lastModified?

A status filter on invoices for a range of systems, and on bills and booking proposals for some. On top of that Maesn adds filters that exist because a specific system needs them, such as paymentStatus for Sage Active, name and email for Lexware Office contacts, number for sevdesk contacts and billDateFrom for BuchhaltungsButler bills. The API reference lists what each endpoint supports.

Can I control the order results come back in?

Yes, on invoices, bills and booking proposals, through orderField and orderDir. Invoices order by invoiceDate, invoiceNumber, createdDate or updatedDate, and orderDir takes ASC or DESC. Bills and booking proposals have their equivalent date and number fields.

Does every resource support filtering, ordering and pagination?

No, and Maesn does not pretend otherwise. Some resources support all three, others a subset, because the capability has to exist somewhere underneath. The API reference documents per endpoint what is supported, so you can see it before you build against it.

How often should I poll?

At the lowest frequency that still gives your users fresh enough data. Polling every minute for data that changes once a day is wasted traffic and cost, while polling too rarely shows stale data. Combine a sensible interval with the lastModified filter, and budget against the target system's rate limits: Maesn adds no limit of its own on the way in, so the system's is the one you can exhaust.

Should I poll or use webhooks?

Both are supported and they solve different problems. Webhooks push an event when something changes, which suits reacting to individual changes. Polling with lastModified suits scheduled reconciliation and catching up after downtime. Many integrations use webhooks for immediacy and a periodic delta poll as a safety net.

Build once on the Unified API.

See how unified pagination and filtering works for your integration, or dive into the technical reference.