API-dokumentation

Byg MitID-underskrift ind i dit eget system. Upload et dokument, opret en underskriftsanmodning, og få besked via webhook, når den er underskrevet.

Kom godt i gang

  1. Lav en testnøgle under Konto → Udvikler. Testnøgler (sk_test_…) underskriver mod testmiljøet: rigtigt underskriftsforløb, men ingen betaling, ingen kvote og ingen rigtige penge.
  2. Upload PDF'en med POST /v1/documents.
  3. Opret underskriften med POST /v1/signature_requests.
  4. Registrer et webhook-endpoint, så siger vi til, når dokumentet er færdigunderskrevet.
  5. Skift til en live-nøgle (sk_live_…), når alt virker.

Godkendelse

Send nøglen som bearer-token. Hver nøgle har et sæt tilladelser (scopes) — den når ikke frem til endpoints, den ikke har fået adgang til, og en nøgle kan aldrig lave en ny nøgle.

Tilladelserne vælger du, når du laver nøglen. Hvert endpoint herunder siger, hvilken det kræver — mangler nøglen den, får du 403 insufficient_scope, og WWW-Authenticate siger, hvad der skulle til.

HTTP

Authorization: Bearer sk_live_...

Idempotens

Send en Idempotency-Key på alle POST-kald. Falder nettet ud midt i et kald, kan du trygt prøve igen med samme nøgle: du får det oprindelige svar tilbage i stedet for en ny underskriftsanmodning. Samme nøgle med et andet indhold giver 422 — det er en fejl hos dig, og den skjuler vi ikke.

curl https://esigner.dk/api/v1/signature_requests \
  -X POST \
  -H "Authorization: Bearer $ESIGNER_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "documentId": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "signers": [
      {
        "name": "Ola Nordmann",
        "email": "ola@example.no"
      }
    ],
    "useSubscription": true,
    "deadlineDays": 14
  }'

Testmiljø

Nøgler med præfikset sk_test_ kører mod testmiljøet. Alt fungerer som i produktion, men underskrivningen sker mod Criiptos testmiljø med en testidentitet, og underskriften er ikke juridisk bindende. Testbestillinger koster ingenting og trækker ikke fra din kvote.

Testmiljøet sender rigtige e-mails og rigtige SMS'er til de modtagere, du angiver. E-mails får «[TEST]» i emnefeltet, og SMS starter med «[TEST]», så ingen tror, en testbesked er ægte. Testdokumenter får aldrig påmindelser.

Da testbeskeder er gratis for dig og sendes fra vores domæne og vores telefonnummer, er mængden begrænset pr. konto: 50 e-mails og 10 SMS'er pr. rullende døgn. Når budgettet er brugt op, sker der ingen fejl — bestillingen oprettes, og signUrl virker som normalt. Beskeden sendes bare ikke, og svaret siger til i testMode-blokken.

// POST /orders  (test key)
{
  "orderId": "c8841198-bb5d-4a28-a121-9f61e31ffd95",
  "signUrl": "https://esigner.no/sign/hGLw0u0Cr6_eIkLI",
  "testMode": {
    "email": { "used": 50, "limit": 50, "suppressed": true,  "resetsAt": "2026-09-03T09:14:22Z" },
    "sms":   { "used": 3,  "limit": 10, "suppressed": false, "resetsAt": "2026-09-03T08:02:11Z" }
  }
}

// GET /api-keys/test-quota  — the same block, on demand
{
  "email": { "used": 50, "limit": 50, "suppressed": true,  "resetsAt": "2026-09-03T09:14:22Z" },
  "sms":   { "used": 3,  "limit": 10, "suppressed": false, "resetsAt": "2026-09-03T08:02:11Z" }
}

Documents

POST/api/v1/documents

Upload the PDF(s) to be signed

Step one of every integration. Upload as multipart/form-data; the id you get back is what POST /signature_requests takes. An sk_test_ key produces a test-mode document, and a test document can only ever be signed by a test key.

Kræver tilladelse: documents:write

Headere

  • Idempotency-Keystring

    Makes the write retry-safe: a retry with the same key replays the original response instead of creating a second signature request. Same key + a different body is a 422.

Forespørgsel multipart/form-data

  • ownerEmailstringpåkrævet

    Where the receipt and the signed PDF are sent. Must contain an @.

    post@firma.no

  • filesbinarypåkrævet

    The document. Repeat the field to upload several — they are merged in the order sent. PDF and Word (.docx) are accepted; a Word file is converted to PDF on upload. Older .doc, .odt and .rtf must be saved as .docx or PDF first. Per-file and per-request limits apply (25 MB per file, 10 files, by default).

  • templateSlugstring

    Optional. The /maler template the document was filled from — echoed on the completion surfaces.

    leieavtale

Svar 200Document

The uploaded document

  • iduuidpåkrævet

    Pass this as documentId when you create the signature request.

  • filenamestringpåkrævet

    Name of the first uploaded file.

    avtale.pdf

  • sizeBytesintegerpåkrævet

    Total size across every uploaded file.

    184320

  • attachmentsobject[]påkrævet

    One entry per uploaded PDF.

  • positionintegerpåkrævet

    0-based. Also the order the PDFs are merged in.

  • filenamestringpåkrævet

    vedlegg.pdf

  • sizeBytesintegerpåkrævet

    92160

Fejlsvar

  • 400The upload failed validation — error carries the sentence, e.g. pdf or word (.docx) files only, <name> must be saved as .docx or pdf first, ownerEmail required.
  • 401unauthorized — no API key, or it has been revoked.
  • 403insufficient_scope — the key is valid but was not granted the scope this endpoint declares.
  • 409idempotency_key_in_progress — the first request with this Idempotency-Key is still running. Retry.
  • 413A file exceeded the per-file size limit, or converting it to PDF produced a file that does.
  • 422A Word file could not be converted to PDF — it is damaged or password-protected.
  • 429too_many_requests — rate limited. Retry after the Retry-After header.
  • 503Maintenance. New uploads and signature requests are paused; existing ones keep running.
curl https://esigner.dk/api/v1/documents \
  -X POST \
  -H "Authorization: Bearer $ESIGNER_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "ownerEmail=post@firma.no" \
  -F "files=@avtale.pdf;type=application/pdf"

Svar

{
  "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
  "filename": "avtale.pdf",
  "sizeBytes": 184320,
  "attachments": [
    {
      "position": 0,
      "filename": "vedlegg.pdf",
      "sizeBytes": 92160
    }
  ]
}

Signature requests

POST/api/v1/signature_requests

Create a signature request

Sends the document to each signer for BankID/eID signing and returns the request in its initial state. Each signer is emailed (or texted) their own signing link. Pass useSubscription: true to draw the signature from the account's quota: the invitations go out immediately and no money moves. Without it the request is created in pending_payment and the response carries a payment_url — send the sender there, and the invitations go out once they've paid. The response carries each signer's own sign_url and sign_url_direct. Combine them with notifySigners: false to run the whole notification side yourself — see those fields.

Kræver tilladelse: orders:write

Headere

  • Idempotency-Keystring

    Makes the write retry-safe: a retry with the same key replays the original response instead of creating a second signature request. Same key + a different body is a 422.

Forespørgsel application/json

  • documentIduuidpåkrævet

    From POST /documents. Must belong to the same account as the key.

  • signersobject[]påkrævet

    1–20 signers, invited in the order given.

  • namestring

    Shown in the invitation. The legal identity comes from the eID, not from this.

    Ola Nordmann

  • emailstring

    ola@example.no

  • phonestring

    E.164 or a local number, e.g. +47 912 34 567. Only honoured while SMS delivery is enabled for the account.

  • useSubscriptionboolean

    Draw the signature from the account's quota instead of a payment. 409 quota_exceeded when the quota is spent.

    true

  • mode"merged" | "separate"

    Sign the uploads as one merged PDF (default) or as separate documents.

  • deadlineDaysinteger

    Days until the request expires. Defaults to the account's configured deadline.

    14

  • passwordstring

    Shared secret a signer must enter before the document is shown.

  • includePasswordInEmailboolean

    Whether that password is included in the invitation email. Defaults to true.

  • smsDeliveryboolean

    Also text the signing link. Free on subscription-covered requests, a surcharge on pay-per-use. Defaults to **true** on a subscription (the quota includes it) and false otherwise — so pass false explicitly if you never want us to text your signers. A phone-only signer is texted regardless, since it is their only channel; to silence that too, use notifySigners: false.

  • notifySignersboolean

    Whether **we** message the signers at all: the invitation, its SMS, the reminder sequence and the completion email. Defaults to true. Set false when your own product owns the conversation — otherwise your customer receives two invitations, from two senders, for one signature. You then have to deliver signers[].sign_url (or sign_url_direct) yourself: with this off, that link is the only way anyone reaches the signing page.

  • notifySenderboolean

    Whether we email the sender (ownerEmail) the receipt, the progress updates and the completed document. Defaults to true. The status and payment URLs are in this response and the signed PDF is downloadable over the API, so an integration loses nothing by turning it off.

  • sealFooterboolean

    Stamp a seal band across the bottom of **every page**: the parties who sign it on the left, "Forseglet via esigner" on the right — the shape Posten's signing product uses. Off by default. The band goes into the file before anyone has signed, so it names the parties rather than announcing a completed signature. **It locks the signer roster.** The band has to go into the PDF before the eID hand-off, because a PAdES signature covers the whole file and nothing can be added afterwards; a signer added later would be missing from a roster already sealed inside the signed document. So POST /orders/{id}/signers/* returns 409 seal_footer_locked for the life of the request. Decide the signer list up front. The band names each signer as Name (email) — or just the email or phone number when you gave no name — exactly what you supplied here. It carries no signing dates and no eID-verified legal names: those are only known after signing, and they are on the provider's attest page inside the same file. Only the copy sent for signing is stamped; the source PDF you uploaded is left untouched.

Svar 201SignatureRequest

The created signature request

  • iduuidpåkrævet

    The signature request id — pass it to GET /signature_requests/{id}.

  • object"signature_request"påkrævet
  • statusstringpåkrævet

    draft | pending_payment | in_progress | completed | expired | signing_failed. in_progress means the signers have been invited; signing_failed means the eID provider rejected the handoff and the request can be retried.

  • documentobjectpåkrævet

    The document being signed.

  • iduuidpåkrævet
  • filenamestringpåkrævet

    avtale.pdf

  • signersobject[]påkrævet

    In invitation order.

  • iduuidpåkrævet

    Stable id for this signer within the request.

  • namestringpåkrævet| null

    Ola Nordmann

  • emailstringpåkrævet| null

    null for a signer reached only by SMS.

    ola@example.no

  • statusstringpåkrævet

    pending | notified | viewed | signed | declined | expired

  • signed_atstringpåkrævet| null

    ISO 8601, null until this signer has signed.

    2026-07-13T10:24:00.000Z

  • sign_urlstringpåkrævet| null

    This signer's private signing page — the document, the deadline, and the button that starts BankID/MitID. It is the same URL the invitation email carries, so it is safe to send even when we also mailed it; with notifySigners: false it is the ONLY copy that exists and you must deliver it yourself. **Treat it as a credential**: the token in the path is what authorises the signature, so anyone holding the link can sign as that person. Send it to the signer, never anywhere public. null until the request has been handed to the eID provider (i.e. while it is still pending_payment), and on requests created before this field existed.

    https://esigner.no/sign/9f1c0f4e3d2b4a719f3a1c0…

  • sign_url_directstringpåkrævet| null

    Same signer, one step shorter: a redirect that takes them straight into BankID/MitID with no esigner page in between. For a flow whose own UI already showed the document and collected the approval click. Falls back to sign_url by redirecting there whenever it cannot skip — the request is password-protected, expired, cancelled, or the eID hand-off has not finished — so it is always safe to link. Note the signer then never sees the document on OUR surface: in the hosted eID flow the provider still presents it before signing, so use this when your own product has already put the document in front of them.

    https://esigner.no/api/sign/9f1c0f4e3d2b4a719f3…

  • livemodebooleanpåkrævet

    false for signature requests created with an sk_test_ key.

  • createdstringpåkrævet

    ISO 8601.

    2026-07-13T10:12:00.000Z

  • payment_urlstringpåkrævet| null

    Where to send the sender to pay, while status is pending_payment. null on a subscription-covered request, and once the payment has gone through. Safe to hand to a human at any time: it resumes the checkout if the request is still unpaid and lands on the status page if it isn't.

    https://esigner.no/api/orders/3f7c1e42-9b0a-4d7…

  • signed_documents_urlstringpåkrævet| null

    Where to list the signed PDFs, once status is completed. null before that. Requires the same API key — unlike the links in our completion email, this is not a public URL.

    https://esigner.no/api/v1/signature_requests/3f…

  • download_expires_atstringpåkrævet| null

    ISO 8601. When the PUBLIC download links (the ones in the completion email and on the status page) stop working. The API download does not expire on it — your account keeps its archive — but treat it as the deadline for fetching and storing your own copy.

    2026-08-13T10:24:00.000Z

Fejlsvar

  • 400validation — the request body failed schema validation. issues carries the field-level detail.
  • 401unauthorized — no API key, or it has been revoked.
  • 403insufficient_scope, or cross_owner_document — the document belongs to another account.
  • 404not_found — no such document.
  • 409quota_exceeded, no_subscription, topup_in_progress, topup_cooldown, or idempotency_key_in_progress.
  • 422idempotency_key_reused — the same Idempotency-Key was already used with a different request body.
  • 429too_many_requests — rate limited. Retry after the Retry-After header.
  • 502signing_failed — the eID provider rejected the handoff. The document is left in signing_failed and the request can be retried.
  • 503Maintenance. New uploads and signature requests are paused; existing ones keep running.
curl https://esigner.dk/api/v1/signature_requests \
  -X POST \
  -H "Authorization: Bearer $ESIGNER_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "documentId": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "signers": [
      {
        "name": "Ola Nordmann",
        "email": "ola@example.no"
      }
    ],
    "useSubscription": true,
    "deadlineDays": 14
  }'

Svar

{
  "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
  "object": "signature_request",
  "status": "in_progress",
  "document": {
    "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "filename": "avtale.pdf"
  },
  "signers": [
    {
      "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
      "name": "Ola Nordmann",
      "email": "ola@example.no",
      "status": "signed",
      "signed_at": "2026-07-13T10:24:00.000Z",
      "sign_url": "https://esigner.no/sign/9f1c0f4e3d2b4a719f3a1c0e5b8d2a44",
      "sign_url_direct": "https://esigner.no/api/sign/9f1c0f4e3d2b4a719f3a1c0e5b8d2a44/start"
    }
  ],
  "livemode": true,
  "created": "2026-07-13T10:12:00.000Z",
  "payment_url": "https://esigner.no/api/orders/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/pay",
  "signed_documents_url": "https://esigner.no/api/v1/signature_requests/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/signed_documents",
  "download_expires_at": "2026-08-13T10:24:00.000Z"
}
GET/api/v1/signature_requests/{id}

Retrieve a signature request

Poll this for status, or subscribe to document.completed and skip the polling.

Kræver tilladelse: orders:read

Sti-parametre

  • iduuidpåkrævet

    The id returned by POST /signature_requests.

Svar 200SignatureRequest

The signature request

Vis felterne i SignatureRequest
  • iduuidpåkrævet

    The signature request id — pass it to GET /signature_requests/{id}.

  • object"signature_request"påkrævet
  • statusstringpåkrævet

    draft | pending_payment | in_progress | completed | expired | signing_failed. in_progress means the signers have been invited; signing_failed means the eID provider rejected the handoff and the request can be retried.

  • documentobjectpåkrævet

    The document being signed.

  • iduuidpåkrævet
  • filenamestringpåkrævet

    avtale.pdf

  • signersobject[]påkrævet

    In invitation order.

  • iduuidpåkrævet

    Stable id for this signer within the request.

  • namestringpåkrævet| null

    Ola Nordmann

  • emailstringpåkrævet| null

    null for a signer reached only by SMS.

    ola@example.no

  • statusstringpåkrævet

    pending | notified | viewed | signed | declined | expired

  • signed_atstringpåkrævet| null

    ISO 8601, null until this signer has signed.

    2026-07-13T10:24:00.000Z

  • sign_urlstringpåkrævet| null

    This signer's private signing page — the document, the deadline, and the button that starts BankID/MitID. It is the same URL the invitation email carries, so it is safe to send even when we also mailed it; with notifySigners: false it is the ONLY copy that exists and you must deliver it yourself. **Treat it as a credential**: the token in the path is what authorises the signature, so anyone holding the link can sign as that person. Send it to the signer, never anywhere public. null until the request has been handed to the eID provider (i.e. while it is still pending_payment), and on requests created before this field existed.

    https://esigner.no/sign/9f1c0f4e3d2b4a719f3a1c0…

  • sign_url_directstringpåkrævet| null

    Same signer, one step shorter: a redirect that takes them straight into BankID/MitID with no esigner page in between. For a flow whose own UI already showed the document and collected the approval click. Falls back to sign_url by redirecting there whenever it cannot skip — the request is password-protected, expired, cancelled, or the eID hand-off has not finished — so it is always safe to link. Note the signer then never sees the document on OUR surface: in the hosted eID flow the provider still presents it before signing, so use this when your own product has already put the document in front of them.

    https://esigner.no/api/sign/9f1c0f4e3d2b4a719f3…

  • livemodebooleanpåkrævet

    false for signature requests created with an sk_test_ key.

  • createdstringpåkrævet

    ISO 8601.

    2026-07-13T10:12:00.000Z

  • payment_urlstringpåkrævet| null

    Where to send the sender to pay, while status is pending_payment. null on a subscription-covered request, and once the payment has gone through. Safe to hand to a human at any time: it resumes the checkout if the request is still unpaid and lands on the status page if it isn't.

    https://esigner.no/api/orders/3f7c1e42-9b0a-4d7…

  • signed_documents_urlstringpåkrævet| null

    Where to list the signed PDFs, once status is completed. null before that. Requires the same API key — unlike the links in our completion email, this is not a public URL.

    https://esigner.no/api/v1/signature_requests/3f…

  • download_expires_atstringpåkrævet| null

    ISO 8601. When the PUBLIC download links (the ones in the completion email and on the status page) stop working. The API download does not expire on it — your account keeps its archive — but treat it as the deadline for fetching and storing your own copy.

    2026-08-13T10:24:00.000Z

Fejlsvar

  • 401unauthorized — no API key, or it has been revoked.
  • 403insufficient_scope — the key is valid but was not granted the scope this endpoint declares.
  • 404not_found — no such signature request, or it belongs to another account. The two are deliberately indistinguishable.
  • 429too_many_requests — rate limited. Retry after the Retry-After header.
curl https://esigner.dk/api/v1/signature_requests/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11 \
  -H "Authorization: Bearer $ESIGNER_API_KEY"

Svar

{
  "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
  "object": "signature_request",
  "status": "in_progress",
  "document": {
    "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "filename": "avtale.pdf"
  },
  "signers": [
    {
      "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
      "name": "Ola Nordmann",
      "email": "ola@example.no",
      "status": "signed",
      "signed_at": "2026-07-13T10:24:00.000Z",
      "sign_url": "https://esigner.no/sign/9f1c0f4e3d2b4a719f3a1c0e5b8d2a44",
      "sign_url_direct": "https://esigner.no/api/sign/9f1c0f4e3d2b4a719f3a1c0e5b8d2a44/start"
    }
  ],
  "livemode": true,
  "created": "2026-07-13T10:12:00.000Z",
  "payment_url": "https://esigner.no/api/orders/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/pay",
  "signed_documents_url": "https://esigner.no/api/v1/signature_requests/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/signed_documents",
  "download_expires_at": "2026-08-13T10:24:00.000Z"
}
GET/api/v1/signature_requests/{id}/signed_documents

List the signed PDFs

Available once status is completed. One entry in merged mode (the default), one per uploaded file in separate mode. Poll GET /signature_requests/{id} for completed, or subscribe to document.completed and come straight here.

Kræver tilladelse: documents:readorders:read(én af dem er nok)

Sti-parametre

  • iduuidpåkrævet

Svar 200object

The signed PDFs

  • dataSignedDocument[]påkrævet
  • positionintegerpåkrævet

    0-based. Pass it to GET /signature_requests/{id}/signed_documents/{position}. In merged mode there is exactly one entry, position 0, covering every uploaded file; in separate mode there is one per upload, in upload order.

  • filenamestringpåkrævet

    signert-avtale.pdf

  • size_bytesintegerpåkrævet| null

    null if the size could not be read.

    214016

  • sha256stringpåkrævet| null

    Hex SHA-256 of the signed PDF, captured at completion. Verify your download against it.

    9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822…

  • urlstringpåkrævet

    Authenticated download for this file. Same API key, application/pdf in response.

    https://esigner.no/api/v1/signature_requests/3f…

  • download_expires_atstringpåkrævet| null

    When the PUBLIC links expire. These API downloads do not — see the field on SignatureRequest.

Fejlsvar

  • 401unauthorized — no API key, or it has been revoked.
  • 403insufficient_scope — the key is valid but was not granted the scope this endpoint declares.
  • 404not_found — no such signature request, or it belongs to another account.
  • 409not_completed — not every signer has signed yet. detail names the current status.
  • 410gone (the files have been deleted) or revoked (an administrator locked the document).
  • 429too_many_requests — rate limited. Retry after the Retry-After header.
curl https://esigner.dk/api/v1/signature_requests/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/signed_documents \
  -H "Authorization: Bearer $ESIGNER_API_KEY"

Svar

{
  "data": [
    {
      "position": 0,
      "filename": "signert-avtale.pdf",
      "size_bytes": 214016,
      "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
      "url": "https://esigner.no/api/v1/signature_requests/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/signed_documents/0"
    }
  ],
  "download_expires_at": "string"
}
GET/api/v1/signature_requests/{id}/signed_documents/{position}

Download a signed PDF

Returns application/pdf — the completed, eID-signed file, with the provider's attest page and its PAdES signature intact. Byte-for-byte what a signer downloads, so a signature check in any PDF reader (or at /kontroller-signatur) validates against it.

Kræver tilladelse: documents:readorders:read(én af dem er nok)

Sti-parametre

  • iduuidpåkrævet
  • positioninteger

    From the list endpoint. Always 0 in merged mode.

Query-parametre

  • download"0" | "1"

    1 sets Content-Disposition: attachment instead of inline.

Svar 200

The signed PDF

Frit JSON-objekt — indholdet varierer.

Fejlsvar

  • 401unauthorized — no API key, or it has been revoked.
  • 403insufficient_scope — the key is valid but was not granted the scope this endpoint declares.
  • 404not_found — no such signature request or position, or it belongs to another account.
  • 409not_completed — not every signer has signed yet.
  • 410gone or revoked — see the list endpoint.
  • 429too_many_requests — rate limited. Retry after the Retry-After header.
curl https://esigner.dk/api/v1/signature_requests/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/signed_documents/0 \
  -H "Authorization: Bearer $ESIGNER_API_KEY"

Events

GET/api/v1/events

List events

The same events delivered to your webhook endpoints, newest first. Useful for backfilling after downtime.

Kræver tilladelse: webhooks:readorders:read(én af dem er nok)

Query-parametre

  • limitinteger

    Page size. Defaults to 25.

  • cursorstring

    Pass the previous page's next_cursor to continue. Returns events strictly older than it.

  • typestring

    Return only this event type.

Svar 200object

A page of events

  • dataEvent[]påkrævet
  • iduuidpåkrævet
  • object"event"påkrævet
  • typestringpåkrævet

    document.completed | document.signing_failed | order.captured

  • livemodebooleanpåkrævet

    false for activity produced by an sk_test_ key.

  • createdstringpåkrævet

    ISO 8601. Also the pagination cursor — pass it as cursor to get the next page.

    2026-07-13T10:24:00.000Z

  • dataobjectpåkrævet

    The same payload the webhook delivery carries for this event type.

  • next_cursorstringpåkrævet| null

    null on the last page. Otherwise pass it back as cursor.

Fejlsvar

  • 401unauthorized — no API key, or it has been revoked.
  • 403insufficient_scope — the key is valid but was not granted the scope this endpoint declares.
  • 429too_many_requests — rate limited. Retry after the Retry-After header.
curl https://esigner.dk/api/v1/events?limit=25&type=document.completed \
  -H "Authorization: Bearer $ESIGNER_API_KEY"

Svar

{
  "data": [
    {
      "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
      "object": "event",
      "type": "document.completed",
      "livemode": true,
      "created": "2026-07-13T10:24:00.000Z",
      "data": {}
    }
  ],
  "next_cursor": "string"
}

Fuld maskinlæsbar specifikation (OpenAPI 3.1): /api/v1/openapi.json

Fejl

Alle fejl svarer med JSON og en stabil kode i error. Koderne herunder er dem, du kan møde — byg logikken på dem, ikke på teksten.

StatusKodeBetyder
400validationThe request body failed schema validation. issues carries the field-level detail.
401unauthorizedMissing, malformed or revoked API key.
403insufficient_scopeThe key is valid but lacks the scope the endpoint declares. WWW-Authenticate names what was needed.
403cross_owner_documentThe documentId belongs to a different account than the key.
404not_foundNo such resource — or it belongs to another account. Deliberately indistinguishable.
409quota_exceededThe subscription has no signatures left. remaining says how many there were.
409no_subscriptionuseSubscription: true but the account has no active subscription.
409topup_in_progressAn automatic top-up is already running. Retry shortly.
409topup_cooldownA renewal failed recently; automatic top-up is on a 24h cool-down.
409not_completedThe signed PDF was requested before every signer had signed. detail names the current status.
409seal_footer_lockedSigners cannot be added to a request created with sealFooter: true — the roster is inside the signed file.
410goneThe files behind this signature request have been deleted.
410revokedAn administrator has locked this document.
409idempotency_key_in_progressThe first request with this Idempotency-Key hasn't finished. Retry.
413A single uploaded file exceeded the size limit.
422idempotency_key_reusedThe same Idempotency-Key was used with a different request body. That's a caller bug, so we surface it.
429too_many_requestsRate limited. Retry-After says how long to wait.
502signing_failedThe eID provider rejected the handoff. The document lands in signing_failed and can be retried.
503Maintenance. New uploads and signature requests are paused; existing ones keep running.

Fejl

{
  "error": "quota_exceeded",
  "remaining": 0
}

Webhooks

Vi POSTer hændelser til din adresse og signerer hver levering med Esigner-Signature: t=<unix>,v1=<hmac>. HMAC'en er over <t>.<rå body>, så en opsnappet besked ikke kan afspilles igen med et andet tidsstempel. Verificér altid, før du stoler på indholdet.

Fejler en levering, prøver vi igen med stigende mellemrum (1 min → 6 t). Svarer endpointet ikke over tid, deaktiverer vi det og siger til på e-mail — hændelserne går ikke tabt, og du kan sende dem igen.

Konvolutten

  • iduuidpåkrævet

    Also sent as the Esigner-Event-Id header. Dedupe on it — a retry reuses the id.

  • typestringpåkrævet

    Also sent as the Esigner-Event-Type header.

    document.completed

  • livemodebooleanpåkrævet
  • createdintegerpåkrævet

    Unix seconds. Note: GET /events returns this field as an ISO string instead.

    1784000000

  • dataobjectpåkrævet

    Per event type — see below.

Node.js

// Verificér Esigner-Signature: t=<unix>,v1=<hmac>
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, header, secret) {
  const [t, v1] = header.split(",").map(p => p.split("=")[1]);
  const expected = createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  return timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}
document.completed

Every signer has signed. The signed PDF is available until downloadExpiresAt.

data

  • documentIduuidpåkrævet
  • orderIduuidpåkrævet

    The same id as SignatureRequest.id.

  • filenamestringpåkrævet

    avtale.pdf

  • mode"merged" | "separate"påkrævet

    Whether the uploads were signed as one merged PDF or individually.

  • downloadExpiresAtstringpåkrævet

    ISO 8601. Fetch the signed PDF before this.

    2026-08-13T10:24:00.000Z

  • signersobject[]påkrævet
  • namestringpåkrævet| null

    Ola Nordmann

  • emailstringpåkrævet| null

    ola@example.no

  • signedAtstringpåkrævet| null

    ISO 8601.

    2026-07-13T10:24:00.000Z

document.completed

{
  "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
  "type": "document.completed",
  "livemode": true,
  "created": 1784000000,
  "data": {
    "documentId": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "orderId": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "filename": "avtale.pdf",
    "mode": "merged",
    "downloadExpiresAt": "2026-08-13T10:24:00.000Z",
    "signers": [
      {
        "name": "Ola Nordmann",
        "email": "ola@example.no",
        "signedAt": "2026-07-13T10:24:00.000Z"
      }
    ]
  }
}
document.signing_failed

The eID provider rejected the handoff. The sender can retry the document.

data

  • documentIduuidpåkrævet
  • orderIduuidpåkrævet
  • messagestringpåkrævet

    Why the eID provider rejected the handoff.

    signature order rejected

document.signing_failed

{
  "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
  "type": "document.signing_failed",
  "livemode": true,
  "created": 1784000000,
  "data": {
    "documentId": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "orderId": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "message": "signature order rejected"
  }
}
order.captured

A pay-per-use payment was captured. Not emitted for subscription-covered requests.

data

  • orderIduuidpåkrævet
  • documentIduuidpåkrævet
  • amountOreintegerpåkrævet

    Captured amount in minor units (øre), VAT included.

    3625

order.captured

{
  "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
  "type": "order.captured",
  "livemode": true,
  "created": 1784000000,
  "data": {
    "orderId": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "documentId": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "amountOre": 3625
  }
}

AI-assistenter (MCP)

Skal en AI-assistent oprette underskrifterne i stedet, har vi en MCP-server: esigner.dk er en godkendt app i ChatGPT, og adressen kan indsættes som egen forbindelse i Claude og andre MCP-klienter. Se hvordan du forbinder.

Sidder du fast? Tag kontakt — vi svarer gerne på integrationsspørgsmål.