Documentación de API

Integre análisis de PSA en sus aplicaciones a través de la API REST. Requiere un plan Pro o Enterprise.

Antes de construir sobre una señal: lo que PSA no hace está documentado aparte, con la medición detrás de cada límite (sin modelo del mundo, la tasa actual de falsas alarmas en tráfico ordinario, los no-objetivos permanentes). Lea Lo que PSA no es.

Autenticación

Incluya su clave API en el encabezado Autorización:

Authorization: Bearer psa_your_api_key_here

Genere claves API desde Configuración.

✓ AUTENTICACIÓN POR CLAVE API

Todos los endpoints soportan autenticación con clave API mediante Authorization: Bearer psa_.

URL base

https://splabs.io

Estado

GET /ping

Verificación de salud simple. No requiere autenticación, sin dependencia de base de datos.

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

Verificación de salud completa con prueba de conectividad de BD. Devuelve 503 si BD no es accesible.

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

API pública v1

v1

Acceso de solo lectura a la sesión con enriquecimiento PSA - tendencia BHS, cambio de régimen. Devuelve los turnos completos de la conversación y los metadatos de sesión.

Sesiones

GET /v1/sessions

Lista de sesiones paginada con enriquecimiento PSA - tendencia BHS, tipo de régimen. Ordena por fecha_creación descendente por defecto.

Parámetros de consulta

páginaentero, predeterminado 1
por_páginaentero, predeterminado 25, máximo 200
búsquedafiltro de nombre de sesión
alertaniveles separados por comas: RED,YELLOW
ordenarcreated_at (predeterminado) | name | max_alert | n_turns
ordendesc (predeterminado) | 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}

Obtener detalle completo de una sesión, incluidos todos los turnos, métricas e historial de alertas.

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 - Análisis de Secuencia de Postura

v2

Clasificación conductual a nivel de oración (C0-C4) y métricas conductuales derivadas.

POST /api/v2/psa/public-analyze Público · sin autenticación

Análisis PSA gratuito y no autenticado que alimenta el widget del landing. Sin estado (análisis no persistido). Limitado a 500 tokens por solicitud.

Cuerpo de solicitud

{
  "turns": [ {"user": "How do I do X?", "model": "I can help with that."} ],
  "clf_context": "clinical"
}
GET /api/v2/psa/public-stats Público · sin autenticación

Estadísticas honestas para el landing: contador acumulativo en vivo analyses_run (lectura O(1) en una sola fila) y número de clasificadores activos. Sin actualizaciones de IA - solo contadores de DB.

POST /api/v2/psa/analyze

Analiza un turno de conversación. Soporta tres modos: solo-agente (solo response_text), solo-usuario (solo user_text) o completo (ambos).

Cuerpo de solicitud

{
  "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)
}

Al menos uno de response_text o user_text debe ser proporcionado - omitir ambos devuelve 422. encrypt_original: si es true y hay una clave pública registrada (ver Privacy API / /psa-privacy), el original almacenado se cifra en reposo (enc_user_text/enc_response_text) y solo se puede leer con la clave privada del cliente; si no hay clave o falla el cifrado, no se almacena texto. Independiente de save_text. Por defecto false.

Que lente evalua el turno del agente: clf_context. "clinical" (por defecto) evalua el comportamiento con C3-v2 y devuelve hri; "agentic" lo evalua con C3-v3 y devuelve abi. Nada deduce la lente: el trafico de agentes de IA se evalua en modo clinico salvo que envies "agentic" en cada llamada. Las bandas de parada son distintas: clinical alert amarillo/rojo, agentic abi >= 0.50 (0.25-0.49 = reformular). Solo se aceptan estos dos valores: cualquier otro devuelve 422. La respuesta siempre indica la lente que realmente evaluo el turno en c3.clf_context, con c3.lens_fallback cuando se pidio "agentic" y respondio C3-v2 (confianza por debajo de 0.50), mas un c3.lens_fallback_reason. No deduzcas la lente de la presencia de abi: no distingue "se pidio clinical" de "agentic no se ejecuto".

Que mide realmente cada forma de turno. Desde el turno 2 en adelante la respuesta tambien lleva analysis_mode partial y missing_user_context true. Simetricamente, un turno user_only no tiene bhs ni alerta de postura, porque no hay turno del agente que evaluar.

Respuesta

{
  "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 son null para los turnos solo-usuario. c0 es null para los turnos solo-agente.

Ejemplo curl - con sesión

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
  }'

Ejemplo curl - prueba seca (sin sesión)

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
  }'

Ejemplo de 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())

Clasificadores

  • C0 - Presión de entrada (posturas I0-I9, puntuación CPI)
  • C1 - Postura de estrés adversarial (21 clases P0-P20, métricas POI/PE/DPI)
  • C2 - Densidad de sicoofancia (SD)
  • C3 - Índice de riesgo de alucinación (HRI)
  • C4 - Densidad de persuasión y diversidad de técnicas (PD/TD)
  • BHS - Puntuación de Salud Conductual (compuesta 0-1)

SRP - Perfil de Riesgo de Sesión: una lectura derivada a nivel de sesión (no un clasificador, no un campo de /analyze) desde c2.sd, c4.pd y bhs contra una norma de registro de rutina; de solo lectura en /api/v2/intel/session-risk-profile/reference.

MCP /mcp

PSA también habla el Model Context Protocol, para que un IDE con IA, un agente o un orquestador puedan leer el comportamiento de forma nativa sin escribir nada de HTTP. Apunta un cliente MCP al endpoint streamable-HTTP alojado más abajo y autentícate con tu clave API como bearer token. Se exponen dos herramientas, cada una sin estado (no se almacena nada) y que devuelven los mismos campos que /analyze:

  • psa_analyze_turn: lee un turno de conversación desde user_text y/o response_text. Devuelve la puntuación de salud del comportamiento, la banda de alerta y las lecturas por postura.
  • psa_analyze_conversation: lee una conversación entera (turns: una lista de objetos {user_text, response_text}) y devuelve las lecturas por turno más un perfil de deriva (media de salud del comportamiento, adulación y persuasión).

Configuración del cliente MCP

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

Llamar a una herramienta directamente (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

Contadores agregados pre-calculados para el usuario autenticado - lectura O(1) por clave primaria. Sin agregados en tiempo real.

{
  "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
}

Use este endpoint en lugar de contar sesiones del lado del cliente. El campo total coincide con sessions.count en el perfil de usuario.

GET /api/v2/psa/sessions

Lista paginada de sesiones DSA. Paginación del lado del servidor - nunca devuelve la tabla completa.

Parámetros de consulta

páginaentero, predeterminado 1
por_páginaentero, predeterminado 50, máximo 200
qcadena de búsqueda (nombre de sesión)
alerta_mínseveridad mínima a devolver: green | yellow | orange | red | critical
ordenar_poralert (más severo primero) u omitir para más reciente primero
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

Clasificación de cambio de régimen para la sesión. Devuelve tipo y confianza.

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

Resumen a nivel de sesión - BHS inicio/fin/promedio/mínimo, tendencia, turno de mayor riesgo, distribución de alertas.

{
  "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]
}

Administración - Aprovisionamiento de Usuarios y Organizaciones

Todos los endpoints /api/admin/ requieren el rol admin (Bearer token o cookie). Usado por el panel Operador.

POST /api/admin/users/provision solo administrador · usuario+clave atómico

Crea atómicamente un usuario + clave API en una sola transacción. Sin límite de tasa. Devuelve la clave en texto plano - nunca más se expondrá.

Cuerpo: email (requerido), name, role, plan, key_name, org_id (UUID, opcional).

POST /api/admin/orgs solo administrador · 201

Cree una nueva organización. Cuerpo: name (requerido), 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 administrador · paginado

Lista todas las organizaciones con member_count y session_count en vivo. Parámetros query: page, per_page, search.

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

Agregue o actualice la membresía de un usuario. Cuerpo: user_id (UUID), role (owner/member).

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

Inscribe en masa todas las cuentas fury-<sector>-* en esta org. Requiere que la org tenga un plan enterprise activo.

Pagos y Facturación

sesión web

Gestión de suscripciones e historial de facturación. Todos los endpoints requieren autenticación cookie.

GET /api/payments/history

Devuelve el historial de pagos paginado para el usuario autenticado, ordenado más reciente primero.

Parámetros de consulta

páginaentero, predeterminado 1
por_páginaentero, predeterminado 10, máximo 100

Respuesta

{
  "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
}

amount está en centavos (p. ej. 2900 = $29.00). valores de status: succeeded | failed.

POST /api/payments/create-checkout

Inicia una sesión de checkout de Stripe o un cambio de suscripción, dependiendo del estado actual del usuario.

Cuerpo de solicitud

{ "plan": "pro" }

Matriz de Comportamiento

Estado actualPlan de destinoResultado
Gratuito / sin suscripciónpro / enterpriseNueva sesión de Checkout de Stripe → {"url": "..."}
Suscripción activamismo planRedirección de portal de facturación → {"url": "...", "action": "portal"}
Pro activoenterprise (upgrade)Subscription.modify() directamente → {"action": "upgraded", "plan": "enterprise"}
Enterprise activopro (downgrade)HTTP 403 - bloqueado con fecha de fin de período
Pro / Enterprise activogratuito (degradación)HTTP 403 - bloqueado

Respuestas

// 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 sesión de portal de facturación de Stripe para administrar métodos de pago y ver facturas.

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

Se cancela al final del período (cancel_at_period_end=true). El acceso continúa hasta la fecha de fin de período.

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

period_end es una marca de tiempo Unix.

POST /api/payments/sync-subscription

Vuelve a obtener el estado de la suscripción desde Stripe y actualiza la base de datos local. Úselo cuando el estado local diverge (p. ej., tras un webhook fallido).

{ "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

Devuelve paquetes de tokens extra disponibles para compra única. Válido durante 12 meses desde la fecha de compra.

{ "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 sesión de checkout de Stripe de pago único para un paquete de tokens adicionales. Redirige al usuario a Stripe para el pago.

// 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 de Conectores

v2

Clasificadores de texto multi-clase personalizados - defina clases, genere datos de entrenamiento via la pasarela CF y entrene modelos MiniLM.

POST /api/v2/connectors/ Autenticación requerida

Registra un nuevo conector desde un esquema JSON. Devuelve 201. Devuelve 409 si el conector ya existe.

// 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/

Enumere todos los conectores - id, nombre, estado, num_classes, created_at. Sin autenticación requerida.

[{
  "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}

Detalle completo del conector que incluye todas las definiciones de clase. Sin autenticación requerida. 404 si no se encuentra.

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

Punto final de sondeo de estado. Autenticación requerida. Devuelve estado actual + 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 Flujo SSE · Autenticación requerida

Guía la pipeline completa generar → entrenar → listo via un flujo Server-Sent Events. Conéctese y espere eventos hasta 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

Clasifique una cadena de texto contra un conector listo. Devuelve 503 si el conector no está listo.

// 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 administrador

Elimina un conector - elimina la fila de DB, todas las filas de clases (cascada) y el archivo de modelo .npz si existe.

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

API de Base de Conocimientos

Base de conocimiento semántica Q&A usando embeddings MiniLM-384 y búsqueda por similitud coseno.

PostgreSQL pgvector Requerido

Requiere la extensión pgvector en PostgreSQL. Devuelve 503 si no está disponible. Estado actual: disponible.

POST /api/v2/knowledge/query

Incrusta la consulta con MiniLM-384 y devuelve elementos de conocimiento semánticamente similares con enrutamiento basado en confianza.

Cuerpo de solicitud

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

Respuesta

{
  "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"
}

Umbrales de Enrutamiento

autoconfianza ≥ 0.85Respuesta devuelta directamente
advertencia0.65 ≤ confianza < 0.85Respuesta con ⚠️ advertencia añadida
escaladoconfianza < 0.65Consulta registrada para revisión humana

Errores

  • 422 - query está vacía
  • 503 - pgvector no está disponible o el servicio de incrustación está caído
POST /api/v2/knowledge/entries solo administrador

Inserción masiva de entradas de conocimiento con embedding MiniLM en el servidor. Las entradas vacías o con menos de 10 caracteres se descartan - las filas degeneradas coinciden con casi cualquier consulta y contaminan la recuperación (#3683).

Cuerpo de solicitud

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

Respuesta

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

Errores

  • 403 - no es administrador
  • 503 - servicio de incrustación no disponible
DELETE /api/v2/knowledge/entries solo administrador

Elimina entradas de conocimiento: una entrada por UUID, una fuente completa, o todo. entry_id tiene prioridad sobre source; omitiendo ambos se borra TODO.

Parámetros de consulta

entry_idUUID opcional de una sola entrada a eliminar - tiene prioridad sobre source (#3683)
sourcefuente opcional cuyas entradas se eliminan todas; omitir ambos parámetros vacía toda la base de conocimiento

Respuesta

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

Errores

  • 422: entry_id no es un UUID válido
  • 403 - no es administrador
  • 503: base de datos no disponible

Lectores independientes (beta)

Lectores independientes servidos junto al análisis principal. Cada uno tiene su propio modelo y su propio interruptor, y ninguno cambia una puntuación o una alerta devuelta por el endpoint de análisis.

POST /api/v2/readers/reader-v2

Lector v2 (beta): un modelo abierto, una pasada, todas las lecturas

Un modelo abierto sin modificar (Qwen3-1.7B) se lee desde su estado interno, con una pequeña capa lineal por lectura. Una sola pasada por el texto devuelve todas las lecturas a la vez: presión de entrada, una respuesta que cede, adulación, riesgo de alucinación, persuasión, presión entre agentes, si un mensaje toca un tema a vigilar y si su intención es obtener ayuda peligrosa. Medido en nuestros datos nunca vistos, al nivel o por encima de nuestro lector entrenado en cada lectura de comportamiento, en cinco idiomas, y en conversaciones de varios mensajes.

Envía la conversación, del mensaje más antiguo al más reciente. Si el último mensaje es del asistente, es la respuesta que se lee. La adulación y el riesgo de alucinación leen la petición junto con la respuesta; la lectura de intención peligrosa lee toda la conversación, así que cuenta también una intención dicha en un mensaje anterior. Clave API, 60 peticiones por minuto.

Cuerpo de la petición

{
  "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."}
  ]
}

Respuesta

{
  "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"}
}

Cada score ordena los textos: más alto significa más de la lectura. No es una probabilidad calibrada. Cada lectura lleva también un level: alert por encima del punto en que salta como mucho 1 vez de cada 100 conversaciones reales ordinarias, watch por encima del punto 5 de cada 100, none por debajo. Ambos puntos se fijaron sobre 2.300 conversaciones reales y se comprobaron sobre otras 2.271.

Errores

  • 422: mensajes no válidos, o nada que leer
  • 503: el lector no puede cargarse en este servidor (por ejemplo, memoria libre insuficiente). La ruta se niega antes que poner en riesgo el servicio.
POST /api/v2/readers/c2-reversal

Lector de cesión C2 (beta): la cesión leída frente a la respuesta anterior

Lee si una respuesta abandona su posición anterior para dar la razón a la insistencia del usuario. Una respuesta que cede suele sonar segura (una disculpa, un "tienes razón" en negrita y luego la afirmación falsa explicada), así que leída sola parece cualquier respuesta segura. Este lector ve también el turno anterior del asistente, así que lee el cambio de postura en sí. En 800 turnos bajo presión nunca vistos, en cinco idiomas, separa ceder de mantenerse a 0,98 (1 es perfecto), con 3 falsas alarmas en 400 respuestas que se mantenían firmes.

Envía el turno anterior del asistente, el turno del usuario que insiste y la respuesta. Una respuesta que acepta la corrección correcta de un error anterior no es una cesión. Clave API, 60 solicitudes por minuto.

Cuerpo de la petición

{
  "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."
}

Respuesta

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

score ordena las respuestas: más alto significa más cesión. No es una probabilidad calibrada. caved aplica el umbral operativo fijado en los datos de validación y guardado con los pesos.

Errores

  • 422: falta el turno anterior o la respuesta, o está vacío
  • 503: el lector no puede cargarse en este host (por ejemplo, memoria libre insuficiente). La ruta se niega antes que poner en riesgo el servicio.
POST /api/v2/readers/c3-swarm

Lector C3 para enjambres (beta): un mensaje de agente que da por cierta una afirmación falsa

Lee si un mensaje de agente le sigue la corriente a una afirmación falsa, incluido un «Correcto, este es el siguiente paso» tranquilo y neutro, sin ninguna adulación, que una lectura de la complacencia no ve. Lee el mensaje junto con lo que el agente recibió. En mensajes de agentes sobre afirmaciones falsas nunca vistas, ordena bien estas confirmaciones cerca de 84 veces de cada 100 pares (la lectura de la complacencia cerca de 60), y encuentra cerca de 7 de cada 10 con 15 falsas alarmas de cada 100.

Envía lo que el agente recibió y lo que escribió. Pensado para mensajes entre agentes que contienen una afirmación: en turnos sueltos de pregunta y respuesta todavía marca muchas respuestas concretas correctas. Clave de API, 60 solicitudes por minuto.

Cuerpo de la petición

{
  "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."
}

Respuesta

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

score ordena los mensajes: cuanto más alto, más probable que dé por cierta una afirmación falsa. No es una probabilidad calibrada. confirms_false_claim aplica el umbral operativo fijado con datos de validación y guardado con los pesos.

Errores

  • 422: falta o está vacío lo que el agente recibió o el mensaje
  • 503: el lector no puede cargarse en este host (por ejemplo, memoria libre insuficiente). La ruta se niega antes que poner en riesgo el servicio.

Fine-tuning como servicio (beta)

Da dos claves y recibe un modelo afinado en tu propio repositorio y un informe de lo que cambió el entrenamiento. Un token de Hugging Face lee tu repositorio de datos y escribe el de salida; un token de Modal ejecuta el entrenamiento en tu cuenta de GPU. Comprobamos los datos, apartamos ejemplos antes de entrenar, afinamos, subimos los pesos a tu repositorio, los recargamos y leemos el comportamiento del modelo antes y después con las mismas peticiones, junto a las respuestas de tus datos. Las claves se guardan cifradas y se borran al terminar la parte de GPU. Prefijo: /api/v2/tune

POST /api/v2/tune/jobs

Crear un trabajo. Ambas claves se comprueban antes de guardar nada. Un trabajo activo por cuenta.

{
  "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

Tus trabajos, del más reciente.

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

Un trabajo: estado (queued, training, reading, done, failed, cancelled), las fases completadas, el avance de la lectura y el informe al terminar.

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

Detener el entrenamiento en la GPU y borrar las claves.

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

Borrar ya las claves guardadas. Un trabajo que aún las necesita se cancela.

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 de Sesiones - /api/sessions

Sesiones CRUD - crear, enumerar, renombrar, eliminar. Todos los puntos finales requieren autenticación de token de portador.

DELETE /api/sessions/{session_id}

Elimine suavemente una sesión única (is_deleted = true). Limitado al propietario.

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

Elimine suavemente todas las sesiones para el usuario autenticado en una sola llamada. Irreversible - sin ventana de deshacer.

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

deleted - número de sesiones marcadas como eliminadas.

Datos de Demostración - /api/auth

Generación y limpieza de dataset de demostración de una sola vez. Estos endpoints requieren autenticación cookie.

Los registros de demostración se insertan con is_demo=true y son visibles junto con los datos reales en el panel.

POST /api/auth/dismiss-demo-modal

Marca el modal de datos de demostración de una sola vez como ignorado (demo_modal_shown = true) para que no aparezca de nuevo.

Sin cuerpo de solicitud. Requiere autenticación de cookie (psa_token). Idempotente - seguro para llamar múltiples veces.

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

Genera un conjunto de datos de demostración único para el usuario autenticado. Crea registros en el panel de sesiones:

  • DSA Hub - 7 sesiones de demostración con secuencias de postura realistas

Devuelve 409 si los datos de demostración ya fueron generados. Establece demo_data_generated = true en el perfil de usuario.

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

Elimina permanentemente todos los registros de demostración (is_demo = true) para el usuario autenticado.

Elimina filas de: sessions.

{ "ok": true }

Sesiones de Calibración de Demostración - /v1/demo

Endpoints públicos - no se requiere autenticación. Explore 149 sesiones de calibración (entrada de usuario + respuesta de agente + puntuaciones PSA completas).

GET /v1/demo/sessions

Enumere sesiones de calibración con filtros de persona/arco opcionales. Paginado del lado del servidor.

Parámetros de consulta

ParamTipoDescripción
personacadenaFiltro: customer_support, educational, hr_policy, it_helpdesk
arcocadenaFiltrar por nombre de arco (p. ej. grief_initial_contact)
páginaintNúmero de página (predeterminado: 1)
por_páginaintResultados por página, 1-100 (predeterminado: 20)
curl "https://splabs.io/v1/demo/sessions?persona=customer_support&page=1"
GET /v1/demo/sessions/{session_key}

Obtenga los turnos completos de conversación para una sola sesión de calibración. Devuelve todos los turnos {human, assistant} con puntuaciones PSA completas.

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":"…"}]}'

Estudios de Caso - /v1/case-studies

Endpoints públicos - no se requiere autenticación. Casos de estudio seleccionados respaldados por sesiones de calibración con puntuaciones PSA reales.

GET /v1/case-studies

Enumere todos los estudios de caso publicados con slug, título, etiquetas y resumen.

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

Respuesta

{ "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}

Obtenga un solo caso de estudio con la transcripción completa de la conversación desde la sesión de calibración vinculada.

Parámetros de Ruta

ParamDescripción
slugSlug del estudio de caso (p. ej. admin-access-authority-pressure-it)
curl "https://splabs.io/api/v1/case-studies/admin-access-authority-pressure-it"

Novedades del lab - /v1/lab-updates

Endpoints públicos, sin autenticación. Las publicaciones del laboratorio: en qué estamos trabajando. El texto sigue lang (en, it, fr, es, de).

GET /v1/lab-updates

Lista las publicaciones, de la más reciente. Filtro por kind; paginado.

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

Una publicación con su detalle técnico (body, markdown).

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

Límites de Velocidad

Plan Análisis/Mes Sesiones Acceso a API
Gratuito505No
Pro5,000IlimitadoSí
UnlimitedIlimitadoIlimitadoSí

Códigos de Error

Todos los errores siguen el formato {"detail": "..."}. Errores estructurados (p. ej. 503) devuelven 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."
  }
}
Código Significado
401Clave API faltante o inválida / no autenticado
403El plan no incluye acceso API o degradación de suscripción bloqueada (verificar detalles).
404Recurso no encontrado
409Turno duplicado - misma sesión + turn_number ya existe
422Cuerpo de solicitud inválido (error de tipo o formato de campo)
429Límite de análisis mensual alcanzado - retroceda y reintente después de Retry-After
500Error interno del servidor
503session_id_required - session_id (UUID) o session_name debe ser proporcionado. Use dry_run=true para probar sin persistir.
GET /api/v2/psa/internal/ha/aggregate Solo administrador

Métricas de drift HA anonimizadas a nivel de población. Requiere una cohorte mínima de 10 usuarios con total_turns > 0. Devuelve HTTP 404 si la cohorte es demasiado pequeña.

{
  "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"
}

Los datos de la Capa 5 se excluyen de los resultados agregados por diseño.