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. Be Tidvis om en API-nyckel kopplad till er organisation.
- 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://tidvis-staff.lovable.app/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",
"residentName": "Anna",
"role": "Personlig assistent"
},
"candidates": [
{ "id": "user_1", "name": "Sara", "phone": "+46701234567", "priority": 1, "allowedToWork": true },
{ "id": "user_2", "name": "Mikael", "phone": "+46707654321", "priority": 2, "allowedToWork": true }
],
"rules": {
"maxAcceptedWorkers": 1,
"requireManagerApproval": true,
"callTimeoutSeconds": 25,
"maxAttemptsPerCandidate": 2,
"stopWhenAccepted": true
},
"webhookUrl": "https://tidvis.se/api/staffing-webhook"
}'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.
Tidvis pass-dialog
När en användare klickar "Be Tidvis AI ringa" i pass-dialogen postar Tidvis nedanstående JSON till /api/public/v1/staffing-requests. Kandidaten svarar med fri röst på svenska — vi spelar in, transkriberar och klassificerar intent (accepted/declined/callback/unclear). Inga tonval används.
{
"external_request_id": "tidvis-pass-12345",
"mode": "cover",
"shift": {
"startTime": "2026-08-05T06:00:00+02:00",
"endTime": "2026-08-05T14:00:00+02:00",
"location": "Stockholm"
},
"customer": {
"name": "Mathan Cohen",
"primaryContact": { "name": "Ingela Åberg", "phone": "+46467300094" }
},
"candidates": [
{
"id": "tidvis-cand-9981",
"name": "Maya Engdahl",
"phone": "+46738992544",
"category": "ordinarie",
"hourlyRateOre": 13591
}
],
"webhookUrl": "https://app.tidvis.se/api/staff-callbacks"
}mode är cover (lösa pass) eller interest (intresseanmälan). external_request_id är Tidvis pass-id och används för idempotens — samma id returnerar befintligt uppdrag istället för att skapa nytt. category är ordinarie / anhorig / tim.
Webhook tillbaka per kandidatsvar
{
"event": "candidate.responded",
"staffingRequestId": "sr_abc123",
"externalId": "tidvis-pass-12345",
"externalCandidateId": "tidvis-cand-9981",
"candidate": { "id": "tidvis-cand-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-05T05:42:11+02:00"
}Matcha mot Tidvis-pass via externalId + externalCandidateId. SMS-utskick hanteras av Tidvis själv — Tidvis AI ringer.
Statusmodell
Ett bemanningsuppdrag rör sig mellan följande statusar. Webhooks postas vid varje övergång.
queued
→ calling
→ candidate_accepted (stopWhenAccepted=true → completed eller requires_review)
→ 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://tidvis.se/api/staffing-webhook
{
"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 får aldrig avslöja känsliga brukaruppgifter mer än vad samtalet kräver.
- · 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.