API reference for AI agents

Tidvis Sign API — endpoints, scopes and schemas

Machine-readable, SSR-rendered reference for the Tidvis Sign REST API v1 and MCP server. Every endpoint, scope, request body and response shape rendered without JavaScript so AI agents, MCP clients and LLM crawlers can index it. Interactive human-facing docs live at /utvecklare/api/spec.

Base URL: https://tidvis.se/api/public/v1 — MCP endpoint: https://tidvis.se/api/mcp — Discovery: /.well-known/mcp.json

Pricing: 3 free signings, then Sign Pro 199 SEK/seat/month or pay-per-agreement 19 SEK. Agreement generator module 99 SEK/user/month + 3 SEK/generation via MCP.

OAuth

Token-utgivning

POST /oauth/access-token

Issue access token (client-credentials)

OAuth

Public (no auth).

Returnerar en bearer-token (TTL 1h). Rate-limited per IP (10/min) och per client_id (30/min).

Request body

  • client_id: string required
  • client_secret: string required
Example
{
  "client_id": "tvs_live_8f3a9c2b1d",
  "client_secret": "sk_live_7e1d4f6a9c2b3e5f8a0c1b2d4e6f7a8b"
}

Responses

  • 200 — OK
    • access_token: string
    • token_type: string
    • expires_in: integer
    • scope: string
  • 400 — Invalid request
  • 401 — Invalid client credentials
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
DELETE /oauth/access-token

Revoke current access token

OAuth

Requires bearer token.

Responses

  • 204 — Revoked
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error

Agreements

Avtal, deltagare, livscykel

GET /agreements

List agreements

Agreements

Scopes: agreements:read

List agreements owned by the calling client. Default returnerar bara aktuella versioner (`group=latest`); använd `group=all` för att inkludera superseded versioner (t.ex. tidigare avtal som ersatts av en ny HubSpot-omsändning). Stödjer filtrering på `external_id`, `external_source` och `status`.

Responses

  • 200 — OK
    • agreements: array
    • next_updated_since: string (date-time) — Skicka med som `updated_since` i nästa pollning.
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /agreements

Create draft agreement

Agreements

Scopes: agreements:create

Request body

  • title: string required
  • message: string
  • expires_at: string (date-time)
  • expires_in_days: integer — Alternativ till expires_at — sätter expires_at till nu + N dagar. expires_at vinner om båda skickas.
  • callback_url: string (uri) — Äldre synonym till `webhook_url`. Används bara om `webhook_url` saknas. Nya integrationer ska använda `webhook_url`.
  • webhook_url: string (uri) — Per-avtal webhook-URL (https). Sätts den skickas alla webhook-händelser för just detta avtal hit i stället för till klientens registrerade URL — använd när varje kundmiljö har sin egen mottagare (t.ex. https://kund.tidvis.se/dokument/signera/tidvissign/status/). Ingen förhandsregistrerad webhook krävs — adressen fungerar fristående och får alla händelsetyper för avtalet. Utskicket signeras med klientens signeringsnyckel (X-Tidvis-Signature). Alternativt kan händelser hämtas med GET /events.
  • metadata: object
  • bankid_required: boolean
  • bankid_identify_required: boolean
  • document_source: string — 'sender' (default) = avsändaren laddar upp PDF via PUT /documents/main. 'recipient' = mottagaren laddar upp PDF/PNG/JPG via publik dropzone; kräver exakt 1 recipient.
  • external_id: string
  • external_source: string
  • external_url: string (uri)
  • completed_delivery: string — Leveranssätt för det signerade avtalet. `email_attachment` (standard): PDF bifogas i klart-mejlet. `secure_link`: ingen bilaga — mottagaren får en länk, identifierar sig med BankID och servern kontrollerar att personnumret tillhör en behörig mottagare innan en personlig nedladdning (giltig 10 min) skapas. Länken gäller 30 dagar efter att avtalet blev klart. Kräver BankID-tillvalet (402 `bankid_disabled`) och att varje signerare är knuten till ett personnummer: `bankid_required: true` eller `signer_personal_number` (annars 400 `personal_number_required_for_secure_delivery` vid utskick). Har kontot "Kräv alltid säker leverans" påslaget avvisas `email_attachment` med 409 `secure_delivery_enforced`. Utelämnas fältet gäller kontots standard. Ingen automatisk nedgradering till bilaga sker någonsin. Obs: BankID ensamt gör inte behandlingen GDPR-kompatibel — kunden ansvarar fortfarande för sin riskbedömning.
  • completed_copy_recipients: array — Kopiemottagare som får hämta avtalet vid säker leverans. Personnumret används för behörighetskontrollen och lagras bara som hash. Vid `secure_link` får adresser i `completed_copy_emails` utan personnummer bara ett meddelande om att avtalet är klart — ingen länk.
  • completed_copy_emails: array — Upp till 5 mejladresser som får bekräftelsemejlet med signerad PDF bifogad när avtalet blir completed. Utelämnas fältet ärvas kontots inställning.
  • sender: object — Avsändare — autosigneras vid send (samma beteende som UI-flödet). Läggs till som is_sender=true och skickar inget signeringsmail.
  • auto_reminders: object — Automatic reminder settings (defaults: enabled=true, interval_days=3, max_reminders=3). Reminders run daily via cron; requires the account-level auto-reminder toggle to also be enabled.
Example
{
  "title": "Konsultavtal Q3 2026 – Acme AB",
  "message": "Hej! Vänligen signera bifogat konsultavtal senast 30 juni.",
  "expires_at": "2026-06-30T23:59:59Z",
  "bankid_required": true,
  "external_id": "deal_38291",
  "external_source": "hubspot",
  "sender": {
    "name": "Anna Andersson",
    "email": "anna@tidvis.se"
  },
  "metadata": {
    "deal_owner": "anna@tidvis.se",
    "region": "SE"
  },
  "auto_reminders": {
    "enabled": true,
    "interval_days": 3,
    "max_reminders": 3
  }
}

Responses

  • 201 — Created
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Enterprise-planen krävs
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
GET /agreements/{id}

Get agreement

Agreements

Scopes: agreements:read

Responses

  • 200 — OK
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
PATCH /agreements/{id}

Update agreement metadata and reminder settings

Agreements

Scopes: agreements:create

Patch a live or draft agreement. `title`, `message`, `metadata`, `callback_url`, `webhook_url`, `external_url`, `sender` och `auto_reminders.*` can be updated. `sender` kan bara ändras i status `draft` (autosigneras vid send). Rejected with 409 for terminal statuses (completed, cancelled, declined, deleted, expired).

Request body

  • title: string
  • message: string
  • callback_url: string (uri) — Äldre synonym till `webhook_url`. Används bara om `webhook_url` saknas. Nya integrationer ska använda `webhook_url`.
  • webhook_url: string (uri) — Per-avtal webhook-URL (https). Sätts den skickas alla webhook-händelser för just detta avtal hit i stället för till klientens registrerade URL — använd när varje kundmiljö har sin egen mottagare (t.ex. https://kund.tidvis.se/dokument/signera/tidvissign/status/). Ingen förhandsregistrerad webhook krävs — adressen fungerar fristående och får alla händelsetyper för avtalet. Utskicket signeras med klientens signeringsnyckel (X-Tidvis-Signature). Alternativt kan händelser hämtas med GET /events. Skicka null för att ta bort.
  • external_url: string (uri)
  • metadata: object
  • completed_delivery: string — Byt leveranssätt innan avtalet är klart. Samma regler som vid skapande.
  • completed_copy_recipients: array — Ersätter kopiemottagarna med personnummer (se POST /agreements).
  • sender: object — Sätter/uppdaterar avsändaren som autosigneras vid send. Skicka null för att ta bort. Endast i status draft.
  • auto_reminders: object
Example
{
  "message": "Uppdaterat: signera senast 30 juni!",
  "auto_reminders": {
    "enabled": true,
    "interval_days": 2,
    "max_reminders": 5
  }
}

Responses

  • 200 — OK
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
DELETE /agreements/{id}

Delete draft agreement

Agreements

Scopes: agreements:create

Responses

  • 204 — Deleted
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
GET /agreements/{id}/versions

Get version chain

Agreements

Scopes: agreements:read

Returnerar hela versionskedjan för avtalet, sorterat på version ASC. Fungerar även när det anropande id:t inte är aktuell version.

Responses

  • 200 — OK
    • agreement_id: string (uuid)
    • versions: array
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
GET /agreements/{id}/signed-pdf

Get signed (stamped) PDF URL

Agreements

Scopes: agreements:read

Returns a short-lived signed URL to the final stamped PDF (with per-page footer, verification page, party details incl. BankID metadata, SHA-256 hash and QR code to public verification). Generated automatically when the last participant signs. Returns 409 if the agreement is not completed yet.

Responses

  • 200 — OK
    • url: string (uri)
    • expires_in: integer
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /agreements/{id}/lifecycle

Send or cancel an agreement

Agreements

Scopes: agreements:send, agreements:cancel

`send` kräver `agreements:send`, `cancel` kräver `agreements:cancel`. `cancel` är tillåtet i status `draft`, `sent`, `viewed` och `partially_signed` — redan signerade signaturer bevaras medan kvarvarande mottagares länkar spärras. Terminala statusar (`completed`, `cancelled`, `declined`, `expired`) ger 409.

Request body

  • action: string required
  • sender: object — Endast vid action=send. Avsändare som autosigneras. Vinner över sender som satts på draften.

Responses

  • 200 — OK
    • ok: boolean
    • status: string
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /agreements/{id}/participants

Add participant (draft or live agreement)

Agreements

Scopes: agreements:create

Add a signer or reviewer. For agreements in `sent`/`partially_signed`, the new participant is issued a signing link and an invite email is sent immediately. For drafts, they are added without a link (link generated on `send`).

Request body

  • name: string required
  • email: string (email) required
  • role: string
  • order: integer
  • job_title: string
  • phone: string
  • personal_id_masked: string
  • signer_personal_number: string — Write-only. Svenskt personnummer (10 eller 12 siffror, ev. bindestreck). Låser BankID-signering och/eller identifiering till exakt denna person. Kräver att agreement.bankid_required och/eller bankid_identify_required = true. Returneras aldrig i svar — använd personal_id_masked för visning.
  • notifications_enabled: boolean
Example
{
  "name": "Anna Andersson",
  "email": "anna@acme.se",
  "role": "signer",
  "order": 0,
  "job_title": "VD",
  "phone": "+46701234567",
  "notifications_enabled": true
}

Responses

  • 201 — Created
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /agreements/{id}/participants/{pid}/decline

Decline on behalf of a participant

Agreements

Scopes: agreements:cancel

Request body

  • reason: string

Responses

  • 200 — OK
    • ok: boolean
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /agreements/{id}/participants/{pid}/delegate

Delegate participant to another person

Agreements

Scopes: agreements:create

Request body

  • name: string required
  • email: string (email) required
  • reason: string

Responses

  • 200 — OK
    • ok: boolean
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
PATCH /agreements/{id}/participants/{pid}/notifications

Toggle reminder notifications for a participant

Agreements

Scopes: agreements:create

Request body

  • enabled: boolean required

Responses

  • 200 — OK
    • ok: boolean
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
GET /agreements/{id}/download

Download signed PDF

Agreements

Scopes: agreements:read

Responses

  • 200 — Signed PDF binary
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
GET /agreements/{id}/events

List audit events for an agreement

Agreements

Scopes: agreements:read

Responses

  • 200 — OK
    • events: array
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /agreements/{id}/documents/main

Attach the main PDF document to a draft

Agreements

Scopes: agreements:create

Responses

  • 200 — OK
    • ok: boolean
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /agreements/quick-send

Atomically create + add recipients + send an agreement

Agreements

Scopes: agreements:create

Convenience endpoint that combines POST /agreements + POST /participants + POST /lifecycle{action:send} in one call. Supports `document_source: recipient` (dropzone på signeringssidan) — då krävs INGEN `document` och exakt 1 recipient.

Request body

  • title: string required
  • message: string
  • document_source: string
  • document: object — Required when document_source='sender'. Base64-encoded PDF.
  • recipients: array required
  • expires_at: string (date-time)
  • expires_in_days: integer — Alternativ till expires_at — sätter expires_at till nu + N dagar. expires_at vinner om båda skickas.
  • callback_url: string (uri) — Äldre synonym till `webhook_url`. Används bara om `webhook_url` saknas. Nya integrationer ska använda `webhook_url`.
  • webhook_url: string (uri) — Per-avtal webhook-URL (https). Sätts den skickas alla webhook-händelser för just detta avtal hit i stället för till klientens registrerade URL — använd när varje kundmiljö har sin egen mottagare (t.ex. https://kund.tidvis.se/dokument/signera/tidvissign/status/). Ingen förhandsregistrerad webhook krävs — adressen fungerar fristående och får alla händelsetyper för avtalet. Utskicket signeras med klientens signeringsnyckel (X-Tidvis-Signature). Alternativt kan händelser hämtas med GET /events.
  • metadata: object
  • bankid_required: boolean
  • bankid_identify_required: boolean
  • external_id: string
  • external_source: string
  • external_url: string (uri)
  • completed_delivery: string — Leveranssätt för det signerade avtalet. `email_attachment` (standard): PDF bifogas i klart-mejlet. `secure_link`: ingen bilaga — mottagaren får en länk, identifierar sig med BankID och servern kontrollerar att personnumret tillhör en behörig mottagare innan en personlig nedladdning (giltig 10 min) skapas. Länken gäller 30 dagar efter att avtalet blev klart. Kräver BankID-tillvalet (402 `bankid_disabled`) och att varje signerare är knuten till ett personnummer: `bankid_required: true` eller `signer_personal_number` (annars 400 `personal_number_required_for_secure_delivery` vid utskick). Har kontot "Kräv alltid säker leverans" påslaget avvisas `email_attachment` med 409 `secure_delivery_enforced`. Utelämnas fältet gäller kontots standard. Ingen automatisk nedgradering till bilaga sker någonsin. Obs: BankID ensamt gör inte behandlingen GDPR-kompatibel — kunden ansvarar fortfarande för sin riskbedömning.
  • completed_copy_recipients: array — Kopiemottagare som får hämta avtalet vid säker leverans. Personnumret används för behörighetskontrollen och lagras bara som hash. Vid `secure_link` får adresser i `completed_copy_emails` utan personnummer bara ett meddelande om att avtalet är klart — ingen länk.
  • completed_copy_emails: array — Upp till 5 mejladresser som får bekräftelsemejlet med signerad PDF bifogad när avtalet blir completed. Utelämnas fältet ärvas kontots inställning.
  • sender: object

Responses

  • 201 — Created + sent. Body = serialized agreement with `participants_with_links`.
    object
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error

Comments & chat

Kommentarer och deltagar-chat

GET /agreements/{id}/chat

List chat messages

Comments & chat

Scopes: agreements:read

Responses

  • 200 — OK
    • messages: array
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /agreements/{id}/chat

Post a chat message

Comments & chat

Scopes: agreements:create

Request body

  • body: string required
  • sender_label: string
  • sender_email: string (email)
  • participant_id: string (uuid)

Responses

  • 201 — Created
    object
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
GET /agreements/{id}/comments

List comments

Comments & chat

Scopes: agreements:read

Responses

  • 200 — OK
    • comments: array
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /agreements/{id}/comments

Post a comment

Comments & chat

Scopes: agreements:create

Request body

  • body: string required
  • page: integer
  • anchor_x: number
  • anchor_y: number
  • author_label: string
  • author_email: string (email)
  • participant_id: string (uuid)

Responses

  • 201 — Created
    object
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
PATCH /agreements/{id}/comments/{commentId}

Resolve or reopen a comment

Comments & chat

Scopes: agreements:create

Request body

  • resolved: boolean required

Responses

  • 200 — OK
    • ok: boolean
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error

Webhooks

Push-notiser med HMAC-signering

GET /webhooks

List webhooks

Webhooks

Requires bearer token.

Responses

  • 200 — OK
    • webhooks: array
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /webhooks

Register a webhook

Webhooks

Requires bearer token.

Returns the signing secret ONCE in `secret`. Store it — used to verify `X-Tidvis-Signature` (HMAC-SHA256 over the raw body). ### Exempel-callback från Tidvis ```http POST /your-webhook HTTP/1.1 Content-Type: application/json X-Tidvis-Event: agreement.signed X-Tidvis-Delivery: 9f1c3a7e-2b4d-4e6a-8c1f-1234567890ab X-Tidvis-Signature: sha256=8a2c…d4f9 { "event": "agreement.signed", "event_type": "agreement.signed", "delivery_id": "9f1c3a7e-2b4d-4e6a-8c1f-1234567890ab", "created_at": "2026-06-20T12:00:00Z", "agreement_id": "a1b2c3d4-1111-4aaa-9bbb-000000000001", "status": "signed", "participant": { "id": "b2c3d4e5-2222-4bbb-9ccc-000000000010", "email": "anna@acme.se" }, "data": { "agreement_id": "a1b2c3d4-1111-4aaa-9bbb-000000000001", "status": "signed", "participant": { "id": "b2c3d4e5-2222-4bbb-9ccc-000000000010", "email": "anna@acme.se" } } } ``` Verifiera signaturen i Node: ```ts import { createHmac, timingSafeEqual } from 'crypto'; const header = req.headers['x-tidvis-signature']; // 'sha256=…' const signature = header.replace(/^sha256=/, ''); const expected = createHmac('sha256', WEBHOOK_SECRET).update(rawBody).digest('hex'); if (!timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) throw new Error('bad signature'); ```

Request body

  • url: string (uri) required
  • events: array required
Example
{
  "url": "https://acme.se/webhooks/tidvis",
  "events": [
    "agreement.signed",
    "agreement.completed",
    "agreement.cancelled"
  ]
}

Responses

  • 201 — Created
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
PUT /webhooks

Register webhook if missing (idempotent)

Webhooks

Requires bearer token.

Självregistrering från en kundmiljö. Finns ingen webhook med samma `url` skapas den och `signing_secret` returneras en gång (`created: true`). Finns den redan aktiveras den, `events` slås ihop med befintliga, och svaret har `created: false` utan nyckel — sätt `rotate_secret: true` för att få en ny nyckel. Utelämnas `events` registreras standarduppsättningen: `agreement.sent`, `agreement.viewed`, `agreement.signed`, `agreement.declined`, `agreement.completed`, `agreement.cancelled`, `agreement.expired`.

Request body

  • url: string (uri) required
  • events: array
  • rotate_secret: boolean — Ge en ny signeringsnyckel för en redan registrerad webhook.
Example
{
  "url": "https://ekens.tidvis.se/dokument/signera/tidvissign/status/"
}

Responses

  • 200 — Already registered
  • 201 — Created
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
DELETE /webhooks/{id}

Delete a webhook

Webhooks

Requires bearer token.

Responses

  • 204 — Deleted
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error

Events

Pollning av händelser — alternativ till webhooks

GET /events

Poll agreement events

Events

Scopes: agreements:read

Hämta händelser i stället för att ta emot webhooks. Varje händelse på ett avtal loggas per API-klient oavsett om någon webhook är registrerad — avsett för miljöer som inte kan ta emot inkommande utskick, eller som sätts upp löpande. **Så pollar du:** spara `next_since_id` från svaret och skicka det som `since_id` vid nästa anrop (t.ex. varje minut). Då får du varje händelse exakt en gång. Är `has_more: true` finns fler händelser att hämta direkt. Innehållet är identiskt med webhook-payloaden (`event`, `event_id`, `data`, …).

Responses

  • 200 — OK
    • events: array
    • next_since_id: integer
    • has_more: boolean
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error

Clients

API-klienter och nycklar

GET /clients

List API clients

Clients

Scopes: clients:manage

Responses

  • 200 — OK
    • clients: array
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /clients

Create API client

Clients

Scopes: clients:manage

Returns the client_secret ONCE.

Request body

  • name: string required
  • scopes: array
Example
{
  "name": "HubSpot-integration (produktion)",
  "scopes": [
    "agreements:read",
    "agreements:create",
    "agreements:send"
  ]
}

Responses

  • 201 — Created
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /clients/{id}/revoke

Revoke API client

Clients

Scopes: clients:manage

Responses

  • 200 — OK
    • ok: boolean
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error

Sub-accounts

Underkonton, medlemmar, parent-access

GET /sub-accounts

List sub-accounts under the parent

Sub-accounts

Scopes: sub_accounts:read

Endast tokens utgivna på huvudkontot (parent) får anropa sub-account-endpoints.

Responses

  • 200 — OK
    • sub_accounts: array
    • license: object
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /sub-accounts

Create sub-account

Sub-accounts

Scopes: sub_accounts:manage

Kräver aktiv årslicens (`sub_account_license_yearly`). Returnerar 403 `plan_required` om licens saknas eller alla seats är upptagna.

Request body

  • name: string required
  • org_number: string
  • inherit_parent_admins: boolean
Example
{
  "name": "Acme Bygg AB",
  "org_number": "556677-8899",
  "inherit_parent_admins": true
}

Responses

  • 201 — Created
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Licens saknas eller alla seats är upptagna
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
GET /sub-accounts/{id}

Get sub-account

Sub-accounts

Scopes: sub_accounts:read

Responses

  • 200 — OK
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
PATCH /sub-accounts/{id}

Update sub-account

Sub-accounts

Scopes: sub_accounts:manage

Request body

  • name: string
  • org_number: string
  • bankid_unlimited: boolean

Responses

  • 200 — OK
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
DELETE /sub-accounts/{id}

Archive sub-account (GDPR — hard-delete after 30 days)

Sub-accounts

Scopes: sub_accounts:manage

Markeras `archived_at`. Cron `purge-archived-sub-accounts-daily` hard-deletar 30 dagar senare.

Responses

  • 204 — Archived
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
PATCH /sub-accounts/{id}/parent-access

Toggle whether parent admins inherit access

Sub-accounts

Scopes: sub_accounts:manage

Request body

  • inherit_parent_admins: boolean required

Responses

  • 200 — OK
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
GET /sub-accounts/{id}/members

List sub-account members

Sub-accounts

Scopes: sub_accounts:members

Responses

  • 200 — OK
    • members: array
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /sub-accounts/{id}/members

Invite member to a sub-account

Sub-accounts

Scopes: sub_accounts:members

Request body

  • email: string (email) required
  • role: string
Example
{
  "email": "cecilia@acme.se",
  "role": "admin"
}

Responses

  • 201 — Invitation created
    • invitation: object
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
PATCH /sub-accounts/{id}/members/{memberId}

Change member role

Sub-accounts

Scopes: sub_accounts:members

409 `conflict` returneras om åtgärden lämnar underkontot utan ägare.

Request body

  • role: string required

Responses

  • 200 — OK
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
DELETE /sub-accounts/{id}/members/{memberId}

Remove member

Sub-accounts

Scopes: sub_accounts:members

409 `conflict` returneras om åtgärden lämnar underkontot utan ägare.

Responses

  • 204 — Removed
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error

Agreement templates

AI-byggda mallar från dina egna exempelavtal

GET /agreement-templates

List agreement templates

Agreement templates

Scopes: templates:read

Kräver att avtalsmodulen är aktiv på kontot (99 kr/användare/månad).

Responses

  • 200 — OK
    • templates: array
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 402 — Avtalsmodulen ej aktiv
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /agreement-templates

Create template from 2–10 example agreements

Agreement templates

Scopes: templates:manage

Skickar exempelavtal (PDF/DOCX) — AI extraherar produkter, prislogik och klausuler. Multipart eller JSON med base64.

Request body

  • name: string required
  • files: array required
Example
{
  "name": "Konsultavtal SaaS 2026",
  "files": [
    {
      "filename": "acme-2025.pdf",
      "mime": "application/pdf",
      "pdf_base64": "JVBERi0xLjQKJ..."
    },
    {
      "filename": "beta-2025.pdf",
      "mime": "application/pdf",
      "pdf_base64": "JVBERi0xLjQKJ..."
    }
  ]
}

Responses

  • 201 — Created
    • id: string (uuid)
    • status: string
    • extraction_error: string
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
GET /agreement-templates/{id}

Get one template with full schema and source files

Agreement templates

Scopes: templates:read

Responses

  • 200 — OK
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
DELETE /agreement-templates/{id}

Delete template and its source files

Agreement templates

Scopes: templates:manage

Responses

  • 204 — Deleted
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
POST /agreement-templates/{id}/generate

Generate a new agreement from this template

Agreement templates

Scopes: generations:create

Utan `send_for_signing`: returnerar DOCX inline (base64). Med `send_for_signing: true` + `recipients`: renderar PDF, skapar en Sign-draft, returnerar `sign_document_id`.

Request body

  • form: object required
  • send_for_signing: boolean
  • title: string
  • message: string
  • signature_method: string
  • recipients: array
Example
{
  "form": {
    "customer": {
      "company": "Acme AB",
      "orgnr": "556677-8899",
      "contact": "Anna Andersson",
      "email": "anna@acme.se"
    },
    "selections": [
      {
        "partId": "seats",
        "moduleId": "pro",
        "quantity": 5
      }
    ],
    "binding": 24,
    "startDate": "2026-07-01"
  },
  "send_for_signing": true,
  "signature_method": "bankid",
  "recipients": [
    {
      "email": "anna@acme.se",
      "name": "Anna Andersson"
    }
  ]
}

Responses

  • 201 — OK
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error

Agreement generations

Genererade avtal från mallar (DOCX / Sign)

GET /agreement-generations

List generated agreements

Agreement generations

Scopes: generations:read

Responses

  • 200 — OK
    • generations: array
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
GET /agreement-generations/{id}

Get one generation

Agreement generations

Scopes: generations:read

Responses

  • 200 — OK
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error
GET /agreement-generations/{id}/download

Get a signed download URL for the generated PDF (10 min TTL)

Agreement generations

Scopes: generations:read

Endast tillgängligt om genereringen kopplats till ett Sign-dokument (har `storage_path`). DOCX-genereringar returneras inline direkt vid `/generate`.

Responses

  • 200 — OK
    • url: string (uri)
    • filename: string
    • expires_in: integer
  • 400 — Invalid request
  • 401 — Missing or invalid bearer token
  • 403 — Forbidden — missing scope, wrong plan, or sub-account license required
  • 404 — Not found
  • 409 — Conflict
  • 429 — Rate limit exceeded
  • 500 — Server error

Untagged

PARAMETERS /agreements/{id}

PARAMETERS /agreements/{id}/versions

PARAMETERS /agreements/{id}/signed-pdf

PARAMETERS /agreements/{id}/lifecycle

PARAMETERS /agreements/{id}/participants

PARAMETERS /agreements/{id}/participants/{pid}/decline

PARAMETERS /agreements/{id}/participants/{pid}/delegate

PARAMETERS /agreements/{id}/participants/{pid}/notifications

PARAMETERS /agreements/{id}/download

PARAMETERS /agreements/{id}/events

PARAMETERS /agreements/{id}/documents/main

PARAMETERS /agreements/{id}/chat

PARAMETERS /agreements/{id}/comments

PARAMETERS /agreements/{id}/comments/{commentId}

PARAMETERS /webhooks/{id}

PARAMETERS /clients/{id}/revoke

PARAMETERS /agreement-templates/{id}

PARAMETERS /agreement-templates/{id}/generate

PARAMETERS /agreement-generations/{id}

PARAMETERS /agreement-generations/{id}/download

PARAMETERS /sub-accounts/{id}

PARAMETERS /sub-accounts/{id}/parent-access

PARAMETERS /sub-accounts/{id}/members

PARAMETERS /sub-accounts/{id}/members/{memberId}

Schemas

Shared response and request shapes referenced above.

Error

  • error: object required

AgreementParty

  • id: string (uuid)
  • name: string
  • email: string (email)
  • role: string
  • order: integer
  • job_title: string
  • phone: string
  • personal_id_masked: string
  • notifications_enabled: boolean
  • status: string
  • declined_at: string (date-time) — Tidpunkt då deltagaren avvisade signeringen.
  • decline_reason: string — Fritextorsak som deltagaren angav vid avvisning. Skickas även med i webhooken agreement.declined.

Agreement

  • id: string (uuid)
  • status: string
  • title: string
  • message: string
  • expires_at: string (date-time)
  • parties: array
  • external_id: string
  • external_source: string
  • external_url: string (uri)
  • metadata: object
  • version: integer — Versionsnummer i kedjan (1 = första).
  • replaces_document_id: string (uuid) — Pekar på det avtal som denna version ersätter.
  • superseded_at: string (date-time) — Sätts när avtalet ersatts av en nyare version.
  • versions: array — Hela versionskedjan (endast med i responsen när avtalet ingår i en kedja, dvs `version > 1` eller är superseded).
  • auto_reminders_enabled: boolean
  • reminder_interval_days: integer
  • max_reminders: integer
  • webhook_url: string (uri) — Avtalets egen webhook-URL. null = klientens registrerade webhook-URL används (eller `callback_url` om den är satt på avtalet).
  • completed_copy_emails: array — Mejladresser som får en kopia av bekräftelsemejlet med den signerade PDF:en bifogad när avtalet blir completed. null = ärver kontots inställning (Inställningar i appen).
  • completed_delivery: string — Leveranssätt satt på avtalet. null = kontots standard gäller.
  • signed_pdf_url: string (uri) — Short-lived signed URL to the stamped final PDF (only present after completion). Alltid null vid säker leverans (`secure_link`) — hämta då filen med API-nyckeln via `GET /agreements/{id}/signed-pdf`.
  • signed_at: string (date-time)
  • signed_sha256: string — Hex SHA-256 of the stamped final PDF.
  • created_at: string (date-time)
  • updated_at: string (date-time)

SubAccount

  • id: string (uuid)
  • parent_account_id: string (uuid)
  • name: string
  • org_number: string
  • plan: string
  • inherit_parent_admins: boolean
  • archived_at: string (date-time)
  • created_at: string (date-time)

SubAccountMember

  • id: string (uuid)
  • user_id: string (uuid)
  • email: string (email)
  • role: string
  • status: string
  • created_at: string (date-time)

Webhook

  • id: string (uuid)
  • url: string (uri)
  • events: array
  • secret_preview: string — First 8 chars of the signing secret
  • created_at: string (date-time)

Event

  • id: integer — Sekvensnummer — skicka som `since_id` för att hämta senare händelser.
  • event: string — Händelsetyp, t.ex. `agreement.completed`.
  • event_type: string
  • event_id: string — Stabil idempotensnyckel för händelsen.
  • created_at: string (date-time)
  • agreement_id: string (uuid)
  • data: object — Händelsens payload.

ApiClient

  • id: string (uuid)
  • client_id: string
  • name: string
  • status: string
  • scopes: array
  • last_used_at: string (date-time)
  • created_at: string (date-time)
  • revoked_at: string (date-time)

AgreementTemplateSummary

  • id: string (uuid)
  • name: string
  • status: string
  • language: string
  • currency: string
  • source_uploads_count: integer
  • created_at: string (date-time)
  • updated_at: string (date-time)

AgreementTemplate

AgreementGeneration

  • id: string (uuid)
  • template_id: string (uuid)
  • agreement_number: string
  • monthly_total_ore: integer — Total månadsbelopp i öre inkl. moms
  • sign_document_id: string (uuid)
  • storage_path: string
  • form_state: object
  • created_at: string (date-time)