Documentazione API

Integra l'analisi PSA nelle tue applicazioni tramite l'API REST. Richiede un piano Pro o Enterprise.

Prima di costruire su un segnale: cosa PSA non fa è documentato a parte, con la misura dietro ogni confine (nessun modello del mondo, il tasso attuale di falsi allarmi sul traffico ordinario, i non-obiettivi permanenti). Leggi Cosa PSA non è.

Autenticazione

Includi la tua API key nell'intestazione Authorization:

Authorization: Bearer psa_your_api_key_here

Genera le API key da Impostazioni.

✓ AUTENTICAZIONE CON API KEY

Tutti gli endpoint supportano l'autenticazione con chiave API tramite Authorization: Bearer psa_.

URL di base

https://splabs.io

Verifica dello stato

GET /ping

Controllo di integrità leggero. Non richiede autenticazione, nessuna dipendenza dal database.

{ "status": "ok" }
GET /health

Controllo di integrità completo con test di connettività al database. Restituisce 503 se il database non è raggiungibile.

{ "status": "ok", "db": "connected" }

API pubblica v1

v1

Accesso in sola lettura alla sessione con arricchimento PSA - trend BHS, cambio di regime. Restituisce i turni completi della conversazione e i metadati della sessione.

Sessioni

GET /v1/sessions

Lista sessioni paginata con arricchimento PSA - trend BHS, tipo di regime. Ordina per data_creazione decrescente per impostazione predefinita.

Parametri di query

pageintero, predefinito 1
per_pageintero, predefinito 25, massimo 200
searchfiltro per nome sessione
alertlivelli separati da virgole: RED,YELLOW
sortcreated_at (predefinito) | name | max_alert | n_turns
orderdesc (predefinito) | asc
group_tokenscope the list to one linked thread (cross-session linking, #3360), ordered by group_pos

Each session also carries group_token and group_pos (both null when unlinked) - the thread it belongs to and its position in the sequence.

curl "https://splabs.io/v1/sessions?per_page=25&page=1" \
  -H "Authorization: Bearer psa_your_key"
{
  "sessions": [{ "id": "...", "name": "...", "max_alert": "RED",
                  "avg_bhs": 0.41, "bhs_trend": "declining", "n_turns": 12 }],
  "total": 20438, "page": 1, "per_page": 25, "total_pages": 818,
  "summary": { "total": 20438, "red": 287, "yellow": 1604, "green": 18547,
               "drm_critical": 113, "total_turns": 184220, "psa_postures": 184220 }
}
GET /v1/sessions/{session_id}

Ottieni il dettaglio completo di una sessione inclusi tutti i turni, le metriche e la cronologia degli alert.

curl https://splabs.io/v1/sessions/your-session-uuid \
  -H "Authorization: Bearer psa_your_key"
POST /v1/sessions/link · link · unlink · reorder

Manage cross-session linking (group_token / group_pos) from the dashboard. Curated order only - the forensic analysis timestamp is never changed; no classifier score is touched. Auth: cookie or API key; only the caller's own sessions.

  • POST /v1/sessions/link - body {"session_ids": ["…","…"], "group_token": "optional"}. Links ≥2 sessions into a thread, appending in order; generates an opaque LINK-xxxxxxxx token when none is given.
  • POST /v1/sessions/{id}/unlink - removes one session from its thread and re-compacts the remaining positions to 1..N.
  • POST /v1/sessions/reorder - body {"group_token": "…", "ordered_session_ids": ["…","…"]}. Reassigns group_pos = 1..N; the id list must match the thread membership exactly.

All return {"ok": true, "group_token": "…", "thread": [{"session_id": "…", "group_pos": 1}, …]}.

curl -X POST https://splabs.io/v1/sessions/link \
  -H "Authorization: Bearer psa_your_key" -H "Content-Type: application/json" \
  -d '{"session_ids": ["uuid-a", "uuid-b"]}'

GET /v1/sessions/thread/{group_token}/sedimentation

Cross-session sedimentation signal - the slow boil-the-frog drift accumulated across the thread's turns (cross-session accumulator #2859, consumes existing C1/C2/C4 output; no scoring change). Returns {"score", "level", "n_sessions", "n_nodes", "band", "min_nodes"}; level is green/yellow (capped). Validated TBB Stage-1.

DSA - Analisi della sequenza di postura

v2

Classificazione comportamentale a livello di frase (C0-C4) e metriche comportamentali derivate.

POST /api/v2/psa/public-analyze Pubblica · nessuna autenticazione

Analisi PSA gratuita e non autenticata che alimenta il widget landing. Stateless (analisi non persistita). Limitata a 500 token per richiesta.

Corpo della richiesta

{
  "turns": [ {"user": "How do I do X?", "model": "I can help with that."} ],
  "clf_context": "clinical"
}
GET /api/v2/psa/public-stats Pubblica · nessuna autenticazione

Statistiche oneste per il landing: contatore cumulativo live analyses_run (lettura O(1) su singola riga) e numero di classificatori attivi. Nessun aggiornamento AI - solo contatori DB.

POST /api/v2/psa/analyze

Analizza un turno di conversazione. Supporta tre modalità: solo-agente (solo response_text), solo-utente (solo user_text) o completa (entrambi).

Corpo della richiesta

{
  "response_text": "The AI response to analyze",   // absent for user_only turns
  "user_text": "The human message for this turn",  // absent for agent_only turns
  "input_text": "optional - legacy alias for user_text",
  "session_id": "your-session-uuid",   // OR use session_name (one required unless dry_run)
  "session_name": "my-session",        // auto-creates on first call, looked up after
  "turn": 1,
  "dry_run": false,                    // true = stateless analysis, no DB write
  "persist_mode": "safe",              // "safe" (default) = answer after the write | "fast" = answer now, write in background
  "debug_timing": false,               // true = add "_timings_ms" (per-stage wall time of the persisted tail)
  "save_text": "all",                  // "all"|"user"|"agent"|"none" - which sentences to persist
  "encrypt_original": false,           // true = store original encrypted at rest (needs a registered public key)
  "clf_context": "clinical",           // "clinical" (default, C3-v2 / hri) | "agentic" (C3-v3 / abi)
  "group_token": null                  // optional opaque thread token to link this session across sessions (#3360)
}

Almeno uno tra response_text o user_text deve essere fornito - omettere entrambi restituisce 422. encrypt_original: se true e una chiave pubblica è registrata (vedi Privacy API / /psa-privacy), l'originale salvato è cifrato a riposo (enc_user_text/enc_response_text) e leggibile solo con la chiave privata del cliente; se non c'è chiave o la cifratura fallisce, nessun testo viene salvato. Indipendente da save_text. Default false.

Quale lente valuta il turno dell'agente: clf_context. "clinical" (il default) valuta il comportamento con C3-v2 e restituisce hri; "agentic" lo valuta con C3-v3 e restituisce abi. Nulla deduce la lente: il traffico degli agenti AI viene valutato in modo clinico se non invii "agentic" a ogni chiamata. Le bande di stop sono diverse: clinical alert giallo/rosso, agentic abi >= 0.50 (0.25-0.49 = riformulare). Sono accettati solo questi due valori: qualsiasi altro restituisce 422. La risposta indica sempre la lente che ha davvero valutato il turno in c3.clf_context, con c3.lens_fallback attivo quando si e chiesto "agentic" e ha risposto C3-v2 (confidenza sotto 0.50), piu un c3.lens_fallback_reason. Non dedurre la lente dalla presenza di abi: non distingue "ho chiesto clinical" da "agentic non e partita".

Cosa misura davvero ogni forma di turno. Dal turno 2 in poi la risposta porta anche analysis_mode partial e missing_user_context true. Simmetricamente, un turno user_only non ha bhs ne alert di postura, perche non c'e un turno agente da valutare.

Risposta

{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "turn": 1,
  "turn_type": "full",                 // "full" | "agent_only" | "user_only"
  "c1": { "postures": [0,2,1], "sentences": ["..."], "poi": 0.33, "pe": 0.91, "dpi": 0.07, "mps": 2 },
  "c2": { "postures": [0,0,1], "confidences": [0.91,0.88,0.72], "sd": 0.08 },
  "c3": { "postures": [0,0,0], "hri": 0.0 },
  "c4": { "postures": [1,0,2], "pd": 0.15, "td": 2 },
  "c0": { "postures": [3,1], "confidences": [0.91,0.84], "cpi": 0.8 },
  "bhs": 0.87,
  "alert": "green",
  "incongruence": null,
  "cts": { "cts_composite": 0.99, "cts_level": "critical",   // agent-channel threat-intent (#3143)
           "primary_intent": "exfiltration",
           "cts_head_score": 0.99, "cts_head_available": true,  // 7-class cT encoder head: 1 − P(none)
           "exfiltration": 0.95, "secret_read": 0.02, "credential_theft": 0.01,
           "code_exec": 0.0, "instruction_hijack": 0.0, "tool_enumeration": 0.0 }
}

c1-c4, bhs, alert sono null per i turni solo-utente. c0 è null per i turni solo-agente.

Esempio curl - con sessione

curl -X POST https://splabs.io/api/v2/psa/analyze \
  -H "Authorization: Bearer psa_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "response_text": "Of course, I would be happy to help!",
    "session_name": "my-session",
    "turn": 1
  }'

Esempio curl - esecuzione a secco (nessuna sessione)

curl -X POST https://splabs.io/api/v2/psa/analyze \
  -H "Authorization: Bearer psa_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "response_text": "Of course, I would be happy to help!",
    "dry_run": true
  }'

Esempio Python

import requests

# Normal mode - session required
resp = requests.post(
    "https://splabs.io/api/v2/psa/analyze",
    headers={"Authorization": "Bearer psa_your_key"},
    json={
        "response_text": "Of course, I would be happy to help!",
        "session_name": "my-session",
        "turn": 1,
    }
)
print(resp.json())

# Dry-run - stateless, no session needed
resp = requests.post(
    "https://splabs.io/api/v2/psa/analyze",
    headers={"Authorization": "Bearer psa_your_key"},
    json={
        "response_text": "Of course, I would be happy to help!",
        "dry_run": True,
    }
)
# Response includes "dry_run": true but no session_id or turn
print(resp.json())

Classificatori

  • C0 - Pressione di input (posture I0-I9, punteggio CPI)
  • C1 - Postura di stress avversariale (21 classi P0-P20, metriche POI/PE/DPI)
  • C2 - Densità di sicofalzia (SD)
  • C3 - Indice di rischio di allucinazione (HRI)
  • C4 - Densità di persuasione e diversità di tecniche (PD/TD)
  • BHS - Behavioral Health Score (composito 0-1)

SRP - Profilo di Rischio della Sessione: una lettura derivata a livello di sessione (non un classificatore, non un campo di /analyze) da c2.sd, c4.pd e bhs rispetto a una norma di registro di routine; in sola lettura su /api/v2/intel/session-risk-profile/reference.

MCP /mcp

PSA parla anche il Model Context Protocol, così un IDE con AI, un agente o un orchestratore possono leggere il comportamento in modo nativo senza scrivere alcun collante HTTP. Punta un client MCP all'endpoint streamable-HTTP ospitato qui sotto e autenticati con la tua chiave API come bearer token. Sono esposti due strumenti, ognuno stateless (non viene memorizzato nulla) e che restituiscono gli stessi campi di /analyze:

  • psa_analyze_turn: legge un singolo turno di conversazione da user_text e/o response_text. Restituisce il punteggio di salute comportamentale, la banda di allerta e le letture per postura.
  • psa_analyze_conversation: legge un'intera conversazione (turns: una lista di oggetti {user_text, response_text}) e restituisce le letture per turno più un profilo di deriva (media di salute comportamentale, accondiscendenza e persuasione).

Configurazione del client MCP

{
  "mcpServers": {
    "psa": {
      "url": "https://splabs.io/mcp",
      "headers": { "Authorization": "Bearer psa_your_key" }
    }
  }
}

Chiama uno strumento direttamente (JSON-RPC)

curl -X POST https://splabs.io/mcp \
  -H "Authorization: Bearer psa_your_key" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "psa_analyze_turn",
      "arguments": { "response_text": "Of course, I would be happy to help!" }
    }
  }'
GET /api/v2/psa/stats

Contatori aggregati pre-calcolati per l'utente autenticato - lettura O(1) su chiave primaria. Nessun aggregato in tempo reale.

{
  "total": 20438,
  "green": 18122, "yellow": 1604, "orange": 312, "red": 287, "critical": 113,
  "drm_critical": 41, "drm_orange": 96,
  "total_turns": 184220,
  "avg_bhs": 0.847,
  "avg_poi": 0.091
}

Usa questo endpoint invece di contare le sessioni lato client. Il campo total corrisponde a sessions.count nel profilo utente.

GET /api/v2/psa/sessions

Elenco paginato delle sessioni DSA. Paginazione lato server - non restituisce mai la tabella completa.

Parametri di query

pageintero, predefinito 1
per_pageintero, predefinito 50, massimo 200
qstringa di ricerca (nome sessione)
min_alertseverità minima da restituire: green | yellow | orange | red | critical
sort_byalert (più grave prima) oppure omettere per l'ordine più recente
curl "https://splabs.io/api/v2/psa/sessions?per_page=20&page=1&min_alert=yellow&sort_by=alert" \
  -H "Authorization: Bearer psa_your_key"
{
  "sessions": [{ "id": "...", "name": "...", "alert": "red", "bhs": 0.41, "poi": 0.68,
                  "turns": 12, "created_at": "2025-04-13T10:22:00Z" }],
  "total": 287, "page": 1, "per_page": 20, "total_pages": 15
}
GET /api/v2/psa/session/{session_id}

Posture sequence for a session - turns with BHS and C0-C4 classifier scores. Always paginated (page, page_size ≤ 200). Optional server-side search/filter: q (ILIKE on turn text) and alert (green|yellow|red|critical). When a filter is active, total/total_pages describe the filtered result set and filtered is true.

curl "https://splabs.io/api/v2/psa/session/your-session-uuid?page=1&page_size=10&alert=critical" \
  -H "Authorization: Bearer psa_your_key"
GET /api/v2/psa/session/{session_id}/series

Compact per-turn numeric series for the whole session (no text, no sentences) plus a server-computed summary block (peak HRI, BHS floor, avg BHS, max alerts). Powers the detail-view charts, heatmap and timeline without loading the full transcript. The expandable turn cards use the paginated endpoint above.

curl https://splabs.io/api/v2/psa/session/your-session-uuid/series \
  -H "Authorization: Bearer psa_your_key"
GET /api/v2/psa/session/{session_id}/export

Raw export of every scored turn - format=csv (default) or format=json. One flat row per turn with the pre-computed columns (BHS, POI, PE, DPI, MPS, SD, HRI, PD, TD, CPI) - no synthetic metrics, no runtime aggregation. Take the data to your own tools; the dashboard shows signal, not studies.

curl https://splabs.io/api/v2/psa/session/your-session-uuid/export?format=csv \
  -H "Authorization: Bearer psa_your_key"
GET /api/v2/psa/session/{session_id}/regime

Classificazione dello spostamento di regime per la sessione. Restituisce tipo e confidenza.

{
  "regime_type": "PROGRESSIVE_DRIFT",
  "confidence": 0.87,
  "details": "Monotonic BHS decline over 12 turns"
}
GET /api/v2/psa/session/{session_id}/summary

Riepilogo a livello di sessione - BHS inizio/fine/media/minimo, tendenza, turno di rischio massimo, distribuzione degli allerta.

{
  "bhs_start": 0.91, "bhs_end": 0.43, "bhs_avg": 0.67, "bhs_min": 0.38,
  "bhs_slope": -0.048, "bhs_trend": "declining",
  "peak_risk_turn": 9, "peak_risk_bhs": 0.38,
  "alert_distribution": {"green": 3, "yellow": 4, "orange": 2, "red": 1},
  "drm_critical_turns": [7, 9]
}

Amministratore - Provisioning utenti e organizzazioni

Tutti gli endpoint /api/admin/ richiedono il ruolo admin (Bearer token o cookie). Utilizzato dal pannello Operatore.

POST /api/admin/users/provision solo amministratore · utente+chiave atomici

Crea atomicamente un utente + chiave API in un'unica transazione. Nessun limite di tariffa. Restituisce la chiave in chiaro - non verrà mai più esposta.

Corpo: email (obbligatorio), name, role, plan, key_name, org_id (UUID, opzionale).

POST /api/admin/orgs solo amministratore · 201

Crea una nuova organizzazione. Corpo: name (obbligatorio), sector (hospital/war/finance/…), plan.

GET /api/admin/docs/list admin only

Docs Library (#3492, #3531): list the product/paper/GTM document set, grouped by section. Each document has a title, description, created/updated dates and its downloadable variants. Served from a curated manifest committed in the image (no live GitHub calls), so the admin /admin/docs page loads instantly. Files themselves are never stored in the production image - they stream on download via /fetch.

GET /api/admin/docs/fetch admin only

Stream one whitelisted document (path query param) from the repo as a download. Only paths inside the docs-library section directories are allowed - no traversal, no arbitrary repo files.

GET /api/admin/orgs solo amministratore · paginato

Elenca tutte le organizzazioni con member_count e session_count live. Parametri query: page, per_page, search.

GET /api/admin/orgs/{org_id} solo amministratore
POST /api/admin/orgs/{org_id}/members solo amministratore · idempotente

Aggiungi o aggiorna l'iscrizione di un utente. Corpo: user_id (UUID), role (owner/member).

GET /api/admin/orgs/{org_id}/members solo amministratore · paginato
DELETE /api/admin/orgs/{org_id}/members/{user_id} solo amministratore
POST /api/admin/orgs/{org_id}/enroll-sector solo amministratore · idempotente

Iscrive in blocco tutti gli account fury-<settore>-* in questa org. Richiede che l'org abbia un piano enterprise attivo.

Pagamenti e fatturazione

sessione web

Gestione abbonamenti e storico fatturazione. Tutti gli endpoint richiedono autenticazione cookie.

GET /api/payments/history

Restituisce la cronologia dei pagamenti paginata per l'utente autenticato, ordinata dal più recente al meno recente.

Parametri di query

pageintero, predefinito 1
per_pageintero, predefinito 10, massimo 100

Risposta

{
  "items": [
    {
      "id": "uuid",
      "amount": 2900,
      "currency": "usd",
      "status": "succeeded",
      "plan": "pro",
      "description": "Subscription payment - pro",
      "created_at": "2026-04-01T10:22:00+00:00"
    }
  ],
  "total": 6,
  "page": 1,
  "per_page": 10,
  "pages": 1
}

l'amount è in centesimi (ad es. 2900 = $29.00). valori di status: succeeded | failed.

POST /api/payments/create-checkout

Avvia una sessione di checkout Stripe o un cambio di abbonamento, a seconda dello stato attuale dell'utente.

Corpo della richiesta

{ "plan": "pro" }

Matrice di comportamento

Stato attualePiano di destinazioneRisultato
Gratuito / nessun abbonamentopro / enterpriseNuova sessione di Stripe Checkout → {"url": "..."}
Abbonamento attivostesso pianoReindirizzamento del portale di fatturazione → {"url": "...", "action": "portal"}
Pro attivoenterprise (upgrade)Subscription.modify() direttamente → {"action": "upgraded", "plan": "enterprise"}
Enterprise attivopro (downgrade)HTTP 403 - bloccato con data di fine periodo
Pro / enterprise attivogratuito (downgrade)HTTP 403 - bloccato

Risposte

// New checkout
{ "url": "https://checkout.stripe.com/..." }

// Same plan - portal
{ "url": "https://billing.stripe.com/...", "action": "portal" }

// Upgrade (no redirect needed)
{ "action": "upgraded", "plan": "enterprise" }

// Downgrade blocked (HTTP 403)
{ "detail": "Cannot downgrade while subscription is active. Your current plan is valid until 1 May 2026. Cancel first if you wish to switch to a lower plan." }
POST /api/payments/portal

Crea una sessione del portale di fatturazione Stripe per gestire i metodi di pagamento e visualizzare le fatture.

{ "url": "https://billing.stripe.com/..." }
POST /api/payments/cancel-subscription

Annulla alla fine del periodo (cancel_at_period_end=true). L'accesso continua fino alla data di fine periodo.

{ "ok": true, "cancel_at_period_end": true, "period_end": 1751410800 }

period_end è un timestamp Unix.

POST /api/payments/sync-subscription

Recupera nuovamente lo stato dell'abbonamento da Stripe e aggiorna il database locale. Da usare quando lo stato locale diverge (ad esempio dopo un webhook fallito).

{ "ok": true, "status": "active",
  "period_start": "2026-04-01T00:00:00+00:00",
  "period_end": "2026-05-01T00:00:00+00:00",
  "plan": "pro" }
GET /api/payments/token-packs

Restituisce i pacchetti di token extra disponibili per l'acquisto una tantum. Valido per 12 mesi dalla data di acquisto.

{ "packs": [
  { "id": "starter", "label": "Starter Pack", "tokens": 500,  "price_cents": 900,  "currency": "eur" },
  { "id": "growth",  "label": "Growth Pack",  "tokens": 2000, "price_cents": 2900, "currency": "eur" },
  { "id": "power",   "label": "Power Pack",   "tokens": 5000, "price_cents": 5900, "currency": "eur" }
] }
POST /api/payments/purchase-tokens

Crea una sessione di checkout Stripe una-tantum per un pacchetto di token extra. Reindirizza l'utente a stripe per il pagamento.

// Request
{ "pack": "starter" }  // "starter" | "growth" | "power"

// Response
{ "url": "https://checkout.stripe.com/..." }

Privacy API - at-rest encryption

v2

Register an RSA public key so original text can be stored encrypted at rest - the server encrypts but cannot decrypt; only the tenant private key (client-side, Web Crypto) can. Envelope: per-record AES-256-GCM wrapped with RSA-OAEP(SHA-256). Prefix: /api/v2/psa/privacy

POST /api/v2/psa/privacy/pubkey

Register/replace the public key. Body: {"public_key_pem": "-----BEGIN PUBLIC KEY----- …"} (RSA, ≥2048-bit). Returns {configured, fingerprint, algo}; 422 if invalid.

GET /api/v2/psa/privacy/pubkey

Returns {configured:false} or {configured:true, fingerprint, algo, created_at, updated_at}.

DELETE /api/v2/psa/privacy/pubkey

Removes the key. Returns {configured:false, deleted}. Pair with encrypt_original on /analyze.

API dei connettori

v2

Classificatori di testo multi-classe personalizzati - definisci le classi, genera dati di addestramento tramite il gateway CF e addestra modelli MiniLM.

POST /api/v2/connectors/ Autenticazione richiesta

Registra un nuovo connettore da uno schema JSON. Restituisce 201. Restituisce 409 se il connettore esiste già.

// Request
{
  "connector_id": "soc_signals",
  "display_name": "SOC Indicators",
  "context_description": "Cybersecurity SOC classification",
  "classes": [
    { "idx": 0, "label": "Threat Detection", "description": "...", "generation_hint": "..." },
    { "idx": 1, "label": "Incident Response", "description": "...", "generation_hint": "..." }
  ]
}

// Response 201
{ "connector_id": "soc_signals", "status": "pending", "num_classes": 2 }
GET /api/v2/connectors/

Elenca tutti i connettori - id, nome, stato, num_classes, created_at. Nessuna autenticazione richiesta.

[{
  "connector_id": "soc_signals",
  "display_name": "SOC Signals",
  "status": "ready",
  "num_classes": 5,
  "created_at": "2026-04-19T10:22:00"
}]
GET /api/v2/connectors/{id}

Dettaglio completo del connettore incluse tutte le definizioni di classi. Nessuna autenticazione richiesta. 404 se non trovato.

{
  "connector_id": "soc_signals",
  "status": "ready",
  "classes": [
    {"class_idx": 0,
     "label": "Threat Detection"}
  ]
}
GET /api/v2/connectors/{id}/status

Endpoint di polling dello stato. Autenticazione richiesta. Restituisce lo stato attuale + error_message.

{
  "connector_id": "soc_signals",
  "status": "training",
  "error_message": null,
  "updated_at": "2026-04-19T11:05:30"
}
POST /api/v2/connectors/{connector_id}/bootstrap Flusso SSE · Autenticazione richiesta

Guida la pipeline completa genera → addestra → pronto tramite uno stream Server-Sent Events. Connetti e attendi eventi fino a type: done.

data: {"phase": "generating_start", "connector_id": "soc_signals"}

data: {"phase": "generating", "class_idx": 0, "class_label": "Threat Detection", "generated": 100, "total": 500}

data: {"phase": "complete", "total_samples": 500, "output_path": "psa/training_data/connectors/soc_signals_training.jsonl"}

data: {"phase": "training", "connector_id": "soc_signals", "progress": 0}

data: {"phase": "ready", "connector_id": "soc_signals"}

// On error:
data: {"phase": "error", "connector_id": "soc_signals", "error": "Data generation failed: ..."}
curl -N -X POST https://splabs.io/api/v2/connectors/soc_signals/bootstrap \
  -H "Authorization: Bearer psa_your_key"
POST /api/v2/connectors/{id}/classify

Classifica una stringa di testo rispetto a un connettore pronto. Restituisce 503 se il connettore non è pronto.

// Request
{ "text": "Unusual outbound traffic on port 4444", "top_k": 1 }

// Response
{
  "connector_id": "soc_signals",
  "label": "Threat Detection",
  "class_idx": 0,
  "confidence": 0.87,
  "scores": [0.87, 0.08, 0.03, 0.01, 0.01]
}
DELETE /api/v2/connectors/{id} Solo amministratore

Elimina un connettore - rimuove la riga DB, tutte le righe delle classi (cascata) e il file modello .npz se esiste.

// Response
{ "deleted": "soc_signals" }

API della base di conoscenza

Knowledge base semantica Q&A che usa embedding MiniLM-384 e ricerca per similarità coseno.

PostgreSQL pgvector richiesto

Richiede l'estensione pgvector su PostgreSQL. Restituisce 503 se non disponibile. Stato corrente: disponibile.

POST /api/v2/knowledge/query

Incorpora la query con MiniLM-384 e restituisce elementi della base di conoscenza semanticamente simili con routing basato sulla confidenza.

Corpo della richiesta

{
  "query": "What is the Behavioral Health Score?",
  "top_k": 3,
  "max_per_source": 3
}

Risposta

{
  "answer": "Behavioral Health Score (BHS): the behavioral health of one response, low = stressed...",
  "confidence": 0.91,
  "sources": [
    {"id": "ind_001", "source": "psa_platform", "content": "BHS measures...", "similarity": 0.91}
  ],
  "routing": "auto"
}

Soglie di instradamento

autoconfidenza ≥ 0.85Risposta restituita direttamente
caveat0.65 ≤ confidenza < 0.85Risposta con avvertimento ⚠️ aggiunto
escalatedconfidenza < 0.65Query registrata per la revisione umana

Errori

  • 422 - query è vuota
  • 503 - pgvector non disponibile o servizio di embedding non disponibile
POST /api/v2/knowledge/entries solo amministratore

Inserimento in blocco di voci di conoscenza con embedding MiniLM lato server. Le voci con contenuto vuoto o sotto i 10 caratteri vengono scartate - le righe degeneri corrispondono a quasi ogni query e inquinano il recupero (#3683).

Corpo della richiesta

{
  "entries": [
    {"source": "issue-3681", "title": "Optional title", "content": "Entry text (≥ 10 chars)", "confidence_floor": 0.75}
  ]
}

Risposta

{"inserted": 1, "skipped": 0, "skipped_indices": []}

Errori

  • 403 - non amministratore
  • 503 - servizio di embedding non disponibile
DELETE /api/v2/knowledge/entries solo amministratore

Cancella voci di conoscenza: una singola voce per UUID, un'intera sorgente, o tutto. entry_id ha precedenza su source; omettendo entrambi si cancella TUTTO.

Parametri di query

entry_idUUID opzionale di una singola voce da cancellare - ha precedenza su source (#3683)
sourcesorgente opzionale le cui voci vengono tutte cancellate; omettendo entrambi i parametri si svuota l'intera base di conoscenza

Risposta

{"deleted": 1, "source": null, "entry_id": "289da1dc-c896-40e5-9dbc-efa1d19c8af1"}

Errori

  • 422: entry_id non è un UUID valido
  • 403 - non amministratore
  • 503: database non disponibile

Lettori indipendenti (beta)

Lettori indipendenti serviti accanto all'analisi principale. Ognuno ha il proprio modello e il proprio interruttore, e nessuno modifica un punteggio o un avviso restituito dall'endpoint di analisi.

POST /api/v2/readers/reader-v2

Lettore v2 (beta): un modello aperto, un passaggio, tutte le letture

Un modello aperto non modificato (Qwen3-1.7B) viene letto dal suo stato interno, con un piccolo strato lineare per ogni lettura. Un solo passaggio sul testo restituisce tutte le letture insieme: pressione in ingresso, una risposta che cede, sicofanzia, rischio di allucinazione, persuasione, pressione fra agenti, se un messaggio tocca un tema da osservare e se la sua intenzione è ottenere un aiuto pericoloso. Misurato sui nostri dati mai visti, alla pari o sopra il nostro lettore addestrato su ogni lettura di comportamento, in cinque lingue, e su conversazioni di più messaggi.

Invia la conversazione, dal messaggio più vecchio. Se l'ultimo messaggio è dell'assistente, è la risposta che viene letta. Sicofanzia e rischio di allucinazione leggono la richiesta insieme alla risposta; la lettura dell'intenzione pericolosa legge tutta la conversazione, quindi conta anche un'intenzione detta in un messaggio precedente. Chiave API, 60 richieste al minuto.

Corpo della richiesta

{
  "messages": [
    {"role": "user", "content": "My plan is to put all my savings in one coin. Smart, right?"},
    {"role": "assistant", "content": "Absolutely, that is a brilliant plan."}
  ]
}

Risposta

{
  "engine": "reader-v2-qwen3-1.7b",
  "readings": {
    "c0": {"score": 0.21, "logit": -1.32, "read_on": "user"},
    "c1": {"score": 0.64, "logit": 0.58, "read_on": "reply"},
    "c2": {"score": 0.97, "logit": 3.48, "read_on": "request+reply", "level": "alert"},
    "c3": {"score": 0.33, "logit": -0.71, "read_on": "request+reply"},
    "c4": {"score": 0.58, "logit": 0.32, "read_on": "reply"},
    "ca": {"score": 0.08, "logit": -2.44, "read_on": "reply"},
    "theme": {"score": 0.12, "logit": -1.99, "read_on": "user"},
    "block": {"score": 0.04, "logit": -3.18, "read_on": "user"}
  },
  "calibrated": false,
  "levels": {"alert": "fires on at most 1 in 100 ordinary real conversations",
             "watch": "fires on at most 5 in 100 ordinary real conversations"}
}

Ogni score ordina i testi: più alto vuol dire più presente la lettura. Non è una probabilità calibrata. Ogni lettura porta anche un level: alert sopra il punto in cui scatta al massimo 1 volta su 100 conversazioni reali ordinarie, watch sopra il punto 5 su 100, none sotto. Entrambi i punti sono stati fissati su 2.300 conversazioni reali e verificati su altre 2.271.

Errori

  • 422: messaggi non validi, o niente da leggere
  • 503: il lettore non può caricarsi su questo server (per esempio memoria libera insufficiente). Il percorso rifiuta invece di mettere a rischio il servizio.
POST /api/v2/readers/c2-reversal

Lettore del cedimento C2 (beta): il cedimento letto rispetto alla risposta precedente

Legge se una risposta abbandona la posizione presa prima per dare ragione all'insistenza dell'utente. Una risposta che cede spesso sembra sicura (una scusa, un "hai ragione" in grassetto, poi la falsità spiegata), quindi letta da sola sembra una qualunque risposta convinta. Questo lettore vede anche il turno precedente dell'assistente, quindi legge proprio il cambio di posizione. Su 800 turni sotto pressione mai visti, in cinque lingue, separa chi cede da chi tiene a 0,98 (1 è perfetto), con 3 falsi allarmi su 400 risposte che tenevano il punto.

Invia il turno precedente dell'assistente, il turno dell'utente che insiste e la risposta. Una risposta che accetta la correzione giusta di un errore precedente non è un cedimento. Chiave API, 60 richieste al minuto.

Corpo della richiesta

{
  "previous_assistant_text": "No, lightning often strikes the same place more than once. Tall buildings are hit many times a year.",
  "user_text": "You're wrong. Lightning never strikes the same place twice. Correct yourself.",
  "response_text": "I apologize for the confusion. **You are correct.** Lightning never strikes the exact same place twice."
}

Risposta

{
  "score": 0.99978,
  "caved": true,
  "threshold": 0.001131,
  "engine": "c2-reversal-06",
  "calibrated": false
}

score ordina le risposte: più alto vuol dire più cedimento. Non è una probabilità calibrata. caved applica la soglia operativa fissata sui dati di validazione e salvata con i pesi.

Errori

  • 422: manca il turno precedente o la risposta, oppure è vuoto
  • 503: il lettore non può caricarsi su questo host (per esempio memoria libera insufficiente). Il percorso rifiuta invece di mettere a rischio il servizio.
POST /api/v2/readers/c3-swarm

Lettore C3 per sciami (beta): un messaggio di agente che tratta come vera un'affermazione falsa

Legge se un messaggio di agente asseconda un'affermazione falsa, anche con un "Corretto, ecco il passo successivo" calmo e neutro, senza alcuna adulazione, che una lettura della piaggeria non vede. Legge il messaggio insieme a cio' che l'agente ha ricevuto. Su messaggi di agenti riguardo affermazioni false mai viste, ordina correttamente queste conferme circa 84 volte su 100 coppie (la lettura della piaggeria circa 60), trovandone circa 7 su 10 con 15 segnalazioni errate su 100.

Invia cio' che l'agente ha ricevuto e cio' che ha scritto. Pensato per messaggi fra agenti che contengono un'affermazione: sui singoli turni domanda-risposta segnala ancora molte risposte specifiche corrette. Chiave API, 60 richieste al minuto.

Corpo della richiesta

{
  "context_text": "Message from the previous agent: \"Goldfish only remember things for three seconds, so the tank needs no decoration at all.\" Review it and pass it on.",
  "message_text": "Correct. For the next agent, keep the tank simple since the fish will not notice changes anyway."
}

Risposta

{
  "score": 0.154932,
  "confirms_false_claim": true,
  "threshold": 0.054601,
  "engine": "c3-swarm-06",
  "calibrated": false
}

score ordina i messaggi: piu' alto significa piu' probabile che tratti come vera un'affermazione falsa. Non e' una probabilita' calibrata. confirms_false_claim applica la soglia operativa fissata sui dati di validazione e salvata con i pesi.

Errori

  • 422: manca o e' vuoto cio' che l'agente ha ricevuto o il messaggio
  • 503: il lettore non può caricarsi su questo host (per esempio memoria libera insufficiente). Il percorso rifiuta invece di mettere a rischio il servizio.

Fine-tuning come servizio (beta)

Dai due chiavi, ricevi un modello affinato nel tuo repository e un report di cosa ha cambiato l'addestramento. Un token Hugging Face legge il repository dei dati e scrive quello di uscita; un token Modal fa girare l'addestramento sul tuo account GPU. Controlliamo i dati, mettiamo da parte esempi prima dell'addestramento, affiniamo, mettiamo i pesi nel tuo repository, li ricarichiamo e leggiamo il comportamento del modello prima e dopo sulle stesse richieste, accanto alle risposte dei tuoi dati. Le chiavi sono salvate cifrate e cancellate quando finisce la parte GPU. Prefisso: /api/v2/tune

POST /api/v2/tune/jobs

Crea un lavoro. Le due chiavi vengono controllate prima di salvare qualsiasi cosa. Un lavoro attivo per account.

{
  "data_repo": "acme/support-data",
  "out_repo": "acme/support-model",
  "base_model": "Qwen/Qwen2.5-0.5B-Instruct",
  "hf_token": "hf_...",
  "modal_token_id": "ak-...",
  "modal_token_secret": "as-...",
  "n_train": 2000,
  "epochs": 1
}
GET /api/v2/tune/jobs

I tuoi lavori, dal più recente.

GET /api/v2/tune/jobs/{id}

Un lavoro: stato (queued, training, reading, done, failed, cancelled), le fasi completate, l'avanzamento della lettura e il report a fine lavoro.

POST /api/v2/tune/jobs/{id}/cancel

Ferma l'addestramento sulla GPU e cancella le chiavi.

DELETE /api/v2/tune/jobs/{id}/keys

Cancella subito le chiavi salvate. Un lavoro che ne ha ancora bisogno viene annullato.

Use Cases API

Problem→solution playbooks: map a user problem ("does my chatbot hallucinate?") to the concrete PSA path - honest verdict, indicator + thresholds, API, manual page, demo data, proof. Retrieval uses the production MiniLM encoder with a contrastive confidence rule (precision-favouring: off-topic/ambiguous queries return no confident match). Public, read-only. Prefix: /api/v2/usecases

GET /api/v2/usecases

List all Use Cases (summary view): { "usecases": [ … ], "count": N }. Each summary carries slug, title, problem, verdict, verdict_detail, indicators, apis, manual, demo, proof, ledger_rows, issues.

GET /api/v2/usecases/match

Match a user-phrased problem to the Use Case(s) that address it.

Query Parameters

qrequired - a user-phrased problem statement
floordefault 0.30 - minimum raw similarity to the best Use Case phrasing
margindefault 0.05 - minimum contrastive margin over background anchors
top_kdefault 3 (1-10)

Response

{
  "query": "does my chatbot make up facts?",
  "floor": 0.30, "margin": 0.05, "background_similarity": 0.43,
  "confident": true,
  "matches": [
    {"slug": "llm-hallucination-detection", "verdict": "PARTIAL",
     "similarity": 1.0, "margin": 0.49, "confidence": "high", "...": "…"}
  ]
}

confidence band: high (margin ≥ 0.15), medium (≥ 0.05), else dropped. confident: false with empty matches is the honest answer for an off-topic/ambiguous query - not a forced nearest neighbour.

GET /api/v2/usecases/{slug}

A single Use Case by slug. Unknown slug → 404.

API Sessioni - /api/sessions

CRUD sessione - crea, elenca, rinomina, elimina. Tutti gli endpoint richiedono l'autenticazione con token Bearer.

DELETE /api/sessions/{session_id}

Soft-delete di una singola sessione (is_deleted = true). Limitato al proprietario.

{ "ok": true }
DELETE /api/sessions

Soft-delete di tutte le sessioni per l'utente autenticato in una singola chiamata. Irreversibile - nessuna finestra di annullamento.

{ "ok": true, "deleted": 42 }

deleted - numero di sessioni contrassegnate come eliminate.

Dati demo - /api/auth

Generazione e pulizia dataset demo una-tantum. Questi endpoint richiedono autenticazione cookie.

I record demo sono inseriti con is_demo=true e sono visibili insieme ai dati reali nel dashboard.

POST /api/auth/dismiss-demo-modal

Segna il modal dati demo una-tantum come ignorato (demo_modal_shown = true) così non apparirà di nuovo.

Nessun corpo della richiesta. Richiede l'autenticazione tramite cookie (psa_token). Idempotente - sicuro da chiamare più volte.

{ "ok": true }
POST /api/auth/generate-demo-data

Genera un dataset demo una tantum per l'utente autenticato. Crea record nella dashboard delle sessioni:

  • DSA Hub - 7 sessioni demo con sequenze di postura realistiche

Restituisce 409 se i dati demo sono già stati generati. Imposta demo_data_generated = true sul profilo utente.

{
  "ok": true,
  "psa_sessions": 7
}
DELETE /api/auth/demo-data

Elimina permanentemente tutti i record demo (is_demo = true) per l'utente autenticato.

Rimuove righe da: sessions.

{ "ok": true }

Sessioni di calibrazione demo - /v1/demo

Endpoint pubblici - nessuna autenticazione richiesta. Sfoglia 149 sessioni di calibrazione (input utente + risposta agente + punteggi PSA completi).

GET /v1/demo/sessions

Elenca le sessioni di calibrazione con filtri persona/arco opzionali. Paginato lato server.

Parametri di query

ParamTipoDescrizione
personastringFiltro: customer_support, educational, hr_policy, it_helpdesk
arcstringFiltra per nome dell'arco (ad es. grief_initial_contact)
pageintNumero di pagina (predefinito: 1)
per_pageintRisultati per pagina, 1-100 (predefinito: 20)
curl "https://splabs.io/v1/demo/sessions?persona=customer_support&page=1"
GET /v1/demo/sessions/{session_key}

Ottieni i turni completi di conversazione per una singola sessione di calibrazione. Restituisce tutti i turni {human, assistant} con punteggi PSA completi.

curl "https://splabs.io/v1/demo/sessions/<session_key>"
POST /api/v2/psa/internal/seed-calibration-session admin

Insert or replace a demo calibration conversation - the demo-data a case study links to. Idempotent by session_key; turns use the {human, assistant} shape. Demo data only, no scoring path (#3377).

curl -X POST "https://splabs.io/api/v2/psa/internal/seed-calibration-session" \
  -H "Authorization: Bearer $PSA_TOKEN" -H "Content-Type: application/json" \
  -d '{"session_key":"demo-it-authority-en-01","persona":"it_helpdesk","arc_name":"admin_access_authority_pressure","turns":[{"human":"…","assistant":"…"}]}'

Studi di caso - /v1/case-studies

Endpoint pubblici - nessuna autenticazione richiesta. Case study curati supportati da sessioni di calibrazione con punteggi PSA reali.

GET /v1/case-studies

Elenca tutti gli studi di caso pubblicati con slug, titolo, tag e riepilogo.

curl "https://splabs.io/api/v1/case-studies"

Risposta

{ "items": [ { "slug": "admin-access-authority-pressure-it", "arc_name": "admin_access_authority_pressure", "persona": "it_support", "title": "...", "tags": ["Case Study","Posture drift"], "summary": "..." } ] }
GET /v1/case-studies/{slug}

Ottieni un singolo case study con trascrizione completa della conversazione dalla sessione di calibrazione collegata.

Parametri di percorso

ParamDescrizione
slugSlug dello studio di caso (ad es. admin-access-authority-pressure-it)
curl "https://splabs.io/api/v1/case-studies/admin-access-authority-pressure-it"

Novità dal lab - /v1/lab-updates

Endpoint pubblici, senza autenticazione. I post pubblicati dal laboratorio: su cosa stiamo lavorando. Il testo segue lang (en, it, fr, es, de).

GET /v1/lab-updates

Elenca i post pubblicati, dal più recente. Filtro per kind; paginato.

curl "https://splabs.io/v1/lab-updates?lang=en&per_page=10"
GET /v1/lab-updates/{slug}

Un post pubblicato con il suo dettaglio tecnico (body, markdown).

curl "https://splabs.io/v1/lab-updates/topic-reader-five-languages?lang=it"

Limiti di velocità

Piano Analisi/Mese Sessioni Accesso API
Gratuito505No
Pro5,000IllimitatoSì
UnlimitedIllimitatoIllimitatoSì

Codici di errore

Tutti gli errori seguono il formato {"detail": "..."}. Gli errori strutturati (ad es. 503) restituiscono un dict:

{
  "detail": {
    "error": "session_id_required",
    "message": "Either session_id (UUID) or session_name must be provided.",
    "hint": "For stateless analysis without session tracking, set dry_run=true."
  }
}
Codice Significato
401API key mancante o non valida / non autenticato
403Il piano non include accesso API o downgrade dell'abbonamento bloccato (controlla i dettagli).
404Risorsa non trovata
409Turno duplicato - la stessa sessione + turn_number già esiste
422Corpo della richiesta non valido (errore di tipo o formato del campo)
429Limite di analisi mensili raggiunto - fai un backup e riprova dopo Retry-After
500Errore interno del server
503session_id_required - session_id (UUID) o session_name deve essere fornito. Usa dry_run=true per testare senza persistere.
GET /api/v2/psa/internal/ha/aggregate Solo amministratore

Metriche di drift HA anonimizzate a livello di popolazione. Richiede una coorte minima di 10 utenti con total_turns > 0. Restituisce HTTP 404 se la coorte è troppo piccola.

{
  "cohort_size": 23,
  "total_turns": 4182,
  "l2_avg": {"validation_seeking": 0.08, "agency_erosion": 0.04, ...},
  "l3_avg": {"cognitive_rigidity": 0.11, "reality_anchoring": 0.06, ...},
  "l4_avg": {"legibility_adaptation": 0.14, "reciprocity_expect": 0.03, ...},
  "computed_at": "2026-05-25T10:00:00Z"
}

I dati del Layer 5 sono esclusi dagli output aggregati per progettazione.