Best Practices
Fewer requests, faster syncs, no 429. Six habits that save the most, each with a copy-and-paste example.
Fetch large pages
Without limit, a list returns only 15 results per page, so a long list needs many requests.
Do this instead: send limit=200, the maximum. A value of 201 or more is rejected with 422.
curl 'https://www.zep-online.de/zepinstanz/next/api/v1/attendances?limit=200' -H 'Authorization: Bearer {token}' -H 'Accept: application/json'Saves roughly 93 % of the requests for a long list (1 instead of 14 for 200 entries).
Fetch only what changed
Reloading everything on every run fetches data that has not changed.
Do this instead: keep the largest modified value of all pages you received and send it as modified_after on the next run (a watermark).
These endpoints accept modified_after: /absences, /attendances, /categories, /customers, /employees, /folders, /invoices, /plannings, /projects, /projects/{id}/tasks, /receipts, /receipts-amounts, /resource-plannings, /tickets, /workhour-balances.
curl 'https://www.zep-online.de/zepinstanz/next/api/v1/attendances?modified_after=2026-10-27T15:57:15.000000Z&limit=200' -H 'Authorization: Bearer {token}' -H 'Accept: application/json'On /attendances the cursor is inclusive: records with exactly the sent modified value are returned again, so a record is never skipped. Your code must ignore the duplicate. If many records share one timestamp, add id_after to continue after the last id you processed. All other endpoints return only records modified strictly after the value. The list is not sorted by modified, so read all pages and take the maximum; store the watermark only after the last page. Exception: on /plannings the next link loses modified_after and limit for now, so do not follow it there; send page yourself and repeat the filters.
Saves roughly all of the data volume of unchanged records once the first full load is done.
Narrow the date range
A range such as 2000 to 2026 makes the server read and send years of data you do not need.
Do this instead: send start_date and end_date (YYYY-MM-DD) for the period you really need.
curl 'https://www.zep-online.de/zepinstanz/next/api/v1/attendances?start_date=2026-10-01&end_date=2026-10-31&limit=200' -H 'Authorization: Bearer {token}' -H 'Accept: application/json'Saves roughly the share of years you cut: one month instead of 26 years is about 99.7 % less data.
Respect the rate limits
Beyond the limits, the API answers with 429 and your sync stops.
Do this instead: read X-RateLimit-Remaining to see your budget, and on 429 wait for the seconds in Retry-After before you retry. Limits and headers are described under Authentication.
curl -i 'https://www.zep-online.de/zepinstanz/next/api/v1/projects?limit=1' -H 'Authorization: Bearer {token}' -H 'Accept: application/json'Saves roughly every retry that would be rejected anyway: waiting once is cheaper than hammering.
Filter flat instead of looping over parents
One request per project or employee multiplies your request count by the number of parents.
Do this instead: ask the list endpoint once and filter by the parent id, for example project_id.
curl 'https://www.zep-online.de/zepinstanz/next/api/v1/attendances?project_id=2' -H 'Authorization: Bearer {token}' -H 'Accept: application/json'Saves roughly one request per parent object you no longer loop over.
Generated clients
You do not have to write the HTTP code yourself: generate a client from the OpenAPI specification. Our CI generates and compiles a Java client from the specification on every change.