Skip to main content

Case Synthesis

List synthesized case narrative threads, poll the build status, and trigger an asynchronous recompute. Synthesis discovers document stories and missing-document findings.

Case synthesis discovers stories (threads) across your resolved documents: chains of documents that explicitly reference one another, plus the deterministic missing-document findings those references imply. For example, an invoice citing a purchase order number that no document in your workspace satisfies surfaces as a missing-document finding on that thread.

Synthesis is a higher-level view than individual cases. Where the linking graph connects documents through shared field values, synthesis narrates each connected group into a readable story with a title, a one-line blurb, and a paragraph summary. It also generalizes recurring threads into process types so you can see the shapes of work flowing through your corpus.

Because synthesis runs an LLM step and can take minutes on a large corpus, it never runs on read. The GET /v1/cases/threads endpoint serves a cached artifact only. To rebuild, call POST /v1/cases/synthesis/recompute, which enqueues the work on a worker and returns immediately, then poll GET /v1/cases/synthesis/status until the build clears.

Reads never trigger synthesis. GET /v1/cases/threads returns whatever was last built. When new documents resolve, the cached artifact is flagged dirty: true, signalling that a recompute would refresh the stories. Recompute is the only operation that spends LLM credits.
GET/v1/cases/threads

Threads Response

Response fields

threadsarrayArray of synthesized story objects. Empty if synthesis has never run.
threads[].idstringThread (story) identifier.
threads[].doc_idsstring[]Document UUIDs belonging to this thread.
threads[].startstring | nullEarliest document date in the thread (ISO 8601 date).
threads[].endstring | nullLatest document date in the thread (ISO 8601 date).
threads[].titlestring | nullLLM-narrated title for the story.
threads[].blurbstring | nullOne-line narrated summary.
threads[].summarystring | nullOne-paragraph narrated summary.
threads[].findingsarrayDeterministic findings (e.g. dangling references to missing documents).
doc_metaobjectMap of document UUID to display metadata used by the stories view.
casesobjectProcess and subject axis generalizations: { process, subject, stats }.
statsobjectSynthesis statistics, including n_threads.
dirtybooleanWhether resolved documents have changed since the last build (a recompute would refresh).
never_builtbooleanPresent and true when synthesis has never been run for this workspace.

Response

{
  "threads": [
    {
      "id": "thr_1a2b3c4d",
      "doc_ids": ["doc_uuid_1", "doc_uuid_2", "doc_uuid_3"],
      "start": "2024-09-01",
      "end": "2024-10-15",
      "title": "Acme Corp PO-2024-001 Procurement",
      "blurb": "Purchase order, invoice, and delivery note for Acme Corp order PO-2024-001.",
      "summary": "Three documents share purchase order PO-2024-001: the original PO, a matching invoice for $4,250, and a delivery note. The chain is complete with no missing references.",
      "findings": []
    }
  ],
  "doc_meta": {
    "doc_uuid_1": { "filename": "po_2024_001.pdf", "doc_type": "Purchase Order", "date": "2024-09-01" }
  },
  "cases": {
    "process": [],
    "subject": [],
    "stats": { "n_process": 0, "n_subject": 0 }
  },
  "stats": { "n_threads": 1 },
  "dirty": false
}

Synthesis Status

Poll this endpoint after triggering a recompute. The building flag is true while a worker is running the pipeline and clears when the build finishes. On success built_at advances and build_error is null; on failure building clears and build_error carries the reason, so a poll loop terminates on a visible error rather than spinning forever.

GET/v1/cases/synthesis/status

Response fields

buildingbooleanWhether a synthesis build is currently running on a worker.
dirtybooleanWhether resolved documents changed since the last successful build.
built_atstring | nullISO 8601 timestamp of the last successful build, or null if never built.
build_errorstring | nullError message from the last failed build, or null.
n_threadsintegerNumber of threads in the current cached artifact.
never_builtbooleanTrue if no artifact has ever been built for this workspace.

Response

{
  "building": true,
  "dirty": true,
  "built_at": "2024-11-01T12:00:00.000Z",
  "build_error": null,
  "n_threads": 14,
  "never_built": false
}

Trigger Recompute

Enqueue an asynchronous synthesis build for the workspace and return immediately. The work runs off the request path on a worker, so the response does not contain the artifact. The operation is idempotent: if a fresh build is already in flight it is not re-enqueued, and the response status reflects whether the build was newly queued or already building.

Recompute spends LLM credits (Haiku calls for field-role typing and narration). It is the only synthesis operation that does. A build older than 20 minutes is treated as crashed and its lock becomes reclaimable, so a stuck build does not block future recomputes indefinitely.
POST/v1/cases/synthesis/recompute

Response fields

okbooleanAlways true on success.
statusstringBuild state: queued (newly enqueued) or building (a build was already in flight).
build_started_atstring | nullISO 8601 timestamp when the current build began, when available.

Response

{
  "ok": true,
  "status": "queued",
  "build_started_at": "2024-11-08T10:00:00.000Z"
}

A typical flow is to call recompute, then poll GET /v1/cases/synthesis/status every few seconds until building is false. When the build clears successfully, fetch the refreshed stories from GET /v1/cases/threads.

Errors

Error responses

400bad_requestA specific customer must be selected; the all master view is not allowed for synthesis.
401unauthorizedMissing or invalid API key.
429rate_limitedToo many requests. Retry after the period indicated in the Retry-After header.