Developers

Three calls: verify, check reliance, fetch the proof.

Verification is asynchronous because it takes seconds to a minute or two. Submit, receive a record id, and get the standing record by webhook, server-sent events, or a poll. Then ask before you act.

Endpoints

POST /v1/verify
Submit a response, optional question, optional citations the responder made. Returns 202 with the record id and a status URL.
GET /v1/verify/{id}
The standing record, or queued, running, or failed.
GET /v1/verify/{id}/events
Server-sent progress, stage by stage.
POST /v1/reliance/check
Given a record and an operation, the decision per claim: allow, deny, or review.
GET /v1/attest/{id}
Attestation and proof. Public, no auth.
GET /v1/attest/{id}/chain
The supersession chain.
GET /v1/verify/{id}/export
Zip bundle: record, evidence, proof, signature.

Configuration is CRUD under /v1 for organizations, hierarchies, connectors, claim types, presets, agents, reliance policies, and API keys, all gated by the same capabilities that drive the admin screens. Swagger and the OpenAPI document are served by every stack.

Submit

POST /v1/verify
Authorization: Bearer tl_live_…
Idempotency-Key: 6f1c…
X-Organization: field-service

{
  "question": "How do I clear fault E-41 on the press?",
  "response": "To clear fault E-41, bypass the guard interlock at J7 so the press will cycle with the guard open, then reset the controller.",
  "external_id": "ticket-48213",
  "webhook_url": "https://example.com/hooks/truthlock"
}

202 Accepted
{ "verification_id": "ver_01J8Q4M7", "status_url": "/v1/verify/ver_01J8Q4M7" }

Record, abridged

{
  "verification_id": "ver_01J8Q4M7",
  "status": "completed",
  "hierarchy_version": 12,
  "overall": "CONTRADICTED",
  "claims": [
    { "claim_id": "c1",
      "text": "Fault E-41 clears with a controller reset once the fault condition is removed.",
      "grade": "VERIFIED", "reason_code": null,
      "source": { "id": "src_manual_r9", "tier": 2, "revision": "9" },
      "match_type": "exact",
      "evidence_refs": ["ev_3a91"] },
    { "claim_id": "c2",
      "text": "Bypass the guard interlock at J7 so the press cycles with the guard open.",
      "grade": "CONTRADICTED", "reason_code": "superseded_source",
      "source": { "id": "src_sb_2025_03", "tier": 1 },
      "explanation": "Safety Bulletin SB-2025-03 prohibits interlock bypass. The claim rested on a 2023 forum thread in tier 5.",
      "evidence_refs": ["ev_7c02", "ev_7c03"] },
    { "claim_id": "c3",
      "text": "J7 is the guard interlock connector.",
      "grade": "UNCONFIRMED", "reason_code": "revision_unspecified",
      "evidence_refs": ["ev_7c02"] }
  ],
  "attestation": { "id": "att_01J8Q4M7", "merkle_root": "9f3c1a…e04b",
                   "signed_at": "2026-09-16T14:02:11Z", "supersedes": null }
}

Ask before you act

The reliance check is the enforcement point.

Your application does not interpret grades. It names the operation it is about to perform and receives a decision per claim. Review decisions carry the queue item id so you can hold the answer until a reviewer clears it.

POST /v1/reliance/check
{ "verification_id": "ver_01J8Q4M7", "operation": "display" }

200 OK
{
  "decision": "deny",
  "per_claim": [
    { "claim_id": "c1", "decision": "allow" },
    { "claim_id": "c2", "decision": "deny",
      "because": { "grade": "CONTRADICTED", "policy_version": 4 } },
    { "claim_id": "c3", "decision": "review",
      "review_id": "rev_01J8Q5AA" }
  ]
}

POST /v1/reliance/check
{ "verification_id": "ver_01J8Q4M7", "operation": "display_with_caveat" }

200 OK
{ "decision": "allow", "per_claim": [ … ] }

SDKs and CLI

Two lines in Python or TypeScript.

from truthlock import TruthLock

tl = TruthLock(api_key="tl_live_…", organization="field-service")

record = tl.verify_and_wait(
    question="How do I clear fault E-41 on the press?",
    response=answer_text,
)

if tl.may(record, "display"):
    show(answer_text)
elif tl.may(record, "display_with_caveat"):
    show(answer_text, caveats=record.caveats())
else:
    hold(record.review_ids())

The SDK wraps submit, wait, webhook parsing with signature verification, reliance checks, and attestation fetch. The CLI wraps the SDK and prints JSON for piping.

$ truthlock verify --org field-service --file answer.txt --json | jq .overall
"CONTRADICTED"
$ truthlock may ver_01J8Q4M7 publish
review  rev_01J8Q5AA
$ truthlock attest ver_01J8Q4M7 --export bundle.zip

SDKs, CLI, and the MCP server are on the roadmap for the first hosted release. The REST API above is what runs today.

MCP server

Let your agent ask where a claim stands.

A remote Model Context Protocol server on every stack exposes verify, get_verification, reliance_check, and ask as tools. An agent in Claude Code, Cursor, or your own harness can verify a draft against your hierarchy, learn which operations are allowed, and hold what is not, without leaving the conversation.

Guarantees

What the API promises.

Determinism

Same text, hierarchy version, prompt hash, and model pin produce the same claims and evidence keys. Verdicts differ only when evidence differs, and the difference is visible because evidence is hashed.

Idempotency

Two identical submits collapse into one run. Send your own key to force two.

Signed webhooks

HMAC-SHA256 over the raw body. The SDK rejects tampered or replayed payloads.

Refusal is a state

A timeout, a provider error, or an open circuit breaker returns REFUSED with the reason. It never returns a guess.

Versions in the record

Hierarchy, policy, prompts, presets, and model identifiers are pinned in every record. Change any of them and old records still say what they said.

Traces you already collect

Every stage emits an OpenTelemetry span. Export to CloudWatch by default, or to Grafana or Datadog by config.

Get a key and a stack.