Översikt
Kopplingen sker i tre steg. Slutanvändaren godkänner själv vilka behörigheter din app får till sitt Tidvis Sign-konto, och kan när som helst bryta kopplingen.
- 1. Din knapp skickar användaren till Tidvis auktoriseringsadress. Vi visar godkännandesidan
/anslut. - 2. Efter godkännande skickas användaren tillbaka till din
redirect_urimed en engångskod. - 3. Din server byter koden mot ett
access_tokenoch ettrefresh_token.
Base URL: https://tidvis.se/api/public/v1. PKCE (code_challenge) är obligatoriskt.
Registrera din app
Registrera appen på /utvecklare/appar. Du anger namn, kontaktmail, hemsida, logotyp, retur-adresser och de behörigheter appen behöver.
client_id(prefixtvapp_) är publikt och används i auktoriseringslänken.client_secret(prefixtvsec_) visas en enda gång vid registrering. Spara den säkert och använd den bara server-side.- Retur-adresser måste matcha exakt —
redirect_urii länken jämförs mot de adresser du registrerat. - Verifierade appar visas med en verifieringsmarkering på godkännandesidan. Kontakta oss för verifiering.
Steg 1 – Auktoriseringslänken
Din “Koppla till Tidvis Sign”-knapp ska peka på GET https://tidvis.se/api/public/v1/oauth/authorize. Alla parametervärden ska vara URL-kodade.
https://tidvis.se/api/public/v1/oauth/authorize
?client_id=tvapp_xxxxxxxxxxxxxxxx
&redirect_uri=https%3A%2F%2Fdin-app.se%2Fintegrationer%2Ftidvis-sign%2Fcallback
&response_type=code
&scope=agreements%3Aread%20agreements%3Acreate%20agreements%3Asend
&state=8f3c1d9a2b
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256| Parameter | Krav | Beskrivning |
|---|---|---|
client_id | Obligatorisk | Appens publika id, prefix tvapp_. |
redirect_uri | Obligatorisk | Måste matcha en av appens registrerade retur-adresser exakt. |
response_type | Obligatorisk | Alltid code. |
scope | Obligatorisk | Mellanslagsseparerad lista. Scopes som appen inte fått tilldelade avvisas. |
code_challenge | Obligatorisk | PKCE: base64url(SHA-256(code_verifier)). Minst 20 tecken. |
code_challenge_method | Obligatorisk | S256 (rekommenderas) eller plain. |
state | Valfri | Slumpvärde som skickas tillbaka oförändrat. Använd alltid för CSRF-skydd. |
Är användaren inte inloggad skickas hen först till inloggningen och därefter tillbaka till godkännandesidan. Har användaren flera Tidvis Sign-konton väljer hen konto där.
Steg 2 – Callback
Efter godkännande skickas användaren till din redirect_uri:
https://din-app.se/integrationer/tidvis-sign/callback?code=tvac_...&state=8f3c1d9a2b- Kontrollera att
statematchar det värde du skickade. - Koden är engångs och giltig i 10 minuter.
- Avbryter användaren får du istället
?error=access_denied&error_description=...&state=....
Steg 3 – Byt koden mot ett token
Gör anropet server-side mot POST https://tidvis.se/api/public/v1/oauth/token. Body kan vara JSON eller formulärdata.
curl -X POST https://tidvis.se/api/public/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "tvac_...",
"code_verifier": "<samma verifier som gav code_challenge>",
"redirect_uri": "https://din-app.se/integrationer/tidvis-sign/callback",
"client_id": "tvapp_...",
"client_secret": "tvsec_..."
}'Svar:
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"expires_at": "2026-01-01T12:00:00.000Z",
"refresh_token": "tvrt_...",
"scope": "agreements:read agreements:create agreements:send"
}access_token gäller i 1 timme. Spara refresh_token krypterat — det är det som håller kopplingen levande.
Förnya token
curl -X POST https://tidvis.se/api/public/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "refresh_token",
"refresh_token": "tvrt_...",
"client_id": "tvapp_...",
"client_secret": "tvsec_..."
}'Varje förnyelse ger ett nytt refresh_token — spara alltid det senaste. Har användaren brutit kopplingen får du invalid_grant.
Använda API:t
Skicka access_token som Bearer-token mot Public API v1.
curl https://tidvis.se/api/public/v1/agreements \
-H "Authorization: Bearer <access_token>"Alla endpoints beskrivs i API-dokumentationen och i OpenAPI-referensen.
Återkalla kopplingen
Din app kan återkalla sin egen koppling via POST https://tidvis.se/api/public/v1/oauth/revoke. Enligt RFC 7009 svarar endpointen alltid 200.
curl -X POST https://tidvis.se/api/public/v1/oauth/revoke \
-H "Content-Type: application/json" \
-d '{"client_id":"tvapp_...","client_secret":"tvsec_...","token":"tvrt_..."}'Slutanvändaren kan själv bryta kopplingen under Sign → Inställningar → Anslutna appar. Då slutar både access- och refresh-token att fungera direkt.
Behörigheter (scopes)
Begär bara de behörigheter appen faktiskt behöver — de visas för användaren på godkännandesidan.
| Scope | Betydelse |
|---|---|
agreements:read | Läs avtal |
agreements:create | Skapa avtal |
agreements:send | Skicka avtal |
agreements:cancel | Avbryt avtal |
templates:read | Läs avtalsmallar |
templates:manage | Skapa/uppdatera avtalsmallar |
generations:create | Generera avtal från mall |
generations:read | Läs & ladda ner genererade avtal |
billing:checkout | Fakturering / checkout |
sub_accounts:read | Läs underkonton |
sub_accounts:manage | Hantera underkonton |
sub_accounts:members | Hantera medlemmar i underkonton |
bankid:identify | BankID legitimering |
bankid:sign:data | BankID – signera text och hash (utan PDF) |
mcp:connect | MCP-anslutning |
Komplett exempel (Node / TypeScript)
PKCE-generering, redirect och callback-hantering.
import crypto from "node:crypto";
const AUTHORIZE = "https://tidvis.se/api/public/v1/oauth/authorize";
const TOKEN = "https://tidvis.se/api/public/v1/oauth/token";
const CLIENT_ID = process.env.TIDVIS_CLIENT_ID!;
const CLIENT_SECRET = process.env.TIDVIS_CLIENT_SECRET!;
const REDIRECT_URI = "https://din-app.se/integrationer/tidvis-sign/callback";
const SCOPES = "agreements:read agreements:create agreements:send";
const b64url = (b: Buffer) => b.toString("base64url");
// 1. Knappen: skapa PKCE + state, spara i session, skicka vidare.
export function startConnect(session: Record<string, string>) {
const verifier = b64url(crypto.randomBytes(32));
const challenge = b64url(crypto.createHash("sha256").update(verifier).digest());
const state = b64url(crypto.randomBytes(16));
session["tidvis_verifier"] = verifier;
session["tidvis_state"] = state;
const url = new URL(AUTHORIZE);
url.searchParams.set("client_id", CLIENT_ID);
url.searchParams.set("redirect_uri", REDIRECT_URI);
url.searchParams.set("response_type", "code");
url.searchParams.set("scope", SCOPES);
url.searchParams.set("state", state);
url.searchParams.set("code_challenge", challenge);
url.searchParams.set("code_challenge_method", "S256");
return url.toString(); // redirecta hit
}
// 2 + 3. Callback: validera state, byt kod mot token.
export async function handleCallback(
requestUrl: string,
session: Record<string, string>,
) {
const q = new URL(requestUrl).searchParams;
if (q.get("error")) throw new Error(q.get("error_description") ?? q.get("error")!);
if (!q.get("state") || q.get("state") !== session["tidvis_state"]) {
throw new Error("Ogiltig state");
}
const res = await fetch(TOKEN, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "authorization_code",
code: q.get("code"),
code_verifier: session["tidvis_verifier"],
redirect_uri: REDIRECT_URI,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
}),
});
if (!res.ok) throw new Error(`Token-bytet misslyckades: ${await res.text()}`);
return (await res.json()) as {
access_token: string;
refresh_token: string;
expires_in: number;
scope: string;
};
}Vanliga fel
| Felkod | Orsak |
|---|---|
unauthorized_client | Okänd client_id — kontrollera att appen är registrerad och aktiv. |
invalid_request (redirect_uri) | redirect_uri matchar ingen av appens registrerade adresser. |
invalid_request (code_challenge) | PKCE saknas eller är för kort. |
unsupported_response_type | Endast response_type=code stöds. |
invalid_scope | Du begär en behörighet appen inte har tilldelats. |
access_denied | Användaren avbröt godkännandet. |
invalid_client | Fel eller saknad client_secret. |
invalid_grant | Koden är använd, utgången, fel redirect_uri, misslyckad PKCE eller återkallad refresh_token. |
unsupported_grant_type | Endast authorization_code och refresh_token stöds. |
Auktoriseringsadressen är rate limitad till 60 anrop/minut per IP och token-endpointen till 20 anrop/minut per IP.