ThunderPhone 2.0 is live.Self-serve, from 2¢/min.Read the announcement

Calls

Investigations

AI-assisted root-cause analysis: generate hypotheses and prompt fixes for recurring call issues, test them, and apply the winner.

An investigation takes a cluster of related issue reports on one agent and works toward a prompt fix: the AI generates hypotheses about the root cause, drafts prompt candidates (you can add your own), each candidate can be tested in an experiment (replaying the failing calls' inputs against the candidate prompt), and the winning candidate is applied to the agent.

Status flow: idlegenerating_hypothesesawaiting_candidatessearchingreadyapplied (or dismissed at any point). Starting an investigation kicks off the whole flow automatically — diagnosis, candidate prompt changes, and replay testing for each one. Steps run asynchronously — poll the detail endpoint.

Endpoints

MethodPathDescription
GET/v1/investigationsList investigations (?agent_id=, ?status=)
POST/v1/investigationsStart an investigation
GET/v1/investigations/{investigation_id}Full detail with candidates + experiments
PATCH/v1/investigations/{investigation_id}Update experiment_config, or dismiss
POST/v1/investigations/{investigation_id}/hypothesesRe-kick diagnosis on an idle investigation
POST/v1/investigations/{investigation_id}/candidates/manualAdd your own candidate prompt
POST/v1/investigations/{investigation_id}/candidates/{candidate_id}/experimentsRun an experiment for a candidate
GET/v1/investigations/{investigation_id}/experiments/{experiment_id}Poll one experiment
POST/v1/investigations/{investigation_id}/applyApply a candidate's prompt to the agent

All ids on this page are UUIDs except agent_id and call_ids.

Investigation object

{
  "id": "e7a91c2d-…",
  "issue_pattern_id": "long-pauses-before-answering",
  "issue_trace_ids": ["5f9a…", "77b1…"],
  "agent_id": 12,
  "baseline_prompt": "You are a helpful support agent…",
  "call_ids": [987654321, 987654322],
  "status": "ready",
  "experiment_config": { "sample_size": 5 },
  "hypotheses": [
    { "id": "h1", "title": "Prompt over-constrains responses", "rationale": "…" }
  ],
  "baseline_results": null,
  "candidates": [
    {
      "id": "c3d4…",
      "source": "ai",
      "hypothesis_id": "h1",
      "label": "Loosen response constraints",
      "prompt": "You are a helpful support agent… (revised)",
      "rationale": "…",
      "created_at": "2026-04-20T18:30:00.000Z",
      "latest_experiment_id": "a1b2…"
    }
  ],
  "experiments": [
    {
      "id": "a1b2…",
      "candidate_id": "c3d4…",
      "status": "completed",
      "config": { "sample_size": 5 },
      "samples": [
        {
          "id": "s1…",
          "call_id": 987654321,
          "sample_index": 0,
          "user_input": "…",
          "original_output": "…",
          "replayed_output": "…",
          "grade": { "verdict": "improved" },
          "created_at": "2026-04-20T18:31:00.000Z"
        }
      ],
      "summary": { "improved": 4, "regressed": 0, "unchanged": 1 },
      "created_at": "2026-04-20T18:30:30.000Z",
      "completed_at": "2026-04-20T18:32:10.000Z",
      "error_message": null
    }
  ],
  "applied_candidate_id": null,
  "applied_agent_version": null,
  "error_message": null,
  "created_at": "2026-04-20T18:24:10.113Z",
  "updated_at": "2026-04-20T18:32:10.000Z"
}

The list endpoint returns {"results": [...]} with the same fields minus the nested candidates and experiments.

FieldTypeDescription
idUUIDInvestigation id
issue_pattern_idstringYour identifier for the issue cluster (≤ 255 chars)
issue_trace_idsarray of stringThe issue reports under investigation
agent_idintegerThe agent being fixed
baseline_promptstringSnapshot of the agent prompt when the investigation started
call_idsarray of integerExample failing calls used as evidence
statusstringidle, generating_hypotheses, awaiting_candidates, generating_candidates, ready, applied, dismissed
experiment_configobjectDefault config passed to experiments
hypothesesarrayAI-generated root-cause hypotheses
candidatesarrayPrompt candidates — source is ai or manual; hypothesis_id links back to a hypothesis
experimentsarrayAll experiments across candidates, newest first. Sample grade compares original_output vs replayed_output
applied_candidate_idUUID | nullSet once applied
applied_agent_versioninteger | nullThe agent version revision the apply created

Start an investigation

cURL
curl -X POST https://api.thunderphone.com/v1/investigations \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "issue_pattern_id": "long-pauses-before-answering",
    "issue_trace_ids": ["5f9a…", "77b1…"],
    "agent_id": 12,
    "call_ids": [987654321, 987654322]
  }'
FieldTypeRequiredDescription
issue_pattern_idstringyes≤ 255 chars
issue_trace_idsarray of stringyesNon-empty
agent_idintegeryesMust belong to your org (404 otherwise)
call_idsarray of integeryesNon-empty
experiment_configobjectnoDefaults to {}

Returns 201 with the investigation already running (status="generating_hypotheses") — creation launches the full flow automatically: diagnosis and baseline replays, then the autonomous fix search. Poll GET …/{investigation_id} until status="ready" and read search_result.

Drive the workflow

  1. Poll GET …/{investigation_id} — the investigation advances through generating_hypothesesawaiting_candidatessearchingready on its own.
  2. POST …/candidates/manual — add your own candidate at any non-terminal point: {"label", "prompt", "rationale"?}201 with the new candidate. Manual candidates join the next search round, or can be tested directly (next step).
  3. POST …/candidates/{candidate_id}/experiments — body {"config": {…}} (optional). Replays the failing calls' inputs against the candidate prompt asynchronously. Returns 201 with the experiment (status: "running"); poll GET …/experiments/{experiment_id} until completed / failed.
  4. POST …/apply — body {"candidate_id": "…"}. Writes the candidate's prompt to the agent, records an agent config version, sets status applied, and auto-resolves the investigation's linked issue reports. Allowed from ready / awaiting_candidates (409 otherwise). Returns the final investigation.
  5. POST …/hypotheses — re-kick diagnosis on an investigation that ended idle (for example after an interrupted run). 409 when a run is already in progress.

To abandon an investigation: PATCH /v1/investigations/{id} with {"status": "dismissed"}dismissed is the only status a client may set directly.

POST /v1/investigations/{investigation_id}/search

Runs the autonomous fix search: the AI drafts diverse prompt changes (and, for eligible agents, a tier upgrade), screens each one in parallel replays of the AI turn, culls weak variants, and confirms the best result on fresh replays plus calls without the issue. The investigation's search_result carries the outcome — tier is fixed (eliminated in a confirmation round), improved (a confirmed reduction), or null when no change was confirmed. A completed search with no winner flags the cluster to ThunderPhone with the replay evidence; when stopped_early is "testing_incomplete" or "replay_budget_exhausted" the search could not finish verifying its candidates, nothing is flagged, and rerunning the search is the right next step (previous searches' best unconfirmed changes are carried into the new round automatically).