Udviklere

API og MCP

Send dokumenter til underskrift med MitID, BankID eller tegnet underskrift direkte fra jeres egne systemer, eller lad en AI-assistent som Claude gøre det for jer.

API'et og MCP-serveren kan det samme som Forsegl-appen. De bruger de samme regler, rettigheder og den samme API-nøgle, så det, I bygger med det ene, virker også med det andet.

TjenesteAdresse
REST APIhttps://app.forsegl.dk/api/v1
MCP-serverhttps://app.forsegl.dk/mcp (Streamable HTTP)
OpenAPI 3.1https://app.forsegl.dk/api/v1/openapi.json

API-nøgler

Opret en nøgle i appen under API og MCP. Nøglen starter med fsg_ og vises kun én gang, så gem den et sikkert sted, f.eks. i en password manager eller jeres secrets-håndtering.

Send nøglen i Authorization-headeren på hver forespørgsel:

Authorization: Bearer fsg_…
  • En nøgle handler på vegne af den bruger, der oprettede den. Sager, den opretter, får brugeren som afsender.
  • Nøglen har adgang til hele organisationens sager, mapper, brands og webhooks.
  • Nøglen kan ikke ændre adgangskoder eller oprette og tilbagekalde andre nøgler. Det kræver, at man er logget ind i appen.
  • Tilbagekald en nøgle i appen, så holder den op med at virke med det samme. Brug én nøgle pr. integration eller person.

Hurtigstart

Fire kald fra kladde til sendt sag. Underskriverne får en mail med et personligt link.

# 1. Opret en sag
curl https://app.forsegl.dk/api/v1/cases \
  -H "Authorization: Bearer $FORSEGL_KEY" -H "Content-Type: application/json" \
  -d '{"title": "Lejekontrakt, Vesterbrogade 14"}'
# → {"id": "6f1c…", "reference": "SG-00042", "status": "draft", …}

# 2. Tilføj en PDF (base64 eller en offentlig https-adresse)
curl https://app.forsegl.dk/api/v1/cases/6f1c…/documents \
  -H "Authorization: Bearer $FORSEGL_KEY" -H "Content-Type: application/json" \
  -d '{"documents": [{"name": "Lejekontrakt.pdf", "url": "https://example.com/lejekontrakt.pdf"}]}'

# 3. Tilføj underskrivere
curl -X PUT https://app.forsegl.dk/api/v1/cases/6f1c…/signers \
  -H "Authorization: Bearer $FORSEGL_KEY" -H "Content-Type: application/json" \
  -d '{"signers": [{"name": "Anna Holm", "email": "anna@example.com"},
                   {"name": "Bo Lund", "email": "bo@example.com", "role": "copy"}]}'

# 4. Send til underskrift
curl -X POST https://app.forsegl.dk/api/v1/cases/6f1c…/send \
  -H "Authorization: Bearer $FORSEGL_KEY"

Følg sagen med GET /cases/{id} eller en webhook. Når status er completed, henter I den underskrevne PDF med GET /cases/{id}/documents/{documentId}/file og signaturbeviset med GET /cases/{id}/certificate.pdf.

Forbind en AI-assistent (MCP)

Forsegl har en MCP-server (Model Context Protocol), så AI-assistenter kan arbejde direkte i Forsegl: oprette sager, tilføje dokumenter og underskrivere, sende, minde om, følge status og hente underskrevne dokumenter. Assistenten bruger jeres API-nøgle og kan det samme som API'et.

Claude Code

claude mcp add --transport http forsegl https://app.forsegl.dk/mcp \
  --header "Authorization: Bearer fsg_…"

Claude Desktop

Åbn Indstillinger → Udvikler → Rediger konfiguration, tilføj følgende, og genstart Claude. Kræver Node.js.

{
  "mcpServers": {
    "forsegl": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://app.forsegl.dk/mcp",
               "--header", "Authorization: Bearer fsg_…"]
    }
  }
}

Cursor, VS Code, Windsurf og andre

Klienter, der understøtter MCP over HTTP med egne headere:

{
  "mcpServers": {
    "forsegl": {
      "type": "http",
      "url": "https://app.forsegl.dk/mcp",
      "headers": { "Authorization": "Bearer fsg_…" }
    }
  }
}
Assistenten beder om lov, før den kalder værktøjer, der sender mails til rigtige mennesker, f.eks. send_case og remind_signers. Tjek modtagerne, før du godkender.

MCP-værktøjer

Hvert værktøj svarer til et eller flere API-kald. Svarene er JSON, og PDF'er returneres som filer, assistenten kan gemme eller læse.

OmrådeVærktøjer
Sagerlist_cases get_case create_case update_case duplicate_case delete_case
Forløbsend_case remind_signers cancel_case extend_deadline set_access_period refresh_case_status
Dokumenteradd_documents add_sample_document rename_document reorder_documents remove_document download_document download_certificate get_evidence
Underskrivereset_signers revoke_signer_link renew_signer_link
Mapperlist_folders create_folder update_folder delete_folder
Brandslist_brands get_brand create_brand update_brand set_default_brand delete_brand upload_brand_image remove_brand_image set_brand_domain verify_brand_domain remove_brand_domain
Webhookslist_webhooks create_webhook update_webhook delete_webhook test_webhook
Kontowhoami get_stats list_activity list_contacts list_emails

create_case kan gøre alt i ét trin: titel, besked, frist, dokumenter, underskrivere og eventuelt afsendelse.

Eksempler på prompts

  • "Send vedhæftede lejekontrakt til underskrift hos Anna Holm (anna@example.com) med frist på fredag."
  • "Hvilke sager venter stadig på underskrift, og hvem mangler?"
  • "Send en påmindelse til alle, der ikke har underskrevet aftalerne i mappen Ansættelser."
  • "Hent de underskrevne dokumenter og signaturbeviset for SG-00042."
  • "Lav et nyt brand til datterselskabet med vores grønne farve, og brug det på mappen Nordic."

Konventioner

  • Alle forespørgsler og svar er JSON (Content-Type: application/json), undtagen fil-downloads.
  • Datoer er ISO 8601 i UTC, f.eks. 2026-12-01T22:59:00.000Z.
  • Id'er er UUID'er. Sagsnummeret (reference, f.eks. SG-00042) er til mennesker og kan bruges i søgning.
  • Ændringer returnerer den opdaterede sag, så I sjældent behøver at hente den igen.
  • En sag er en kladde (draft), indtil den sendes. Dokumenter, underskrivere og de fleste felter kan kun ændres i en kladde.

Sagens status

StatusBetydning
draftKladde, ikke sendt
pendingSendt og afventer underskrifter
completedAlle har underskrevet. Underskrevne PDF'er og signaturbevis kan hentes
rejectedEn underskriver har afvist
expiredFristen er overskredet. Kan forlænges
cancelledAnnulleret af afsender

Sager

EndpointBeskrivelse
GET/casesListe. Filtre: status (all, draft, pending, completed, rejected, expired, cancelled, action), folder, q
POST/casesOpret kladde: title, folderId, language
GET/cases/{id}Sag med dokumenter, underskrivere (inkl. personlige links) og hændelseslog
PATCH/cases/{id}title, message, language, sequential, expiresAt, reminderDays, folderId, brandId. Efter afsendelse kun title, folderId og reminderDays
DELETE/cases/{id}Slet en sag, der ikke er i gang
POST/cases/{id}/sendSend til underskrift
POST/cases/{id}/remindPåmind alle, der mangler, eller én: {"signerId": "…"}
POST/cases/{id}/cancelAnnullér en igangværende sag
POST/cases/{id}/extendNy frist: {"expiresAt": "…"}
POST/cases/{id}/accessHvor længe underskrivere kan åbne de underskrevne dokumenter: {"accessUntil": "…" | null}
POST/cases/{id}/duplicateKopiér til ny kladde
POST/cases/{id}/syncHent seneste status fra eID-udbyderen
GET/cases/{id}/certificate.pdfSignaturbevis (fuldførte sager)
GET/cases/{id}/evidenceRå eID-bevis pr. underskriver: ID-token signeret af eID-udbyderen (med nøglen) eller BankID-signaturen, og dokument-bindingen. nonce = base64url(SHA-256(binding)), så beviset kan efterprøves uafhængigt af Forsegl. Indeholder personoplysninger.
GET/cases/{id}/archive.zipAlle dokumenter og signaturbeviset som ZIP

Dokumenter

EndpointBeskrivelse
POST/cases/{id}/documentsTilføj PDF'er (højst 20 pr. kald, 25 MB pr. fil)
POST/cases/{id}/documents/sampleTilføj et eksempeldokument til test
PATCH/cases/{id}/documents/{docId}Omdøb: {"name": "…"}
PUT/cases/{id}/documents/orderRækkefølge: {"ids": ["…", "…"]}
DELETE/cases/{id}/documents/{docId}Fjern fra kladde
GET/cases/{id}/documents/{docId}/filePDF'en. version=signed (standard) eller original, download=1 for vedhæftning

Dokumenter kan sendes som JSON med base64 eller en offentlig https-adresse, som Forsegl henter, eller som multipart/form-data i feltet files:

{
  "documents": [
    { "name": "Kontrakt.pdf", "contentBase64": "JVBERi0xLjcK…" },
    { "name": "Bilag 1.pdf",  "url": "https://example.com/bilag-1.pdf" }
  ]
}

Svaret indeholder sagen samt added (nye dokument-id'er) og rejected (navne på filer, der ikke var gyldige PDF'er eller ikke kunne hentes).

Underskrivere

EndpointBeskrivelse
PUT/cases/{id}/signersErstat listen af underskrivere og kopimodtagere i en kladde
POST/cases/{id}/signers/{signerId}/revokeSpær et link, f.eks. hvis mailen gik til en forkert
POST/cases/{id}/signers/{signerId}/renewUdsted et nyt link og send det: {"notify": true}
{
  "signers": [
    { "name": "Anna Holm", "email": "anna@example.com", "title": "Direktør", "authMethod": "mitid" },
    { "name": "Erik Berg", "email": "erik@example.se", "language": "en", "authMethod": "bankid_se" },
    { "name": "Revisor", "email": "revisor@example.com", "role": "copy" }
  ]
}
FeltBeskrivelse
rolesigner (standard) underskriver, copy modtager kun de underskrevne dokumenter
authMethodmitid, mitid_business, bankid_se, bankid_no eller drawn (tegnet underskrift). Udelades den, bruges brandets standard
languageda eller en til mails og underskriftsside. Standard er sagens sprog
idMedtag id for at beholde en eksisterende underskriver, når listen erstattes

Med "sequential": true på sagen underskriver de i listens rækkefølge, og den næste får først mail, når den forrige har underskrevet.

Mapper og brands

EndpointBeskrivelse
GET/foldersMapper med antal sager
POST/foldersOpret: name, color, brandId
PATCH/folders/{id}Ret navn, farve eller brand
DELETE/folders/{id}Slet mappen. Sagerne bevares
GET/brandsBrand-profiler
POST/brandsOpret: name, evt. copyFrom
GET/brands/{id}Profil med alle indstillinger: farver, skrifttype, tekster, mailskabeloner, underskriftsforløb
PATCH/brands/{id}name og/eller hele settings-objektet
POST/brands/{id}/defaultGør til organisationens standard
DELETE/brands/{id}Slet (ikke standarden)
POST/brands/{id}/assets/{kind}Billede (logo, logoDark, icon, cover): {"contentBase64": "…"}
PUT/brands/{id}/domainEget domæne til underskriftslinks: {"domain": "sign.firma.dk"}
POST/brands/{id}/domain/verifyKontrollér DNS og aktivér domænet

Konto

EndpointBeskrivelse
GET/meBrugeren og organisationen bag nøglen
GET/statsAntal sager pr. status, fuldført denne måned, gennemsnitlig tid til underskrift
GET/activitySeneste hændelser på tværs af sager (limit op til 100)
GET/contactsTidligere underskrivere (q til søgning)
GET/outboxMails sendt af Forsegl med leveringsstatus

Fejl og grænser

Fejl returnerer en HTTP-statuskode og en maskinlæsbar kode:

{ "error": "not_draft" }
StatusEksempler
400validation (med issues), not_draft, no_documents, no_signers, not_completed
401unauthorized, invalid_api_key
403session_required (kun muligt i appen)
404not_found: findes ikke i jeres organisation
413file_too_large
429too_many_requests, se headeren Retry-After

Grænser: 600 API-kald og 300 MCP-kald i minuttet pr. nøgle. 25 MB pr. PDF, 20 dokumenter pr. kald og 50 underskrivere pr. sag.

Webhooks

Tilføj en https-adresse under API og MCP i appen eller via API'et, så sender Forsegl en POST med JSON, når der sker noget i en sag.

EndpointBeskrivelse
GET/webhooksEndpoints med seneste leveringsresultat
POST/webhooksOpret: {"url": "https://…", "events": ["case.completed"]}. Svaret indeholder secret
GET/webhooks/{id}Ét endpoint inkl. secret
PATCH/webhooks/{id}Ret url eller events
DELETE/webhooks/{id}Fjern
POST/webhooks/{id}/testSend en ping-hændelse
GET/webhooks/eventsHændelser, der kan abonneres på

Svar med en 2xx-status inden for 10 sekunder. Fejler leveringen, prøver Forsegl igen efter 30 sekunder, 5 minutter og 30 minutter. Den samme hændelse kan i sjældne tilfælde komme mere end én gang, så brug id til at se bort fra dubletter.

Hændelser

HændelseHvornår
case.sentSagen er sendt til underskrift
signer.openedEn underskriver har åbnet sit link
signer.signedEn underskriver har underskrevet
signer.rejectedEn underskriver har afvist
case.completedAlle har underskrevet, og dokumenterne er klar
case.cancelledSagen er annulleret
case.expiredFristen er overskredet
case.extendedFristen er forlænget
signer.remindedDer er sendt en påmindelse
signer.link_revoked, signer.link_renewedEt link er spærret eller fornyet
{
  "id": "evt_3b0d6f8e-…",
  "type": "case.completed",
  "createdAt": "2026-11-05T09:41:12.004Z",
  "data": {
    "case": { "id": "6f1c…", "reference": "SG-00042", "title": "Lejekontrakt, Vesterbrogade 14",
              "status": "completed", "completedAt": "2026-11-05T09:41:11.870Z" },
    "signer": null
  }
}

Verificér signaturen

Hver levering har headeren Forsegl-Signature: t=<unix-tid>,v1=<hex>. v1 er HMAC-SHA256 af "{t}.{rå body}" med webhookens hemmelighed (whsec_…). Afvis forespørgsler, hvor signaturen ikke passer, eller hvor t er mere end 5 minutter gammel.

import crypto from 'node:crypto';

export function verifyForsegl(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
import hmac, hashlib, time

def verify_forsegl(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    expected = hmac.new(secret.encode(), f'{parts["t"]}.'.encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
Brug den rå body, præcis som den blev modtaget. Parser I JSON'en og serialiserer den igen, passer signaturen ikke.

Sikkerhed

  • Al trafik går over HTTPS. Data og dokumenter opbevares hos Akamai (Linode) i Frankfurt, Tyskland.
  • API-nøgler gemmes kun som hash. Mister I en nøgle, så tilbagekald den og opret en ny.
  • Dokument-adresser og webhook-adresser skal være offentlige https-adresser. Interne netværk afvises.
  • Personlige underskriftslinks i API-svar giver adgang til at underskrive. Behandl dem som fortrolige.
  • Alt, der sker i en sag, også via API og MCP, logges i sagens hændelseslog og signaturbeviset.

Spørgsmål til integrationen? Skriv til info@wecode.dk.