Menu

Signing requests

BankID signing of archived PDFs: send a document to its signers (one link each, mailed when they have an address), read who has signed, remind a pending signer, cancel. Once everyone has signed, the signed PDF and the evidence package are archive documents. No endpoint signs: each signer signs in person with BankID.

Endpoints


GET /api/v1/companies/:companyId/signing-requests

signing-requests.list · scope documents:read

List the company's BankID signing requests, newest first, with every signer's status.

Filter by status (open, completed, declined, cancelled) or document_id (the signed PDF, its signed copy or its evidence package). An open request past its expiry reads as state expired.

Use when: You need to know which documents wait for signatures, or find the request behind a document.

Don't use for: One request in detail (GET /signing-requests/{id}).

Pitfalls

  • status filters the stored status; an expired request still has status open and reads state expired.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no

Query parameters

NameTypeRequiredDescription
status"open" | "completed" | "declined" | "cancelled"noOnly requests with this status.
document_idstringnoOnly requests covering this document.
limitnumbernoAt most this many (default 50).

Response fields

NameTypeDescription
signing_requests[].signing_request_idstring
signing_requests[].titlestring
signing_requests[].messagestring | null
signing_requests[].state"open" | "completed" | "declined" | "cancelled" | "expired"
signing_requests[].document_idstringThe PDF that is signed (an archive document).
signing_requests[].document_sha256stringIts SHA-256 when the request was made; every BankID signature carries it.
signing_requests[].annual_report_version_idstring | null
signing_requests[].created_bystring
signing_requests[].created_via"app" | "api" | "mcp"
signing_requests[].created_atstring
signing_requests[].expires_atstring
signing_requests[].completed_atstring | null
signing_requests[].declined_atstring | null
signing_requests[].cancelled_atstring | null
signing_requests[].signed_document_idstring | nullOnce completed: the signed PDF with its Signeringsbevis page (GET /documents/{id}/download).
signing_requests[].evidence_document_idstring | nullOnce completed: Bevispaket.zip, the original, each BankID signature and OCSP response, and a manifest.
signing_requests[].signersobject[]

Example response

{
  "data": {
    "signing_requests": [
      {
        "signing_request_id": "8e1d…",
        "title": "Hyresavtal kontor",
        "message": null,
        "state": "open",
        "document_id": "4f1c…",
        "document_sha256": "9b2e…",
        "annual_report_version_id": null,
        "created_by": "a3f0…",
        "created_via": "mcp",
        "created_at": "2026-10-25T09:00:00.000Z",
        "expires_at": "2026-11-08T09:00:00.000Z",
        "completed_at": null,
        "declined_at": null,
        "cancelled_at": null,
        "signed_document_id": null,
        "evidence_document_id": null,
        "signers": [
          {
            "signer_id": "2c7b…",
            "position": 1,
            "name": "Anna Svensson",
            "email": "anna@example.se",
            "role_label": "Hyresvärd",
            "user_id": null,
            "annual_report_signature_id": null,
            "lock": "none",
            "status": "pending",
            "signed_at": null,
            "bankid_name": null,
            "declined_at": null,
            "decline_reason": null,
            "last_error_code": null,
            "link_sent_at": "2026-10-25T09:00:01.000Z",
            "last_reminded_at": null
          }
        ]
      }
    ]
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

GET /api/v1/companies/:companyId/signing-requests/:id

signing-requests.get · scope documents:read

Read one BankID signing request: every signer's status and, once completed, the signed PDF and the evidence package.

signed_document_id is the signed PDF (the original plus a Signeringsbevis page with each signer's BankID name, time and masked personnummer) and evidence_document_id is Bevispaket.zip (the original, every BankID signature with its OCSP response, and a manifest). Download either through GET /documents/{id}/download.

Use when: You sent a document for signing and need to know who has signed, or want the signed copy.

Don't use for: Listing requests (GET /signing-requests).

Pitfalls

  • A completed request may show null copies for a moment while they are sealed; read it again.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no

Response fields

NameTypeDescription
signing_request_idstring
titlestring
messagestring | null
state"open" | "completed" | "declined" | "cancelled" | "expired"
document_idstringThe PDF that is signed (an archive document).
document_sha256stringIts SHA-256 when the request was made; every BankID signature carries it.
annual_report_version_idstring | null
created_bystring
created_via"app" | "api" | "mcp"
created_atstring
expires_atstring
completed_atstring | null
declined_atstring | null
cancelled_atstring | null
signed_document_idstring | nullOnce completed: the signed PDF with its Signeringsbevis page (GET /documents/{id}/download).
evidence_document_idstring | nullOnce completed: Bevispaket.zip, the original, each BankID signature and OCSP response, and a manifest.
signersobject[]

Example response

{
  "data": {
    "signing_request_id": "8e1d…",
    "title": "Hyresavtal kontor",
    "message": null,
    "state": "open",
    "document_id": "4f1c…",
    "document_sha256": "9b2e…",
    "annual_report_version_id": null,
    "created_by": "a3f0…",
    "created_via": "mcp",
    "created_at": "2026-10-25T09:00:00.000Z",
    "expires_at": "2026-11-08T09:00:00.000Z",
    "completed_at": null,
    "declined_at": null,
    "cancelled_at": null,
    "signed_document_id": null,
    "evidence_document_id": null,
    "signers": [
      {
        "signer_id": "2c7b…",
        "position": 1,
        "name": "Anna Svensson",
        "email": "anna@example.se",
        "role_label": "Hyresvärd",
        "user_id": null,
        "annual_report_signature_id": null,
        "lock": "none",
        "status": "pending",
        "signed_at": null,
        "bankid_name": null,
        "declined_at": null,
        "decline_reason": null,
        "last_error_code": null,
        "link_sent_at": "2026-10-25T09:00:01.000Z",
        "last_reminded_at": null
      }
    ]
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/signing-requests

signing-requests.create · scope documents:write

Send an archived PDF for BankID signing: one link per signer, mailed when they have an address.

Freezes the document by its SHA-256 and creates one link per signer to a page at /signera/<token> where they read the PDF and sign it with BankID (an advanced electronic signature under eIDAS). Links are valid expires_in_days (default 14, at most 90). Signers with an email get a mail; the answer carries the url only for a signer no mail reached. A member of the company (user_id) can also sign in the app. When everyone has signed, the archive gets the signed PDF (with a Signeringsbevis page) and Bevispaket.zip, and every signer with an email gets the signed PDF; read them from GET /signing-requests/{id} or the signing.completed webhook. Dry-runnable: ?dry_run=true answers what would be sent and mints nothing.

Use when: An agreement, minutes, a policy or any other PDF in the archive needs signatures from people inside or outside the company.

Don't use for: Signing bank payments (POST /payments/signing-links), the fastställelseintyg (signed at Bolagsverket), or documents that are not PDFs.

Pitfalls

  • The url is the credential: hand it to that signer only, once, and do not store it.
  • SIGNING_DOCUMENT_UNSUPPORTED: only the current version of a PDF of at most 10 MB without a password can be signed.
  • SIGNING_SIGNERS_INVALID: a name is required, an email or member appears once, and user_id must be a member of the company (details.reason says which).
  • A personnummer cannot be sent here; locking a signer to one person is done in the app.
  • SIGNING_UNAVAILABLE: BankID signing is not switched on for this company.
  • SIGNING_CAPABILITY_BLOCKED: signing is part of the paid plan and the trial; the company has neither.

Risk: medium · Idempotent: yes · Reversible: no · Dry-run supported: yes · Stage for approval: yes (?stage=true)

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.
stagestringnotrue stages the write for approval instead of committing it: 202 with the pending operation to approve via POST /pending-operations/{id}/approve. Absent or false commits; other values are 400. A reason in the body (why you propose it, at most 500 characters) is shown to the approver as agent_reason and kept with the decision.

Request body

NameTypeRequiredDescription
document_idstringyesThe PDF in the archive (document_id).
titlestringyesWhat the signers sign, shown in the mail, on the page and in the BankID app.
messagestring | nullnoA message to the signers, shown in the mail and on the page.
expires_in_daysnumber | nullnoHow long the links work. Default 14.
send_emailbooleannoMail each signer with an email (default true).
signersobject[]yes1-20 signers; everyone signs, in any order.

Response fields

NameType
signing_request_idstring
status"open"
titlestring
document_idstring
document_sha256string
expires_atstring
signersobject[]

Example request

{
  "document_id": "4f1c…",
  "title": "Hyresavtal kontor",
  "signers": [
    {
      "name": "Anna Svensson",
      "email": "anna@example.se",
      "role_label": "Hyresvärd"
    }
  ]
}

Example response

{
  "data": {
    "signing_request_id": "8e1d…",
    "status": "open",
    "title": "Hyresavtal kontor",
    "document_id": "4f1c…",
    "document_sha256": "9b2e…",
    "expires_at": "2026-11-08T09:00:00.000Z",
    "signers": [
      {
        "signer_id": "2c7b…",
        "position": 1,
        "name": "Anna Svensson",
        "email": "anna@example.se",
        "annual_report_signature_id": null,
        "lock": "none",
        "email_sent": true,
        "url": null
      }
    ]
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/signing-requests/:id/cancel

signing-requests.cancel · scope documents:write

Cancel an open signing request: its links stop working; signatures already made stay.

The sender, an owner or an admin. Cancelling twice answers the same. A completed or declined request cannot be cancelled. Dry-runnable.

Use when: The document changed or was sent to the wrong people.

Don't use for: Removing a signature (a signature is evidence and is kept).

Pitfalls

  • SIGNING_REQUEST_CLOSED: the request completed or was declined.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Response fields

NameType
signing_request_idstring
cancelledtrue

Example response

{
  "data": {
    "signing_request_id": "8e1d…",
    "cancelled": true
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/signing-requests/:id/signers/:signerId/remind

signing-requests.remind · scope documents:write

Mail a pending signer again, once a day at most; the mail carries a new link and the old one stops working.

For an open request and a signer who has not signed and has an email. Refused while the signer is in a BankID signing right now. The answer carries the new link only when no mail went out. Dry-runnable.

Use when: A signer has not signed after a while.

Don't use for: A signer without an email (hand them the link you got at creation) or a closed request.

Pitfalls

  • SIGNING_REMIND_TOO_SOON: one mail per signer per 24 hours, the first mail included.
  • SIGNING_SESSION_ACTIVE: the signer started BankID in the last few minutes; try later.
  • The old link stops working: only the newest link signs.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: yes · Stage for approval: yes (?stage=true)

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.
stagestringnotrue stages the write for approval instead of committing it: 202 with the pending operation to approve via POST /pending-operations/{id}/approve. Absent or false commits; other values are 400. A reason in the body (why you propose it, at most 500 characters) is shown to the approver as agent_reason and kept with the decision.

Response fields

NameType
signing_request_idstring
signer_idstring
email_sentboolean
urlstring | null

Example response

{
  "data": {
    "signing_request_id": "8e1d…",
    "signer_id": "2c7b…",
    "email_sent": true,
    "url": null
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}