Skip to main content
In this guide, you create a public form: a hosted link where visitors answer questions and then sign a document. You configure the questions and the document, publish the link, and sync the responses to your system. Each form owns its own template, which only the form uses. Every submission creates an ordinary sajn document from it.

Before you begin

  • Get a bearer token for the workspace: an API key, or an OAuth access token. To create an API key, go to workspace settings in the sajn app, then Utvecklare (Developer) > API-nycklar (API keys). Store it in the SAJN_API_KEY environment variable.
  • Make sure your workspace role has the permissions for the task: MANAGE_FORM to create, edit, and publish forms, DELETE_FORM to delete them, and READ_FORM_SUBMISSIONS to read responses. To publish, you also need permission to create documents and send them without approval, and the workspace must not require approval before sending.
An OAuth app requests the following scopes. An API key uses its user’s workspace permissions instead.

Create and publish a form

1

Create a draft

Send a POST request to /api/v1/forms. To copy an existing template, send its templateId. To start with a blank document and one respondent party, send {}:
Replace TEMPLATE_ID with the template to copy. The response is the form: its id, templateId (the form’s own template), respondentPartyId, url, its settings, and publishIssues. The copy has new field and party IDs, and later changes to the source template don’t affect it.
2

Configure the questions and settings

Send a PATCH request to /api/v1/forms/FORM_ID:
Replace FORM_ID with the form id. The request works as follows:
  • Settings you leave out stay unchanged, and null clears a nullable setting. For password, leaving it out keeps the password, null removes it, and a string replaces it. Responses show hasPassword, never the password.
  • questions replaces the whole ordered list. The kinds are NAME, EMAIL, PHONE, FIELD, TEXT, HEADING, and PARAGRAPH. The NAME question is always required.
  • A FIELD question writes its answer into the document, through a key from the form’s slots. A TEXT answer is stored on the submission only.
  • redirectUrl must be an http or https URL.
Saving settings doesn’t publish the form, reopen a closed form, or change its sender.
3

Edit the template and the respondent

Read GET /api/v1/forms/FORM_ID/template for the template’s settings, content blocks, and parties. Then change them with the form’s template endpoints:
  • PATCH /api/v1/forms/FORM_ID/template: change name and templateMeta, such as signingMode. The response is the template.
  • PUT /api/v1/forms/FORM_ID/template/fields: replace fields with the complete ordered list of content blocks. Include the IDs of fields to keep, and leave out IDs for new fields. Fields you leave out are deleted, and existing fields can’t change type.
  • PUT /api/v1/forms/FORM_ID/template/parties: replace parties. Include the IDs of parties to keep, and leave out IDs for new parties. Properties you leave out on kept parties, including contact links and write-only PINs, stay unchanged, and null clears a property. Parties you leave out are deleted. Like other parties, these nest their company in company, with name, orgNumber, and role.
  • PUT /api/v1/forms/FORM_ID/respondent with { "partyId": "PARTY_ID" }: choose which SIGNER party the visitor becomes.
Existing submissions don’t change. An edit to a published form can close it when a readiness check finds a blocker, so read GET /api/v1/forms/FORM_ID afterward for publishIssues and the bindable slots.
4

Publish the form

Publishing checks the document, the expiry, the capacity, the sender’s permissions, the workspace policy, and access to the signing methods. You become the sender of every document the form creates. If the request reports blockers, fix them and publish again. Then share the form’s url. Visitors answer and sign on the hosted pages; the API can’t submit or sign on their behalf.To stop new submissions and keep the URL for later, send a POST request to /api/v1/forms/FORM_ID/unpublish. To replace the link, for example after it leaked, send a POST request to /api/v1/forms/FORM_ID/rotate-token; the response is the form with the new url. The old link stops working, including printed QR codes, but signing links already sent keep working.
5

Sync the responses

Subscribe a webhook to form.submitted to hear about each submission; the event’s data.object is the submission. To sync the submissions, page through the ones that changed since your last sync, oldest change first:
The response has the submissions in data, plus hasMore and nextCursor. While hasMore is true, pass nextCursor as cursor with the same filters and sort. A submission can change while you page, so upsert each one by its ID.
  • updatedAfter finds status changes since your last sync. submittedAfter filters by submission time.
  • status takes STARTED, SUBMITTED, COMPLETED, or EXPIRED.
  • Each submission has the respondent’s contact details, the answers, timestamps, and the documentId of the document it created. Read the document’s content and signing progress through the document endpoints. Submissions don’t include IP addresses, user agents, or signing tokens.

Delete a form

DELETE /api/v1/forms/FORM_ID permanently removes the form, its template, and its submission records, and returns { "id": "FORM_ID", "deleted": true }. Export the responses you need first. Documents the form already created stay as ordinary documents.

Handle errors

  • 400 VALIDATION_FAILED: a question or a setting is invalid. Read issues for the path.
  • An error from publish: a readiness check failed. Read GET /api/v1/forms/FORM_ID for publishIssues, fix each one, and publish again.
  • 403 PERMISSION_DENIED or 403 INSUFFICIENT_SCOPE: your role or token lacks a permission or scope from the preceding table.
For the error shape and every code, see Errors.

Next steps

Templates and forms

Learn how forms differ from templates.

Webhook events

Handle form.submitted and document events.

Create a draft form

See the form endpoints in the API reference.