Tidvis
Utvecklare · Enterprise

Tidvis Sign Public API

REST API v1 och webhooks för att bygga egna integrationer mot Tidvis Sign. OAuth2 client credentials, signerade webhooks och fullt avtalsflöde.

Snabbstart

Base URL: https://tidvis.se/api/public/v1. Alla anrop använder JSON. Public API är en del av Enterprise-planen.

  1. 1. Skapa en API-klient under Sign → Inställningar → API och spara client_id och client_secret.
  2. 2. Hämta en access_token via OAuth2 client credentials.
  3. 3. Skapa ett avtal, ladda upp en PDF, lägg till deltagare och skicka.
curl -X POST https://tidvis.se/api/public/v1/oauth/access-token \
  -H "Content-Type: application/json" \
  -d '{"client_id":"tvc_...","client_secret":"tvs_..."}'

En enda modell — API och plattform är samma avtal

Avtal skapade via API:t ligger i samma tabell och delar hela infrastrukturen med avtal skapade i webb-UI:t. Konkret betyder det:

  • Syns direkt i plattformen under fliken Via integration i /app/sign/dokument.
  • Går att öppna, patcha, påminna och avbryta både via API och i UI:t.
  • Ingår i samma auto-reminder-cron, arkiv och 18-månaders retention som native-avtal.
  • Deltagarnas signeringslänk är alltid https://tidvis.se/sign/<token> — samma flöde som avtal skapade i plattformen.
  • UI:t visar en Via {external_source}-badge (klickbar när external_url är satt) så administratören kan hoppa tillbaka till CRM-posten.
  • Skickar du med sender i requesten autosigneras avsändaren vid send — samma beteende som när en användare skickar från plattformen.

Autentisering

OAuth2 client credentials. Du byter client_id+client_secret mot en JWT som är giltig i 1 timme. Skicka Authorization: Bearer <access_token> på alla efterföljande anrop.

POST/oauth/access-token

Hämta en access token (1h TTL).

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "expires_at": "2026-06-07T13:00:00.000Z"
}
DELETE/oauth/access-token

Återkalla den aktuella token (kräver Authorization-header).

Avtal

POST/agreements

Skapa ett avtals-utkast (status: draft).

curl -X POST https://tidvis.se/api/public/v1/agreements \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"Anställningsavtal Anna","expires_in_days":14,"bankid_required":true,"sender":{"name":"Anna Andersson","email":"anna@tidvis.se"}}'

sender (valfritt) — avsändaren autosigneras vid send (skapar en is_sender=true-rad) och får inget signeringsmail. Samma beteende som UI-flödet. Kan även sättas vid PATCH (medan draft) eller skickas direkt i lifecycle-anropet: {"action":"send","sender":{...}}.

Body-fält (renderas från OpenAPI-specen)

FältTypBeskrivning
titleobligatorisktstring—
messagestring—
expires_atstring (date-time)—
expires_in_daysintegerAlternativ till expires_at — sätter expires_at till nu + N dagar. expires_at vinner om båda skickas.
callback_urlstring (uri)Äldre synonym till `webhook_url`. Används bara om `webhook_url` saknas. Nya integrationer ska använda `webhook_url`.
webhook_urlstring (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.(kan vara null)
metadataobject—
bankid_requiredboolean—
bankid_identify_requiredboolean—
document_source"sender" | "recipient"'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_idstring—
external_sourcestring—
external_urlstring (uri)—
completed_delivery"email_attachment" | "secure_link"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_recipientsarray[object] (max 5)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_emailsarray[email] (max 5)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.
senderobjectAvsändare — autosigneras vid send (samma beteende som UI-flödet). Läggs till som is_sender=true och skickar inget signeringsmail.
sender.namestring—
sender.emailobligatorisktstring (email)—
auto_remindersobjectAutomatic 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.
auto_reminders.enabledboolean—
auto_reminders.interval_daysinteger—
auto_reminders.max_remindersinteger—
GET/agreements

Lista avtal. Stödjer ?status, ?limit, ?cursor.

GET/agreements/:id

Hämta ett avtal med deltagare och status. Detta är även status-polling-endpointen — se nedan.

Svarsfält — Agreement (renderas från OpenAPI-specen)

FältTypBeskrivning
idstring (uuid)—
status"draft" | "sent" | "partially_signed" | "signed" | "completed" | "cancelled" | "declined" | "expired"—
titlestring—
messagestring—(kan vara null)
expires_atstring (date-time)—(kan vara null)
partiesarray[object]—
external_idstring—(kan vara null)
external_sourcestring—(kan vara null)
external_urlstring (uri)—(kan vara null)
metadataobject—(kan vara null)
versionintegerVersionsnummer i kedjan (1 = första).
replaces_document_idstring (uuid)Pekar på det avtal som denna version ersätter.(kan vara null)
superseded_atstring (date-time)Sätts när avtalet ersatts av en nyare version.(kan vara null)
versionsarray[object]Hela versionskedjan (endast med i responsen när avtalet ingår i en kedja, dvs `version > 1` eller är superseded).(kan vara null)
auto_reminders_enabledboolean—
reminder_interval_daysinteger—
max_remindersinteger—
webhook_urlstring (uri)Avtalets egen webhook-URL. null = klientens registrerade webhook-URL används (eller `callback_url` om den är satt på avtalet).(kan vara null)
completed_copy_emailsarray[email] (max 5)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).(kan vara null)
completed_delivery"email_attachment" | "secure_link"Leveranssätt satt på avtalet. null = kontots standard gäller.(kan vara null)
signed_pdf_urlstring (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`.(kan vara null)
signed_atstring (date-time)—(kan vara null)
signed_sha256stringHex SHA-256 of the stamped final PDF.(kan vara null)
created_atstring (date-time)—
updated_atstring (date-time)—
GET/agreements/:id/events

Audit-trail (sent, viewed, signed, completed...).

GET/agreements/:id/download

Hämta signerad PDF (returnerar signed URL eller binärt innehåll).

Kopia av signerade avtal (completed_copy_emails)

Skicka completed_copy_emails (upp till 5 adresser) när avtalet skapas via POST /agreements eller quick-send. Varje adress får bekräftelsemejlet med den signerade PDF:en bifogad när avtalet är klart. Utelämnas fältet (null) ärvas kontots inställning under Inställningar → Kopia av signerade avtal till. Fältet returneras på GET /agreements/:id — se fälttabellerna ovan.

Polla signeringsstatus

Tidvis Sign har ingen separat /status-resurs — GET /agreements/:id är pollingendpointen. Svaret innehåller avtalets status samt participants[].status och signed_at per part, så ni kan visa t.ex. "1 av 2 har signerat" i ert eget UI utan att vänta på webhook.

Rekommendation: använd webhooks (agreement.viewed, agreement.signed, agreement.completed) som primär kanal och fall tillbaka på polling med 30 s intervall när webhook inte är ett alternativ. Avtals-status kan vara draft, sent, partially_signed, completed, declined eller cancelled.

# Polla status och räkna signerade
curl -s https://tidvis.se/api/public/v1/agreements/$ID \
  -H "Authorization: Bearer $TOKEN" \
  | jq '{
      status,
      signed: ([.participants[] | select(.status == "signed")] | length),
      total: (.participants | length),
      participants: [.participants[] | {name, email, status, signed_at}]
    }'

Exempel-svar (1 av 2 signerade):

{
  "status": "partially_signed",
  "signed": 1,
  "total": 2,
  "participants": [
    { "name": "Anna Andersson", "email": "anna@example.com", "status": "signed",  "signed_at": "2026-06-17T09:14:22Z" },
    { "name": "Erik Eriksson",  "email": "erik@example.com",  "status": "viewed",  "signed_at": null }
  ]
}

CRM-länkning (external_id)

Länka varje avtal till motsvarande objekt i ert CRM (deal, opportunity, ärende) genom att skicka external_id, external_source och external_url när avtalet skapas. Fälten returneras på GET /agreements/:id och bifogas i alla webhook-payloads (agreement.sent, agreement.signed, ...) så ni kan koppla tillbaka eventet till rätt CRM-post utan att slå upp via metadata.

Detta är grunden för att integrera Tidvis Sign med vilket CRM som helst – HubSpot, Upsales, Pipedrive, Salesforce, Lime, SuperOffice – via Zapier, Make, n8n eller egen kod.

curl -X POST https://tidvis.se/api/public/v1/agreements \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Anställningsavtal Anna",
    "external_id": "deal_98231",
    "external_source": "hubspot",
    "external_url": "https://app.hubspot.com/contacts/123/deal/98231"
  }'

Webhook när avtalet signeras:

{
  "event": "agreement.signed",
  "delivery_id": "whd_...",
  "created_at": "2026-06-20T12:00:00Z",
  "agreement_id": "agr_...",
  "external_id": "deal_98231",
  "external_source": "hubspot",
  "participant": { "id": "p_...", "email": "anna@example.com" },
  "data": {
    "agreement_id": "agr_...",
    "external_id": "deal_98231",
    "external_source": "hubspot",
    "participant": { "id": "p_...", "email": "anna@example.com" }
  }
}

Låsa BankID till ett specifikt personnummer

Sätt signer_personal_number på en mottagare för att kräva att exakt den personen legitimerar sig och/eller signerar med BankID. Avvisas i BankID-appen om någon annan försöker. Kräver att avtalet har bankid_required: true och/eller bankid_identify_required: true. Fältet är write-only — det returneras aldrig i svar; använd personal_id_masked (t.ex. 19800101-XXXX) för visning.

{
  "title": "Anställningsavtal Anna",
  "bankid_required": true,
  "participants": [
    {
      "name": "Anna Andersson",
      "email": "anna@example.com",
      "signer_personal_number": "198001011234"
    }
  ]
}

Accepterat format: 10 eller 12 siffror, valfritt bindestreck (t.ex. 198001011234, 800101-1234). Luhn-valideras. Skickas personnummer utan att BankID är aktiverat på avtalet, eller om numret inte klarar Luhn-kontrollen, returneras 400 invalid_request.

Versionering & ersättning

När ni skickar POST /agreements med samma external_source + external_id som ett tidigare avtal skapar vi automatiskt en ny version i kedjan. Den föregående versionen markeras som superseded_at och auto-cancelleras om den fortfarande var öppen. Signeringslänkar från den gamla versionen slutar fungera.

  • Ärver send-läget: om föregående version var sent kopieras signatärer + avsändare, och den nya versionen skickas automatiskt så fort PDF:en laddats upp (eller när första participant lagts till om PDF redan finns).
  • Listning: GET /agreements returnerar per default bara aktuella versioner. Använd ?group=all för att inkludera superseded.
  • Historik: GET /agreements/:id/versions ger hela kedjan sorterad ASC. GET /agreements/:id inkluderar versions[] när avtalet ingår i en kedja.
  • Fält: version (1..N), replaces_document_id, superseded_at.
# Skicka en uppdaterad version av samma HubSpot-deal
curl -X POST https://tidvis.se/api/public/v1/agreements \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Anställningsavtal Anna (v2 – uppdaterad lön)",
    "external_id": "deal_98231",
    "external_source": "hubspot"
  }'

# Hämta hela kedjan
curl https://tidvis.se/api/public/v1/agreements/$NEW_ID/versions -H "Authorization: Bearer $TOKEN"

Idempotency

Skicka headern Idempotency-Key: <unikt-värde> på POST /agreements för att säkert kunna retry:a vid timeout/nätfel utan att skapa dubletter. Vi cachar svaret i 24 timmar; identiska anrop returnerar samma resultat med headern Idempotent-Replayed: true. Anrop med samma key men annan body returnerar 409 conflict.

Rekommendation: använd ert CRM:s deal-id + ett datum, eller en UUID per logiskt avtal.

curl -X POST https://tidvis.se/api/public/v1/agreements \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: deal_98231-2026-06-12" \
  -H "Content-Type: application/json" \
  -d '{"title":"Anställningsavtal Anna","external_id":"deal_98231","external_source":"hubspot"}'

Deltagare

POST/agreements/:id/participants

Lägg till en signatär (utkast-status).

{
  "name": "Anna Andersson",
  "email": "anna@example.com",
  "role": "signer",
  "job_title": "VD",
  "phone": "+46701234567",
  "personal_id_masked": "19800101-XXXX",
  "notifications_enabled": true
}
POST/agreements/:id/participants/:pid/decline

Avvisa avtalet för en specifik deltagare. Sätter avtalets status till 'declined'.

{ "reason": "Felaktiga villkor" }
POST/agreements/:id/participants/:pid/delegate

Delegera signeringen till en ny person. Befintlig deltagare markeras 'delegated'.

{
  "name": "Erik Eriksson",
  "email": "erik@example.com",
  "job_title": "CFO",
  "phone": "+46707654321",
  "message": "Hänvisar till Erik som ansvarar för signering."
}
PATCH/agreements/:id/participants/:pid/notifications

Slå av/på påminnelse-mejl för en enskild deltagare.

{ "enabled": false }
GET/agreements/:id/chat

Lista chat-meddelanden mellan agenten och deltagarna på ett avtal.

POST/agreements/:id/chat

Skicka ett chat-meddelande. Ange participant_id för att vidarebefordra ett meddelande från en deltagare. Triggar webhook agreement.chat_message.

{
  "body": "Hej, kan ni granska bilaga 2?",
  "sender_label": "Tidvis Sign Agent",
  "participant_id": "8a3..."
}

PDF-dokument

PUT/agreements/:id/documents/main

Ladda upp huvuddokument. Stöd för application/pdf (binärt) eller JSON med base64.

# JSON / base64
curl -X PUT https://tidvis.se/api/public/v1/agreements/$ID/documents/main \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"filename":"avtal.pdf","content_base64":"JVBERi0..."}'

# Eller binärt
curl -X PUT https://tidvis.se/api/public/v1/agreements/$ID/documents/main \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/pdf" \
  --data-binary @avtal.pdf

Livscykel

POST/agreements/:id/lifecycle

Skicka eller avbryt ett avtal.

{"action": "send"}   // eller "cancel"

OTP via e-post (standardmetod)

Alla avtal signeras som standard med e-post + engångskod (OTP). Ingen extra konfiguration krävs – när bankid_required är false eller utelämnas använder varje deltagare denna metod. Räknas som enkel elektronisk signatur (SES) enligt eIDAS.

Hur det fungerar

  • Mottagaren får en personlig länk till sin e-postadress (samma som i participants[].email).
  • En 6-siffrig engångskod skickas vid öppning av dokumentet, giltig i 15 minuter, max 5 försök.
  • Identiteten bekräftas innan signaturen registreras; IP, user agent, tidpunkt och kodverifiering loggas i audit-trailen.
  • Ingår på alla planer – ingen add-on eller styckkostnad.

Skapa OTP-avtal (default)

curl -X POST https://tidvis.se/api/public/v1/agreements \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Konsultavtal Anna",
    "expires_in_days": 14
  }'

Webhook-payload vid OTP-signering

{
  "event": "agreement.signed",
  "delivery_id": "whd_...",
  "created_at": "2026-06-11T08:42:11.000Z",
  "agreement_id": "agr_...",
  "participant": { "id": "p_...", "email": "anna@example.com" },
  "signature": {
    "method": "otp_email",
    "signer_name": "Anna Andersson",
    "email": "anna@example.com",
    "code_verified_at": "2026-06-11T08:41:53.000Z",
    "signed_at": "2026-06-11T08:42:11.000Z",
    "ip": "203.0.113.42",
    "user_agent": "Mozilla/5.0 ..."
  }
}

BankID

Alternativ till standardmetoden OTP via e-post när ni behöver avancerad elektronisk signatur (AES). Sätt bankid_required: true när du skapar avtalet (eller via PATCH innan det skickas). När avtalet skickas signerar varje deltagare med BankID på mobilen eller datorn istället för engångskod.

Krav

  • Kontot måste ha planen Sign Pro och tillvalet BankID aktiverat.
  • Pris: 100 BankID-signeringar ingår per månad, därefter 5 kr/signering enligt prislista.
  • Saknar kontot tillvalet returnerar POST /agreements/:id/lifecycle med action: "send" felet 402 bankid_disabled.

Skapa BankID-avtal

curl -X POST https://tidvis.se/api/public/v1/agreements \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Anställningsavtal Anna",
    "bankid_required": true
  }'

Svar (förkortat):

{
  "id": "agr_...",
  "status": "draft",
  "bankid_required": true,
  "document": { "uploaded": false, "sha256": null },
  "participants": []
}

Webhook-payload när BankID används

För avtal med bankid_required: true kompletteras agreement.signed-händelsen med ett signature-block. Av integritetsskäl exponeras varken personnummer, certifikat eller OCSP-svar via API:t eller webhooks.

{
  "event": "agreement.signed",
  "delivery_id": "whd_...",
  "created_at": "2026-06-11T08:42:11.000Z",
  "agreement_id": "agr_...",
  "participant": { "id": "p_...", "email": "anna@example.com" },
  "signature": {
    "method": "bankid",
    "signer_name": "Anna Andersson",
    "signed_at": "2026-06-11T08:42:11.000Z"
  }
}

Fältet signature.method är alltid en av "otp_email" eller "bankid". Avtal med blandade deltagare loggar metoden per signatär.

BankID-identifiering innan öppning

Separat tillval som tvingar mottagaren att legitimera sig med BankID innan dokumentet visas. Detta är en gate framför själva PDF:en och är oberoende av vald signaturmetod — du kan alltså kräva BankID-legitimering och sedan låta signering ske med OTP via e-post, klick-signatur eller penn-signatur. Kombineras gärna med bankid_required: true när du vill ha både identifiering före öppning och BankID-signering vid undertecknande.

Slå på vid skapande

curl -X POST https://tidvis.se/api/public/v1/agreements \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "NDA Anna",
    "bankid_identify_required": true
  }'

Svar (förkortat):

{
  "id": "agr_...",
  "status": "draft",
  "bankid_required": false,
  "bankid_identify_required": true,
  "participants": []
}

Beteende för mottagaren

  • När mottagaren öppnar länken visas en BankID-gate istället för PDF:en.
  • Mottagaren måste starta BankID på mobil eller via QR-kod och bekräfta legitimering.
  • Först därefter öppnas PDF:en och vald signaturmetod blir aktiv.
  • "viewed"-händelsen loggas efter legitimeringen, inte vid första klicket på länken.

Krav och kostnad

  • Kontot måste ha Sign Pro och tillvalet BankID aktiverat — samma krav som bankid_required.
  • Identifieringar räknas inte mot BankID-kvoten på 100 signeringar/mån. Endast faktiska BankID-signeringar (bankid_required: true) debiteras 5 kr/styck efter inkluderade.
  • Saknar kontot tillvalet returnerar POST /agreements/:id/lifecycle med action: "send" felet 402 bankid_disabled.

Webhook-payload

En extra händelse agreement.identified sänds när en mottagare legitimerat sig. Personnummer, certifikat och OCSP-svar exponeras aldrig via API:t.

{
  "event": "agreement.identified",
  "delivery_id": "whd_...",
  "created_at": "2026-06-12T10:21:04.000Z",
  "agreement_id": "agr_...",
  "participant": { "id": "p_...", "email": "anna@example.com" },
  "identification": {
    "method": "bankid",
    "signer_name": "Anna Andersson",
    "identified_at": "2026-06-12T10:21:04.000Z"
  }
}

Avtalsgenerator

Bygg en AI-driven avtalsmall från 2–10 exempelavtal och generera sedan nya avtal med bara ett anrop. Genererade avtal kan laddas ner som DOCX ellerskickas direkt till signering via Sign — i så fall får du tillbaka ett sign_document_id.

Krav & pris

  • Avtalsmodulen måste vara aktiv på kontot (99 kr/användare/månad). Annars returneras 402 plan_required.
  • Vid MCP/agent-anrop tillkommer 3 kr per generering ovanpå modulpriset.
  • Källdokument max 20 MB per fil, 2–10 filer per mall (PDF eller DOCX).

Scopes

  • templates:read — lista och hämta mallar
  • templates:manage — skapa och radera mallar
  • generations:create — generera nya avtal från mall
  • generations:read — lista, hämta och ladda ner genererade avtal
  • Om signature_method: "bankid" används i generate krävs även agreements:send.
GET/agreement-templates

Lista sparade avtalsmallar.

POST/agreement-templates

Skapa mall från 2–10 exempelavtal. Multipart (files[]) eller JSON med base64 (sources[]).

curl -X POST https://tidvis.se/api/public/v1/agreement-templates \
  -H "Authorization: Bearer $TOKEN" \
  -F "name=Konsultavtal" \
  -F "files=@konsult1.pdf" \
  -F "files=@konsult2.pdf" \
  -F "files=@konsult3.docx"
GET/agreement-templates/:id

Hämta ett mallschema (fält, block, källor).

DELETE/agreement-templates/:id

Radera en mall (permanent).

POST/agreement-templates/:id/generate

Generera nytt avtal från mall. Returnerar DOCX inline (default) eller ett skickat Sign-utkast om send_for_signing=true och recipients anges.

{
  "values": {
    "kund_namn": "Acme AB",
    "belopp": 45000,
    "startdatum": "2026-08-01"
  },
  "send_for_signing": true,
  "signature_method": "bankid",
  "recipients": [
    { "name": "Anna Andersson", "email": "anna@acme.se" }
  ]
}
GET/agreement-generations?template_id=...

Lista genererade avtal (filtrera valfritt på template_id).

GET/agreement-generations/:id

Hämta metadata om en generering.

GET/agreement-generations/:id/download

Signerad nedladdnings-URL (giltig 10 min) till genererad PDF/DOCX.

Fullständiga request/response-scheman finns i OpenAPI-referensen.

Underkonton (multi-tenant)

Hantera underkonton (dotterbolag, avdelningar, franchises) under huvudkontot. Kräver en aktiv årslicens (1 188 kr/år per underkonto, lookup-key sub_account_license_yearly) som aktiveras i Tidvis Sign → Underkonton. Alla endpoints kräver att din token tillhör huvudkontot, inte ett underkonto.

Scopes

  • sub_accounts:read — läs lista och detaljer
  • sub_accounts:manage — skapa, uppdatera, arkivera, ändra parent-access
  • sub_accounts:members — hantera medlemmar och roller

Scopes är opt-in per token — gå till Inställningar → API för att tilldela dem.

GET/sub-accounts

Lista underkonton. ?include_archived=true tar med arkiverade.

POST/sub-accounts

Skapa nytt underkonto. Kräver aktiv årslicens.

curl -X POST https://tidvis.se/api/public/v1/sub-accounts \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stockholmskontoret",
    "org_number": "5560000000",
    "inherit_parent_admins": true
  }'
GET/sub-accounts/:id

Hämta ett underkonto.

PATCH/sub-accounts/:id

Uppdatera namn eller org.nummer.

DELETE/sub-accounts/:id

Arkivera (soft-delete). Hård radering sker 30 dagar senare via daglig GDPR-purge.

PATCH/sub-accounts/:id/parent-access

Slå av/på automatisk åtkomst för huvudkontots ägare/admins.

{ "inherit_parent_admins": false }
GET/sub-accounts/:id/members

Lista medlemmar på underkontot.

POST/sub-accounts/:id/members

Bjud in ny medlem via e-post.

{ "email": "anna@example.com", "role": "admin" }
PATCH/sub-accounts/:id/members/:memberId

Ändra medlemsroll (owner/admin/member). Sista ägaren kan inte degraderas.

DELETE/sub-accounts/:id/members/:memberId

Ta bort medlem. Sista ägaren kan inte tas bort.

Felkoder (specifika för underkonton)

  • 403 license_required — ingen aktiv årslicens på huvudkontot
  • 403 license_seat_limit — alla seats på licensen är upptagna
  • 403 plan_limit — planen tillåter inte fler underkonton
  • 403 parent_only — token tillhör ett underkonto; använd huvudkontots token
  • 404 not_found — underkontot finns inte eller tillhör annat huvudkonto
  • 409 conflict — underkontot är arkiverat

GDPR / retention

Arkiverade underkonton raderas permanent efter 30 dagar via ett dagligt jobb (purge_archived_sub_accounts()) som tar bort kontot, medlemmar, inbjudningar samt tillhörande dokument i storage. Alla skrivningar via API:t loggas i sign_account_hierarchy_audit med actor_api_client_id så ni kan spåra exakt vilken token som utförde åtgärden.

CRM (read-only)

Underkonton exponeras inte via CRM-API:t som skrivbara entiteter — CRM:t är read-only enligt befintlig spec. Använd /v1/sub-accounts för all hantering.

Webhooks

Tidvis levererar händelser till din URL via POST med JSON. Varje request är signerad med HMAC-SHA256 i headern X-Tidvis-Signature: sha256=<hex> baserat på din signing_secret och raw body. Eventnamnet finns både i X-Tidvis-Event och i JSON-bodyts event/event_type. Vi retryar misslyckade leveranser med exponentiell backoff i upp till 24h.

Vill du att olika avtal ska rapporteras till olika mottagare (t.ex. en URL per kundmiljö) skickar du webhook_url när avtalet skapas — då levereras alla händelser för det avtalet dit i stället för till den registrerade URL:en. Ingen förhandsregistrerad webhook behövs — adressen fungerar fristående och får alla händelser för avtalet, signerade med klientens signeringsnyckel. Alternativt hämtar du händelser med GET /events.

POST/webhooks

Registrera en webhook.

{
  "client_id": "<api_client uuid>",
  "url": "https://din-app.se/webhooks/tidvis",
  "events": ["agreement.sent","agreement.signed","agreement.completed"]
}
GET/webhooks

Lista registrerade webhooks.

DELETE/webhooks/:id

Ta bort en webhook.

Händelser

  • agreement.sent
  • agreement.viewed
  • agreement.identified
  • agreement.signed
  • agreement.completed
  • agreement.cancelled
  • agreement.declined
  • agreement.delegated
  • participant.notifications_changed
  • agreement.chat_message
  • agreement.comment_created
  • agreement.comment_resolved
  • agreement.expired
  • agreement.pdf_replaced
  • agreement.superseded
  • *

För avtal med bankid_required: true innehåller agreement.signed/agreement.completed även ett signature-block. Se BankID.

Varje leverans har ett event_id i bodyn. Samma händelse har alltid samma event_id — använd det för att filtrera bort upprepningar. agreement.completed innehåller dessutom participants och document.signed_pdf_url (hämta PDF:en med din API-nyckel).

Verifiera signatur (Node.js)

import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, header, secret) {
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  const given = (header || "").replace(/^sha256=/, "");
  if (given.length !== expected.length) return false;
  return timingSafeEqual(Buffer.from(expected), Buffer.from(given));
}

Felkoder

HTTPCodeBetydelse
400invalid_requestFelaktig input.
401unauthorizedSaknad/ogiltig token.
402plan_requiredKontot saknar Enterprise-plan. Innehåller upgrade_url.
402bankid_disabledAvtalet har bankid_required: true men kontot saknar BankID-tillvalet.
403forbiddenSaknar rättighet till resursen.
404not_foundResursen hittades inte.
409conflictOgiltigt tillståndsbyte (t.ex. skicka redan skickat).
429rate_limitedFör många anrop.
500server_errorServerfel – försök igen.

Rate limits

Standardgräns: 60 requests/minut per klient och 600/timme för uppladdningar. Vid överskridning får du 429 rate_limited med Retry-After-header. Behöver du mer? Kontakta oss.

MCP & AI-agenter

Tidvis Sign exponerar en Model Context Protocol-server så AI-agenter (ChatGPT, Claude Desktop, egna agenter) kan skapa, skicka och följa avtal med ett enkelt verktygsanrop. Discovery-manifest finns på /.well-known/mcp.json.

Endpoint

POST https://tidvis.se/api/mcp
Authorization: Bearer <access_token>
Content-Type: application/json
Accept: application/json, text/event-stream

Scopes

API-tokens kan begränsas till specifika scopes. För MCP krävs mcp:connect + valfri kombination av:

  • agreements:read – hämta avtal & events
  • agreements:create – skapa utkast & deltagare
  • agreements:send – skicka avtal till signering
  • agreements:cancel – avbryt avtal
  • billing:checkout – generera checkout-länk
  • templates:read / templates:manage – hantera avtalsmallar
  • generations:create / generations:read – generera & läsa avtal från mall
  • mcp:connect – krävs för MCP-åtkomst

Tillgängliga verktyg

  • tidvis_sign_create_agreement
  • tidvis_sign_add_participant
  • tidvis_sign_upload_pdf
  • tidvis_sign_send_agreement
  • tidvis_sign_cancel_agreement
  • tidvis_sign_get_agreement
  • tidvis_sign_list_events
  • tidvis_sign_create_checkout
  • tidvis_sign_decline_agreement
  • tidvis_sign_delegate_signing
  • tidvis_sign_set_participant_notifications
  • tidvis_sign_list_chat_messages
  • tidvis_sign_post_chat_message
  • tidvis_sign_list_agreement_templates
  • tidvis_sign_get_agreement_template
  • tidvis_sign_create_agreement_template
  • tidvis_sign_generate_agreement
  • tidvis_sign_list_agreement_generations
  • tidvis_sign_get_agreement_generation

Claude Desktop-konfiguration

{
  "mcpServers": {
    "tidvis-sign": {
      "url": "https://tidvis.se/api/mcp",
      "headers": { "Authorization": "Bearer YOUR_ACCESS_TOKEN" }
    }
  }
}

Agent-checkout & pay-per-agreement

Två sätt för en agent att låta slutkunden betala:

  • Sign Pro (prenumeration) – månadsavgift per säte, obegränsat antal avtal. Använd tidvis_sign_create_checkout med product: "sign_pro_monthly" och valfritt antal seats.
  • Pay-per-agreement – 19 SEK/avtal som förbetalda krediter, perfekt för agenter med låg volym. Använd product: "sign_per_agreement" och valfritt antal krediter (1–500). Krediter dras automatiskt när send_agreement körs på free-plan-konton.

Checkout-verktyget returnerar en Stripe-hostad URL som agenten visar för slutkunden. Vid 402 plan_required på send_agreement är konto utan krediter och utan Sign Pro – svaret innehåller upgrade_url som agenten kan länka till.

Redo att se Tidvis?

Boka en förutsättningslös demo. Vi visar systemet utifrån just din verksamhet, utan säljpress.