{"openapi":"3.1.0","info":{"title":"esigner API","version":"1.0.0","description":"Create BankID/eID signature requests and receive signed PDFs.\n\nAuthenticate with an API key: `Authorization: Bearer sk_live_…`. Test-mode keys (`sk_test_…`) run against the provider sandbox and never consume quota or move money.\n\nPOSTs accept an `Idempotency-Key` header — retrying with the same key replays the original response rather than creating a second signature request.\n\nEvents are delivered to webhook endpoints, signed with `Esigner-Signature: t=<unix>,v1=<hmac>`."},"servers":[{"url":"/api/v1"}],"x-error-codes":[{"status":400,"code":"validation","meaning":"The request body failed schema validation. `issues` carries the field-level detail."},{"status":401,"code":"unauthorized","meaning":"Missing, malformed or revoked API key."},{"status":403,"code":"insufficient_scope","meaning":"The key is valid but lacks the scope the endpoint declares. `WWW-Authenticate` names what was needed."},{"status":403,"code":"cross_owner_document","meaning":"The documentId belongs to a different account than the key."},{"status":404,"code":"not_found","meaning":"No such resource — or it belongs to another account. Deliberately indistinguishable."},{"status":409,"code":"quota_exceeded","meaning":"The subscription has no signatures left. `remaining` says how many there were."},{"status":409,"code":"no_subscription","meaning":"`useSubscription: true` but the account has no active subscription."},{"status":409,"code":"topup_in_progress","meaning":"An automatic top-up is already running. Retry shortly."},{"status":409,"code":"topup_cooldown","meaning":"A renewal failed recently; automatic top-up is on a 24h cool-down."},{"status":409,"code":"idempotency_key_in_progress","meaning":"The first request with this Idempotency-Key hasn't finished. Retry."},{"status":413,"code":"—","meaning":"A single uploaded file exceeded the size limit."},{"status":422,"code":"idempotency_key_reused","meaning":"The same Idempotency-Key was used with a different request body. That's a caller bug, so we surface it."},{"status":429,"code":"too_many_requests","meaning":"Rate limited. `Retry-After` says how long to wait."},{"status":502,"code":"signing_failed","meaning":"The eID provider rejected the handoff. The document lands in `signing_failed` and can be retried."},{"status":503,"code":"—","meaning":"Maintenance. New uploads and signature requests are paused; existing ones keep running."}],"x-webhook-events":[{"type":"document.completed","description":"Every signer has signed. The signed PDF is available until `downloadExpiresAt`.","dataSchema":"DocumentCompletedData"},{"type":"document.signing_failed","description":"The eID provider rejected the handoff. The sender can retry the document.","dataSchema":"DocumentSigningFailedData"},{"type":"order.captured","description":"A pay-per-use payment was captured. Not emitted for subscription-covered requests.","dataSchema":"OrderCapturedData"}],"x-webhook-envelope":"WebhookEvent","components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your API key, e.g. `Authorization: Bearer sk_live_…`. Create one at /konto."}},"schemas":{"WebhookEvent":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Also sent as the `Esigner-Event-Id` header. Dedupe on it — a retry reuses the id."},"type":{"type":"string","description":"Also sent as the `Esigner-Event-Type` header.","example":"document.completed"},"livemode":{"type":"boolean"},"created":{"type":"integer","description":"Unix seconds. Note: GET /events returns this field as an ISO string instead.","example":1784000000},"data":{"type":"object","additionalProperties":{"nullable":true},"description":"Per event type — see below."}},"required":["id","type","livemode","created","data"]},"DocumentCompletedData":{"type":"object","properties":{"documentId":{"type":"string","format":"uuid"},"orderId":{"type":"string","format":"uuid","description":"The same id as `SignatureRequest.id`."},"filename":{"type":"string","example":"avtale.pdf"},"mode":{"type":"string","enum":["merged","separate"],"description":"Whether the uploads were signed as one merged PDF or individually."},"downloadExpiresAt":{"type":"string","description":"ISO 8601. Fetch the signed PDF before this.","example":"2026-08-13T10:24:00.000Z"},"signers":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","nullable":true,"example":"Ola Nordmann"},"email":{"type":"string","nullable":true,"example":"ola@example.no"},"signedAt":{"type":"string","nullable":true,"description":"ISO 8601.","example":"2026-07-13T10:24:00.000Z"}},"required":["name","email","signedAt"]}}},"required":["documentId","orderId","filename","mode","downloadExpiresAt","signers"]},"DocumentSigningFailedData":{"type":"object","properties":{"documentId":{"type":"string","format":"uuid"},"orderId":{"type":"string","format":"uuid"},"message":{"type":"string","description":"Why the eID provider rejected the handoff.","example":"signature order rejected"}},"required":["documentId","orderId","message"]},"OrderCapturedData":{"type":"object","properties":{"orderId":{"type":"string","format":"uuid"},"documentId":{"type":"string","format":"uuid"},"amountOre":{"type":"integer","description":"Captured amount in minor units (øre), VAT included.","example":3625}},"required":["orderId","documentId","amountOre"]},"SignatureRequest":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"The signature request id — pass it to GET /signature_requests/{id}."},"object":{"type":"string","enum":["signature_request"]},"status":{"type":"string","description":"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.","example":"in_progress"},"document":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"filename":{"type":"string","example":"avtale.pdf"}},"required":["id","filename"],"description":"The document being signed."},"signers":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","nullable":true,"example":"Ola Nordmann"},"email":{"type":"string","nullable":true,"description":"null for a signer reached only by SMS.","example":"ola@example.no"},"status":{"type":"string","description":"pending | notified | viewed | signed | declined | expired","example":"signed"},"signed_at":{"type":"string","nullable":true,"description":"ISO 8601, null until this signer has signed.","example":"2026-07-13T10:24:00.000Z"}},"required":["name","email","status","signed_at"]},"description":"In invitation order."},"livemode":{"type":"boolean","description":"false for signature requests created with an sk_test_ key."},"created":{"type":"string","description":"ISO 8601.","example":"2026-07-13T10:12:00.000Z"},"payment_url":{"type":"string","nullable":true,"description":"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.","example":"https://esigner.no/api/orders/3f7c1e42-9b0a-4d7e-8c31-2a5f6b9d0e11/pay"}},"required":["id","object","status","document","signers","livemode","created","payment_url"]},"Error":{"type":"object","properties":{"error":{"type":"string","description":"A stable machine-readable code — see the error table. The upload endpoint's validation failures carry a human-readable sentence here instead.","example":"insufficient_scope"},"detail":{"type":"string","description":"Human-readable elaboration, when there is one."}},"required":["error"]},"Event":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"object":{"type":"string","enum":["event"]},"type":{"type":"string","description":"document.completed | document.signing_failed | order.captured","example":"document.completed"},"livemode":{"type":"boolean","description":"false for activity produced by an sk_test_ key."},"created":{"type":"string","description":"ISO 8601. Also the pagination cursor — pass it as `cursor` to get the next page.","example":"2026-07-13T10:24:00.000Z"},"data":{"type":"object","additionalProperties":{"nullable":true},"description":"The same payload the webhook delivery carries for this event type."}},"required":["id","object","type","livemode","created","data"]},"Document":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Pass this as `documentId` when you create the signature request."},"filename":{"type":"string","description":"Name of the first uploaded file.","example":"avtale.pdf"},"sizeBytes":{"type":"integer","description":"Total size across every uploaded file.","example":184320},"attachments":{"type":"array","items":{"type":"object","properties":{"position":{"type":"integer","description":"0-based. Also the order the PDFs are merged in."},"filename":{"type":"string","example":"vedlegg.pdf"},"sizeBytes":{"type":"integer","example":92160}},"required":["position","filename","sizeBytes"]},"description":"One entry per uploaded PDF."}},"required":["id","filename","sizeBytes","attachments"]}},"parameters":{}},"paths":{"/signature_requests/{id}":{"get":{"summary":"Retrieve a signature request","description":"Poll this for status, or subscribe to `document.completed` and skip the polling.","tags":["Signature requests"],"security":[{"bearerAuth":[]}],"x-scopes":["orders:read"],"parameters":[{"schema":{"type":"string","format":"uuid","description":"The id returned by POST /signature_requests."},"required":true,"description":"The id returned by POST /signature_requests.","name":"id","in":"path"}],"responses":{"200":{"description":"The signature request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignatureRequest"}}}},"401":{"description":"`unauthorized` — no API key, or it has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`insufficient_scope` — the key is valid but was not granted the scope this endpoint declares.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — no such signature request, or it belongs to another account. The two are deliberately indistinguishable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`too_many_requests` — rate limited. Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/events":{"get":{"summary":"List events","description":"The same events delivered to your webhook endpoints, newest first. Useful for backfilling after downtime.","tags":["Events"],"security":[{"bearerAuth":[]}],"x-scopes":["webhooks:read","orders:read"],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":100,"description":"Page size. Defaults to 25.","example":25},"required":false,"description":"Page size. Defaults to 25.","name":"limit","in":"query"},{"schema":{"type":"string","description":"Pass the previous page's `next_cursor` to continue. Returns events strictly older than it."},"required":false,"description":"Pass the previous page's `next_cursor` to continue. Returns events strictly older than it.","name":"cursor","in":"query"},{"schema":{"type":"string","description":"Return only this event type.","example":"document.completed"},"required":false,"description":"Return only this event type.","name":"type","in":"query"}],"responses":{"200":{"description":"A page of events","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"next_cursor":{"type":"string","nullable":true,"description":"null on the last page. Otherwise pass it back as `cursor`."}},"required":["data","next_cursor"]}}}},"401":{"description":"`unauthorized` — no API key, or it has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`insufficient_scope` — the key is valid but was not granted the scope this endpoint declares.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`too_many_requests` — rate limited. Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/documents":{"post":{"summary":"Upload the PDF(s) to be signed","description":"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.","tags":["Documents"],"security":[{"bearerAuth":[]}],"x-scopes":["documents:write"],"parameters":[{"schema":{"type":"string","maxLength":255,"description":"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.","example":"9f1c0f4e-3d2b-4a71-9f3a-1c0e5b8d2a44"},"required":false,"description":"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.","name":"Idempotency-Key","in":"header"}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"ownerEmail":{"type":"string","description":"Where the receipt and the signed PDF are sent. Must contain an @.","example":"post@firma.no"},"files":{"type":"string","format":"binary","description":"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)."},"templateSlug":{"type":"string","description":"Optional. The /maler template the document was filled from — echoed on the completion surfaces.","example":"leieavtale"}},"required":["ownerEmail","files"]}}}},"responses":{"200":{"description":"The uploaded document","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Document"}}}},"400":{"description":"The 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`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — no API key, or it has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`insufficient_scope` — the key is valid but was not granted the scope this endpoint declares.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_key_in_progress` — the first request with this Idempotency-Key is still running. Retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"A file exceeded the per-file size limit, or converting it to PDF produced a file that does.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A Word file could not be converted to PDF — it is damaged or password-protected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`too_many_requests` — rate limited. Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Maintenance. New uploads and signature requests are paused; existing ones keep running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/signature_requests":{"post":{"summary":"Create a signature request","description":"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.\n\nPass `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.","tags":["Signature requests"],"security":[{"bearerAuth":[]}],"x-scopes":["orders:write"],"parameters":[{"schema":{"type":"string","maxLength":255,"description":"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.","example":"9f1c0f4e-3d2b-4a71-9f3a-1c0e5b8d2a44"},"required":false,"description":"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.","name":"Idempotency-Key","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"documentId":{"type":"string","format":"uuid","description":"From POST /documents. Must belong to the same account as the key."},"signers":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Shown in the invitation. The legal identity comes from the eID, not from this.","example":"Ola Nordmann"},"email":{"type":"string","example":"ola@example.no"},"phone":{"type":"string","description":"E.164 or a local number, e.g. +47 912 34 567. Only honoured while SMS delivery is enabled for the account."}},"description":"Each signer needs at least one of `email` or `phone`."},"minItems":1,"maxItems":20,"description":"1–20 signers, invited in the order given."},"useSubscription":{"type":"boolean","description":"Draw the signature from the account's quota instead of a payment. 409 `quota_exceeded` when the quota is spent.","example":true},"mode":{"type":"string","enum":["merged","separate"],"description":"Sign the uploads as one merged PDF (default) or as separate documents."},"deadlineDays":{"type":"integer","minimum":1,"maximum":90,"description":"Days until the request expires. Defaults to the account's configured deadline.","example":14},"password":{"type":"string","minLength":4,"maxLength":64,"description":"Shared secret a signer must enter before the document is shown."},"includePasswordInEmail":{"type":"boolean","description":"Whether that password is included in the invitation email. Defaults to true."},"smsDelivery":{"type":"boolean","description":"Also text the signing link. Free on subscription-covered requests, a surcharge on pay-per-use."}},"required":["documentId","signers"]}}}},"responses":{"201":{"description":"The created signature request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignatureRequest"}}}},"400":{"description":"`validation` — the request body failed schema validation. `issues` carries the field-level detail.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — no API key, or it has been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`insufficient_scope`, or `cross_owner_document` — the document belongs to another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — no such document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`quota_exceeded`, `no_subscription`, `topup_in_progress`, `topup_cooldown`, or `idempotency_key_in_progress`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`idempotency_key_reused` — the same Idempotency-Key was already used with a different request body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`too_many_requests` — rate limited. Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"`signing_failed` — the eID provider rejected the handoff. The document is left in `signing_failed` and the request can be retried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Maintenance. New uploads and signature requests are paused; existing ones keep running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}