Guide 8 af 8
Implementering
Kogebog til at flytte en advokatvirksomhed ind i Levano. Skrevet til den der sidder med migreringen og skal kunne stå på mål for tallene bagefter, ikke til den der skal overbevises om noget.
Den er delt i to kapitler:
| Kapitel | Kan du i dag |
|---|---|
| 1. Recon og afstemning | Ja. Alt herunder kræver kun læse-scopes |
| 2. Import | Ja. POST med Idempotency-Key |
Rækkefølgen er ikke et kompromis. Afstemningen i kapitel 1 er den samme kode du bruger til at kontrollere importen bagefter, så den er ikke spildt arbejde fordi den kommer først.
Kapitel 1: Recon og afstemning
Målet er tre ting, i den rækkefølge:
- Baseline. Præcis hvad ligger der i organisationen lige nu.
- Nøglekort. Hvilken række i kildesystemet svarer til hvilken række i Levano.
- Afstemning. Stemmer antal og beløb, og hænger referencerne sammen.
Forudsætninger
En API-nøgle med de rigtige scopes, oprettet af en administrator under Indstillinger, API-nøgler: https://app.levano.io/settings/api.
Vælg scopes én gang og vælg dem rigtigt. Et scope kan ikke sættes på en nøgle bagefter. Mangler du ét midt i et udtræk, skal du oprette en ny nøgle og starte forfra, så mint hele læsesættet med det samme:
| Scope | Giver dig |
|---|---|
clients:read |
/clients, /people, /companies |
matters:read |
/matters |
time:read |
/time-entries |
invoices:read |
/invoices |
documents:read |
/documents, kun metadata |
reports:read |
/reports/{type}, den uafhængige kontrol |
webhooks:manage hører ikke til en migrering. Det er den eneste familie i v1 der kan skrive, og den skriver ikke sagsdata.
export LEVANO_KEY="lev_live_..."
export LEVANO_API="https://app.levano.io/api/v1"Nøglen er et kodeord til hele organisationens sagsdata. Den skal ikke i et repository, ikke i en ticket og ikke i en log.
Preflight: kontrollér nøglen før du henter noget
Et kald pr. liste, med per_page=1, koster syv kald af rate limit-budgettet og sparer dig for at opdage et manglende scope efter tyve minutters udtræk.
for endpoint in clients people companies matters time-entries invoices documents; do
code=$(curl -sS -o /dev/null -w '%{http_code}' "$LEVANO_API/$endpoint?per_page=1" \
-H "Authorization: Bearer $LEVANO_KEY" \
-H "Accept: application/json")
printf '%s /%s\n' "$code" "$endpoint"
done200 betyder at scopet er der. 403 betyder at det mangler, og svarets required_scope siger hvilket. 401 betyder at nøglen selv er forkert, spærret eller udløbet, og at intet af resten virker.
Rækkefølgen
Hent i den rækkefølge tingene peger på hinanden, så en reference altid er hentet før den bliver brugt.
| # | Endpoint | Peger på |
|---|---|---|
| 1 | /people, /companies |
Ingenting. Parterne er bunden |
| 2 | /clients |
company_id eller person_id fra 1 |
| 3 | /matters |
client_id fra 2 |
| 4 | /time-entries |
matter_id fra 3 |
| 5 | /invoices |
client_id fra 2, matter_id fra 3 |
| 6 | /documents |
matter_id fra 3 |
Det er også importrækkefølgen i kapitel 2. Den ændrer sig ikke fordi retningen gør.
Klient er et engagement, ikke en part
Det her er den ene modelforskel der vælter en migrering hvis den opdages sent.
En klient i Levano er et engagement: relationen mellem virksomheden og en part. Stamdata, altså navn, adresse, e-mail, CVR, ligger ikke på klienten. Den ligger på parten, som er en række i /people eller /companies, og klienten peger på den med person_id eller company_id.
/clients viser stamdata med, men det er en afledt visning af parten. Den autoritative række er partens.
Konsekvensen er konkret: den samme virksomhed kan holde flere engagementer, og så er der én partrække og flere klientrækker. Nøgler du dit kort på klienten, opretter du den samme virksomhed to gange i kildesystemet og får to sæt stamdata der langsomt driver fra hinanden.
Nøgl på parten. Klienten er relationen.
Naturlige nøgler til kortet
Der er ingen "ekstern id"-kolonne i v1, så kortet mellem kildesystem og Levano bygges på de felter der faktisk identificerer en række.
| Enhed | Match på | Note |
|---|---|---|
| Virksomhed | cvr |
/companies?cvr=12345678 matcher på ciffer, så 12 34 56 78 finder den samme række |
| Person | name + email |
Der er intet CPR på ledningen. Se nedenfor |
| Klient | Parten plus type |
Bruger virksomheden e-conomic, er economic_customer_number ofte den bedste nøgle |
| Sag | matter_number, ellers reference |
reference er et frit tekstfelt til klientens egen reference, og den stærkeste nøgle når det blev udfyldt ved indlæsningen |
| Tid | matter_id + date + description |
Der er ingen naturlig nøgle. Se nedenfor |
| Faktura | invoice_number |
Entydig inden for organisationen, men tom på kladder. En kladde matches ikke på nummer |
| Dokument | matter_id + filename |
size_bytes og mime skiller de fleste dubletter |
Personer kan ikke matches maskinelt på CPR. CPR er ikke i svaret, i nogen form, og kommer det ikke. Er der to Jens Hansen uden e-mail i registret, er det et menneske der afgør hvilken der er hvilken. Regn med det i planen frem for at opdage det i uge tre.
Tidsregistreringer har ingen naturlig nøgle. Kombinationen ovenfor er heuristik, ikke identitet: to registreringer på samme sag samme dag med samme tekst er lovlige. Til afstemning tæller og summerer du derfor pr. sag frem for at parre række mod række.
Udtrækket
Listerne er cursor-paginerede. Reglerne, kort, fordi de bestemmer om udtrækket er komplet:
- Der er ingen
page-parameter og ingentotal. Du følgerlinks.nextindtil den ernull. links.nexter en absolut URL med dine filtre bevaret. Du sætter den ikke sammen selv.per_pageer 50 som standard og maksimum 100. Beder du om mere, får du 100. Beder du om nul eller negativt, får du 50. Ingen af delene er en fejl, så du får ikke besked.- Cursoren er uigennemsigtig. Gem den ikke som et bogmærke du vender tilbage til i næste uge.
- Sorteringen er nyeste først med id som tiebreaker, og der er ingen
sort-parameter.
Detaljerne står i Paginering.
dump () {
# `endpoint`, not `path`: zsh ties a `path` variable to `PATH`, so the
# innocent-looking name empties your PATH for the rest of the function.
local endpoint="$1" out="$2" url response code body
url="$LEVANO_API/$endpoint?per_page=100"
: > "$out"
while [ -n "$url" ] && [ "$url" != "null" ]; do
response=$(curl -sS -w '\n%{http_code}' "$url" \
-H "Authorization: Bearer $LEVANO_KEY" \
-H "Accept: application/json")
code=${response##*$'\n'}
body=${response%$'\n'*}
if [ "$code" != "200" ]; then
printf '%s: afbrudt på HTTP %s: %s\n' "$endpoint" "$code" "$body" >&2
return 1
fi
printf '%s' "$body" | jq -c '.data[]' >> "$out"
url=$(printf '%s' "$body" | jq -r '.links.next')
done
printf '%8s %s\n' "$(wc -l < "$out" | tr -d ' ')" "$out"
}
dump people personer.ndjson
dump companies virksomheder.ndjson
dump clients klienter.ndjson
dump matters sager.ndjson
dump time-entries tid.ndjson
dump invoices fakturaer.ndjson
dump documents dokumenter.ndjsonStatuskontrollen i løkken er ikke pynt. Uden den er den her løkke farlig: et 429 midtvejs giver et svar uden data og uden links.next, jq skriver ingenting, url bliver null, og løkken stopper stille med en halv fil. Det ser i afstemningen ud som om der mangler data i Levano, og du leder det forkerte sted i en halv dag. Stop på alt der ikke er 200, og genstart det ene udtræk.
Vil du hellere genforsøge end stoppe: 429 bærer en Retry-After i sekunder, og den skal læses frem for gættes. Se Fejl og rate limits.
Rate limit tælles pr. nøgle, ikke pr. IP, som standard 120 kald i minuttet. Kører du udtrækket parallelt fra fire maskiner med den samme nøgle, deler de ét budget. per_page=100 henter det samme som fire kald med per_page=25 for ét kald af budgettet, så den største side er også den billigste.
Beløbene i udtrækket
Hvert beløb er et heltal i øre plus en currency. Aldrig en decimal, aldrig et flydende tal, aldrig en formateret streng. 162375 med currency: "DKK" er 1.623,50 kr.
{
"invoice_number": "2026-0184",
"status": "sent",
"subtotal": 1299000,
"vat_amount": 324750,
"total": 1623750,
"paid_amount": 0,
"outstanding": 1623750,
"vat_rate": 0.25,
"currency": "DKK"
}Hvilke felter der er beløb, og hvilke der ligner:
| Er øre | Er ikke øre |
|---|---|
Sag: hourly_rate, fixed_fee_amount, budget_amount |
vat_rate er en brøk. 0.25 er 25 procent |
Tid: hourly_rate, internal_rate, amount, internal_amount |
duration_minutes er minutter |
Faktura: subtotal, vat_amount, total, paid_amount, outstanding, procesrente_amount |
Fakturalinje: quantity er et antal |
Fakturalinje: unit_price, amount |
Dokument: size_bytes er bytes |
Tre regler der holder afstemningen ren:
- Summér i heltal. Divider aldrig med 100 før du lægger sammen. Et beløb der er blevet til en float på vejen kommer ikke tilbage som et heltal.
- Gem heltal. Læser du øre ind i en float for at gemme dem i en
DECIMAL(10,2), har du genindført præcis det problem heltallet fjernede. - Genberegn ikke moms.
vat_amounter det tal Levano har bogført, inklusive momszone og eventuel omvendt betalingspligt.subtotal * vat_ratekan lovligt afvige, og fakturaen er facit.
Baggrunden står i Beløb.
Tæl
for f in personer virksomheder klienter sager tid fakturaer dokumenter; do
printf '%8s %s\n' "$(wc -l < "$f.ndjson" | tr -d ' ')" "$f"
doneSæt tallene op mod kildesystemet med det samme, før du kigger på beløb. Et antal der er langt forkert er næsten altid et udtræk der stoppede for tidligt, ikke data der mangler i Levano, og det er billigere at opdage nu.
Bemærk at /clients og /companies ikke skal have samme antal, og at ingen af dem er den andens loft. /clients tæller engagementer, /companies og /people tæller parter. Én part kan holde flere engagementer, og en part kan ligge i registret uden at holde nogen. Det der skal holde, er referencerne, og dem tager næste afsnit.
Afstem referencerne
Inden for én organisation er referencerne garanteret af databasen. Peger en sag i dit udtræk på en klient du ikke har, betyder det derfor én af to ting, og aldrig at Levano har mistet noget.
jq -r '.id' klienter.ndjson | sort -u > .klient-id
jq -r '.client_id // empty' sager.ndjson | sort -u > .sag-klient
comm -13 .klient-id .sag-klientTom output er rent. Kør det samme par for matter_id fra tid, dokumenter og fakturaer, og for company_id og person_id fra klienter.
// empty i filteret er med vilje: null er en lovlig værdi og betyder "ikke knyttet til noget". En tidsregistrering uden sag hører til ingen klient og skal ikke tælle som en brudt reference. Det er et ikke-tomt id du ikke har, der er symptomet.
Hver linje output er så enten:
- Et ufuldstændigt udtræk. Løkken stoppede for tidligt. Det er langt det almindeligste, og det er derfor statuskontrollen ovenfor er der.
- En slettet forælder. Alle enheder her er soft-deletede. En slettet klient forsvinder fra
/clients, men dens sager bliver liggende på/mattersmedclient_idintakt, fordi en sletning af klienten ikke sletter sagerne. Der er intetdeleted_atpå ledningen og ingen måde at hente den slettede række, så en peger uden mål er alt du får at se.
Kør udtrækket igen før du konkluderer. Er den samme peger uden mål begge gange, er det den anden slags, og så er den rigtige beslutning et produktspørgsmål til virksomheden frem for en teknisk rettelse: hvad skal der ske med en sag hvis klient er slettet.
Afstem beløbene mod Levanos egne tal
/reports/{type} regner over de samme rækker og er derfor en uafhængig anden mening. Det er også det tal virksomheden ser inde i Levano, så det er dét de vil holde dig op på.
Den ene kontrol der er en præcis facitliste:
# Åbne fakturaer i dit udtræk
jq -s 'map(select(
(.status == "sent" or .status == "partially_paid" or .status == "overdue")
and .outstanding > 0
)) | length' fakturaer.ndjson
# Levanos eget tal
curl -sS "$LEVANO_API/reports/ar-aging?date_from=2025-01-01&date_to=2026-09-01" \
-H "Authorization: Bearer $LEVANO_KEY" -H "Accept: application/json" \
| jq '.data.stats.open_count'De to tal skal være ens. Er de ikke, mangler dit fakturaudtræk rækker.
Fælden i rapport-endpointet, og den rammer midt i en afstemning: udelader du date_from og date_to, får du indeværende måned. Ikke alt. En "samlet omsætning" der stille kun dækker september ser ud som katastrofalt datatab når du holder den op mod kildesystemet. Sæt altid begge datoer eksplicit, også når du vil have hele historikken, og hold ellers vinduet til den periode du faktisk skal afstemme.
ar-aging er en undtagelse værd at kende: den aldrer mod dags dato, eller mod reference_date hvis du sætter den, og datovinduet påvirker ikke hvilke fakturaer der tælles med. Derfor er open_count ovenfor uafhængig af hvilke datoer du sender.
Totalerne, altså total_outstanding på ar-aging og total_wip_value på wip, regnes over rapportens egne rækker, og rækkelisten er afkortet på store datasæt. Brug dem som størrelsesorden og som samtaleåbner med bogholderiet, ikke som facit til øre. wip er derudover afgrænset af datovinduet og tæller kun tid der ikke allerede står på en fakturalinje, så den er ikke det samme tal som en rå sum over tidsudtrækket.
Rapporttyperne: revenue, time-distribution, matter-status, productivity, ar-aging, wip, write-offs, lead-conversion, kyc-status.
Genkør trinvist
Når baseline er på plads, skal du ikke hente alt igen for at se hvad der er ændret. updated_since findes på /matters, /clients, /people og /companies:
curl -sS "$LEVANO_API/matters?updated_since=2026-09-01T00:00:00Z&per_page=100" \
-H "Authorization: Bearer $LEVANO_KEY" \
-H "Accept: application/json"Gem tidspunktet for hvornår du kørte, ikke cursoren. Overlap et par minutter og lad deduplikering på id tage resten. Bruger du et offset frem for Z, skal + URL-kodes som %2B, ellers læses det som et mellemrum.
/time-entries, /invoices og /documents har ikke updated_since. Der afgrænser du i stedet på det du faktisk skal se på: date_from og date_to på tid, issue_date_from og issue_date_to på fakturaer, matter_id på dokumenter.
Typiske fejl
| Symptom | Årsag | Rettelse |
|---|---|---|
403 med required_scope |
Nøglen mangler det scope | Opret en ny nøgle. Scopes kan ikke tilføjes bagefter |
| Udtrækket stopper stille med en halv fil | 429 eller 5xx midt i løkken |
Kontrollér statuskoden i hver iteration. Læs Retry-After |
429 selvom hver proces kalder pænt |
Grænsen er pr. nøgle, ikke pr. IP | Fire parallelle udtræk deler ét budget. Serialisér, eller hæv per_page |
per_page=500 giver 100 rækker |
Loftet er 100, og det er ikke en fejl | Forvent 100 |
Fakturaen har ingen lines |
Linjer kommer kun med på /invoices/{id} |
Hent den enkelte faktura når du skal bruge linjerne |
/time-entries/{id} giver 404 |
Der er ikke noget opslag på enkeltregistrering | Brug listen med matter_id |
| Beløb er 100 gange for små | Øre blev læst som kroner | Alt er heltal i mindste enhed |
| Momsen afviger en øre | subtotal * vat_rate blev genberegnet |
Brug vat_amount fra svaret |
404 på et id kildesystemet kender |
Rækken findes ikke i denne organisation | 404 er ikke "slettet". Slet ikke lokale rækker på ét 404 |
Et client_id der ikke er i klientudtrækket |
Afkortet løkke, eller en soft-deletet klient | Kør udtrækket igen. Er den der stadig, er forælderen slettet |
| Rapporten viser en brøkdel | date_from og date_to blev udeladt |
Uden datoer får du indeværende måned |
| To virksomheder med samme CVR i kortet | Der blev nøglet på klienten i stedet for parten | Nøgl på company_id eller person_id |
updated_since returnerer for meget |
+ i offsettet blev læst som mellemrum |
URL-kodning: %2B |
Hvad API'et ikke kan endnu
Vær ærlig om det her i projektplanen fra dag ét. Det er ikke mangler der lukker sig selv inden fredag.
- Ingen
PATCHellerDELETEpå sagsdata. Create-only import findes (se Import). Webhooks er den eneste familie medPATCH/DELETE. - Ingen filer.
/documentsudleverer metadata: navn, filnavn, MIME-type, størrelse, sag og mappe. Aldrig filens indhold, aldrig en download-URL, aldrig et preview. - Ingen krypterede noter. Noter på klient og part, og KYC-noter, er krypteret i hvile og kommer ikke med i noget svar.
- Ingen totaltælling på lister.
metabærerper_pageog ikke andet. Skal du bruge tal frem for rækker, er det rapporterne. - Ingen sortering. Rækkefølgen er en del af kontrakten fordi cursoren afhænger af den.
- Ingen slettede rækker. Alt er soft-deletet internt, men der er hverken
deleted_ati svaret eller en parameter der henter dem frem. En slettet række er væk fra API'ets synspunkt. settings:readogsettings:writeåbner ingenting. Navnene findes i kataloget og kan sættes på en nøgle. Byg ikke logik på dem.
CPR er ikke på ledningen
Det fortjener sin egen linje, fordi det både er en teknisk og en juridisk grænse.
CPR indgår ikke i noget svar fra dette API, i nogen form. Ikke som felt, ikke maskeret, ikke som hash du kan slå op på. /people giver navn, e-mail, telefon og adresse, og det er hele partens offentlige flade.
Og den anden vej: CPR importeres ikke gennem dette API. Det gælder også når kapitel 2 lander. Skal en personoplysning af den type ind i Levano, sker det gennem sagsbehandlingen eller KYC-flowet inde i produktet, hvor der er en identificeret bruger der står bag, en audit-log der fanger det, og en kryptering i hvile der holder. En bulk-import over en integrationsnøgle har ingen af de tre.
Praktisk konsekvens for migreringen: læg CPR-matchning ind i planen som manuelt arbejde, ikke som noget scriptet der løser sig selv. Se afsnittet om naturlige nøgler.
Import
Skrivning er create only. Der er hverken PATCH eller DELETE nogen steder i v1: Levano er systemets facit, og en ekstern nøgle skal ikke kunne rette eller slette et klientforhold, en sag eller en faktureret time. POST findes udelukkende fordi en migrering ellers ikke kan lægge sine data ind.
Rækkefølgen
Fire trin, i denne orden, fordi hvert trin peger på rækken før det og ikke kan tryllebinde den frem:
| Trin | Kald | Scope | Du får |
|---|---|---|---|
| 1 | POST /companies eller POST /people |
parties:write |
id på parten |
| 2 | POST /clients |
clients:write |
id på engagementet |
| 3 | POST /matters |
matters:write |
id på sagen |
| 4 | POST /time-entries |
time:write |
id på timen |
Der findes ikke et kald der laver flere trin på én gang. Det er med vilje: en samlet payload skjuler hvilken halvdel der fejlede, og efterlader dig uden et id at genforsøge imod.
Idempotency-Key er påkrævet
Hvert POST skal have en Idempotency-Key-header. Den er ikke valgfri, for en import man ikke tør genforsøge er en import der går i stå første gang netværket blinker.
Brug kildesystemets eget række-id. Nøglen gælder i 24 timer og er afgrænset pr. endpoint, så det samme række-id godt kan bruges til både virksomheds-kaldet og klient-kaldet bagefter.
| Situation | Svar |
|---|---|
| Ny nøgle | 201 med den oprettede ressource |
| Samme nøgle, samme body | 200 med Idempotent-Replayed: true. Der blev ikke oprettet noget nyt |
| Samme nøgle, anden body | 409 |
| Samme nøgle mens det første kald stadig kører | 409 |
Et kald der fejlede brænder ikke nøglen. Får du 422, retter du rækken og sender den igen under den samme nøgle.
Trin 1 og 2: parter og klienter
curl -sS -X POST "https://app.levano.io/api/v1/companies" \
-H "Authorization: Bearer $LEVANO_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: kilde-firma-4711" \
-d '{"name": "Nordisk Byg ApS", "cvr": "12345678", "city": "Aarhus"}'curl -sS -X POST "https://app.levano.io/api/v1/clients" \
-H "Authorization: Bearer $LEVANO_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: kilde-klient-4711" \
-d '{"company_id": "<id fra trin 1>", "risk_level": "low"}'Send præcis ét af company_id og person_id. Begge dele, eller ingen af dem, er 422. En part holder højst ét engagement, så et gentaget forsøg er 422 der nævner det engagement der allerede har den.
kyc_status åbner på pending for hvert importeret engagement. En status virksomheden ikke er nået frem til, er ikke en status en importør må påstå.
Trin 3: sager
curl -sS -X POST "https://app.levano.io/api/v1/matters" \
-H "Authorization: Bearer $LEVANO_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: kilde-sag-4711" \
-d '{
"client_id": "<id fra trin 2>",
"title": "Overdragelse af Nørrebrogade 12",
"matter_number": "2019-0442",
"status": "dormant",
"practice_area": "real_estate",
"hourly_rate": 250000,
"opened_at": "2019-04-02"
}'client_id og title er de eneste påkrævede felter. To ting er værd at gøre bevidst:
Send matter_number. Udelader du det, tildeler Levano det næste nummer i sin egen SAG-0001-serie. Virksomhedens breve, deres bogholderi og deres klienter refererer til det gamle sagsnummer, og der er ingen PATCH til at sætte det bagefter.
Vælg status med omtanke. Feltet defaulter til active, og hvidvasklovens § 14 gælder for en nøgle nøjagtig som den gælder for en advokat: en sag kan ikke åbne som aktiv mens klientens KYC-sag mangler, ikke er godkendt eller er udløbet. Det er 422 på status.
Vejen udenom er ikke en undtagelse, men en rækkefølge: importér historiske sager som dormant, closed eller archived, og aktivér dem i Levano når kundekendskabsproceduren er godkendt. Undtagelsen i § 14, stk. 2 kan ikke nås gennem API'et, fordi den er en dokumenteret beslutning truffet af en navngiven person med en bestemt tilladelse, og en nøgle er ingen.
Er sagen lukket, sender du closed_at sammen med status: "closed". Create-kaldet stempler den ikke selv, så uden feltet mister sagen sin lukkedato. closed_at på en sag der ikke er lukket er 422.
legal_domain findes ikke i skemaet. Sporbestemmelsen efter § 1, stk. 1, nr. 13 træffes pr. sag i Levano, og en importeret sag der ikke har fået den beslutning truffet er ærligt udetermineret.
Sætter du budget_amount, tænder du samtidig budgetadvarslerne på sagen. Når de importerede timer i trin 4 passerer 75 procent af budgettet, får den ansvarlige advokat en besked, præcis som hvis timerne var registreret i dag. Det er højst én besked pr. sag, men på en natlig import af flere tusinde sager er det stadig værd at vide på forhånd. Udelad feltet, hvis budgetterne først skal bruges fremadrettet.
Trin 4: tid
curl -sS -X POST "https://app.levano.io/api/v1/time-entries" \
-H "Authorization: Bearer $LEVANO_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: kilde-tid-88213" \
-d '{
"matter_id": "<id fra trin 3>",
"user_id": "<bruger i organisationen>",
"date": "2019-04-14",
"duration_minutes": 90,
"description": "Gennemgang af skøde",
"activity_code": "RES",
"hourly_rate": 250000
}'matter_id, user_id, date og duration_minutes er påkrævede.
duration_minuteser minutter. Ikke timer, og ikke øre. Nul er422.hourly_ratekan udelades, og så arver posten sagens egen sats.amounter beregnet ud fra varighed og sats. Sender du den, er det422frem for en stille omregning til et andet tal.statusåbner altid pådraft.invoicedbetyder at en fakturalinje i Levano peger på posten, og det gør ingen endnu.activity_codeskal være koden på en aktiv aktivitetskode i organisationen. Opret koderne i Levano først.
Der er ikke noget Location-svar på dette ene kald, fordi v1 ikke har et opslag på en enkelt tidspost. Find den igen på GET /time-entries med matter_id og et datointerval.
Beløb er heltal i øre, også på vej ind
250000 er 2.500,00 kr. Det gælder hourly_rate, fixed_fee_amount, budget_amount og internal_rate.
En decimal er 422, og det gælder også 1250.00. Det er med vilje: en body der bærer kroner hvor kontrakten siger øre, er forkert med en faktor hundrede, og den fejl er billigere at få som en statuskode end at finde i en afstemning et halvt år senere.
Afstem bagefter
Kør kapitel 1 igen mod Levano når importen er kørt, og sammenlign med de tal du skrev ned. Fire tællinger der stemmer er den eneste kvittering der betyder noget.
Vær opmærksom på at et 200 med Idempotent-Replayed: true ikke er en oprettelse. Tæller dit script kun 2xx, tæller det for højt.
Tjekliste
- Nøgle oprettet med hele læsesættet på én gang
- Preflight kørt: syv lister svarer
200 - Udtræk med statuskontrol i løkken, ikke en bar
while - Antal pr. enhed holdt op mod kildesystemet
- Referencer afstemt med
comm, tom output -
open_countfraar-agingmatcher fakturaudtrækket - Beløb gemt som heltal hele vejen ned i jeres egen database
- Nøglekort bygget på parten, ikke på klienten
- CPR-matchning planlagt som manuelt arbejde
- Import-nøgle med
parties:write,clients:write,matters:write,time:write -
Idempotency-Keyer kildesystemets række-id - Kapitel 1 kørt igen efter import, fire tællinger stemmer