Skip to main content
Each entry lists the breaking changes in one version, compared to the version before it. Additive changes, such as new endpoints and new response fields, ship to every supported version and aren’t listed. For how versions work, see API versioning. To move to the latest version, with before-and-after examples, see Upgrading to 2026-10.

2026-10

No sunset date.

Documents and parties

  • Removes the deprecated signer aliases. On POST /api/v1/documents, send parties instead of signers. On POST /api/v1/documents/:id/reminders, send partyIds instead of signerIds. The ACCEPTOR role is rejected; send SIGNER instead. Responses from POST /api/v1/documents and GET /api/v1/documents/:id, and document webhook payloads, no longer include signers; read parties instead. The /api/v1/documents/:id/signers endpoints are removed; use the /api/v1/documents/:id/parties endpoints instead.
  • Returns the full document from POST /api/v1/documents, in the same shape as GET /api/v1/documents/:id. Read the document ID from id instead of documentId, and each party ID from parties[].id instead of parties[].signerId. GET /api/v1/documents/:id leaves out fields unless you pass expand=fields. The externalId filter on GET /api/v1/documents matches the whole value, case-sensitively; for a partial match, use query. GET /api/v1/documents/:id/download is removed; use GET /api/v1/documents/:id/files/:type. POST /api/v1/documents/:id/parties/:partyId/remind is removed; use POST /api/v1/documents/:id/reminders with partyIds, which reports each party’s outcome in results instead of failing the request.
  • GET /api/v1/documents/:id/parties/:partyId no longer returns the party’s token; signingUrl already carries it. An uploaded attachment in a FORM subfield’s fieldMeta.value is { filename, mimeType, size }, without the storage key and checksum. POST /api/v1/files returns the file object, the same as GET /api/v1/files/:id, with uploadUrl and key; use id instead of fileId.
  • Uses ISO 3166-1 alpha-2 country codes everywhere, as organizations and companies already did. A party’s country, on documents, templates, and forms, is alpha-2 (SE instead of SWE) in requests and responses, and GET /api/v1/helpers/countries returns alpha-2 codes.
  • Returns the same template everywhere, in the shape of GET /api/v1/templates/:id: the list, create, update, duplicate, delete, and tag endpoints return templateMeta, parties, tags, and deletedAt. GET /api/v1/templates/:id leaves out the content blocks, with fields: null, unless you pass expand=fields. DELETE /api/v1/templates/:id moves the template to the trash and returns it with deletedAt set, and POST /api/v1/templates/:id/tags and DELETE /api/v1/templates/:id/tags/:tagId return the template, instead of { success }. DELETE /api/v1/templates/:id/parties/:partyId returns { id, deleted: true }. In PATCH /api/v1/templates/:id/parties/:partyId, null clears a property. Updating or deleting a locked template, or changing its parties, fails with 409 INVALID_STATE instead of 403 PERMISSION_DENIED.
  • Makes internal approval its own resource. To request approval of a draft, call POST /api/v1/approval-requests with documentId and approverIds, or without approverIds to submit the approvers already on the document, instead of POST /api/v1/documents/:id/send with approverId. GET /api/v1/approval-requests lists requests, filtered by documentId, approverId, and status, and GET /api/v1/approval-requests/:id returns one. To act on a request, call POST /api/v1/approval-requests/:id/approve, /reject, or /cancel; each returns the request. A request lists each approver as { user, stage, orGroup, status, comment, resolvedAt }, where user is { id, email, name }. Removes GET /api/v1/documents/:id/approval, DELETE /api/v1/documents/:id/approval, POST /api/v1/documents/:id/approval/approve, and POST /api/v1/documents/:id/approval/reject. Removes GET /api/v1/approvers; call GET /api/v1/members?permission=APPROVE_DOCUMENT, which also lists you.
  • Returns the full document, in the shape of GET /api/v1/documents/:id without fields, from PATCH /api/v1/documents/:id, POST /api/v1/documents/:id/send, POST /api/v1/documents/:id/withdraw, POST /api/v1/documents/:id/extend-expiration, and DELETE /api/v1/documents/:id. A document has deletedAt, templateId, folderId, and responsibleUserId, and a full document also has approvalRequestId. A deleted document is returned with deletedAt set. POST /api/v1/documents/:id/send no longer submits the document for approval: when the document needs one, it returns 409 APPROVAL_REQUIRED instead of 403, never 202, and a request with approverId is rejected; request the approval with POST /api/v1/approval-requests instead. DELETE /api/v1/documents/:id/parties/:partyId, DELETE /api/v1/files/:id, and DELETE /api/v1/documents/:id/links/:linkId return { id, deleted: true }. GET /api/v1/templates/:id/documents is removed; use GET /api/v1/documents?templateId=. GET /api/v1/documents/:id/download/:fileType is removed; use GET /api/v1/documents/:id/files/:type, or GET /api/v1/documents/:id/files for every file that is ready.
  • Nests a party’s company in company, with id, name, orgNumber, and role, in place of companyId, companyName, companyOrgNumber, and companyRole, on document parties and template parties alike. company is null for a private individual. Requests to POST /api/v1/documents, PATCH /api/v1/documents/:id/parties/:partyId, POST /api/v1/templates/:id/parties, PATCH /api/v1/templates/:id/parties/:partyId, and PUT /api/v1/forms/:id/template/parties take company too, and a request that still sends one of the flat keys fails with 400 VALIDATION_FAILED. In a PATCH request, null clears phone, externalId, nationalId, or a key of company, and company: null clears the whole company. A document party gains createdAt, when it was added to the document; a template party keeps createdAt and updatedAt.
  • POST /api/v1/documents/:id/links returns the link in the shape of an item from GET /api/v1/documents/:id/links, and every link has fromDocumentId and toDocumentId. POST /api/v1/files/:id/confirm returns the file, in the shape of GET /api/v1/files/:id, instead of { id, status, size, checksumVerified }. GET /api/v1/folders/:id, POST /api/v1/folders, and PATCH /api/v1/folders/:id return childFolderCount and itemCount. PATCH /api/v1/documents/:id/parties/:partyId on a sent document changes the name, email, and phone of a party who hasn’t signed, instead of failing; any other property fails with 409 INVALID_STATE. POST /api/v1/documents/:id/parties and PATCH /api/v1/documents/:id/parties/:partyId return the party in the shape of GET /api/v1/documents/:id/parties/:partyId, with its signingUrl, and log that you read the signing URL, as the GET request does.
  • Each document in GET /api/v1/documents has parties, with every party’s id, name, email, role, signingStatus, and signedAt, and tags, so you can tell who has signed without reading each document.

Fields

  • Returns the field itself from the field endpoints. POST /api/v1/documents/:id/fields and POST /api/v1/templates/:id/fields return the created fields in data, each in the shape of GET /api/v1/documents/:id/fields/:fieldId; read the document or template ID from each field instead of the top-level documentId or templateId. A PATCH request to a field returns the updated field in that shape. A DELETE request to a field returns { id, deleted: true }; to keep the deleted content, read the field before you delete it.
  • Gives fields one write path. PATCH /api/v1/documents/:id/fields/:fieldId and PATCH /api/v1/templates/:id/fields/:fieldId take a field ID only; the key: path prefix is removed. To fill in a FORM subfield or another value, use PATCH /api/v1/documents/:id/field-values. To find the field that holds a key, pass key to GET /api/v1/documents/:id/fields or GET /api/v1/templates/:id/fields. PATCH /api/v1/documents/:id/fields, which updated FORM subfields by key, is removed for the same reason. The key property of a field in the create and update requests, which sajn ignored, is removed.
  • Uses partyId and customFieldId inside fieldMeta, like everywhere else. A box in placedFields and the fieldMeta of a FORM subfield take partyId instead of signerId, a PRODUCT_TABLE takes selectionPartyId instead of selectionSignerId, and visibilityRule takes customFieldId instead of customInputId. This applies to every request and response that carries fieldMeta, including upsert on the placed-fields endpoints, initialFields on POST /api/v1/templates, and blocks. GET /api/v1/documents/:id/field-values returns partyId instead of signerId. A request that still sends an old name inside fieldMeta or upsert fails with 400 VALIDATION_FAILED.
  • Spells every enum inside fieldMeta in uppercase, like the rest of the API. A placed box has kind INPUT or STATIC, and its inputType (such as SIGNATURE), fillSource (such as SIGNER), and style.align are uppercase. So are the type of a FORM subfield and of its fieldMeta (such as DATEPICKER), an attachment’s allowedTypes, the type of an AcroForm field in formFields and of a TABLE column, a DURATION field’s durationType and period unit, and sectionStyle.background and sectionStyle.padding. On GET and PATCH /api/v1/documents/:id/field-values, kind is FORM, PDF_ACROFORM, or PDF_PLACED, filledBy is SENDER or SIGNER, and type is uppercase. A request that sends a lowercase value inside fieldMeta fails with 400 VALIDATION_FAILED.
  • Names VAT vat instead of the Swedish moms in product tables. In a PRODUCT_TABLE field’s fieldMeta, a product’s moms is vat, pricing.pricesIncludeMoms, defaultMomsRate, and showMomsBreakdown are pricesIncludeVat, defaultVatRate, and showVatBreakdown, the same settings on a column are renamed the same way, the VAT column is keyed vat, and summaryLabels.moms is summaryLabels.vat. In productTables on GET /api/v1/documents/:id and on webhooks, a row’s moms and lineMoms are vat and lineVat, and momsAmount in the totals is vatAmount. Prices and amounts are decimal amounts in the table’s currency, as before. A request that still sends a moms name fails with 400 VALIDATION_FAILED.
  • POST /api/v1/documents/:id/fields and POST /api/v1/templates/:id/fields take { fields: [...] }, also for one field, instead of a field or an array of fields, and an issue path starts with fields.. The placed-fields endpoints return the whole field, in the shape of GET /api/v1/documents/:id/fields/:fieldId. GET /api/v1/documents/:id/field-values and PATCH /api/v1/documents/:id/field-values return the values in data instead of values. On PATCH /api/v1/documents/:id/field-values, a result’s error is { code, message, userMessage } instead of a Swedish string, with code NOT_FOUND, INVALID_STATE, or VALIDATION_FAILED, and the top-level success is removed; check each result’s success.

Naming

  • Renames the Swedish BankID codes to their eID scheme names, like every other scheme: SE_BANKID replaces BANKID in requiredSignature, and SE_BANKID_BEFORE_SIGNING replaces BANKID_BEFORE_SIGNING in twoStepVerification and accessVerification. Requests that send the old codes are rejected, and GET /api/v1/helpers/signature-methods and GET /api/v1/helpers/two-step-verifications return the new ones.
  • Renames the remaining signer fields to party fields. GET /api/v1/documents/:id/signatures returns partyId and partyName instead of signerId and signerName. The reminder endpoints return partyId, partyEmail, and partyName instead of signerId, signerEmail, and signerName. GET /api/v1/documents/:id/delegations returns originalPartyId, delegatePartyId, originalParty, and delegateParty instead of originalSignerId, delegateSignerId, originalSigner, and delegateSigner. The form endpoints return respondentPartyId instead of respondentSignerId, and PUT /api/v1/forms/:id/respondent takes partyId instead of signerId.
  • Renames the national identity number from ssn to nationalId on parties, template parties, and contacts, and in the POST /api/v1/sajn-id request. documentMeta.ssnDisplayMode and templateMeta.ssnDisplayMode become nationalIdDisplayMode. In GET /api/v1/documents/:id/signatures, identity.match.method returns NATIONAL_ID instead of SSN. The SECURITY_SIGNATURE_IDENTITY_ACCESSED webhook payload sends nationalIdDisplayMode instead of ssnDisplayMode. Requests that still send ssn or ssnDisplayMode are rejected.
  • Renames custom field IDs to customFieldId. On POST /api/v1/documents and PATCH /api/v1/documents/:id, send customFields[].customFieldId instead of customFields[].customInputId. GET /api/v1/document-categories returns customFieldIds instead of customInputIds.
  • Uses uppercase enum values everywhere. Workspace members have status ACTIVE or INACTIVE, and the status filter on GET /api/v1/members takes ACTIVE or INACTIVE; leave it out for every member. Login sessions on GET /api/v1/login/sessions/:id have status PENDING, COMPLETED, FAILED, or EXPIRED. Removes the event types DOCUMENT_OPENED, DOCUMENT_RECREATED, WORKFLOW_STARTED, WORKFLOW_COMPLETED, and WORKFLOW_FAILED, which sajn never emits, from the events and webhook deliveries endpoints. To track a party opening a document, use document.party.opened; to track changes to a sent document, use document.updated.
  • Spells the remaining closed enums in uppercase. Form questions have kind NAME, EMAIL, PHONE, FIELD, TEXT, HEADING, or PARAGRAPH and width FULL or HALF; form slots have an uppercase type, and publishIssues have severity BLOCKER or WARNING. A document link’s origin is MANUAL, DUPLICATE, or RENEWAL. In documentStyle.theme, bodyFont and headingFont (such as OPEN_SANS), density, and textSize are uppercase. A sajn ID verification that was cancelled has status CANCELLED, the spelling documents use, in responses and in the status filter. In POST /api/v1/documents, integrationLink.integration is HUBSPOT and integrationLink.type is DEAL.
  • Renames a form’s title to name, in responses and in PATCH /api/v1/forms/:id. POST /api/v1/forms returns 200 instead of 201. POST /api/v1/forms/:id/unpublish and POST /api/v1/forms/:id/rotate-token return the form instead of { success } and { url }, and DELETE /api/v1/forms/:id returns { id, deleted: true }. The form’s template moves from /api/v1/forms/:id/document to /api/v1/forms/:id/template, with /template/fields and /template/parties, and PATCH /api/v1/forms/:id/template returns the template instead of { success }. Its templateMeta.signingOrder is signingMode, as on templates.
  • Moves sajn ID verifications from /api/v1/sajn-id to /api/v1/identity-checks, the name the identity_check.* webhook events and the identityChecks quota in GET /api/v1/limits use. An identity check has language, an ISO 639-1 code such as sv, instead of the free-form locale; send language instead of locale when you create one. createdBy is { id, email, name }. A check in a list has the same fields as one you get by ID, with audits, data, and verificationUrl set to null. The response to the create request no longer returns token, which verificationUrl already carries. Requests that set email or phone to an empty string fail with 400 VALIDATION_FAILED; leave the field out instead.
  • Moves a document’s chat from /api/v1/documents/:id/chat to /api/v1/documents/:id/messages. The list returns its messages in data, like every other list. A message and a comment have one shape: the text is body, in responses and in POST /api/v1/documents/:id/messages, instead of a message’s content, and the author is author: { type, id, name, email }, instead of a comment’s separate authorType. A comment’s author is always present: id, name, and email are null for the AI assistant or an author who no longer exists.
  • Renames the PARALLEL or SEQUENTIAL setting in templateMeta from signingOrder to signingMode, in responses and in PATCH /api/v1/templates/:id, so that signingOrder names only a party’s position. A request that still sends templateMeta.signingOrder fails with 400 VALIDATION_FAILED.
  • Folds the contact and company searches into the lists. GET /api/v1/contacts/search is removed: pass email, phone, or externalId to GET /api/v1/contacts, where every filter you pass must match, instead of any one. GET /api/v1/companies/search is removed: pass orgNumber or name to GET /api/v1/companies, again matching every filter. GET /api/v1/companies/lookup?orgNr= moves to GET /api/v1/company-registry/:orgNumber, which returns basic.orgNumber instead of basic.orgNr. A contact returns its address (addressLine1, addressLine2, postalCode, city, state, and country), which you can also set when you create or update one, and its company is the full company. A company returns createdAt and updatedAt. GET /api/v1/companies/:id no longer returns contacts; list them with GET /api/v1/contacts?companyId=.
  • Gives a member one shape everywhere: POST /api/v1/members, GET /api/v1/members, GET /api/v1/members/:userId, and PATCH /api/v1/members/:userId return { id, email, name, role, status, lastActiveAt, joinedAt }, where id is the user ID that was userId. POST /api/v1/members only adds a member of the organization to the workspace and returns 404 for any other email address; it no longer takes sendInvite or returns invited, roleId, roleName, workspaceId, organizationId, or addedAt. To invite someone new, call POST /api/v1/member-invites with email and roleId, which returns the invitation. DELETE /api/v1/members/:userId and DELETE /api/v1/member-invites/:id return { id, deleted: true }, and POST /api/v1/member-invites/:id/resend returns the invitation, instead of { success: true }. An invitation’s invitedBy is { id, email, name }, and so is every other user reference. GET /api/v1/me returns the user ID as id instead of userId. /api/v1/workspace-roles is renamed /api/v1/roles, and /api/v1/workspace-permissions is renamed /api/v1/permissions; DELETE /api/v1/roles/:id returns { id, deleted: true }. To list the members who can approve documents, call GET /api/v1/members?permission=APPROVE_DOCUMENT instead of GET /api/v1/approvers.
  • Renames the PARALLEL or SEQUENTIAL setting in documentMeta from signingOrder to signingMode, in responses and in POST /api/v1/documents and PATCH /api/v1/documents/:id, so that signingOrder names only a party’s position. A request that still sends documentMeta.signingOrder fails with 400 VALIDATION_FAILED.
  • Gives every user reference one shape, { id, email, name }. Templates, folders, and blocks return createdBy instead of createdById, and a form returns sender instead of senderUserId. A file’s uploadedBy has name instead of firstName and lastName. In GET /api/v1/documents/:id/reminders, triggeredBy adds the user’s id, and it’s null for a reminder sajn sent automatically, instead of an object with a null name and email.
  • Renames preferredLanguage in documentMeta and templateMeta to language, and a block’s locale to language, in responses and requests. reminderIntervalDays in documentMeta and templateMeta is an integer instead of a string: 0 turns reminders off, and null uses the default of 3 days. A request that still sends preferredLanguage fails with 400 VALIDATION_FAILED.

Query strings and pagination

  • Reads query parameters as plain strings: send externalId=12345 or archived=false as is, not JSON-encoded. Booleans take true or false, dates take an ISO 8601 date or date-time (UTC without an offset), and multi-value filters such as status, tagId, and type take a comma-separated list or a repeated parameter. An unknown query parameter returns 400 instead of being ignored. Renames search to query on GET /api/v1/templates, since and until to createdAfter and createdBefore on GET /api/v1/events, and dateFrom and dateTo to createdAfter and createdBefore on GET /api/v1/identity-checks, which replaces GET /api/v1/sajn-id, and locale to language on GET /api/v1/blocks. createdBefore includes its boundary, whereas until excluded it, and the identity checks list no longer defaults to the last seven days. GET /api/v1/companies/:id returns the company without the company wrapper.
  • Gives every list one contract. Page with limit, from 1 to 100 with a default of 25, and cursor, the nextCursor of the previous response; page and perPage are removed, and sending them returns 400. A list returns { data, hasMore, nextCursor }, and total only when you pass include=total; totalPages is removed. Lists that returned everything in one response are paginated too: GET /api/v1/folders, /document-categories, /workspaces, /companies, /documents/:id/activity, and /documents/:id/comments. Lists bounded by their parent, such as a document’s parties, fields, and signatures, and the /helpers lists return every item in one response, as { data, hasMore: false, nextCursor: null }. Lists sort newest first by default, including GET /api/v1/documents, which sorted by updatedAt, and GET /api/v1/forms and /forms/:id/submissions, which sorted by updatedAt ascending; pass orderBy and orderDirection to change it. Documents page by keyset on every orderBy, with empty completedAt and expiresAt values last. Filters that reference another resource end in Id and take several values (createdBy is createdById on GET /api/v1/templates, and tagId, companyId, roleId, and the delivery and submission status take a list). ALL is no longer a filter value: leave the filter out instead. Block scope values are uppercase. folderId=root replaces the empty folderId for top-level documents and templates. Mutable resources take createdAfter, createdBefore, updatedAfter, and updatedBefore, all inclusive.
  • Renames the scope filter of GET /api/v1/blocks to visibility, with the values a block’s visibility takes. A block sajn curates has visibility: SHARED instead of WORKSPACE, so visibility=WORKSPACE returns exactly the blocks that list WORKSPACE.

Errors

  • Gives every error response the same shape: code, message, userMessage, requestId, and, where they apply, resource, issues, requiredScopes, and grantedScopes. code is always present and comes from a closed, documented list; branch on it, not on the status or the message. message is always English text for developers, and userMessage, always present, is the text that’s safe to show your users, usually Swedish. requestId replaces errorId and matches the Sajn-Request-Id response header. Each validation issue gains a code, its message is English, and a VALIDATION_FAILED error always lists at least one issue. A NOT_FOUND error names the missing resource’s type in resource, and a path that matches no endpoint returns ROUTE_NOT_FOUND. Codes are split or renamed: INVALID_REQUEST, INVALID_BODY, and VALIDATION_ERROR become VALIDATION_FAILED; FORBIDDEN becomes PERMISSION_DENIED, INSUFFICIENT_SCOPE, PLAN_REQUIRED, or ACCOUNT_INACTIVE; TOO_MANY_REQUESTS becomes RATE_LIMITED or DAILY_QUOTA_EXCEEDED; a reused Idempotency-Key returns IDEMPOTENCY_KEY_REUSED or IDEMPOTENCY_KEY_IN_USE; and a failing external service or integration returns UPSTREAM_UNAVAILABLE. 401 UNAUTHORIZED means only that the token is missing, invalid, expired, or revoked. A valid token that isn’t allowed to do something returns 403 PERMISSION_DENIED instead of 401. A state conflict returns 409 INVALID_STATE instead of 400, including editing a document that isn’t a draft, editing the fields of a locked template (previously 401), and downloading a file that isn’t produced yet (previously 404). Other statuses change: sending a document past the monthly limit returns 403 LIMIT_EXCEEDED instead of 400, a deactivated user returns 403 ACCOUNT_INACTIVE instead of 401, a duplicate unique value returns 409 ALREADY_EXISTS and an invalid value caught deeper in the API returns 400 VALIDATION_FAILED instead of 500, and a failing external service returns a retryable 503 UPSTREAM_UNAVAILABLE instead of 500.
  • A form’s publishIssues have an English message, and the Swedish text that message had is in userMessage.

Webhooks

  • Removes the X-Sajn-Secret header, which carried the endpoint secret in plain text, from webhook deliveries. Verify deliveries with the Standard Webhooks webhook-signature header instead.
  • Uses the REST API’s names in webhook payloads. On document events, the document has name instead of title, and SECURITY_DOCUMENT_DOWNLOADED and SECURITY_SIGNATURE_IDENTITY_ACCESSED have documentName instead of documentTitle. A party without an email address has email: null instead of "". In productTables, on webhooks and on GET /api/v1/documents/:id, selectionPartyId replaces selectionSignerId. Documents add expiresAt and completedAt, and parties add role and signedAt. DOCUMENT_ARCHIVE_UPLOADED no longer includes s3Key, an internal storage key.
  • Gives DOCUMENT_PARTY_AUTH_FAILED and DOCUMENT_PARTY_DELEGATED the { document, party } payload of every other party event, in place of the flat documentId, documentName, and signerId. DOCUMENT_PARTY_AUTH_FAILED keeps method, hintCode, and failedAt next to them, and method uses the eID scheme name, such as SE_BANKID instead of BANKID. DOCUMENT_PARTY_DELEGATED moves the delegation into delegation, with id, delegate, reason, and delegatedAt; read the delegating party from party instead of originalSigner.
  • Changes the webhook delivery body to the event itself, { id, type, createdAt, apiVersion, workspaceId, environment, actor, data }, which GET /api/v1/events and GET /api/v1/events/:id also return. type is a dotted lowercase event name such as document.party.signed instead of DOCUMENT_PARTY_SIGNED, and the event filter of GET /api/v1/events becomes type. data.object is the resource the event is about; a party event adds data.party, and the document, previously in payload.document, moves to data.object. environment is PRODUCTION or SANDBOX, and actor is the user, API key or system behind the event, or null. id is the event ID and createdAt the time of the event, so both stay the same across retries and replays; webhookEndpoint is removed. Some events are renamed or merged: DOCUMENT_SIGNED is document.fully_signed; DOCUMENT_ARCHIVE_UPLOADED is no longer sent, because document.created reports the same document with data.source; the two reminder events become document.party.reminded with data.trigger set to AUTOMATIC or MANUAL; ID_* events are identity_check.*, with ID_CANCELED as identity_check.cancelled; and SECURITY_* events are security.*, with the actor in actor and without occurredAt. DOCUMENT_MODIFIED is document.updated, and the workspace membership events drop the workspace_ prefix: SECURITY_WORKSPACE_MEMBER_REMOVED, SECURITY_WORKSPACE_MEMBER_ROLE_CHANGED, and SECURITY_WORKSPACE_ROLE_UPDATED are security.member_removed, security.member_role_changed, and security.role_updated. previousAttributes replaces changedFields on template.updated and previousExpiresAt on document.expiration_extended. Enum values in data are uppercase: via on member.added and scope on security.documents_exported. Deliveries are signed according to Standard Webhooks, with the webhook-id, webhook-timestamp and webhook-signature headers in place of X-Sajn-Signature, X-Sajn-Delivery and X-Sajn-Environment, and a new 2026-10 webhook gets a whsec_ secret.
  • Renames a webhook’s webhookUrl to url and eventTriggers to events, which takes the dotted event types such as document.completed. A webhook adds status (ENABLED, DISABLED or PAUSED), pausedAt and pauseReason, and a paused webhook resumes with POST /api/v1/webhooks/:id/reactivate. PATCH /api/v1/webhooks/:id no longer takes secret: rotate the secret with POST /api/v1/webhooks/:id/rotate-secret, which signs deliveries with both secrets for 24 hours. A secret you pass on create must be a Standard Webhooks secret, whsec_ followed by base64. DELETE /api/v1/webhooks/:id returns { id, deleted: true } instead of { success: true }.
  • Lists a webhook’s deliveries instead of its attempts in GET /api/v1/webhooks/:id/deliveries. A delivery is one event sent to one endpoint, so its id is the delivery and every attempt to send it is in attempts, with durationMs and the request and response headers. A delivery adds eventId and type, and its status is SUCCESS, FAILED or PENDING. The event filter becomes type, eventId filters on one event, and status filters on the delivery’s status instead of an attempt’s. GET /api/v1/webhooks/:id/deliveries/:deliveryId and POST /api/v1/webhooks/:id/deliveries/:deliveryId/retry take the delivery ID, and the retry returns the new delivery instead of { ok, queuedReplayOf }. POST /api/v1/events/:id/replay is removed: to send an event again, retry its delivery to that endpoint. A retry, in every API version, sends the delivery under a new delivery ID.
  • Returns a webhook’s secret only from POST /api/v1/webhooks and POST /api/v1/webhooks/:id/rotate-secret. No other webhook response returns it. Store the secret when you create the webhook; to replace a lost one, rotate it.
  • Makes data.object on webhook events the object the REST API returns for the resource, without expand, so a field added to the REST object also appears in the event. A document event carries the document as GET /api/v1/documents/:id returns it, with documentMeta, tags, customFields, templateId, folderId, responsibleUserId and approvalRequestId, and without organizationId; data.party is the party as GET /api/v1/documents/:id/parties lists it, with alpha-2 country and a nested company. A contact event carries the contact as GET /api/v1/contacts/:id returns it, with postalCode instead of zipCode, and without companyName and workspaceId. A template event carries the template as GET /api/v1/templates/:id returns it. member.added carries the member as GET /api/v1/members/:userId returns it, with the user ID in id, and via moves to data.via. An identity check event carries the check as GET /api/v1/identity-checks lists it, with status CANCELLED instead of CANCELED, and identity_check.failed adds data.failureReason. Payloads leave out national identity numbers. template.updated sets previousAttributes to the earlier values of the fields that changed, and document.created sets data.source for a document created from the email inbox. Security events name users as { id, email, name }: the user who acted is user, and the member a membership event is about is member, instead of workspaceMemberId and email. A call by an OAuth application has the actor { type: "OAUTH_APP", id }, with the application’s ID.

Limits

  • Removes the deprecated bankIdSignatures and aiTokens aliases from GET /api/v1/limits. Read signatures.SE_BANKID instead of bankIdSignatures, and aiBudgetOre, aiSpentOre, and aiSpentOreSigner instead of aiTokens and aiTokensSigner.
  • Removes the cost fields from GET /api/v1/limits: quota.additionalBankidCost, quota.smsCost, and additionalCosts. They always returned 0. To track use beyond the plan, compare usage with quota; the prepaid credit that pays for it is in balance.
  • Gives the amounts of money in GET /api/v1/limits as { amount, currency }, with amount in the currency’s minor unit, such as öre for SEK. Product table prices and totals stay decimal amounts in the table’s currency, and documentMeta.value and templateMeta.value stay free text. In GET /api/v1/limits, balance is in the organization’s currency, and the AI budget fields lose their Ore suffix: quota.aiBudget, remaining.aiBudget, usage.aiSpent, and usage.aiSpentSigner, always in SEK. An unlimited AI budget stays null. GET /api/v1/organization returns postalCode instead of zipCode.

Other changes

  • Returns every response property, with null when it has no value, instead of leaving it out. This applies to, among others, fields on GET /api/v1/documents/:id without expand=fields, options on field values and form slots, error on a successful field-value or batch result, verificationUrl and audits on identity checks, folder counts, and the template settings on GET /api/v1/forms/:id/template.
  • A custom field’s options is an array of strings, such as ["Small","Large"], instead of a JSON-encoded string, in requests and responses.
  • POST /api/v1/login/sessions returns the session, with the same fields as GET /api/v1/login/sessions/:id, plus loginUrl and expiresAt, which are null on every other read. In claims, a claim your client isn’t approved for is null instead of missing. method names the eID as the rest of the API does, SE_BANKID instead of BANKID_SE.
  • DELETE /api/v1/contacts/:id and DELETE /api/v1/custom-fields/:id return { id, deleted: true } instead of the deleted object; to keep its content, read it before you delete it. POST /api/v1/documents/:id/tags and DELETE /api/v1/documents/:id/tags/:tagId return the document, in the shape of GET /api/v1/documents/:id, instead of { success: true }. In PATCH /api/v1/contacts/:id, null clears email, phone, nationalId, externalId, and companyRole.

2026-09

Deprecated: from 2027-10-01, requests on this version return 400 API_VERSION_SUNSET. The first dated version: the v1 API as it was on its release date.