Snabbstart
Base URL: /api/public/v1. Alla anrop använder JSON. API:t är en del av Enterprise-paketet och kräver en utfärdad API-nyckel.
- 1. Skapa en API-nyckel i admin under
/admin/api-keys— eller maila hej@tidvis.se. - 2. Skicka ert första bemanningsuppdrag med
POST /staffing-requests. - 3. Konfigurera er webhook-endpoint så ni får tillbaka resultatet.
curl -X POST https://staff.tidvis.se/api/public/v1/staffing-requests \
-H "Authorization: Bearer $TIDVIS_STAFF_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: shift_83922-2026-06-23" \
-d '{
"externalId": "shift_83922",
"organizationId": "org_123",
"shift": {
"id": "shift_83922",
"startTime": "2026-06-24T08:00:00+02:00",
"endTime": "2026-06-24T16:00:00+02:00",
"location": "Stockholm",
"role": "Servitör"
},
"candidates": [
{ "id": "user_1", "name": "Sara", "phone": "+46701234567", "priority": 1, "allowedToWork": true },
{ "id": "user_2", "name": "Mikael", "phone": "+46707654321", "priority": 2, "allowedToWork": true }
],
"rules": {
"requireManagerApproval": false,
"minConfidenceForAutoBook": 0.9,
"stopWhenAccepted": true,
"maxAcceptedWorkers": 1,
"maxAttemptsPerCandidate": 2,
"callTimeoutSeconds": 25,
"allowPartial": true
},
"webhookUrl": "https://example.com/webhooks/tidvis-staff"
}'Direkt-svar:
{
"staffingRequestId": "sr_abc123",
"status": "queued",
"message": "Staffing request created and calling will begin shortly."
}Autentisering
Skicka ert API-nyckel som Authorization: Bearer <key> på alla anrop. Nyckeln är kopplad till en organisation och en webhook-signeringshemlighet.
Nyckeln har prefixet tvv_live_ i produktion och tvv_test_ i sandlådan. Återanvänd inte samma nyckel mellan miljöer.
Verktyg & hälsa
Tre hjälp-endpoints för uptime-monitorering och felsökning.
/api/public/v1/healthzIngen auth. Returnerar {ok:true,...}. Lämplig för uptime-monitor.
/api/public/v1/meReturnerar nyckelns organisation och behörigheter. Använd för att verifiera nyckelinstallation.
/api/public/v1/staffing-requests/:id/test-webhookSkickar en signerad ping-payload till uppdragets webhookUrl så ni kan verifiera HMAC-implementationen.
Staffing requests
/api/public/v1/staffing-requestsSkapa ett bemanningsuppdrag. Returnerar staffingRequestId och status queued.
/api/public/v1/staffing-requests/:idHämta ett bemanningsuppdrag inklusive calls och nuvarande resultat.
{
"id": "sr_abc123",
"status": "requires_review",
"shiftId": "shift_83922",
"result": {
"outcome": "candidate_accepted",
"selectedCandidate": { "id": "user_1", "name": "Sara", "phone": "+46701234567" },
"confidence": 0.94,
"requiresManagerApproval": true
},
"calls": [
{
"candidateId": "user_1",
"status": "accepted",
"startedAt": "2026-06-23T14:02:11+02:00",
"endedAt": "2026-06-23T14:02:58+02:00",
"durationSeconds": 47,
"summary": "Sara tackade ja till passet.",
"transcript": "Hej Sara... Ja, jag kan jobba det passet."
}
]
}/api/public/v1/staffing-requests/:id/cancelAvbryt ett pågående uppdrag. Inga fler kandidater ringas.
/api/public/v1/staffing-requests/:id/retryFörsök igen från första olästa kandidaten. Tillgänglig när status är failed eller no_answer.
/api/public/v1/staffing-requests/:id/callsLista alla call attempts för ett uppdrag.
/api/public/v1/staffing-requests/:id/eventsAudit-trail för uppdraget: skapelse, varje statusövergång, webhook-utskick.
Integrationsmönster
När någon i ert system triggar bemanning — via en pass-dialog, ett schemaöversikts-UI, ett Slack-kommando eller ett bakgrundsjobb — postar ni nedanstående payload till /api/public/v1/staffing-requests. Motorn ringer kandidaterna i prio-ordning, transkriberar svaret, klassificerar intent (accepted / declined / needs_callback / unclear) och postar resultatet tillbaka till er webhookUrl. Inga tonval (DTMF) — kandidaten svarar med fri röst på svenska.
Minimal payload
{
"externalId": "shift_2026-08-05_kvallspass",
"mode": "cover",
"shift": {
"startTime": "2026-08-05T16:00:00+02:00",
"endTime": "2026-08-05T23:00:00+02:00",
"location": "Restaurang Kungsholmen",
"role": "Servitör"
},
"candidates": [
{ "id": "u_9981", "name": "Maya Engdahl", "phone": "+46738992544", "priority": 1 }
],
"webhookUrl": "https://example.com/webhooks/tidvis-staff"
}externalId— ert eget pass-id. Används för idempotens: samma id returnerar befintligt uppdrag istället för att skapa nytt.mode— valfritt.cover(lös ett ofyllt pass, default) ellerinterest(intresseanmälan — AI:n samlar bara svar och bokar inte).- Använd snake_case eller camelCase i payload — API:t normaliserar (
external_request_id≡externalId,start_time≡startTime, osv).
Branschvalfria fält
Tre fält är opt-in för vertikalspecifika behov: shift.residentName (vård / personlig assistans — vem passet gäller), candidate.category ( ordinarie / anhorig / tim — bemanningsbolag) och candidate.hourlyRateOre (timpris i öre som kommuniceras till kandidaten). AI:n nämner dem bara om de är satta.
Webhook tillbaka per kandidatsvar
{
"event": "candidate.responded",
"staffingRequestId": "sr_abc123",
"externalId": "shift_2026-08-05_kvallspass",
"externalCandidateId": "u_9981",
"candidate": { "id": "u_9981", "name": "Maya Engdahl", "phone": "+46738992544" },
"intent": "accepted",
"confidence": 0.92,
"transcript": "Ja, jag kan ta passet",
"note": "Vill bli uppringd igen efter klockan 17",
"recordingUrl": "https://api.46elks.com/.../r-xxx.mp3",
"answeredAt": "2026-08-05T15:42:11+02:00"
}Matcha mot ert pass via externalId + externalCandidateId.
Regler & autonomi
rules-objektet på POST /staffing-requests styr om flödet är helt autonomt eller kräver mänskligt godkännande. Manuellt godkännande är en inställning — inte ett krav. Sätt requireManagerApproval: false för att låta motorn boka första kandidat som svarar tydligt ja.
| Fält | Default | Beteende |
|---|---|---|
requireManagerApproval | true | Vid false + tillräcklig confidence → status går direkt till completed (auto-bokning). Vid true → requires_review väntar på manuellt klick. |
minConfidenceForAutoBook | 0.9 | Tröskel (0–1) för auto-bokning när manager-approval är av. Under tröskeln → requires_review. |
stopWhenAccepted | true | Sluta ringa när en kandidat tackat ja. |
maxAcceptedWorkers | 1 | Hur många pass som ska fyllas (1–10). |
maxAttemptsPerCandidate | 1 | Återuppringningar per kandidat (1–5). |
callTimeoutSeconds | 25 | Hur länge AI:n låter telefonen ringa (5–120s). |
allowPartial | true | Tillåt del-acceptans (en kandidat tar del av passet, nästa täcker resten). |
Helt autonomt (agentic)
Första kandidat i prio-listan som svarar accepted med confidence ≥ tröskeln bokas och uppdraget går direkt till completed. Ingen människa behöver klicka.
"rules": {
"requireManagerApproval": false,
"minConfidenceForAutoBook": 0.9,
"stopWhenAccepted": true
}Människa godkänner alltid
Alla accepterade kandidater hamnar i requires_review. Er samordnare bekräftar via egen integration mot staffing_requests.status.
"rules": {
"requireManagerApproval": true
}Statusmodell
Ett bemanningsuppdrag rör sig mellan följande statusar. Webhooks postas vid varje övergång.
queued
→ calling
→ candidate_accepted
→ completed (requireManagerApproval=false OCH confidence ≥ minConfidenceForAutoBook)
→ requires_review (requireManagerApproval=true ELLER låg confidence)
→ candidate_declined (ringer nästa)
→ no_answer (ringer nästa, eller failed när listan är slut)
→ requires_review (låg confidence eller manager-godkännande krävs)
→ cancelled (manuellt avbrutet)
→ completed (alla regler uppfyllda)
→ failed (ingen kandidat tillgänglig)Calls
Varje samtalsförsök loggas som ett CallAttempt med transkribering, AI-tolkning och tidsstämplar. Transkribering är opt-in per organisation.
{
"id": "ca_88...",
"candidateId": "user_1",
"providerCallId": "call_xxxxxxxx",
"status": "accepted",
"startedAt": "2026-06-23T14:02:11+02:00",
"endedAt": "2026-06-23T14:02:58+02:00",
"durationSeconds": 47,
"summary": "Sara tackade ja till passet.",
"transcript": "Hej Sara... Ja, jag kan jobba det passet.",
"intent": "accepted",
"confidence": 0.94
}AI-intents
AI:n returnerar alltid en av följande intents. Den fattar inga andra beslut.
acceptedTackar ja till passetdeclinedTackar nejmaybeTveksam, behöver tänkawrong_personFel mottagare svaradeasked_questionVill veta mer innan beslutneeds_callbackBe att bli ringd senareunclearSvaret går inte att tolkaangry_or_sensitiveEskalera till människa
Webhooks
Tidvis Staff postar event till er webhookUrl. Payloaden signeras med HMAC-SHA256 över `${timestamp}.${rawBody}` med er per-organisations webhook_secret. Verifiera signaturen och att tidsstämpeln är färsk (rekommenderat: max 300s drift) innan ni behandlar eventet — det skyddar mot replay.
Header: X-Tidvis-Signature: t=<unix>,v1=<hex> samt X-Tidvis-Timestamp: <unix>. Vi gör retries med exponentiell backoff vid svar utanför 2xx.
Retry-policy: 1m, 5m, 30m, 2h, 12h. Efter 5 misslyckade försök markeras leveransen som given_up. Varje leverans har även headern X-Tidvis-Delivery-Id som ni kan logga för felsökning.
Verifiera signaturen så här (Node):
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody: string, header: string, secret: string) {
const m = /^t=(\d+),v1=([a-f0-9]+)$/.exec(header);
if (!m) return false;
const [, ts, sig] = m;
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // replay-skydd
const expected = createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
const a = Buffer.from(sig);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}POST https://example.com/webhooks/tidvis-staff
{
"event": "candidate.accepted",
"staffingRequestId": "sr_abc123",
"externalId": "shift_83922",
"candidate": { "id": "user_1", "name": "Sara", "phone": "+46701234567" },
"result": {
"answer": "accepted",
"confidence": 0.94,
"requiresReview": true,
"summary": "Sara tackade ja till passet."
}
}Event-typer:
staffing.queuedstaffing.callingcandidate.acceptedcandidate.declinedcandidate.no_answerstaffing.requires_reviewstaffing.completedstaffing.failedstaffing.cancelled
Idempotency
Skicka Idempotency-Key: <unikt-värde> på POST /staffing-requestsför säker retry. Vi cachar svaret i 24 timmar; identiska anrop returnerar samma svar med headern Idempotent-Replayed: true.
Rekommendation: använd ert eget shift_id + datum eller en UUID per logiskt uppdrag.
CORS
Endpoints under /api/public/v1 svarar på OPTIONS-preflight med tillåtna metoder, Authorization, Content-Type, Idempotency-Key och X-Request-Id. Per default tillåts alla origins (*); per API-nyckel kan ni låsa till specifika origins (allowedOrigins).
Skydd & GDPR
- · Samtal spelas bara in när ni har laglig grund. Inspelning är opt-in per organisation.
- · Transkribering kan stängas av – då tas svaret som ren AI-intent utan att text sparas.
- · AI:n avslöjar bara uppgifter om passet (tid, roll, plats) — aldrig personuppgifter om tredje part som inte är direkt nödvändiga för samtalet.
- · Kandidater kan säga ”ring inte igen”. Beslutet loggas och respekteras i alla uppdrag.
- · Alla beslut loggas i en audit-trail åtkomlig via
GET /staffing-requests/:id/events.
Felkoder
| Kod | Betydelse |
|---|---|
400 invalid_request | Payloaden klarade inte Zod-validering. |
401 unauthorized | Bearer-token saknas eller är ogiltig. |
403 forbidden | Nyckeln saknar åtkomst till den begärda resursen. |
404 not_found | Bemanningsuppdraget finns inte. |
409 idempotency_conflict | Idempotency-Key återanvänd med annan body. |
429 rate_limited | För många anrop. Backa och försök igen. |
500 internal_error | Något oväntat. Vi loggar och utreder. |
Rate limits
Standardgräns: 60 anrop per minut per API-nyckel. Webhook-callbacks räknas inte. Vid 429 returneras Retry-After i sekunder.
Varje svar inkluderar headers X-RateLimit-Limit, X-RateLimit-Remaining och X-RateLimit-Reset (unix-sekund då bucket-fönstret nollställs). Använd X-Request-Id i felrapportering — vi ekkoar samma värde i svarsheadern.
Behöver ni högre gräns? Se Enterprise-paketeringen.