API-dokumentaatio

Rakenna allekirjoitus pankkitunnuksilla omaan järjestelmääsi. Lataa asiakirja, luo allekirjoituspyyntö ja saa webhookilla tieto, kun se on allekirjoitettu.

Perus-URL
https://esigner.fi/api/v1
Tunnistautuminen
Bearer sk_live_… / sk_test_…
Muoto
JSON · PDF multiparttina
Webhookit
HMAC-SHA256-allekirjoitetut

Pääse alkuun#

  1. 1Luo testiavain kohdassa Tili → Kehittäjä. Testiavaimet (sk_test_…) allekirjoittavat testiympäristöä vasten: aito allekirjoitusprosessi, mutta ei maksua, ei kiintiötä eikä oikeaa rahaa.
  2. 2Lataa PDF komennolla POST /v1/documents.
  3. 3Luo allekirjoitus komennolla POST /v1/signature_requests. Ilman paymentMethod-kenttää pyyntö odottaa maksua, eikä ketään kutsuta ennen kuin se on maksettu: silloin payment_required on true ja next_action.url on maksulinkki. Lähetä paymentMethod: "balance", "card" tai "auto", jos haluat maksaa ilman maksusivua.
  4. 4Rekisteröi webhook-päätepiste, niin ilmoitamme, kun asiakirja on valmiiksi allekirjoitettu.
  5. 5Vaihda live-avaimeen (sk_live_…), kun kaikki toimii.

Viralliset SDK:t#

Valmiit asiakaskirjastot, jotka hoitavat tunnistautumisen, idempotenssiavaimet, uudelleenyritykset, sivutuksen ja webhookien varmennuksen — tyypitetty saman määrittelyn mukaan kuin tämä sivu.

Node.js ja TypeScript

Node 18+, Bun, Deno ja edge — ei riippuvuuksia.

npm

npm install @esigner/sdk

npm ↗GitHub ↗

PHP ja WordPress

PHP 8.1+, vain ext-curl — sopii suoraan WordPressiin.

Composer

composer require esigner/sdk

Packagist ↗GitHub ↗

Tunnistautuminen#

Lähetä avain bearer-tokenina. Jokaisella avaimella on joukko oikeuksia (scopes) — se ei pääse päätepisteisiin, joihin sille ei ole annettu oikeutta, eikä avain voi koskaan luoda uutta avainta.

Oikeudet valitset avainta luodessasi. Jokaisen alla olevan päätepisteen kohdalla kerrotaan, minkä oikeuden se vaatii — jos avaimelta puuttuu se, saat 403 insufficient_scope, ja WWW-Authenticate kertoo, mitä olisi tarvittu.

Mitä kiintiöstä vähennetään ja mitä perumisessa ja vanhenemisessa tapahtuu: Hinnat ja allekirjoitusten laskeminen →

HTTP

Authorization: Bearer sk_live_...

Idempotenssi#

Lähetä Idempotency-Key kaikissa POST-kutsuissa. Jos yhteys katkeaa kesken kutsun, voit turvallisesti yrittää uudelleen samalla avaimella: saat alkuperäisen vastauksen etkä uutta allekirjoituspyyntöä. Sama avain eri sisällöllä palauttaa 422 — se on virhe sinun päässäsi, emmekä piilota sitä.

curl https://esigner.fi/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,
    "copyEmails": [
      "ola@example.no"
    ],
    "returnUrl": "https://app.example.com/contracts/123/signed",
    "signingEids": [
      "no_bankid"
    ]
  }'

Testiympäristö#

Avaimet, joiden etuliite on sk_test_, käyttävät testiympäristöä. Kaikki toimii kuten tuotannossa, mutta allekirjoitus tehdään Iduran testiympäristössä testitunnistautumisella, eikä allekirjoitus ole oikeudellisesti sitova. Testitilaukset eivät maksa mitään eivätkä kuluta kiintiötäsi.

Testitunnisteet eID:ittäin

Testiympäristö hyväksyy vain testikäyttäjiä — oikea BankID, MitID tai muu eID ei toimi siellä, ja esimerkiksi ruotsalainen BankID-sovellus ilmoittaa ”virheellinen QR-koodi”. Näin allekirjoittaja saa testitunnisteen: Iduran yleiskatsaus testikäyttäjiin ↗

FTN (Suomi)Idura ↗
Idura julkaisee valmiit testitunnukset pankeittain, esimerkiksi Nordean DEMOUSER1. Kaikki tapahtuu selaimessa; sovellusta ei tarvita.
BankID (Norja)Idura ↗BankID RA preprod ↗
Luo testikäyttäjä norjalaisen BankID:n preprod-portaalissa (RA preprod); sen numerogeneraattori antaa kelvollisen norjalaisen henkilötunnuksen. Allekirjoita selaimessa kertakoodilla otp ja salasanalla qwer1234. Uuden testikäyttäjän aktivoituminen voi kestää jopa tunnin.
BankID (Ruotsi)Idura ↗BankID for test ↗BankID test portal ↗
BankID-sovellus on määritettävä uudelleen testiympäristöä varten (ks. ”BankID for test”), eikä se voi olla yhtä aikaa testi- ja tuotantotilassa — käytä erillistä puhelinta. Myönnä sitten test-BankID BankID:n testiportaalissa keksityllä mutta kelvollisella henkilötunnuksella. Tavallinen BankID-sovellus ilmoittaa QR-koodin virheelliseksi.
MitID (Tanska)Idura ↗MitID Test Tool ↗
Luo testi-identiteetti MitID Test Toolissa ja hyväksy sen sovellussimulaattorilla — puhelinta ei tarvita. MitID:n oma testisovellus toimii myös; Androidilla se ei voi olla asennettuna oikean sovelluksen rinnalla.
iDIN (Alankomaat)Idura ↗
Idura ei julkaise testikäyttäjiä iDIN:lle. Kirjoita meille ennen testaamista, niin järjestämme sen yhdessä.
OneID (Iso-Britannia)Idura ↗
OneID:n hiekkalaatikko simuloi pankkia (”model bank”): valitse selaimessa jokin sen testi-identiteeteistä. Sovellusta ei tarvita.
Personalausweis (Saksa)Idura ↗AusweisApp ↗
Käytä AusweisAppia sen sisäänrakennetulla korttisimulaattorilla, joka vastaa aina testihenkilönä ”Erika Mustermann”. Ota kehittäjätila käyttöön (napauta versionumeroa kymmenen kertaa), sitten testitila ja sisäinen korttisimulaattori. Simulaattori on kytkettävä pois ennen kuin sovellus voi lukea oikean kortin.

Testiympäristö lähettää oikeita sähköposteja ja oikeita tekstiviestejä antamillesi vastaanottajille. Sähköpostien aiheessa on «[TEST]» ja tekstiviestit alkavat «[TEST]». Testiasiakirjoista ei koskaan lähetetä muistutuksia.

Koska testiviestit ovat sinulle ilmaisia ja lähtevät meidän verkkotunnuksestamme ja numerostamme, määrä on rajattu tiliä kohden: 50 sähköpostia ja 10 tekstiviestiä liukuvaa vuorokautta kohden. Kun budjetti on käytetty, mitään virhettä ei tapahdu — tilaus luodaan ja signUrl toimii normaalisti. Vain viestiä ei lähetetä, ja vastaus kertoo siitä testMode-lohkossa.

// 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" }
}

Asiakirjat#

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.

Vaatii oikeuden: documents:write

Otsakkeet

  • 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.

Pyyntö multipart/form-data

  • ownerEmailstringpakollinen

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

    post@firma.no

  • filesbinarypakollinen

    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

Vastaus 200Document

The uploaded document

  • iduuidpakollinen

    Pass this as documentId when you create the signature request.

  • filenamestringpakollinen

    Name of the first uploaded file.

    avtale.pdf

  • sizeBytesintegerpakollinen

    Total size across every uploaded file.

    184320

  • attachmentsobject[]pakollinen

    One entry per uploaded PDF.

  • positionintegerpakollinen

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

  • filenamestringpakollinen

    vedlegg.pdf

  • sizeBytesintegerpakollinen

    92160

Virhevastaukset

  • 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.fi/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"

Vastaus

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

Allekirjoituspyynnöt#

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 (and without paymentMethod) 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. That response is still a 201, so check payment_required: while it is true, nobody has been invited. To pay without a payment page, name paymentMethod: balance, card, or auto for whatever the account pays with. How the quota is drawn: one signature per signer, taken when the request is created — not when it completes. The number of files doesn't matter. Cancel a request before anyone signs and they all come back (POST /signature_requests/{id}/cancel); an expired request keeps them. A document whose request was cancelled can be sent again with the same documentId. 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.

Vaatii oikeuden: orders:write

Otsakkeet

  • 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.

Pyyntö application/json

  • documentIduuidpakollinen

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

  • signersobject[]pakollinen

    At least one signer, invited in the order given. There is no upper limit.

  • 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.

  • birthDatestring

    Lock this signer to a date of birth (YYYY-MM-DD). The eID provider compares it with the date of birth in the signer's BankID/MitID and refuses anyone else: they see an error in the signing window, and the request is unchanged, so the right person can still sign. Works with sign_url and sign_url_direct. While any signer is locked, the request is signed with the market's national eID only. Norway, Denmark, Finland and Germany; elsewhere 400 birthdate_lock_unsupported. A malformed date is 400 invalid_birthdate.

  • useSubscriptionboolean

    Draw the signature from the account's quota instead of a payment. 409 quota_exceeded when the quota is spent. Equivalent to paymentMethod: "subscription".

    true

  • paymentMethodstring (5 values)

    How to pay. Omitted: the request waits in pending_payment with a payment_url, unless useSubscription: true — the same as before this field existed, so an existing integration never starts spending quota or money without asking for it. - subscription — the quota. Same as useSubscription: true. - balance — the prepaid balance, at the pay-per-use price (signers × unit price, plus the SMS add-on when smsDelivery is on). The invitations go out immediately; 402 insufficient_balance when it doesn't cover the request. Check it with GET /balance. - card — the card saved under /konto/betaling, charged immediately at the pay-per-use price. A declined card refuses the request (402 payment_method_declined, or payment_method_action_required when the bank wants the customer present — the card then has to be saved again). Nothing is created for a declined card. - checkout — always a payment_url, even on an account with a subscription. - auto — whatever this account pays with: its subscription quota when it has one (never falling back to money when the quota runs out), else the way it chose under /konto/betaling. Never a checkout link: 409 no_payment_method when there is nothing to charge. Use it from a server that has no human to send to a payment page.

  • 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.

  • copyEmailsemail[]

    Up to five addresses that receive the completion email — the signed PDF attached, the download links, and Reply-To set to ownerEmail — without being a party to the signing: an accountant, a lawyer, the counterparty's office. They get no invitation and cannot sign. Lowercased and de-duplicated; the sender and the signers are dropped because they receive that email already. Read back as copy_emails.

    ["regnskap@example.no"]

  • returnUrluri

    Where each signer is sent after the eID step, instead of our status page — so they come back to your product. We add signature_request_id and signer_id as query parameters. Must be https; plain http is accepted with a test key, for localhost. The redirect says who came back, not how it went: the signing page sends a signer here after a refusal too. Read the outcome from GET /signature_requests/{id} or the signer.signed / signer.declined webhooks. Read back as return_url.

    https://app.example.com/contracts/123/signed

  • signingEidsstring (7 values)[]

    Restrict which eIDs the signing page offers. ["no_bankid"] makes Norwegian BankID the only choice; no other method appears. Omitted or empty follows your account's stored choice (set under /konto), then the deployment default — which, unless the operator changed it, is no restriction: every eID the signing provider has enabled is offered. Ids: no_bankid (Norway), se_bankid (Sweden), dk_mitid (Denmark), fi_ftn (Finland), nl_idin (the Netherlands), uk_oneid (the UK), de_personalausweis (Germany). GET /config lists the ones this deployment offers under signingEids.available; one it does not offer is refused with 400 signing_eid_unavailable rather than silently dropped. Read back as signing_eids.

  • 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.

Vastaus 201SignatureRequest

The created signature request

  • iduuidpakollinen

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

  • object"signature_request"pakollinen
  • statusstringpakollinen

    draft | pending_payment | in_progress | completed | expired | cancelled | signing_failed. in_progress means the signers have been invited; cancelled means it was stopped before anyone signed (see POST /signature_requests/{id}/cancel); signing_failed means the eID provider rejected the handoff and the request can be retried.

  • documentobjectpakollinen

    The document being signed.

  • iduuidpakollinen
  • filenamestringpakollinen

    avtale.pdf

  • signersobject[]pakollinen

    In invitation order.

  • iduuidpakollinen

    Stable id for this signer within the request.

  • namestringpakollinen| null

    Ola Nordmann

  • emailstringpakollinen| null

    null for a signer reached only by SMS.

    ola@example.no

  • statusstringpakollinen

    pending | notified | viewed | signed | declined | expired | removed (taken off the request by our support so it could complete)

  • signed_atstringpakollinen| null

    ISO 8601, null until this signer has signed.

    2026-07-13T10:24:00.000Z

  • verified_namestringpakollinen| null

    The signer's name as their eID (BankID, MitID, …) verified it — not the name you sent. Set when the whole request is completed: the eID provider releases the evidence only then. null before that, and for a signature whose evidence carried no name.

    Ola Nordmann

  • verified_birth_datestringpakollinen| null

    The signer's date of birth (YYYY-MM-DD) as their eID verified it. Set at completion, like verified_name. Always null for Swedish BankID, which does not issue a date of birth.

    1990-01-31

  • birth_date_lockstringpakollinen| null

    The date of birth this signer was locked to (signers[].birthDate at creation), or null.

    1990-01-31

  • sign_urlstringpakollinen| 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_directstringpakollinen| 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…

  • paymentobjectpakollinen

    How this request was paid for.

  • methodstringpakollinen

    subscription | balance | checkout | comped | test

  • amount_oreintegerpakollinen

    What this request cost in minor units (øre), VAT included. 0 on a subscription, comped or in test mode.

    5800

  • currencystringpakollinen

    NOK

  • livemodebooleanpakollinen

    false for signature requests created with an sk_test_ key.

  • createdstringpakollinen

    ISO 8601.

    2026-07-13T10:12:00.000Z

  • payment_urlstringpakollinen| 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…

  • payment_requiredbooleanpakollinen

    true while the request waits for a payment and no signer has been invited. The same condition as status: "pending_payment", as a flag you can branch on: a 201 does not mean the invitations went out. Read next_action for what to do.

    false

  • next_actionobjectpakollinen| null

    What has to happen before the request can proceed, or null when nothing does. Present exactly when payment_required is true. The hosted checkout behind url expires after a few minutes if nobody completes it; the URL itself does not, and opens a fresh checkout. To pay without a human, create the request with paymentMethod: "balance", "card" or "auto" instead.

  • type"pay"pakollinen

    Always pay for now. Switch on it; other types may be added.

  • urlstringpakollinen

    Same as payment_url.

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

  • messagestringpakollinen

    A human-readable explanation, in English, safe to log or show a developer.

  • signed_documents_urlstringpakollinen| 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_atstringpakollinen| 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

  • copy_emailsstring[]pakollinen

    Addresses that receive the completion email with the signed PDF without being signers, as given in copyEmails on creation — lowercased, de-duplicated, and without the sender or any signer. Empty when none were given.

    ["regnskap@example.no"]

  • return_urlstringpakollinen| null

    Where each signer is sent after the eID step, as given in returnUrl. null = our status page.

    https://app.example.com/contracts/123/signed

  • signing_eidsstring[]pakollinen| null

    The eIDs the signing page offers on this request, as given in signingEids or resolved from your account and the deployment default. null = no restriction: every eID the signing provider has enabled is offered.

    ["no_bankid"]

Virhevastaukset

  • 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, document already has an order, 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.fi/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,
    "copyEmails": [
      "ola@example.no"
    ],
    "returnUrl": "https://app.example.com/contracts/123/signed",
    "signingEids": [
      "no_bankid"
    ]
  }'

Vastaus

{
  "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",
      "verified_name": "Ola Nordmann",
      "verified_birth_date": "1990-01-31",
      "birth_date_lock": "1990-01-31",
      "sign_url": "https://esigner.no/sign/9f1c0f4e3d2b4a719f3a1c0e5b8d2a44",
      "sign_url_direct": "https://esigner.no/api/sign/9f1c0f4e3d2b4a719f3a1c0e5b8d2a44/start"
    }
  ],
  "payment": {
    "method": "balance",
    "amount_ore": 5800,
    "currency": "NOK"
  },
  "livemode": true,
  "created": "2026-07-13T10:12:00.000Z",
  "payment_url": "https://esigner.no/api/orders/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/pay",
  "payment_required": false,
  "next_action": {
    "type": "pay",
    "url": "https://esigner.no/api/orders/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/pay",
    "message": "string"
  },
  "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",
  "copy_emails": [
    "regnskap@example.no"
  ],
  "return_url": "https://app.example.com/contracts/123/signed",
  "signing_eids": [
    "no_bankid"
  ]
}
GET/api/v1/signature_requests/{id}

Retrieve a signature request

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

Vaatii oikeuden: orders:read

Polkuparametrit

  • iduuidpakollinen

    The id returned by POST /signature_requests.

Vastaus 200SignatureRequest

The signature request

Näytä kohteen SignatureRequest kentät
  • iduuidpakollinen

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

  • object"signature_request"pakollinen
  • statusstringpakollinen

    draft | pending_payment | in_progress | completed | expired | cancelled | signing_failed. in_progress means the signers have been invited; cancelled means it was stopped before anyone signed (see POST /signature_requests/{id}/cancel); signing_failed means the eID provider rejected the handoff and the request can be retried.

  • documentobjectpakollinen

    The document being signed.

  • iduuidpakollinen
  • filenamestringpakollinen

    avtale.pdf

  • signersobject[]pakollinen

    In invitation order.

  • iduuidpakollinen

    Stable id for this signer within the request.

  • namestringpakollinen| null

    Ola Nordmann

  • emailstringpakollinen| null

    null for a signer reached only by SMS.

    ola@example.no

  • statusstringpakollinen

    pending | notified | viewed | signed | declined | expired | removed (taken off the request by our support so it could complete)

  • signed_atstringpakollinen| null

    ISO 8601, null until this signer has signed.

    2026-07-13T10:24:00.000Z

  • verified_namestringpakollinen| null

    The signer's name as their eID (BankID, MitID, …) verified it — not the name you sent. Set when the whole request is completed: the eID provider releases the evidence only then. null before that, and for a signature whose evidence carried no name.

    Ola Nordmann

  • verified_birth_datestringpakollinen| null

    The signer's date of birth (YYYY-MM-DD) as their eID verified it. Set at completion, like verified_name. Always null for Swedish BankID, which does not issue a date of birth.

    1990-01-31

  • birth_date_lockstringpakollinen| null

    The date of birth this signer was locked to (signers[].birthDate at creation), or null.

    1990-01-31

  • sign_urlstringpakollinen| 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_directstringpakollinen| 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…

  • paymentobjectpakollinen

    How this request was paid for.

  • methodstringpakollinen

    subscription | balance | checkout | comped | test

  • amount_oreintegerpakollinen

    What this request cost in minor units (øre), VAT included. 0 on a subscription, comped or in test mode.

    5800

  • currencystringpakollinen

    NOK

  • livemodebooleanpakollinen

    false for signature requests created with an sk_test_ key.

  • createdstringpakollinen

    ISO 8601.

    2026-07-13T10:12:00.000Z

  • payment_urlstringpakollinen| 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…

  • payment_requiredbooleanpakollinen

    true while the request waits for a payment and no signer has been invited. The same condition as status: "pending_payment", as a flag you can branch on: a 201 does not mean the invitations went out. Read next_action for what to do.

    false

  • next_actionobjectpakollinen| null

    What has to happen before the request can proceed, or null when nothing does. Present exactly when payment_required is true. The hosted checkout behind url expires after a few minutes if nobody completes it; the URL itself does not, and opens a fresh checkout. To pay without a human, create the request with paymentMethod: "balance", "card" or "auto" instead.

  • type"pay"pakollinen

    Always pay for now. Switch on it; other types may be added.

  • urlstringpakollinen

    Same as payment_url.

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

  • messagestringpakollinen

    A human-readable explanation, in English, safe to log or show a developer.

  • signed_documents_urlstringpakollinen| 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_atstringpakollinen| 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

  • copy_emailsstring[]pakollinen

    Addresses that receive the completion email with the signed PDF without being signers, as given in copyEmails on creation — lowercased, de-duplicated, and without the sender or any signer. Empty when none were given.

    ["regnskap@example.no"]

  • return_urlstringpakollinen| null

    Where each signer is sent after the eID step, as given in returnUrl. null = our status page.

    https://app.example.com/contracts/123/signed

  • signing_eidsstring[]pakollinen| null

    The eIDs the signing page offers on this request, as given in signingEids or resolved from your account and the deployment default. null = no restriction: every eID the signing provider has enabled is offered.

    ["no_bankid"]

Virhevastaukset

  • 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.fi/api/v1/signature_requests/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11 \
  -H "Authorization: Bearer $ESIGNER_API_KEY"

Vastaus

{
  "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",
      "verified_name": "Ola Nordmann",
      "verified_birth_date": "1990-01-31",
      "birth_date_lock": "1990-01-31",
      "sign_url": "https://esigner.no/sign/9f1c0f4e3d2b4a719f3a1c0e5b8d2a44",
      "sign_url_direct": "https://esigner.no/api/sign/9f1c0f4e3d2b4a719f3a1c0e5b8d2a44/start"
    }
  ],
  "payment": {
    "method": "balance",
    "amount_ore": 5800,
    "currency": "NOK"
  },
  "livemode": true,
  "created": "2026-07-13T10:12:00.000Z",
  "payment_url": "https://esigner.no/api/orders/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/pay",
  "payment_required": false,
  "next_action": {
    "type": "pay",
    "url": "https://esigner.no/api/orders/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/pay",
    "message": "string"
  },
  "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",
  "copy_emails": [
    "regnskap@example.no"
  ],
  "return_url": "https://app.example.com/contracts/123/signed",
  "signing_eids": [
    "no_bankid"
  ]
}
POST/api/v1/signature_requests/{id}/cancel

Cancel a signature request

Stops a request that nobody has signed yet. The signing links stop working and the request ends in cancelled. - Subscription: every signature it drew goes back to the quota — including signers added later. - Pay-per-use: the payment is returned (refunded if captured, released if only authorised). - Test mode: nothing was spent, so nothing comes back. Once anyone has signed, it can no longer be cancelled (409 already_signed). A request that has expired cannot be cancelled, and the signatures it drew are not returned — cancel requests you know won't be signed rather than letting them lapse. Correcting a mistake: cancel, then create a new signature request. Pass the same documentId to reuse the upload, or upload the corrected file. It is counted once. Idempotent: cancelling a request that is already cancelled returns 200 with already_cancelled: true and returns nothing a second time.

Vaatii oikeuden: orders:write

Polkuparametrit

  • iduuidpakollinen

Otsakkeet

  • 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.

Pyyntö application/json

  • reasonstring

    Optional. Why it was cancelled — kept in the request's audit trail, never shown to the signers.

    Wrong signer email — resending

Vastaus 200object

The request is cancelled

  • signature_requestSignatureRequestpakollinen
  • iduuidpakollinen

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

  • object"signature_request"pakollinen
  • statusstringpakollinen

    draft | pending_payment | in_progress | completed | expired | cancelled | signing_failed. in_progress means the signers have been invited; cancelled means it was stopped before anyone signed (see POST /signature_requests/{id}/cancel); signing_failed means the eID provider rejected the handoff and the request can be retried.

  • documentobjectpakollinen

    The document being signed.

  • iduuidpakollinen
  • filenamestringpakollinen

    avtale.pdf

  • signersobject[]pakollinen

    In invitation order.

  • iduuidpakollinen

    Stable id for this signer within the request.

  • namestringpakollinen| null

    Ola Nordmann

  • emailstringpakollinen| null

    null for a signer reached only by SMS.

    ola@example.no

  • statusstringpakollinen

    pending | notified | viewed | signed | declined | expired | removed (taken off the request by our support so it could complete)

  • signed_atstringpakollinen| null

    ISO 8601, null until this signer has signed.

    2026-07-13T10:24:00.000Z

  • verified_namestringpakollinen| null

    The signer's name as their eID (BankID, MitID, …) verified it — not the name you sent. Set when the whole request is completed: the eID provider releases the evidence only then. null before that, and for a signature whose evidence carried no name.

    Ola Nordmann

  • verified_birth_datestringpakollinen| null

    The signer's date of birth (YYYY-MM-DD) as their eID verified it. Set at completion, like verified_name. Always null for Swedish BankID, which does not issue a date of birth.

    1990-01-31

  • birth_date_lockstringpakollinen| null

    The date of birth this signer was locked to (signers[].birthDate at creation), or null.

    1990-01-31

  • sign_urlstringpakollinen| 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_directstringpakollinen| 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…

  • paymentobjectpakollinen

    How this request was paid for.

  • methodstringpakollinen

    subscription | balance | checkout | comped | test

  • amount_oreintegerpakollinen

    What this request cost in minor units (øre), VAT included. 0 on a subscription, comped or in test mode.

    5800

  • currencystringpakollinen

    NOK

  • livemodebooleanpakollinen

    false for signature requests created with an sk_test_ key.

  • createdstringpakollinen

    ISO 8601.

    2026-07-13T10:12:00.000Z

  • payment_urlstringpakollinen| 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…

  • payment_requiredbooleanpakollinen

    true while the request waits for a payment and no signer has been invited. The same condition as status: "pending_payment", as a flag you can branch on: a 201 does not mean the invitations went out. Read next_action for what to do.

    false

  • next_actionobjectpakollinen| null

    What has to happen before the request can proceed, or null when nothing does. Present exactly when payment_required is true. The hosted checkout behind url expires after a few minutes if nobody completes it; the URL itself does not, and opens a fresh checkout. To pay without a human, create the request with paymentMethod: "balance", "card" or "auto" instead.

  • type"pay"pakollinen

    Always pay for now. Switch on it; other types may be added.

  • urlstringpakollinen

    Same as payment_url.

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

  • messagestringpakollinen

    A human-readable explanation, in English, safe to log or show a developer.

  • signed_documents_urlstringpakollinen| 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_atstringpakollinen| 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

  • copy_emailsstring[]pakollinen

    Addresses that receive the completion email with the signed PDF without being signers, as given in copyEmails on creation — lowercased, de-duplicated, and without the sender or any signer. Empty when none were given.

    ["regnskap@example.no"]

  • return_urlstringpakollinen| null

    Where each signer is sent after the eID step, as given in returnUrl. null = our status page.

    https://app.example.com/contracts/123/signed

  • signing_eidsstring[]pakollinen| null

    The eIDs the signing page offers on this request, as given in signingEids or resolved from your account and the deployment default. null = no restriction: every eID the signing provider has enabled is offered.

    ["no_bankid"]

  • already_cancelledbooleanpakollinen

    True when an earlier call had already cancelled it. Nothing was returned by this call.

  • released_signaturesintegerpakollinen

    Signatures returned to the subscription's quota by this call.

    2

  • refunded_oreintegerpakollinen

    Pay-per-use amount no longer charged by this call, in minor units (øre), VAT included.

    0

  • returned_to_balance_oreintegerpakollinen

    Amount put back on the prepaid balance by this call, in minor units (øre).

    0

Virhevastaukset

  • 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.
  • 409already_signed — at least one signer has signed. not_cancellable — the request is no longer open (completed, expired, failed, or locked by an administrator).
  • 429too_many_requests — rate limited. Retry after the Retry-After header.
  • 502payment_reversal_failed — the payment could not be returned, so the request was left open and nothing changed. Retry.
curl https://esigner.fi/api/v1/signature_requests/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/cancel \
  -X POST \
  -H "Authorization: Bearer $ESIGNER_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Wrong signer email — resending"
  }'

Vastaus

{
  "signature_request": {
    "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",
        "verified_name": "Ola Nordmann",
        "verified_birth_date": "1990-01-31",
        "birth_date_lock": "1990-01-31",
        "sign_url": "https://esigner.no/sign/9f1c0f4e3d2b4a719f3a1c0e5b8d2a44",
        "sign_url_direct": "https://esigner.no/api/sign/9f1c0f4e3d2b4a719f3a1c0e5b8d2a44/start"
      }
    ],
    "payment": {
      "method": "balance",
      "amount_ore": 5800,
      "currency": "NOK"
    },
    "livemode": true,
    "created": "2026-07-13T10:12:00.000Z",
    "payment_url": "https://esigner.no/api/orders/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/pay",
    "payment_required": false,
    "next_action": {
      "type": "pay",
      "url": "https://esigner.no/api/orders/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/pay",
      "message": "string"
    },
    "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",
    "copy_emails": [
      "regnskap@example.no"
    ],
    "return_url": "https://app.example.com/contracts/123/signed",
    "signing_eids": [
      "no_bankid"
    ]
  },
  "already_cancelled": true,
  "released_signatures": 2,
  "refunded_ore": 0,
  "returned_to_balance_ore": 0
}
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.

Vaatii oikeuden: documents:readorders:read(yksi niistä riittää)

Polkuparametrit

  • iduuidpakollinen

Vastaus 200object

The signed PDFs

  • dataSignedDocument[]pakollinen
  • positionintegerpakollinen

    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.

  • filenamestringpakollinen

    signert-avtale.pdf

  • size_bytesintegerpakollinen| null

    null if the size could not be read.

    214016

  • sha256stringpakollinen| null

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

    9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822…

  • urlstringpakollinen

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

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

  • download_expires_atstringpakollinen| null

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

Virhevastaukset

  • 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.fi/api/v1/signature_requests/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/signed_documents \
  -H "Authorization: Bearer $ESIGNER_API_KEY"

Vastaus

{
  "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.

Vaatii oikeuden: documents:readorders:read(yksi niistä riittää)

Polkuparametrit

  • iduuidpakollinen
  • positioninteger

    From the list endpoint. Always 0 in merged mode.

Kyselyparametrit

  • download"0" | "1"

    1 sets Content-Disposition: attachment instead of inline.

Vastaus 200

The signed PDF

Vapaamuotoinen JSON-objekti — sisältö vaihtelee.

Virhevastaukset

  • 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.fi/api/v1/signature_requests/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/signed_documents/0 \
  -H "Authorization: Bearer $ESIGNER_API_KEY"

Saldo#

GET/api/v1/balance

Retrieve the prepaid balance

What paymentMethod: "balance" draws from. Each top-up is spendable for 12 months; lots lists what is still spendable and when each part expires, oldest first — the order it is drawn in. A test-mode key always sees zero: test requests never touch the balance.

Vaatii oikeuden: billing:read

Vastaus 200object

The balance

  • object"balance"pakollinen
  • livemodebooleanpakollinen
  • balance_oreintegerpakollinen

    Spendable now, in minor units (øre).

    120000

  • currencystringpakollinen

    NOK

  • lotsobject[]pakollinen
  • remaining_oreintegerpakollinen
  • expires_atstringpakollinen| null

    ISO 8601.

Virhevastaukset

  • 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.fi/api/v1/balance \
  -H "Authorization: Bearer $ESIGNER_API_KEY"

Vastaus

{
  "object": "balance",
  "livemode": true,
  "balance_ore": 120000,
  "currency": "NOK",
  "lots": [
    {
      "remaining_ore": 0,
      "expires_at": "string"
    }
  ]
}
GET/api/v1/balance/transactions

List balance transactions

Every movement on the prepaid balance, newest first: top-ups, draws for signature requests, money returned by a cancel, refunds and expiry. amount_ore is signed.

Vaatii oikeuden: billing:read

Kyselyparametrit

  • limitinteger
  • cursorstring

    The previous page's next_cursor.

Vastaus 200object

A page of transactions

  • dataobject[]pakollinen
  • iduuidpakollinen
  • typestringpakollinen

    topup | draw | draw_return | withdrawal | withdrawal_reversal | refund | refund_reversal | expiry

  • amount_oreintegerpakollinen
  • balance_after_oreintegerpakollinen
  • signature_request_iduuidpakollinen| null
  • createdstringpakollinen
  • next_cursorstringpakollinen| null

Virhevastaukset

  • 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.fi/api/v1/balance/transactions?limit=25 \
  -H "Authorization: Bearer $ESIGNER_API_KEY"

Vastaus

{
  "data": [
    {
      "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
      "type": "string",
      "amount_ore": 0,
      "balance_after_ore": 0,
      "signature_request_id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
      "created": "string"
    }
  ],
  "next_cursor": "string"
}

Data processing agreements#

GET/api/v1/data_processing_agreements

List data processing agreements

The agreements this account has requested, newest first, with where each one is.

Vaatii oikeuden: account:read

Vastaus 200object

The requests

  • dataDataProcessingAgreement[]pakollinen
  • iduuidpakollinen
  • object"data_processing_agreement"pakollinen
  • status"received" | "sent_for_signing" | "signed" | "closed"pakollinen

    received — we have it and are reviewing it. sent_for_signing — the signing invitation is out to the contact person. signed — everyone has signed; the signed agreement went to the contact person by email. closed — withdrawn without a signature.

  • companyobjectpakollinen
  • namestringpakollinen
  • org_numberstringpakollinen
  • contactobjectpakollinen| null
  • namestringpakollinen
  • emailstringpakollinen
  • languagestringpakollinen| null

    nb-NO

  • requested_atstringpakollinen

    ISO 8601.

  • sent_for_signing_atstringpakollinen| null
  • signed_atstringpakollinen| null

Virhevastaukset

  • 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.fi/api/v1/data_processing_agreements \
  -H "Authorization: Bearer $ESIGNER_API_KEY"

Vastaus

{
  "data": [
    {
      "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
      "object": "data_processing_agreement",
      "status": "received",
      "company": {
        "name": "string",
        "org_number": "string"
      },
      "contact": {
        "name": "string",
        "email": "string"
      },
      "language": "nb-NO",
      "requested_at": "string",
      "sent_for_signing_at": "string",
      "signed_at": "string"
    }
  ]
}
POST/api/v1/data_processing_agreements

Request a data processing agreement

Asks us for our data processing agreement (GDPR art. 28), with your company as controller and us as processor. Send who you are and who should sign; we prepare the agreement with those details, review it and send it for signing to contact.email, usually within one working day. When everyone has signed, the contact person gets the signed agreement by email. Poll GET /data_processing_agreements for the status. Only the fields below are yours to fill; the rest of the text is our standard agreement. A key that belongs to a company account may leave out company — the account's own name and org number are used. One open request per organisation: while one is not yet signed, asking again returns it (200) instead of creating a second. Not available with a test key.

Vaatii oikeuden: account:write

Otsakkeet

  • 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.

Pyyntö application/json

  • companyobject

    The controller. Optional for a company account's key.

  • namestringpakollinen

    Eksempel AS

  • org_numberstringpakollinen

    912 345 678

  • contactobjectpakollinen

    The person who signs for the controller and receives the signed agreement.

  • namestringpakollinen

    Kari Nordmann

  • emailemailpakollinen

    kari@eksempel.no

  • languagestring (7 values)

    The agreement's language. Default: this market's.

  • instructionsstring

    Optional documented instructions from the controller (clause on instructions). Plain text.

Vastaus 200DataProcessingAgreement

A request for this organisation was already open; it is returned unchanged

  • iduuidpakollinen
  • object"data_processing_agreement"pakollinen
  • status"received" | "sent_for_signing" | "signed" | "closed"pakollinen

    received — we have it and are reviewing it. sent_for_signing — the signing invitation is out to the contact person. signed — everyone has signed; the signed agreement went to the contact person by email. closed — withdrawn without a signature.

  • companyobjectpakollinen
  • namestringpakollinen
  • org_numberstringpakollinen
  • contactobjectpakollinen| null
  • namestringpakollinen
  • emailstringpakollinen
  • languagestringpakollinen| null

    nb-NO

  • requested_atstringpakollinen

    ISO 8601.

  • sent_for_signing_atstringpakollinen| null
  • signed_atstringpakollinen| null

Virhevastaukset

  • 400validation — see issues. company_required — a personal account's key must send company.
  • 401unauthorized — no API key, or it has been revoked.
  • 403test_mode — data processing agreements are real agreements; use a live key.
  • 429too_many_requests — rate limited. Retry after the Retry-After header.
curl https://esigner.fi/api/v1/data_processing_agreements \
  -X POST \
  -H "Authorization: Bearer $ESIGNER_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "contact": {
      "name": "Kari Nordmann",
      "email": "kari@eksempel.no"
    }
  }'

Vastaus

{
  "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
  "object": "data_processing_agreement",
  "status": "received",
  "company": {
    "name": "string",
    "org_number": "string"
  },
  "contact": {
    "name": "string",
    "email": "string"
  },
  "language": "nb-NO",
  "requested_at": "string",
  "sent_for_signing_at": "string",
  "signed_at": "string"
}

Tapahtumat#

GET/api/v1/events

List events

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

Vaatii oikeuden: webhooks:readorders:read(yksi niistä riittää)

Kyselyparametrit

  • 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.

Vastaus 200object

A page of events

  • dataEvent[]pakollinen
  • iduuidpakollinen
  • object"event"pakollinen
  • typestringpakollinen

    document.completed | document.signing_failed | order.captured

  • livemodebooleanpakollinen

    false for activity produced by an sk_test_ key.

  • createdstringpakollinen

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

    2026-07-13T10:24:00.000Z

  • dataobjectpakollinen

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

  • next_cursorstringpakollinen| null

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

Virhevastaukset

  • 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.fi/api/v1/events?limit=25&type=document.completed \
  -H "Authorization: Bearer $ESIGNER_API_KEY"

Vastaus

{
  "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"
}

Täysi koneluettava määrittely (OpenAPI 3.1): /api/v1/openapi.json

Virheet#

Kaikki virheet vastaavat JSON-muodossa ja vakaalla koodilla kentässä error. Alla olevat koodit ovat ne, joita voit kohdata — rakenna logiikka niiden varaan, älä tekstin.

TilaKoodiTarkoittaa
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 too few signatures left for this request. remaining says how many there are. Every plan has a hard limit — Team's is a per-seat monthly pool shared by the whole company.
400payment_method_conflictpaymentMethod contradicts useSubscription or a free-signing link. Send one or the other.
409no_payment_methodpaymentMethod: "auto" but the account has no subscription or payment method to charge.
402insufficient_balanceThe prepaid balance doesn't cover the request. balanceOre and requiredOre say by how much. Nothing was created.
402payment_method_declinedThe saved card was declined. declineCode carries the bank's reason. Nothing was created.
402payment_method_action_requiredThe bank requires the cardholder to be present. Save the card again under /konto/betaling.
503payment_unconfirmedThe card charge's outcome could not be confirmed. orderId is returned — read it before retrying, so a held payment isn't doubled.
400invalid_return_urlreturnUrl is not an absolute https URL (http only with a test key), or it carries credentials. message says which.
400invalid_birthdateA signers[].birthDate is not a real date in the past in YYYY-MM-DD. message names the signer. Nothing was created.
400birthdate_lock_unsupportedsigners[].birthDate was sent in a market whose eID cannot be checked against a date of birth (currently Norway, Denmark, Finland and Germany can). Nothing was created.
400signing_eid_unavailablesigningEids names an eID this deployment does not offer. unavailable lists them and available lists what you may use. Nothing was created.
409payment_method_unavailableThe named paymentMethod is not offered on this deployment.
409already_signedCancel was refused: at least one signer has already signed.
409not_cancellableCancel was refused: the request is no longer open (completed, expired, failed, or locked).
502payment_reversal_failedCancel could not return the pay-per-use payment, so the request was left open. Retry.
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.
409signer_roster_fullThe request has room for no more signers: someone has already signed, and the signing provider sizes the signature pages before the first signature. Create a new request for the extra signers.
410goneThe files behind this signature request have been deleted.
410revokedThis document is locked — by its sender or by an administrator.
409idempotency_key_in_progressThe first request with this Idempotency-Key hasn't finished. Retry.
413—A 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.
503—Maintenance. New uploads and signature requests are paused; existing ones keep running.

Virheet

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

Webhookit#

Lähetämme tapahtumat POST-kutsuina osoitteeseesi ja allekirjoitamme jokaisen toimituksen otsakkeella Esigner-Signature: t=<unix>,v1=<hmac>. HMAC lasketaan arvosta <t>.<raaka body>, jotta siepattua viestiä ei voi toistaa toisella aikaleimalla. Varmenna aina ennen kuin luotat sisältöön.

Jos toimitus epäonnistuu, yritämme uudelleen kasvavin välein (1 min → 6 t). Jos päätepiste ei vastaa pitkään aikaan, poistamme sen käytöstä ja ilmoitamme sähköpostitse — tapahtumat eivät katoa ja voit lähettää ne uudelleen.

Kuori

  • iduuidpakollinen

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

  • typestringpakollinen

    Also sent as the Esigner-Event-Type header.

    document.completed

  • livemodebooleanpakollinen
  • createdintegerpakollinen

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

    1784000000

  • dataobjectpakollinen

    Per event type — see below.

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

// rawBody: the body exactly as received, before JSON.parse
function verify(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
  const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= toleranceSec;
  return fresh && parts.v1?.length === expected.length
    && timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}

document.completed#

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

data

  • documentIduuidpakollinen
  • orderIduuidpakollinen

    The same id as SignatureRequest.id.

  • filenamestringpakollinen

    avtale.pdf

  • mode"merged" | "separate"pakollinen

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

  • downloadExpiresAtstringpakollinen

    ISO 8601. Fetch the signed PDF before this.

    2026-08-13T10:24:00.000Z

  • signersobject[]pakollinen
  • namestringpakollinen| null

    Ola Nordmann

  • emailstringpakollinen| null

    ola@example.no

  • signedAtstringpakollinen| null

    ISO 8601.

    2026-07-13T10:24:00.000Z

  • verifiedNamestringpakollinen| null

    The name as the signer's eID verified it.

    Ola Nordmann

  • verifiedBirthDatestringpakollinen| null

    YYYY-MM-DD, as the signer's eID verified it. Null for Swedish BankID, which issues none.

    1990-01-31

  • birthDateLockstringpakollinen| null

    The date of birth the signer was locked to, or null.

    1990-01-31

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",
        "verifiedName": "Ola Nordmann",
        "verifiedBirthDate": "1990-01-31",
        "birthDateLock": "1990-01-31"
      }
    ]
  }
}

document.signing_failed#

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

data

  • documentIduuidpakollinen
  • orderIduuidpakollinen
  • messagestringpakollinen

    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"
  }
}

document.cancelled#

The request was cancelled before anyone signed — over the API or from the account. Its signing links no longer work.

data

  • documentIduuidpakollinen
  • orderIduuidpakollinen

    The same id as SignatureRequest.id.

  • releasedSignaturesintegerpakollinen

    Signatures returned to the subscription's quota. 0 on pay-per-use and in test mode.

    3

  • refundedOreintegerpakollinen

    Pay-per-use amount no longer charged, in minor units (øre), VAT included. 0 on a subscription.

  • returnedToBalanceOreintegerpakollinen

    Amount put back on the prepaid balance, in minor units (øre). 0 unless the request was paid from balance.

document.cancelled

{
  "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
  "type": "document.cancelled",
  "livemode": true,
  "created": 1784000000,
  "data": {
    "documentId": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "orderId": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "releasedSignatures": 3,
    "refundedOre": 0,
    "returnedToBalanceOre": 0
  }
}

signer.signed#

One signer has signed. Sent once per signer, before document.completed for the last one — follow a request with several parties as it goes.

data

  • orderIduuidpakollinen

    The same id as SignatureRequest.id.

  • documentIduuidpakollinen
  • signerobjectpakollinen
  • iduuidpakollinen

    The same id as SignatureRequest.signers[].id.

  • namestringpakollinen| null

    Ola Nordmann

  • emailstringpakollinen| null

    ola@example.no

  • phonestringpakollinen| null

    +4791234567

  • positionintegerpakollinen

    0-based order in the request's signer list.

  • signedAtstringpakollinen

    ISO 8601.

    2026-07-13T10:24:00.000Z

  • signersSignedintegerpakollinen

    How many of the request's signers have signed, this one included.

    1

  • signersTotalintegerpakollinen

    3

signer.signed

{
  "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
  "type": "signer.signed",
  "livemode": true,
  "created": 1784000000,
  "data": {
    "orderId": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "documentId": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "signer": {
      "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
      "name": "Ola Nordmann",
      "email": "ola@example.no",
      "phone": "+4791234567",
      "position": 0
    },
    "signedAt": "2026-07-13T10:24:00.000Z",
    "signersSigned": 1,
    "signersTotal": 3
  }
}

signer.declined#

One signer refused to sign. The request stays open; cancel it or send a new one.

data

  • orderIduuidpakollinen

    The same id as SignatureRequest.id.

  • documentIduuidpakollinen
  • signerobjectpakollinen
  • iduuidpakollinen

    The same id as SignatureRequest.signers[].id.

  • namestringpakollinen| null

    Ola Nordmann

  • emailstringpakollinen| null

    ola@example.no

  • phonestringpakollinen| null

    +4791234567

  • positionintegerpakollinen

    0-based order in the request's signer list.

  • reasonstringpakollinen| null

    What the signer wrote, when the signing page asked.

  • signersSignedintegerpakollinen
  • signersTotalintegerpakollinen

signer.declined

{
  "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
  "type": "signer.declined",
  "livemode": true,
  "created": 1784000000,
  "data": {
    "orderId": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "documentId": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
    "signer": {
      "id": "3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11",
      "name": "Ola Nordmann",
      "email": "ola@example.no",
      "phone": "+4791234567",
      "position": 0
    },
    "reason": "string",
    "signersSigned": 0,
    "signersTotal": 0
  }
}

order.captured#

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

data

  • orderIduuidpakollinen
  • documentIduuidpakollinen
  • amountOreintegerpakollinen

    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
  }
}

Tekoälyavustajat (MCP)#

Jos haluat tekoälyavustajan luovan allekirjoitukset, meillä on MCP-palvelin: esigner.fi on hyväksytty sovellus ChatGPT:ssä, ja osoitteen voi lisätä omana yhteytenä Claudeen ja muihin MCP-asiakkaisiin. Katso, miten yhdistät.

Usein kysytyt kysymykset#

Mitä API maksaa?

API:n käyttö on maksutonta. Allekirjoitukset maksetaan kuten verkkosivulla: yksi allekirjoitus allekirjoittajaa kohden, tilauksen kiintiöstä tai allekirjoituskohtaisesti. Testiavaimet ovat maksuttomia eivätkä kuluta kiintiötä.

Onko testiympäristöä?

On. Avaimet, jotka alkavat sk_test_, allekirjoittavat testiympäristössä testitunnisteilla — sama API, ei maksua eikä oikeudellisesti sitovaa allekirjoitusta.

Mistä tiedän, että webhook tulee esigner.fi-palvelusta?

Jokaisessa toimituksessa on otsake Esigner-Signature, jossa on HMAC-SHA256 aikaleimasta ja raa'asta bodysta päätepisteen salaisuudella laskettuna. Katso esimerkit Node.js:llä, PHP:llä ja Pythonilla kohdasta Webhookit.

Voiko allekirjoittaja palata sivulleni allekirjoituksen jälkeen?

Voi. Lähetä returnUrl pyyntöä luodessasi, niin jokainen allekirjoittaja ohjataan sinne tunnistautumisen jälkeen — signature_request_id- ja signer_id-parametrien kanssa.

Voinko rajata tarjottavat tunnistusvälineet?

Voit, signingEids-kentällä, esimerkiksi ["fi_ftn"] vain suomalaisille pankkitunnuksille. Tai tallenna valinta tilille tai yritykselle, jolloin se koskee kaikkia pyyntöjä.

Jäitkö jumiin? Ota yhteyttä — vastaamme mielellämme integraatiokysymyksiin.