Dokflow integrasjonshåndbok
Server-til-server API · v1

Dokflow External API v1

Dokflow External API er et server-til-server API for å opprette Dokflow-forespørsler og e-CMR fra eksterne systemer, som ERP-, CRM- og fagsystemer eller egne apper.

Kom i gang på 5 minutter

  1. Gå til Innstillinger → Stripe Connect → HTTP API i Dokflow.
  2. Opprett en API-nøkkel, for eksempel Produksjonsintegrasjon.
  3. Gi nøkkelen bare rettighetene integrasjonen trenger.
  4. Kopier nøkkelen med én gang. Den vises bare én gang.
  5. Kall GET /api/external/v1/ for å kontrollere nøkkelen og se hvilke endepunkter organisasjonen faktisk har tilgang til.
  6. Hent deretter maler med GET /templates.php, finn ønsket template_id og feltets key, og opprett forespørselen med POST /requests.php.

Viktig: e-CMR-endepunktet og e-CMR-scopes er bare tilgjengelige når e-CMR-modulen er aktivert for organisasjonen. Hvis modulen er deaktivert, annonseres ikke e-CMR i discovery-responsen og API-nøkkelen kan ikke få e-CMR-rettigheter.

Base og autentisering

Base:

/api/external/v1

Send API-nøkkelen på alle kall:

Authorization: Bearer dfk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

X-Dokflow-API-Key støttes også, men Authorization: Bearer anbefales.

API-nøkler opprettes av administrator under Innstillinger → Stripe Connect → HTTP API. Klartekstnøkkelen vises bare én gang og skal kun lagres på serversiden. Ikke bygg nøkkelen inn i nettleser-JavaScript eller en distribuert mobilapp.

Alle svar inneholder request_id, og samme verdi sendes i HTTP-headeren X-Request-Id.

Test API-nøkkelen

GET /api/external/v1/

Returnerer organisasjon, nøkkelnavn, scopes og bare de V1-endepunktene som faktisk er tilgjengelige for denne organisasjonen og API-nøkkelen. Dette er anbefalt discovery-kall ved oppstart av en integrasjon.

Scopes

Tilgjengelige scopes avhenger også av hvilke Dokflow-moduler organisasjonen har aktivert. e-CMR-scopes vises ikke og kan ikke tildeles dersom e-CMR-modulen ikke er aktiv.

Første testkall

cURL

curl -sS \
  -H "Authorization: Bearer dfk_DIN_NOKKEL" \
  -H "Accept: application/json" \
  https://dokflow.no/api/external/v1/

PHP

<?php
$apiKey = getenv('DOKFLOW_API_KEY');

$ch = curl_init('https://dokflow.no/api/external/v1/');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $apiKey,
        'Accept: application/json',
    ],
]);

$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode((string) $response, true);

Bruk alltid HTTPS i produksjon og hold API-nøkkelen på serveren.

Maler

Hent alle aktive maler

GET /api/external/v1/templates.php

Hent én mal

GET /api/external/v1/templates.php?id=12

Responsen inneholder malens felt og key for hvert felt. Felt med api_writable: true kan sendes i fields ved opprettelse. Dette er felter som i Dokflow er satt til Dere ved opprettelse (låst). Filfelt lastes ikke opp gjennom JSON-endepunktet i V1.

For flerpartssignering returneres også signing.roles. Bruk role_key fra denne listen når signatarer sendes inn.

Feltmapping

Et eksternt system trenger ikke vite hvordan malen ser ut visuelt. Integrasjonen bruker feltenes stabile key.

Eksempel fra GET /templates.php?id=12:

{
  "key": "ordrenummer",
  "label": "Ordrenummer",
  "type": "text",
  "required": true,
  "filled_by": "sender",
  "api_writable": true
}

Send deretter verdien slik:

{
  "fields": {
    "ordrenummer": "84291"
  }
}

Bare felter med api_writable: true skal fylles fra API-et. Felt mottakeren skal fylle ut selv, signaturfelt og vanlige filopplastinger sendes ikke som fritekst gjennom fields.

Opprett Dokflow-forespørsel

POST /api/external/v1/requests.php

Eksempel:

{
  "external_id": "system:case:84291:contract",
  "template_id": 12,
  "title": "Avtale – ordre 84291",
  "recipient": {
    "name": "Ola Nordmann",
    "phone": "+4791234567",
    "email": "ola@example.no"
  },
  "fields": {
    "ordrenummer": "84291",
    "arbeidsadresse": {
      "street": "Eksempelveien 1",
      "postal_code": "5003",
      "city": "Bergen",
      "country": "Norge"
    },
    "avtalt_belop": 12500
  },
  "message": "Se gjennom og signer dokumentet.",
  "delivery": {
    "send_sms": true,
    "send_email": false
  }
}

Ved vellykket opprettelse returneres blant annet Dokflow-ID, offentlig URL, status og eventuell leveringsstatus.

Flerpartssignering

Når malen har flere signaturroller:

{
  "external_id": "system:case:84291:handover",
  "template_id": 18,
  "fields": {
    "ordrenummer": "84291"
  },
  "signing_mode": "parallel",
  "participants": [
    {
      "role_key": "kunde",
      "name": "Ola Nordmann",
      "phone": "+4791234567",
      "email": "ola@example.no"
    },
    {
      "role_key": "montor",
      "name": "Kari Montør",
      "phone": "+4798765432"
    }
  ],
  "delivery": {
    "send_participant_sms": true,
    "send_participant_email": false
  }
}

role_key må være en rolle som returneres fra mal-endepunktet.

SMS OTP og signaturintegritet

Et signaturfelt kan ha signature_sms_otp: true. Innstillingen styres per signaturfelt i malen og returneres av GET /templates.php. Når OTP er aktivert må den aktuelle signataren ha et gyldig telefonnummer. For enkeltsignering brukes recipient.phone (eller et telefonfelt som er mappet i malen). For flerpartssignering må den aktuelle posten i participants ha phone, med mindre rollen får telefon fra et senderutfylt felt.

Etter hver godkjente SES-signatur lager Dokflow et uforanderlig evidence-snapshot, beregner SHA-256 og sender kun hashen til OpenTimestamps. Ved flerpartssignering opprettes et eget anchor etter hvert signeringssteg. GET /requests.php returnerer disse i request.signature_anchors, blant annet med trigger_signature_id, signature_count, evidence_sha256, proof_sha256, calendar, state og submitted_at. Hvis en offentlig OTS-kalender er midlertidig utilgjengelig beholdes snapshotet, og Dokflow forsøker eldre ventende anchors igjen ved et senere vellykket signeringssteg. state: submitted betyr at kalenderen har mottatt commitmentet og at .ots-proofet er lagret; Bitcoin-attestasjonen blir tilgjengelig senere gjennom normal OpenTimestamps-oppgradering.

Betalingsfelt, produktlinjer, MVA og kvittering

Hvis malen inneholder betaling, returnerer mal-endepunktet betalingsfeltets konfigurasjon. payment_line_items_mode kan være:

For fixed returneres payment_line_items. For repeat returneres payment_repeat_field_key, payment_repeat_description_column_key, valgfri payment_repeat_quantity_column_key, payment_repeat_unit_amount_column_key og valgfri payment_repeat_vat_column_key. line_items_source_field beskriver tabellfeltet, inkludert kolonnene og om feltet er API-skrivbart.

Et leverings-/ytelsessted kan være et felt som fylles ut ved opprettelse (filled_by: sender) eller av mottakeren (filled_by: recipient). Mottakerutfylte verdier trenger ikke sendes i API-kallet; Dokflow kontrollerer og fryser den faktiske verdien når kunden starter betalingen. Hvis kildesystemet skal sende verdien på forhånd, må malen bruke et sender-felt.

For betalingsmaler må opprettelsen i tillegg inneholde receipt_buyer. Kjøper må ha navn og enten organisasjonsnummer eller full adresse. email brukes til automatisk utsending av kvitteringslenken når betalingen er captured.

Én betalingslinje med beløp angitt ved opprettelse

Hvis betalingsfeltet bruker single og beløpskilden er Angis ved opprettelse, send beløpet i payment_fields. Bruk betalingsfeltets key fra mal-endepunktet:

{
  "template_id": 22,
  "external_id": "system:case:84291:deposit",
  "fields": {
    "ordrenummer": "84291"
  },
  "payment_fields": {
    "depositum": {
      "amount": 2500
    }
  },
  "recipient": {
    "name": "Ola Nordmann",
    "phone": "+4791234567",
    "email": "ola@example.no"
  },
  "receipt_buyer": {
    "name": "Eksempelbedriften AS",
    "organization_number": "999999999",
    "vat_number": "999999999",
    "address": "Eksempelveien 1",
    "postal_code": "5003",
    "city": "Bergen",
    "country": "Norge",
    "email": "regnskap@example.no"
  },
  "delivery": {
    "send_sms": true
  }
}

Beløp i amount oppgis i kroner. amount_ore kan brukes dersom integrasjonen heller vil sende heltall i øre.

Produktlinjer fra gjentakende tabell

Når payment_line_items_mode er repeat, sender integrasjonen radene til feltet som står i payment_repeat_field_key. Kolonnenavnene er nøklene som returneres i tabellfeltets columns.

Eksempel dersom tabellfeltet heter ordrelinjer og betalingsfeltet mapper produkt, antall, enhetspris og mva:

{
  "template_id": 31,
  "external_id": "erp:ordre:5833042:betaling",
  "fields": {
    "ordrenummer": "5833042",
    "ordrelinjer": [
      {
        "produkt": "Montering",
        "antall": 2,
        "enhetspris": 1250,
        "mva": 25
      },
      {
        "produkt": "Kjøring",
        "antall": 1,
        "enhetspris": 500,
        "mva": 25
      }
    ]
  },
  "recipient": {
    "name": "Ola Nordmann",
    "phone": "+4791234567",
    "email": "ola@example.no"
  },
  "receipt_buyer": {
    "name": "Eksempelbedriften AS",
    "organization_number": "999999999",
    "address": "Eksempelveien 1",
    "postal_code": "5003",
    "city": "Bergen",
    "country": "Norge",
    "email": "regnskap@example.no"
  }
}

enhetspris oppgis i kroner og er inkludert MVA. Dokflow beregner antall × enhetspris for hver rad og bruker summen som betalingsbeløp. Ikke send et separat beløp for samme betalingsfelt. Hvis ingen antallskolonne er mappet, brukes antall 1. Hvis ingen MVA-kolonne er mappet, brukes betalingsfeltets payment_vat_percent.

Produktlinjene fryses i forespørselen når den opprettes. De brukes videre i Stripe Checkout, vises for kunden og legges på salgsbilaget. Hver linje kan ha egen MVA-sats. request.payments[].line_items returnerer produktlinjene for et opprettet betalingsforsøk, og request.payments[].receipt.credit_notes inneholder eventuelle kreditnotaer og PDF-lenker.

Dokflow lager et maskinelt nummerert salgsbilag først når beløpet faktisk er captured; en ren depositumsreservasjon utsteder ikke kvittering. Ved Stripe-refund utstedes kreditnota automatisk.

Hent forespørselsstatus

Med Dokflow-ID:

GET /api/external/v1/requests.php?id=1234

Med ekstern ID:

GET /api/external/v1/requests.php?external_id=system%3Acase%3A84291%3Acontract

Responsen inneholder forespørselsstatus, workflow-status, mottaker, offentlig URL, submission-status, signaturdeltakere, signature_anchors og payments. Når et captured betalingsforsøk har fått salgsbilag, inneholder betalingsobjektet også receipt med bilagsnummer, beløp og PDF-URL.

Idempotens / external_id

Det anbefales sterkt at alle opprettelser har en stabil external_id fra kildesystemet, for eksempel:

system:case:84291:contract

Samme external_id + identisk JSON kan trygt sendes på nytt. Dokflow returnerer da eksisterende objekt med:

{
  "idempotent_replay": true
}

Hvis samme external_id brukes med annet innhold, svarer API-et med HTTP 409 og code: "idempotency_conflict". Dette hindrer at retries lager duplikater eller at en eksisterende ekstern referanse utilsiktet får nytt innhold.

Modulbaserte endepunkter

Noen API-funksjoner følger Dokflow-moduler. V1 har foreløpig e-CMR som modulbasert API.

Dette gjør at integrasjoner kan bruke discovery-responsen som fasit i stedet for å anta at alle Dokflow-moduler finnes.

e-CMR

Hent e-CMR-systemskjema

GET /api/external/v1/ecmr.php?schema=1

Dette returnerer den interne ECMR_SYSTEM_V1-strukturen.

Opprett e-CMR

POST /api/external/v1/ecmr.php

Eksempel:

{
  "external_id": "transport:route:55021",
  "data": {
    "reference": "55021",
    "issue_place": "Bergen",
    "issue_date": "2026-08-28",
    "pickup_place": "Eidsvågveien 150, 5105 Eidsvåg i Åsane, Norway",
    "pickup_date": "2026-08-29T08:00",
    "delivery_place": "Industrigatan 10, 211 20 Malmö, Sweden",
    "delivery_date": "2026-08-30T14:00",
    "vehicle_registration": "AB12345",
    "trailer_registration": "CD6789",
    "sender_instructions": "Lastes fra rampe 3.",
    "customs_instructions": "",
    "carriage_terms": "Freight prepaid",
    "cash_on_delivery": "",
    "special_instructions": "",
    "sender": {
      "company_name": "Avsender AS",
      "name": "Anne Avsender",
      "address": "Eidsvågveien 150",
      "postal_code": "5105",
      "city": "Eidsvåg i Åsane",
      "country": "NO",
      "org_number": "999999999",
      "phone": "+4791111111",
      "email": "anne@example.no"
    },
    "carrier": {
      "company_name": "Transport AS",
      "name": "Jan Sjåfør",
      "address": "Transportveien 2",
      "postal_code": "5000",
      "city": "Bergen",
      "country": "NO",
      "org_number": "888888888",
      "phone": "+4792222222",
      "email": "jan@example.no"
    },
    "consignee": {
      "company_name": "Mottaker AB",
      "name": "Eva Mottaker",
      "address": "Industrigatan 10",
      "postal_code": "21120",
      "city": "Malmö",
      "country": "SE",
      "org_number": "SE556000000001",
      "phone": "+46701111111",
      "email": "eva@example.se"
    },
    "goods": [
      {
        "marks": "PAL-001",
        "packages": 4,
        "packaging_type": "EUR-pall",
        "description": "Kjøkkeninnredning",
        "statistical_number": "94034010",
        "gross_weight_kg": 720,
        "volume_m3": 6.2,
        "adr_un_number": "",
        "adr_class": "",
        "packing_group": ""
      }
    ],
    "reservations": []
  },
  "participants": {
    "sender": {
      "person_name": "Anne Avsender",
      "phone": "+4791111111",
      "email": "anne@example.no",
      "require_otp": true,
      "require_gps": false,
      "require_id": false
    },
    "carrier": {
      "person_name": "Jan Sjåfør",
      "phone": "+4792222222",
      "email": "jan@example.no",
      "require_otp": true,
      "require_gps": true,
      "require_id": true
    },
    "consignee": {
      "person_name": "Eva Mottaker",
      "phone": "+46701111111",
      "email": "eva@example.se",
      "require_otp": true,
      "require_gps": true,
      "require_id": false
    }
  },
  "mark_ready": true,
  "send_sms": ["carrier", "consignee"]
}

Hvis participants utelates, oppretter Dokflow automatisk signeringspartene fra data.sender, data.carrier og data.consignee med e-CMR-standardkravene.

send_sms kan være:

Når SMS skal sendes, blir e-CMR automatisk gjort klar først og alle obligatoriske CMR-data valideres.

Opprettelsen feiler ikke bare fordi en offentlig OpenTimestamps-kalender er midlertidig utilgjengelig. I så fall returneres en melding i ecmr.integrity_warnings, den frosne PDF-en beholdes, og Dokflow forsøker automatisk å forankre den manglende milepælen ved en senere vellykket forankring.

Hent e-CMR-status

GET /api/external/v1/ecmr.php?id=55

eller:

GET /api/external/v1/ecmr.php?external_id=transport%3Aroute%3A55021

Responsen inkluderer gjeldende revisjon, innholdshash, strukturert e-CMR-data, signeringsparter, signaturer, offentlige vedlegg, OpenTimestamps-forankringer (anchors), verifikasjons-URL og resultatet av kontroll av audit-hashkjeden.

Dokflow forankrer viktige e-CMR-milepæler separat: opprettelse, utstedelse/klar for signering, ny revisjon etter en endring, avsenders signatur, transportør/sjåførs signatur og mottakers signatur. Hver anchor inneholder derfor blant annet milestone, revision_no, content_sha256, signature_count, SHA-256 av den eksakte frosne PDF-en (pdf_sha256), kalender og tidspunkt for innsending.

Når en e-CMR er utstedt (ready eller senere), er revisjonen låst. Senere endringer i CMR-data, signeringsparter eller offentlige vedlegg oppretter en ny revisjon i stedet for å overskrive den tidligere versjonen.

Hent e-CMR som PDF

Gjeldende revisjon:

GET /api/external/v1/ecmr.php?id=55&format=pdf

En bestemt historisk revisjon:

GET /api/external/v1/ecmr.php?id=55&revision=2&format=pdf

PDF-kallet bruker samme Authorization: Bearer ...-header og krever ecmr:read. Dersom den eksakte revisjonstilstanden (samme content_sha256 og samme antall signaturer) er OpenTimestamps-forankret, returnerer Dokflow den frosne PDF-en som faktisk ble hashet og sendt til forankring. Et eldre anchor fra samme revisjonsnummer brukes ikke dersom utkastet senere ble endret.

Hent OpenTimestamps-proof

For gjeldende revisjon:

GET /api/external/v1/ecmr.php?id=55&format=ots

For en historisk revisjon:

GET /api/external/v1/ecmr.php?id=55&revision=2&format=ots

Responsen er en standard .ots-fil for den nyeste forankringen som dekker den eksakte revisjonstilstanden. Den kan lagres sammen med PDF-en og senere oppgraderes/verifiseres uavhengig med en OpenTimestamps-klient. Bare SHA-256-hashen av PDF-en sendes til den eksterne kalenderen; selve e-CMR-dokumentet eller signaturbildene sendes ikke.

Svarformat og feilsøking

Vanlige JSON-svar inneholder ok og request_id.

Vellykket svar:

{
  "ok": true,
  "request_id": "7c5e3e8c0a5e..."
}

Feilsvar:

{
  "ok": false,
  "error": "Forklaring på hva som er galt.",
  "code": "validation_error",
  "request_id": "7c5e3e8c0a5e..."
}

Lagre gjerne request_id i loggen til kildesystemet. Da er det mye enklere å finne det samme API-kallet i Dokflows audit-logg.

HTTP-statuskoder

Vanlige svar:

Rate limit og logging

En API-nøkkel kan gjøre opptil 300 kall per minutt. Alle API-kall logges med API-nøkkel, organisasjon, request-id, tidspunkt, HTTP-status, IP-adresse og user-agent.

Anbefalt integrasjonsflyt

En vanlig integrasjon kan gjøre følgende:

  1. Ved oppsett: kall GET /api/external/v1/ og les hvilke capabilities og endepunkter som er tilgjengelige.
  2. Hent maler med GET /templates.php, og la integrasjonen eller en administrator velge hvilken Dokflow-mal som skal brukes.
  3. Lagre valgt template_id og mapping mellom feltene i kildesystemet og Dokflow field.key.
  4. Når noe skal sendes til signering: kall POST /requests.php med en stabil external_id.
  5. Lagre returnert Dokflow id og URL i kildesystemet.
  6. Hent status ved behov med GET /requests.php?external_id=..., for eksempel ved synkronisering eller når brukeren åpner saken.
  7. Bruk modulbaserte endepunkter, som e-CMR, bare når de annonseres i discovery-responsen.

Eksempel på stabil external_id:

system:object:84291:agreement:v1

Bruk helst en stabil ID fra kildesystemet i stedet for en tilfeldig UUID. Det gjør retries idempotente og reduserer risikoen for duplikater.

Sikkerhet

API-et er laget for server-til-server bruk. CORS aktiveres ikke av V1. Lagre API-nøkkelen som en hemmelighet i kildesystemets backend/miljø, og gi hver integrasjon kun scopes den trenger. Tilbakekall nøkkelen i Dokflow hvis den kan ha kommet på avveie.