Sources & Auditing
Verifact evaluates claims only against the sources you provide. Verification and authorization are deterministic by default and do not fetch external URLs unless explicitly enabled. Verifact also emits signed, chained audit records (Ed25519; linked by prev_record_hash) for verify/authorize outcomes.
Sources (mental model)
Think of sources as the only evidence Verifact is allowed to use. You can provide sources inline with a request, or you can ingest them once via the Sources API and reference them repeatedly using source_id. For URL content, the recommended model is two-step: ingest the URL via POST /v2/sources (fetch + extract + persist), then use the returned source_id in verify/authorize.
Source APIs
Use the Sources API to ingest content (PDFs & URLs) and get stable source_id values that can be referenced in later requests.
POST /v2/sources
Ingest one or more sources for later reuse (JSON or multipart upload). Returns stable source_id values and content-level checksum hashes.
Use when you want to store content once (including URL content) and reference it deterministically in verification/authorization.
GET /sources/{source_id}
Retrieve a stored source by ID (v1 read endpoint). v2 ingestion uses /v2/sources.
Useful for debugging and reproducing verification decisions by inspecting the persisted canonical content and metadata.
For URL content, ingest via POST /v2/sources and reference the returned source_id. By default, verification/authorization won't fetch URLs; set options.allow_network to opt in.
How Sources Work
- Only sources included in the request are considered
- LLM output is never treated as evidence.
- No inference beyond the supplied content.
- Verification is separate from authorization; auditing records decisions so you can prove what happened.
For deterministic results, provide explicit claims as separate items (one claim per statement). When you use authorize-output, Verifact may extract simple claims deterministically from text, but extraction is intentionally conservative and does not perform semantic inference. For precise verification, prefer explicit claims.
Audit guarantees
- Signed records: each audit record includes a
record_hashand is signed by the server's signing key (Ed25519). - Hash chaining: records link via
prev_record_hash, enabling detection of missing or reordered records. - Proof visibility: proofs and minimal audit handles may be included in responses when available. Use the audit endpoints to retrieve canonical proofs and public keys for verification.
Request examples
Example 1 — verify with inline claims and inline source content
POST /v2/verify
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"claims": [
{ "id": "c1", "text": "We can refund purchases within 30 days of purchase." }
],
"sources": [
{
"source_id": "s1",
"type": "text",
"title": "Refund Policy",
"content": "Refunds are permitted within 30 days of purchase."
}
]
}Example 2 — reference a stored source_id
POST /v2/verify
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"claims": [ { "id": "c1", "text": "Refunds are allowed within 30 days." } ],
"sources": [ { "source_id": "src_abc123" } ]
}Example 3 — ingest a URL as a source (two-step model)
# Step 1: ingest (server fetch + extract + persist)
POST /v2/sources
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"sources": [
{
"type": "html",
"title": "Refund Policy",
"uri": "https://example.com/policies/refund-v3.html"
}
]
}
# Response (example)
{
"sources": [
{ "source_id": "src_abc123", "type": "html", "uri": "https://example.com/policies/refund-v3.html", "checksum": "sha256:..." }
],
"errors": []
}
# Step 2: verify using the stored source_id (deterministic by default)
POST /v2/verify
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"claims": [ { "id": "c1", "text": "Refunds are allowed within 30 days." } ],
"sources": [ { "source_id": "src_abc123" } ]
}Optional — allow URL fetching during verify (explicit opt-in)
POST /v2/verify
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"claims": [ { "id": "c1", "text": "Refunds are allowed within 30 days." } ],
"sources": [
{ "type": "html", "title": "Refund Policy", "uri": "https://example.com/policies/refund-v3.html" }
],
"options": { "allow_network": true }
}Upload PDF Example (multipart)
# cURL (multipart)
curl -X POST 'https://verifact-api.fly.dev/v2/sources' \
-H 'Authorization: Bearer REPLACE_WITH_API_KEY' \
-F 'file=@document.pdf' \
-F 'title=Refund policy'
# Node.js (fetch + form-data)
import fs from 'fs';
import FormData from 'form-data';
const form = new FormData();
form.append('file', fs.createReadStream('document.pdf'));
form.append('title', 'Refund policy');
const res = await fetch('https://verifact-api.fly.dev/v2/sources', {
method: 'POST',
headers: { 'Authorization': 'Bearer REPLACE_WITH_API_KEY', ...form.getHeaders() },
body: form
});
console.log(await res.json());
# Python (requests)
import requests
with open('document.pdf', 'rb') as f:
files = {'file': ('document.pdf', f, 'application/pdf')}
data = {'title': 'Refund policy'}
res = requests.post(
'https://verifact-api.fly.dev/v2/sources',
headers={'Authorization': 'Bearer REPLACE_WITH_API_KEY'},
files=files,
data=data
)
print(res.json())
# Response (example)
{
"sources": [
{
"source_id": "src_abcdef123456",
"type": "pdf",
"title": "Refund policy",
"checksum": "sha256:...",
"status": "created",
"bytes": 123456
}
],
"errors": []
}Note: uploads are size-limited and rate-limited. Extraction failures are reported per-source in errors.
Audit proof example
Audit handles may be returned inline when available; use the audit endpoints below to fetch canonical proofs and public keys.
Compact (default response; audit handle may be present)
{
"request_id": "req_abc123",
"request_hash": "sha256:abc123",
"verdict": "supported",
"coverage_score": 1.0,
"checks": [
{
"claim_id": "c1",
"claim_text": "Paris is the capital of France",
"coverage": 1.0,
"citations": [ { "source_id": "s1", "quote": "Paris is the capital of France", "start": 0, "end": 29 } ]
}
],
"metadata": {
"audit": {
"record_id": "99ed92e7-8aaf-4e0f-b1b3-1a2b3c4d5e6f",
"record_hash": "sha256:32514406a64...",
"key_id": "audit_v1",
"merkle_root": "sha256:5b0ab5785e0b...",
"proof": [ { "hash": "sha256:aaa111...", "position": "left" } ]
}
},
"errors": []
}Verbose (per-claim details)
{
"request_id": "req_abc123",
"request_hash": "sha256:abc123",
"verdict": "supported",
"coverage_score": 1.0,
"checks": [
{
"claim_id": "c1",
"claim_text": "Paris is the capital of France",
"coverage": 1.0,
"citations": [ { "source_id": "s1", "quote": "Paris is the capital of France", "start": 0, "end": 29 } ]
}
],
"metadata": {
"audit": {
"record_id": "99ed92e7-8aaf-4e0f-b1b3-1a2b3c4d5e6f",
"record_hash": "sha256:32514406a64...",
"key_id": "audit_v1"
}
},
"errors": []
}How to request verbose responses
POST /v2/verify
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"claims": [ { "id": "c1", "text": "We can refund purchases within 30 days of purchase." } ],
"sources": [
{ "type": "text", "title": "Refund Policy", "content": "Refunds are permitted within 30 days of purchase." }
],
"response": { "mode": "verbose" }
}Note: response.mode = "verbose" returns additional per-claim detail; for canonical proofs and signatures, use the audit endpoints.
How to verify a proof
- Extract
record_idandrecord_hashfrommetadata.audit. - Fetch the canonical proof (includes the batch signature when available) from
GET /v2/audit/{record_id}/proof. - Fetch the public key for
key_idviaGET /v2/audit/keys/{key_id}. - Verify the signature over the returned
merkle_rootusing Ed25519 and the returned public key; then verify inclusion via the proof.