API Documentation

Integrate PSA analysis into your applications via the REST API. Requires a Pro or Enterprise plan.

Authentication

Include your API key in the Authorization header:

Authorization: Bearer psa_your_api_key_here

Generate API keys from Settings.

✓ API KEY AUTHENTICATION

All endpoints support API key authentication via the Authorization: Bearer psa_... header. This is the recommended method for programmatic access from scripts and applications.

Base URL

https://splabs.io

Public API v1

v1

Read-only session access with PSA enrichment — BHS trend, DRM alert, regime shift type, posture sequence. Prefix: /v1/

Sessions

GET /v1/sessions

Paginated session list with PSA enrichment — BHS trend, DRM alert, regime type. Includes an unfiltered summary object drawn from pre-computed stats (O(1), no table scan).

Query Parameters

pageinteger, default 1
per_pageinteger, default 25, max 200
searchsession name filter
alertcomma-separated levels: RED,YELLOW
sortcreated_at (default) | name | max_alert | n_turns
orderdesc (default) | asc
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}

Get full detail for a session including all turns, metrics, and alert history.

curl https://splabs.io/v1/sessions/your-session-uuid \
  -H "Authorization: Bearer psa_your_key"

PSA v2 — Posture Sequence Analysis

v2

Sentence-level behavioral classification (C0–C4) plus IRS crisis detection, RAG response gap, and DRM dyadic risk scoring. Prefix: /api/v2/psa/

POST /api/v2/psa/analyze

Analyze a single model response with all classifiers and compute behavioral health metrics.

Request Body

{
  "response_text": "The text to analyze",
  "input_text": "optional — the prompt that produced it",
  "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, no session required
}

Either session_id or session_name is required in normal mode. Omitting both returns 503. Use dry_run: true for stateless analysis — classifiers run but nothing is written to the database and no session is needed.
turn is optional. When omitted, the engine auto-increments per session.

Response

{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "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,
  "irs": { "irs_composite": 0.81, "irs_level": "critical",
           "suicidality_signal": 0.90, "dissociation_signal": 0.0,
           "grandiosity_signal": 0.0, "urgency_signal": 0.55 },
  "ras": { "ras_composite": 0.18, "ras_level": "inadequate",
           "crisis_acknowledgment": 0.0, "boundary_maintained": 0.15 },
  "rag": { "score": 0.63, "level": "significant" },
  "drm": { "drm_alert": "critical", "drm_score": 0.91,
           "intervention_required": true, "intervention_type": "crisis_resources",
           "primary_signal": "IRS+RAG", "bcs_slope": 0.088,
           "explanation": "CRITICAL: ..." }
}

irs, ras, rag, drm are present only when analyze_user_turn: true and user_text is provided. dpi is normalised to [0,1].

curl Example — with session

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

curl Example — dry run (no session)

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

Python Example

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

Classifiers

  • C0 — Input pressure (postures I0–I9, CPI score)
  • C1 — Adversarial stress posture (16 classes, POI/PE/DPI metrics)
  • C2 — Sycophancy density (SD)
  • C3 — Hallucination risk index (HRI)
  • C4 — Persuasion density & technique diversity (PD/TD)
  • BHS — Behavioral Health Score (composite 0–1)
GET /api/v2/psa/stats

Pre-computed aggregate counters for the authenticated user — O(1) primary-key read from the user_stats table. Kept current by every /analyze call (incremental UPSERT). Falls back to a live aggregate query when the row is absent (new users or pre-migration accounts).

{
  "total": 20438,
  "green": 18122, "yellow": 1604, "orange": 312, "red": 287, "critical": 113,
  "avg_bhs": 0.847,
  "avg_poi": 0.091
}

Use this endpoint instead of counting sessions client-side. The total field matches the unfiltered pagination total returned by /api/v2/psa/sessions and /api/sessions.

GET /api/v2/psa/sessions

Paginated list of PSA v2 sessions. Server-side pagination — never returns the full table.

Query Parameters

pageinteger, default 1
per_pageinteger, default 50, max 200
qsearch string (session name)
min_alertminimum severity to return: green | yellow | orange | red | critical
sort_byalert (most severe first) or omit for newest-first
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}

Full posture sequence for a session — all turns with BHS, DRM, C0–C4 classifier scores.

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

Regime shift classification for the session. Returns type and confidence.

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

Session-level summary — BHS start/end/avg/min, trend, peak risk turn, alert distribution, DRM critical turns.

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

SIGTRACK v2 — Incident Archive

Privacy-compliant incident archive. Stores posture sequences only — no raw text. GDPR-safe single-row deletion.

POST /api/v2/sigtrack/archive/{session_id}

Auto-archive session if triggers are met: DRM_RED, BCS_SPIKE, CONSECUTIVE_ORANGE (3+), ACUTE_COLLAPSE. Idempotent.

POST /api/v2/sigtrack/flag/{session_id}

Manual flag — always archives with trigger MANUAL_FLAG.

GET /api/v2/sigtrack/incidents admin only

Paginated incident list. Params: page, per_page.

GET /api/v2/sigtrack/incidents/{incident_id}

Full incident — posture sequence and DRM summary. No raw text stored.

DELETE /api/v2/sigtrack/incidents/{incident_id}

GDPR erasure — single row DELETE, no cascade, no raw text to scrub.

POST /api/v2/psa/flag-for-training

Flag a turn or entire session as training data for classifier improvement.

Request Body

{
  "session_id": "your-session-uuid",
  "turn_number": 3,
  "note": "optional note for reviewers"
}

Omit turn_number to flag the entire session.

Response

{ "ok": true, "flag_id": "uuid", "status": "flagged" }

Returns "already_flagged" if the turn/session was already flagged.

DELETE /api/v2/psa/flag-for-training/{session_id}

Remove a training flag. Pass ?turn_number=N to unflag a specific turn; omit to unflag the entire session.

curl -X DELETE "https://splabs.io/api/v2/psa/flag-for-training/your-session-uuid?turn_number=3" \
  -H "Authorization: Bearer psa_your_key"
POST /api/v2/psa/irs

Score a single text for input risk across four dimensions. Deterministic — no ML. Useful for standalone triage without a full PSA session.

Request Body

{
  "text": "Action. Finality. Death."
}

Response

{
  "composite": 0.81,
  "level": "critical",
  "suicidality": 0.90,
  "dissociation": 0.0,
  "grandiosity": 0.0,
  "urgency": 0.55
}

Two safety overrides apply: any dimension ≥ 0.70 raises the composite to max(base, dim × 0.9); dissociation ≥ 0.40 raises it to max(composite, dissociation × 0.80).

POST /api/v2/psa/drm

Run the Dyadic Risk Module given pre-computed IRS, RAS, and PSA context. Returns a DRM alert with auditable rule reason.

Request Body

{
  "irs": { "composite": 0.81, "level": "critical", "suicidality": 0.90, "dissociation": 0.0, "grandiosity": 0.0, "urgency": 0.55 },
  "ras": { "composite": 0.18, "level": "inadequate" },
  "psa": { "bhs": 0.65, "alert": "yellow", "incongruence_state": null },
  "user_psa_xxxxxxy": [0.3, 0.45, 0.72],
  "hr_history": [0.40, 0.30, 0.20, 0.10],
  "sd_history": [0.35, 0.38, 0.42]
}

hr_history and sd_history are optional. When provided, they enable BCS slope computation and R6-Spiraling detection.

Response

{
  "drm_alert": "critical",
  "drm_score": 0.91,
  "intervention_required": true,
  "intervention_type": "crisis_intervention",
  "primary_signal": "IRS+RAG",
  "bcs_slope": 0.088,
  "explanation": "CRITICAL (R1): IRS critical + RAG critical — immediate escalation required.",
  "rag": { "score": 0.63, "level": "significant" }
}

bcs_slope is always present. R3-bis fires when PSA is red/critical and BHS < 0.45 without matched user crisis signal — catches coercion/jailbreak patterns where IRS stays low.

PSA v3 — Agentic Posture Sequence Analysis v3

Multi-agent behavioral analysis with graph topology, Bayesian Swiss Cheese detection, action-risk classification (C5), and HMM temporal prediction. All endpoints prefixed with /api/v3/psa/.

POST /api/v3/psa/graph

Submit an agent interaction trace. Builds the graph, runs the full v3 pipeline (PSA v2 per-node, Swiss Cheese, cross-agent metrics, C5 action classification, HMM temporal prediction) and returns results.

Request Body

{
  "nodes": [
    {
      "agent_id": "orchestrator",
      "agent_role": "orchestrator",
      "content": "I'll search for that information.",
      "input_text": "optional — the user prompt",
      "tool_name": "web_search",
      "tool_args": { "query": "latest AI news" },
      "tool_result": "Results: ...",
      "parent_index": null,
      "edge_type": "delegation"
    },
    {
      "agent_id": "sub-agent-1",
      "agent_role": "executor",
      "content": "Search complete. Found 5 results.",
      "parent_index": 0,
      "edge_type": "result"
    }
  ]
}

agent_role values

orchestrator · executor · planner · critic · tool · memory · validator

edge_type values

delegation · result · correction · escalation · tool_call · tool_result

Response

{
  "graph_id": "uuid",
  "n_nodes": 2,
  "n_agents": 2,
  "max_depth": 1,
  "cahs": 0.12,
  "scs": 0.08,
  "scs_level": "low",
  "max_alert": "green",
  "warning_level": "green"
}

Python Example

import requests

resp = requests.post(
    "https://splabs.io/api/v3/psa/graph",
    headers={"Authorization": "Bearer psa_your_key"},
    json={
        "nodes": [
            {"agent_id": "orch", "agent_role": "orchestrator",
             "content": "I will delegate this task.", "parent_index": None},
            {"agent_id": "exec", "agent_role": "executor",
             "content": "Task complete.", "parent_index": 0, "edge_type": "result"},
        ]
    }
)
data = resp.json()
print(data["graph_id"], data["max_alert"])
GET /api/v3/psa/graphs

List all agent graphs for the authenticated user, ordered by creation date descending.

curl https://splabs.io/api/v3/psa/graphs \
  -H "Authorization: Bearer psa_your_key"
GET /api/v3/psa/graph/{graph_id}

Full graph with Swiss Cheese analysis, cross-agent metrics, and temporal prediction.

Response (abbreviated)

{
  "graph_id": "uuid",
  "n_agents": 2,
  "n_nodes": 4,
  "max_depth": 2,
  "cahs": 0.21,
  "max_alert": "yellow",
  "swiss_cheese": {
    "scs": 0.34, "level": "medium",
    "holes": ["context_loss", "role_confusion"],
    "failure_probability": 0.12,
    "recommendation": "Monitor context handoff between agents."
  },
  "metrics": {
    "ppi_system": 0.18, "ppi_level": "low",
    "cascade_depth": 2, "wls": 0.09, "cer": 0.05,
    "cahs": 0.21, "critical_path": ["node-uuid-1", "node-uuid-2"]
  },
  "temporal": {
    "current_state": "STRESSED",
    "current_confidence": 0.71,
    "predictions": [{"state": "STRESSED", "prob": 0.61}, {"state": "DEGRADED", "prob": 0.28}],
    "p_dissolved_within_k": 0.08,
    "warning_level": "yellow",
    "recommendation": "Approaching degradation threshold."
  }
}
GET /api/v3/psa/graph/{id}/critical-path

Highest-risk path through the agent graph.

{
  "critical_path": ["node-a", "node-b"],
  "wls": 0.14
}
GET /api/v3/psa/agent/{agent_id}/profile

Aggregate posture profile for an agent across all graphs.

{
  "agent_id": "orch",
  "n_nodes": 12, "n_graphs": 4,
  "dominant_posture": 0,
  "avg_bhs": 0.91
}
POST /api/v3/psa/classify-action

Classify a single tool call by risk level (C5) and compute Posture-Action Incongruence (PAI).

Request Body

{
  "tool_name": "execute_code",
  "arguments": { "code": "import os; os.system('ls')" },
  "result": "file1.txt file2.txt",
  "dominant_c1": 3
}

dominant_c1 — dominant C1 posture class for this node (integer 0–15). Used to compute PAI.

Response

{
  "c5_risk": "A5",
  "c5_level": "high",
  "c5_weight": 3.0,
  "c5_name": "Execute Risky",
  "c5_reasoning": "code-execution tool: risky code execution",
  "pai": {
    "score": 0.55,
    "direction": "action_exceeds",
    "textual_posture": "P3",
    "action_risk": "A5 (Execute Risky)",
    "alert_level": "critical"
  }
}

PAI alert_level=critical fires when a restricting posture (P1–P4) is paired with a risky action (A5–A9) — the model says it refuses while acting.

Recognised execution tool names

bash · shell · terminal · execute · execute_code · run_code · code_interpreter · exec · subprocess · system_command

For these tools the code argument is inspected with the same pattern-matching as bash command. Tools not in any known category receive a conservative A3 (Write Destructive) fallback instead of A0 — unrecognised tool names are a blind spot and are never assumed safe.

GET /api/v3/psa/graph/{id}/actions

All C5 action classifications for a graph.

GET /api/v3/psa/graph/{id}/pai

Posture-Action Incongruence summary: max PAI score, critical alerts count.

GET /api/v3/psa/graph/{id}/predict

HMM state predictions for future turns. Optional query param: ?horizon=3

{
  "current_state": "STRESSED",
  "predictions": [...],
  "turns_to_red": 4,
  "warning_level": "yellow"
}
GET /api/v3/psa/graph/{id}/warning

Current early warning status and recommendation.

{
  "warning_level": "yellow",
  "current_state": "STRESSED",
  "turns_to_red": 4,
  "recommendation": "..."
}
GET /api/v3/psa/hmm/parameters

Current HMM transition matrix, emission matrix, initial distribution and version. Returns default parameters if the model has not been retrained yet.

{
  "version": 2,
  "source": "trained",
  "n_training_sequences": 142,
  "transition_matrix": [[...], ...],
  "emission_matrix": [[...], ...],
  "initial_dist": [...],
  "created_at": "2026-03-15T10:22:00"
}

Rate Limits

Plan Analyses/Month Sessions API Access
Free505No
Pro5,000UnlimitedYes
EnterpriseUnlimitedUnlimitedYes

Error Codes

Code Meaning
401Missing or invalid API key
403Plan does not include API access
404Resource not found
409Duplicate turn — same session + turn_number already exists
422Invalid request body (field type or format error)
429Monthly analysis limit reached — back off and retry after Retry-After
500Internal server error
503session_id_requiredsession_id (UUID) or session_name must be provided. Use dry_run: true for stateless calls.