DuckdayDevelopers

Pagination

Every list endpoint is paginated the same way.

Request parameters

Parameter Default Max Description
page 1 — Page number, 1-based.
per_page 25 100 Items per page.
curl "https://<host>/api/public/v1/users?page=2&per_page=50" \
  -H "X-Api-Key: $DUCKDAY_API_KEY"

Response envelope

{
  "data": [ ... ],
  "pagination": {
    "page": 2,
    "per_page": 50,
    "total": 137,
    "total_pages": 3
  }
}

A page past the last one answers with an empty data array and the real pagination.total. A page, per_page, updated_since or date filter that is not a valid value — per_page=500, updated_since=yesterday, date_from=2026-02-30 — answers 422 validation_error, with details naming the parameter.

Stable ordering

Lists are ordered by id ascending. Ids are monotonically increasing, so new records always append at the end: iterating page by page never skips or duplicates rows created while you paginate.

Incremental sync

For periodic synchronization, avoid re-reading everything: filter with updated_since (ISO 8601) and keep a high-water mark on your side.

curl "https://<host>/api/public/v1/users?updated_since=2026-07-01T00:00:00Z" \
  -H "X-Api-Key: $DUCKDAY_API_KEY"

Store the timestamp you started the sync at, and pass it as updated_since on the next run. Every timestamp the API returns is UTC with a Z suffix, to the millisecond. URL-encode the offset if you send one (+02:00 is %2B02:00): an unencoded + arrives as a space and the value is rejected.

Day filters (date_from/date_to, due_from/due_to) are inclusive at both ends and name whole days: date_to=2026-07-31 includes a record at 23:59:59. A to-do due at a specific time counts on the company's calendar day — a deadline at 00:30 in Rome on the 20th is due on the 20th, although its UTC timestamp reads the 19th. An all-day due date (due_is_all_day: true) and an expense date are the day that was entered, in every timezone. Overlapping windows are safe — process records idempotently by id.