Authentication, request signing, the submit-then-poll pattern, idempotency, error codes, and the OpenAPI spec. The signing protocol is open — the toolchain is your call.
Your team uploads an RSA or EC public key in the admin UI. The matching private key never leaves your infrastructure — we never see it, store it, or transmit it. Every request is signed with that private key; we verify the signature against your stored public key.
Build a four-line string with the HTTP method, the request path, the timestamp, and the SHA-256 hex digest of the body, separated by literal newline characters. Sign that string with your private key using SHA-256. Send the base64-encoded signature in X-TLG-Signature. The timestamp must be within the last 5 minutes — older timestamps are rejected as replays.
METHOD\nPATH\nTIMESTAMP\nSHA256(BODY)
X-TLG-API-Key — the key identifier returned when you uploaded your public key.X-TLG-Signature — base64-encoded RSA or EC signature of the canonical string.X-TLG-Timestamp — ISO-8601 UTC timestamp (e.g., 2026-05-11T20:14:22Z). Must be within 5 minutes of server time.Idempotency-Key — UUID v7 recommended, optional but strongly advised on POST endpoints. See the Idempotency section below.This is one example. The protocol is "sign the canonical string with your RSA or EC private key using SHA-256" — use any crypto library or toolchain your team prefers (Node.js crypto, Python cryptography, Go crypto/rsa, Java java.security, .NET System.Security.Cryptography, AWS KMS, Azure Key Vault, etc.). Nothing in this signing flow requires openssl or shell.
# 1. Build the canonical string and sign it TS=$(date -u +%Y-%m-%dT%H:%M:%SZ) BODY_HASH=$(printf '' | openssl dgst -sha256 -hex | awk '{print $2}') SIG=$(printf 'POST\n/v1/auth/session\n%s\n%s' "$TS" "$BODY_HASH" \ | openssl dgst -sha256 -sign private.pem | base64 -w0) # 2. Open a session (returns a bearer token used by all subsequent calls) curl -X POST "https://api.theliggettgroup.com/v1/auth/session" \ -H "X-TLG-API-Key: ${API_KEY}" \ -H "X-TLG-Signature: ${SIG}" \ -H "X-TLG-Timestamp: ${TS}" # → 200 { "session_token": "...", "expires_at": "..." }
In the admin UI, paste your RSA public key (PEM). We mint a parked key in pending state and return a one-time nonce. You sign the nonce with your private key and submit the signature back. On valid signature, the key is activated and we hand you the API key plus two deterministic test payloads. On invalid signature, the parked key is hard-deleted — no orphan rows that could be claimed without proving possession.
POST /v1/claim-checks returns one of two HTTP statuses on submit. The poll endpoint always returns 200 — the work state lives in the response body's status field.
The verdict is returned inline. result.cached is true and result.created_at carries the original scoring timestamp. No poll needed.
Returns job_id and poll_url. Poll the GET endpoint until status transitions from queued / processing to complete.
Always 200 while the job exists. status is one of queued, processing, complete, failed. result is present once status is complete.
# Cache hit on submit (HTTP 200) { "job_id": "job-a3b4c5d6e7f80910", "job_type": "claim", "status": "complete", "result": { "verdict": "SUPPORTED", "score": { "population": 2, "endpoint": 2, "magnitude": 1, "context": 2, "total": 7 }, "evidence": [{ "source": "DailyMed", "quote": "...", "fetched_at": "2026-05-11T14:23:00Z" }], "cached": true, "created_at": "2026-05-08T09:14:22Z" }, "charge": 0.05, "duration_ms": 0 } # Cache miss on submit (HTTP 202) { "job_id": "job-a3b4c5d6e7f80910", "status": "queued", "poll_url": "/v1/claim-checks/job-a3b4c5d6e7f80910" } # Poll response (HTTP 200, status transitions over time) # → { "status": "processing" } # → { "status": "complete", "result": {...}, "charge": 0.50, "duration_ms": 4280 }
First poll at 2 seconds, then exponential backoff with a 30-second cap, for up to 10 minutes. Most cache misses complete within 6 to 90 seconds depending on evidence-source latency. There is no per-key poll budget — polls do not consume credits and do not count against rate limits.
Pass Idempotency-Key on every POST. UUID v7 is the recommended format because it embeds the millisecond timestamp, which keeps your client-side logs sortable.
job_id as the original request, even if the original is still processing. Safe to retry on connection failure.Every error response is JSON: {"error": "code", "message": "..."}. Build retry logic against the HTTP status — the message is for human operators.
job_id does not exist, has expired, or belongs to another org.Idempotency-Key reused with a different request body. Mint a new key.Retry-After; back off; resume.Idempotency-Key after exponential backoff.Connect any MCP-aware AI client (Claude Code, Cursor, custom agent) over Server-Sent Events. The agent calls EvidenceHub the same way it calls any other MCP tool — no REST wrapper code required.
{
"mcpServers": {
"evidencehub": {
"transport": "sse",
"url": "https://mcp.theliggettgroup.com/sse",
"headers": {
"Authorization": "Bearer ${EVIDENCEHUB_MCP_KEY}"
}
}
}
}
# Tools exposed after connect:
# submit_claim_check -> returns job_id (or cached result inline)
# get_claim_check_result -> poll by job_id
# submit_reference_check -> returns job_id (or cached result inline)
# get_reference_check_result
# MCP key generation: /app/admin/ → "Generate EvidenceHub MCP Key"
# Requires SECURITY_GROUP role + step-up + 2FA.
Test mode. Send X-TLG-Test-Mode: integration on a REST call (or set the equivalent flag on the MCP client) to use deterministic test payloads issued at key activation. Test calls do not consume credits, do not hit production rate limits, and return a canned verdict.
The OpenAPI 3.1 spec is published alongside the API and can be used to generate clients in any language your tooling supports. Contact us for the spec URL until it is published publicly.