Guide 7 af 9
På denne side
Underskrifter
Sign er den ene ressource i v1 du driver frem for at læse eller importere. Et underskriftsflow oprettes som kladde, får et dokument og nogle underskrivere, bliver sendt, og bliver så til en retshandling med et bevisspor. Derfor har den både POST, PATCH og DELETE, hvor parter, sager og tid bevidst kun kan oprettes.
Hele fladen ligger under https://app.levano.io/api/v1/signing-flows.
Hvad der skal være på plads
| Forudsætning | Ellers |
|---|---|
Nøglen har signing:read (læsning) eller signing:write (skrivning) |
403 med required_scope |
| Organisationen har modulet Sign | 404, præcis som et flow der ikke findes |
| Abonnementet er aktivt | 403 med subscription_inactive på skrivninger. Læsning virker stadig |
At et manglende modul svarer 404 og ikke 403 er med vilje: svaret må ikke afsløre hvilke moduler en advokatvirksomhed har købt. Se Autentificering og scopes.
Hvem handler
Bevissporet navngiver en person, og en nøgle er ingen. Hver skrivning må derfor kunne pege på en bruger:
acting_user_idi bodyen, hvis du ved hvilken kollega integrationen handler på vegne af. Brugeren skal være medlem af nøglens egen organisation.- Udelader du feltet, tilskrives handlingen den bruger der oprettede nøglen, så længe vedkommende stadig er medlem.
- Er ingen af delene opfyldt, er svaret
422medcode: "acting_user_required".
Feltet er valgfrit på alle skrivninger og virker ens alle steder.
Idempotency-Key
Hvert POST på fladen kræver en Idempotency-Key. PATCH og DELETE gør ikke: den samme body sendt to gange efterlader den samme række. Samme nøgle med samme body afspiller det oprindelige svar, samme nøgle med en anden body er 409. Se Implementering for detaljerne.
1. Opret kladden
curl -sS -X POST "https://app.levano.io/api/v1/signing-flows" \
-H "Authorization: Bearer lev_live_..." \
-H "Idempotency-Key: ejeraftale-2026-0417" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"title": "Ejeraftale, Nordbro Holding",
"message": "Skriv venligst under inden fredag.",
"matter_id": "3f9a1c02-6d4e-4a8b-9f10-0c2b7d5e8a41"
}'Svaret er 201 med flowet under data og en Location-header til opslaget. Kun title er påkrævet. matter_id er valgfri, og en virksomhed på en Sign-only-plan har ingen sager at pege på.
Kladden er tom med vilje. Intet dokument og ingen underskrivere. Et opret-kald der tog hele flowet på én gang ville skulle lykkes halvt på én forkert indtastet e-mail, og et halvt underskriftsflow er den ene tilstand det her domæne aldrig må gemme.
Gem data.id. Resten af guiden kalder den $FLOW.
2. Læg dokumentet på
To veje, og du vælger den ene. Ingen af dem kræver dokumentmodulet eller documents:read: Sign sælges for sig, og en virksomhed uden dokumentmodulet skal stadig kunne lægge en PDF foran en underskriver.
Upload en fil. multipart/form-data med ét felt, pdf:
curl -sS -X POST "https://app.levano.io/api/v1/signing-flows/$FLOW/pdf" \
-H "Authorization: Bearer lev_live_..." \
-H "Idempotency-Key: ejeraftale-2026-0417-pdf" \
-H "Accept: application/json" \
-F "pdf=@ejeraftale.pdf"PDF og Word tages imod, og Word konverteres til PDF ved ankomsten. Filen virusscannes før noget som helst gemmes. En afvist fil er 422 med pdf_rejected, og en scanner der ikke kan nås er 503 med virus_scan_unavailable: filen fejler måske ingenting, så genforsøg frem for at eksportere den om.
Eller peg på et dokument virksomheden allerede har, med et id fra GET /documents:
curl -sS -X POST "https://app.levano.io/api/v1/signing-flows/$FLOW/document" \
-H "Authorization: Bearer lev_live_..." \
-H "Idempotency-Key: ejeraftale-2026-0417-doc" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"document_id": "7a2c9e51-3b88-4f20-9d6e-1c4b8f0a3d72"}'Bytes kopieres over i flowets egen opbevaring, så en senere rettelse i biblioteksdokumentet ikke kan flytte sig ind under en underskrift. Et dokument fra en anden organisation er 404, aldrig 403.
Begge svarer med hele flowet. source_pdf_sha256 er hashen på det der blev gemt, så du kan holde den op mod din egen kopi.
Kun en kladde må skifte dokument. Efter send er PDF'en det underskriverne blev bedt om at skrive under på, og et forsøg er 422 med flow_not_draft. På en kladde skifter du dokument med DELETE /signing-flows/$FLOW/document og derefter upload eller attach igen. Underskriverne bliver stående: de er deres egen ressource, og et skiftet dokument har ikke ændret hvem der skriver under.
3. Underskriverne
curl -sS -X POST "https://app.levano.io/api/v1/signing-flows/$FLOW/recipients" \
-H "Authorization: Bearer lev_live_..." \
-H "Idempotency-Key: ejeraftale-2026-0417-underskriver-1" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"full_name": "Mette Overgaard",
"email": "mette@nordbro.dk",
"role": "signer",
"group_order": 0
}'role er påkrævet og er en af tre:
| Rolle | Betyder |
|---|---|
signer |
Skriver under |
approver |
Godkender, uden at underskrive |
viewer |
Får dokumentet at se |
full_name og email er påkrævede, medmindre du sender person_id: så hentes navn, e-mail og en eventuel forventet CPR fra partsregistret på skrivetidspunktet, fordi et forseglet dokument skal kunne læses som det var sandt da det blev sendt. Sender du fri tekst uden et parts-id, er det ikke en nødløsning, men vejen ind i registret: Levano slår op på CVR og CPR først, så på navn og e-mail, og opretter ellers parten.
expected_cpr kan sendes ind til MitID-binding. Det er skrivebart, aldrig læsbart: det gemmes krypteret og står ikke i noget svar, hverken i klartekst eller som hash.
Ret en underskriver med PATCH .../recipients/{id}, fjern med DELETE .../recipients/{id}, og omrokér hele rækkefølgen på én gang:
curl -sS -X POST "https://app.levano.io/api/v1/signing-flows/$FLOW/recipients/reorder" \
-H "Authorization: Bearer lev_live_..." \
-H "Idempotency-Key: ejeraftale-2026-0417-orden" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"order": {
"018f2c3d-0000-7000-8000-000000000001": 0,
"018f2c3d-0000-7000-8000-000000000002": 1
}
}'Send hele kortet, ikke et enkelt træk. Et fuldt kort er det der gør et genforsøg ufarligt, og et halvt efterlader to underskrivere der begge mener det er deres tur.
Alle fire er kladde-kun. Efter send holder hver underskriver et link der virker og en rolle modparten er blevet fortalt, så listen lukker. Svaret er 409 med flow_must_be_draft. De to rettelser der overlever send, en påmindelse og en byttet e-mail, har deres egne kald.
4. Send
curl -sS -X POST "https://app.levano.io/api/v1/signing-flows/$FLOW/send" \
-H "Authorization: Bearer lev_live_..." \
-H "Idempotency-Key: ejeraftale-2026-0417-send" \
-H "Accept: application/json"Kladden skal være klar: et dokument med en hash, mindst én underskriver, og en identitetsmetode hver underskriver kan gennemføre. Er den ikke det, er svaret 422 med flow_incomplete, og flowet bliver stående som kladde. Der sendes aldrig halvt.
Lykkes det, står flowet på sent, sikkerhedsniveau og virksomhedens branding fryses, og invitationsmails lægges i kø præcis som fra appen. Et flow der ikke er en kladde er 422 med flow_invalid_state.
5. Efter send
Fra det øjeblik dokumentet er ude, er flowet en handling frem for et udkast, og fladen smalner ind til det appen selv tilbyder:
| Kald | Gør | Grænse |
|---|---|---|
PATCH .../message |
Skriver beskeden om | Ikke på et afsluttet eller annulleret flow |
POST .../extend |
Flytter fristen, og hvert udestående link med den | sent og partially_signed. Tidspunktet skal ligge i fremtiden |
POST .../recipients/{id}/remind |
Sender invitationen igen på et nyt link | Ikke til en der har underskrevet eller afvist |
PATCH .../recipients/{id}/email |
Retter én underskrivers e-mail | Kun før vedkommende har åbnet sit link |
POST .../cancel |
Stopper flowet og spærrer alle links | sent og partially_signed |
POST .../retry-seal |
Sætter forseglingen i kø igen | sent og partially_signed, og kun mens has_sealed_pdf er false |
Tre ting er værd at kende:
Fristen flyttes med extend, ikke med PATCH /signing-flows/{flow}. Flowets frist styrer også underskrivernes links, og at flytte den ene uden den anden ville efterlade virkende links efter datoen. Derfor afviser flowets egen PATCH feltet expires_at efter send, sammen med flow_mode og default_identity_methods: de er de vilkår underskriverne fik at vide. title og message bliver ved med at kunne rettes.
En påmindelse roterer linket. Den bærer et friskt link, så den virker også når den første mail forsvandt, og det gamle link holder op med at virke. Det samme gælder en byttet e-mail. Linket står aldrig i svaret: det går til underskriverens indbakke og ingen andre steder.
En byttet e-mail afvises når underskriveren har set sit link. Fra da af binder bevissporet adressen til en IP og et tidspunkt, og at flytte den bagefter ville efterlade et spor der ikke længere beskriver hvad der skete. Svaret er 422 med recipient_invalid_state.
retry-seal svarer 202, ikke 200. Det garanterede er at genforsøget er noteret og jobbet lagt i kø. Poll flowet for has_sealed_pdf.
6. Slet
curl -sS -X DELETE "https://app.levano.io/api/v1/signing-flows/$FLOW" \
-H "Authorization: Bearer lev_live_..." \
-H "Accept: application/json"204 på en kladde og på et flow der er afsluttet på den ene eller den anden måde: completed, declined, expired eller cancelled.
Et flow der er ude til underskrift, altså sent eller partially_signed, er 409 med flow_must_be_cancelled_first. Underskriverne holder links der stadig virker, og det er annulleringen der spærrer dem. Annullér først, slet så.
Sletningen er en dato på flowets egen række. Bevissporet røres ikke, og en forseglet PDF der allerede findes, kan stadig verificeres.
7. Læs status og spor
GET /signing-flows er registret, cursor-pagineret og filtrerbart på status, matter_id og updated_since. GET /signing-flows/{flow} er det enkelte flow med sine underskrivere.
De syv statusser:
| Status | Betyder |
|---|---|
draft |
Under opbygning. Intet er sendt |
sent |
Ude hos underskriverne |
partially_signed |
Mindst én har skrevet under, ikke alle |
completed |
Alle har skrevet under |
declined |
En underskriver har afvist |
expired |
Fristen løb ud |
cancelled |
Stoppet af virksomheden |
GET /signing-flows/{flow}/events er bevissporet, ældst først, fordi en revisionslog læses forfra og en integration der poller vil paginere ind i de nye rækker frem for tilbage gennem de gamle.
Opslaget på det enkelte flow bærer source_pdf_url og sealed_pdf_url. Begge er forhåndssignerede links der lever 15 minutter, og begge er null når filen ikke findes. De er kun på opslaget, ikke på listen: et registeropslag til rapportering har ingen brug for halvtreds links der udløber i næste kvarter.
Det her kommer aldrig ud, i noget svar: det magiske link der åbner en underskrift, svaret på et kontrolspørgsmål, den CPR afsenderen har angivet til MitID-binding, og underskriverens MitID-subjekt. Heller ikke på bevissporet, hvor sådanne nøgler filtreres ud af hændelsens payload.
8. Webhooks
Tre hændelser følger flowets liv, og en fjerde følger forseglingen. Abonnér med webhooks:manage, se Webhooks.
| Hændelse | Sker når |
|---|---|
signing_flow.sent |
Flowet er sendt til underskriverne |
signing_flow.declined |
En underskriver har afvist |
signing_flow.cancelled |
Flowet er annulleret |
document.signed |
Flowet er forseglet, altså at den underskrevne PDF findes |
document.signed er ikke "nogen skrev under". Den kommer når flowet er forseglet, og den er derfor den der fortæller at der er en sealed_pdf_url at hente. Abonnér på livscyklussen og på forseglingen hver for sig.
data.signing_flow bærer id, status og matter_id, ikke hele flowet, og en recipient_id ved siden af når ændringen kom fra én underskriver. Skal du bruge resten, henter du det med din egen nøgle.
expires_at er en dato, ikke en hændelse. Der findes ingen webhook når fristen løber ud. Vil du reagere på det, læser du expires_at og status på flowet.
Underskriveren skriver under i browseren
Selve underskriften sker på Levanos egen underskriftsside, som underskriveren kommer ind på gennem linket i sin mail. API'et udleverer ikke det link, hverken ved oprettelse, ved send, ved påmindelse eller på bevissporet.
Det er ikke en mangel der skal lukkes. Linket er legitimationen: den der har det, kan skrive under i en andens navn. Det hører hjemme i én indbakke, og en integration der kunne hente det ville gøre hver eneste log, kø og fejlrapport undervejs til et sted hvor en underskrift kunne stjæles.
Din integration driver altså flowet og følger med i status. Underskriften selv er noget der sker et sted du ikke er.
Fejlkoder på fladen
Ud over message bærer disse svar et stabilt code du kan forgrene på:
| Kode | Status | Sker når |
|---|---|---|
acting_user_required |
422 |
Hverken acting_user_id eller nøglens opretter kunne bruges |
flow_incomplete |
422 |
Send på en kladde uden dokument eller uden underskriver |
flow_invalid_state |
422 |
Overgangen findes ikke fra den status flowet står i |
recipient_invalid_state |
422 |
Underskriveren har underskrevet, afvist eller allerede åbnet sit link |
flow_already_sealed |
422 |
retry-seal på et flow der allerede er forseglet |
flow_not_draft |
422 |
Dokumentet kan ikke skiftes efter send |
pdf_rejected / document_rejected |
422 |
Filen eller dokumentet blev afvist |
flow_must_be_draft |
409 |
Underskriverlisten er lukket efter send |
flow_must_be_cancelled_first |
409 |
Slet på et flow der er ude til underskrift |
subscription_inactive |
403 |
Abonnementet er udløbet. Læsning virker stadig |
virus_scan_unavailable |
503 |
Scanneren kunne ikke nås. Genforsøg |
Intet afvist kald ændrer noget. Den autoritative beskrivelse af hvert enkelt endpoint står i referencen under taggen Signing.