Docs / HTTP API reference

HTTP API

Base URL: your Paraph host (e.g. http://localhost:3000). All requests authenticate with a Paraph API key as a Bearer token:

Authorization: Bearer <api-key>

The @paraph/sdk client wraps these endpoints; use them directly for other languages or an OTel exporter.

Record an event

POST /api/v1/events

{
  "run": "loan-4821",
  "agent": "underwriter-agent",
  "event": {
    "kind": "tool_call",
    "role": "tool",
    "name": "pull_credit_report",
    "content": "score=690",
    "metadata": { "bureau": "Experian" }
  }
}

Ensures the run exists (by its run external id) and appends the event to the run's hash chain.

  • Event kind: message · model_call · tool_call · decision · error.
  • Response 200: { "runId": "<internal id>", "seq": 0 }.

Open an approval gate

POST /api/v1/runs/{run}/approvals

{ "action": "Approve €40,000 loan for applicant #4821", "policy": "block" }

Creates a pending approval and records an approval_requested decision event into the run.

  • Response 200: { "run": "loan-4821", "seq": 0, "status": "pending" }.

Poll an approval

GET /api/v1/runs/{run}/approvals/{seq}

The waiting agent polls this until status is no longer pending.

  • Response 200:
{
  "seq": 0,
  "action": "Approve €40,000 loan for applicant #4821",
  "status": "approved",
  "reviewer": "Jane Okafor",
  "note": "Income verified; within policy",
  "requestedAt": 1782278196255,
  "resolvedAt": 1782278237000
}

status: pending · approved · rejected · expired.

Close a run

POST /api/v1/runs/{run}/end

{ "status": "complete" }

Marks the run finished (status: complete · error; defaults to complete on an empty body). The SDKs call this from end() — the Python client's context manager does it automatically, closing as error if the block raised. The record itself stays append-only; status is presentation.

  • Response 200: { "run": "loan-4821", "status": "complete" }.

OTel-GenAI ingestion

POST /api/v1/otel/v1/traces

Point any OpenTelemetry exporter at …/api/v1/otel (it appends /v1/traces), with the API key as a Bearer token. Spans carrying GenAI semantics (gen_ai.*) become run events — chat/completion/embeddingsmodel_call, execute_tooltool_call — in start-time order. The run is keyed by the paraph.run attribute, else gen_ai.conversation.id, else the trace id; non-GenAI spans are ignored. This lets any OTel-instrumented agent — any language or framework, including LangChain — feed the record without the SDK.

Both OTLP/HTTP encodings are accepted: http/protobuf (the exporter default — no configuration needed) and http/json. The mapping → record path is verified end-to-end against Postgres.

Evidence package (period export)

GET /api/evidence?days=90 · GET /api/evidence?from=<ms>&to=<ms> (session-authenticated, from the console)

Returns one JSON package containing every run in the window as a full evidence bundle, a summary (runCount, allVerified, brokenRuns, signoffs), and a single RFC 3161 anchor over the whole package (anchor.digest = SHA-256 of the package canonicalized without its anchor field). Exports are audit-logged.

Webhooks (outbound)

Register endpoints in Settings → Webhooks. A gate opening or resolving POSTs:

{
  "type": "approval.requested",
  "run": "loan-4821",
  "seq": 0,
  "action": "Approve €40,000 consumer loan for applicant #4821",
  "agent": "underwriter-agent",
  "at": 1782278196255
}

approval.resolved adds "status" (approved · rejected) and "reviewer". Headers: x-paraph-event (the type) and x-paraph-signature — verify authenticity:

const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
if (expected !== req.headers["x-paraph-signature"]) reject();

Delivery is best-effort with a 3-second timeout — a slow receiver never blocks a gate.

Errors

Status When
400 Malformed body, missing action, or invalid seq.
401 Missing or invalid API key.
404 Unknown approval (or one belonging to another tenant).

Approvals are resolved by a human in the dashboard control room (/dashboard/approvals), not via the public API — the reviewer's identity comes from their authenticated session, and the resolution is recorded as an approval_resolved decision event.