Guide 3 af 8
Paginering
Alle lister i v1 er cursor-pagineret. Der er ingen page-parameter, ingen total, og ingen "gå til side 7".
Formen
{
"data": [ /* rækkerne */ ],
"meta": { "per_page": 50 },
"links": { "next": "https://app.levano.io/api/v1/matters?per_page=50&cursor=eyJpZCI6..." }
}To parametre styrer det:
| Parameter | Betydning |
|---|---|
per_page |
Sidestørrelse. Standard 50, maksimum 100 |
cursor |
Uigennemsigtig markør. Du opfinder den ikke, du følger den |
Beder du om mere end 100, får du 100. Beder du om nul eller et negativt tal, får du standarden. Ingen af delene er en fejl.
Løkken
Følg links.next indtil den er null. Det er hele algoritmen.
url="https://app.levano.io/api/v1/time-entries?per_page=100"
while [ -n "$url" ] && [ "$url" != "null" ]; do
body=$(curl -sS "$url" -H "Authorization: Bearer $LEVANO_KEY" -H "Accept: application/json")
echo "$body" | jq -c '.data[]'
url=$(echo "$body" | jq -r '.links.next')
doneimport requests
url = "https://app.levano.io/api/v1/time-entries?per_page=100"
headers = {"Authorization": f"Bearer {key}", "Accept": "application/json"}
while url:
body = requests.get(url, headers=headers, timeout=30).json()
for row in body["data"]:
handle(row)
url = body["links"]["next"]links.next er en absolut URL med alle dine oprindelige query-parametre bevaret. Du skal ikke sætte den sammen selv, og du skal ikke bære dine filtre med over manuelt.
Behandl cursoren som ugennemsigtig
Cursoren er base64 over et internt sorteringsnøglepar. Format og indhold er ikke en del af kontrakten og kan ændre sig uden en versionsbump. Gem den ikke i en database som et "sted i listen" du vender tilbage til i næste uge, og byg den ikke selv.
Vil du hente ændringer siden sidst, så filtrér på et datofelt frem for at gemme en cursor. Se næste afsnit.
Hvorfor ikke offset
Offset-paginering (?page=4) har to problemer der begge rammer i praksis:
- Rækker flytter sig. Bliver der oprettet en sag mens du henter side 3, skubbes en række fra side 3 ned på side 4, og du får den to gange. Bliver en slettet, springer du en over.
- Dyb paginering bliver dyr.
OFFSET 50000betyder at databasen tæller sig frem gennem 50.000 rækker for at kaste dem væk.
Cursor-paginering læser videre fra det sidste sete sorteringsnøglepar, så den er stabil under samtidige skrivninger og lige hurtig på side 1 og side 900.
Filtrér frem for at hente alt
Listerne tager filtre, og de kombineres med paginering: filtrene bæres automatisk med over i links.next.
| Liste | Filtre |
|---|---|
/matters |
client_id, status, updated_since |
/clients |
type, updated_since |
/people |
updated_since |
/companies |
updated_since, cvr |
/time-entries |
matter_id, client_id, user_id, date_from, date_to, status |
/invoices |
client_id, matter_id, status, issue_date_from, issue_date_to |
/documents |
matter_id |
updated_since er den rigtige måde at lave en trinvis synkronisering på:
curl -sS "https://app.levano.io/api/v1/matters?updated_since=2026-03-01T00:00:00Z&per_page=100" \
-H "Authorization: Bearer $LEVANO_KEY" \
-H "Accept: application/json"updated_since er ISO-8601 og tager alt der er ændret på eller efter tidspunktet. Sender du et offset frem for Z, skal + URL-kodes som %2B, ellers læses det som et mellemrum.
Gem tidsstemplet for hvornår du kørte, ikke cursoren. Næste kørsel spørger efter det tidspunkt. Overlap et minut eller to for at være sikker, og lad deduplikeringen på id tage resten.
Den fulde parameterliste pr. endpoint står i referencen.
Sortering
Lister er sorteret nyeste først, på oprettelsestidspunkt med id som tiebreaker. Rækkefølgen er en del af kontrakten fordi cursoren afhænger af den. Der er ingen sort-parameter i v1.
Ingen totaltælling
meta indeholder per_page, og ikke andet. Der er bevidst ingen total: et præcist tal kræver en COUNT over hele tabellen ved hver eneste side, hvilket er den dyreste del af forespørgslen og næsten aldrig den brugeren læser.
Har du brug for tal frem for rækker, så brug rapport-endpointet i stedet for at tælle en liste igennem.
KYC-API'en pagineres anderledes
/api/kyc/v1 bruger klassisk sidepaginering: page og per_page ind, og et meta-objekt med current_page, per_page, total og last_page ud. Der er ingen links.next at følge, og der er et totaltal.
Det er ikke en fejl, og de to skemaer bliver ikke ensrettet uden en versionsbump på den ene af dem. Læs meta for den kontrakt du faktisk kalder, frem for at antage at den ligner den anden.