A public, stateless instance of @sourcedhq/core. Paste claims
below and read the verdicts, or call POST /api/assess from
your own code — full reference further down.
The prefilled example demonstrates the guarantees: four independent origins
report the same event (count climbs to CONFIRMED),
a repeat from reuters adds nothing (G3), and the single-origin
story stays bare (G5). Edit freely — it is the live engine.
Each call is batch-scoped and stateless: corroboration is
computed across the claims you send in that request (titles that describe
the same event merge via the dual gate; origins are counted distinct).
Nothing is stored between calls — firstSeenAt equals the
request time, and no data you send is retained.
That makes this endpoint ideal for evaluating the engine and for
“given these N reports, what corroborates what?” batch questions. If you
need persistent event memory — first-seen timelines that survive
across runs — run @sourcedhq/core yourself with a store
(one load()/save() pair, any KV; see the
spec §3). A hosted persistent tier
is on the roadmap.
clusters and skip the guesswork; Sourced then only counts.
POST /api/assess with content-type: application/json:
| Field | Type | Semantics |
|---|---|---|
| claims required | Claim[] (1–200) | The batch to assess. Order is preserved in the response. |
| claims[].id required | string | Stable report id; used to look up clusters entries. |
| claims[].title required | string | The claim in words — event identity derives from it. |
| claims[].origin required | string | The source; the unit of independence. Normalize consistently. |
| claims[].publishedAt required | string, ISO 8601 | Upstream publish time; drives the “breaking” window. Unparseable → that claim can’t be “breaking”, everything else works. |
| clusters optional | { [claimId]: string[] } | Your own event grouping: claim id → all origins reporting that event in this batch (≤ 100 origins per entry). Duplicates collapse (G3). |
| config optional | object | Threshold overrides — whitelisted numeric keys only, clamped to sane ranges (§5). |
200 OK, JSON:
| Field | Type | Semantics |
|---|---|---|
| verdicts | (Verdict | null)[] | One entry per input claim, in input order. null = unassessable (e.g. title had no meaning-bearing tokens) — the claim passes through unlabeled, per G7. |
| verdicts[].corroboration | number ≥ 1 | Distinct independent origins for the claim’s event within this batch. |
| verdicts[].corroboratingSources | string[] | Receipts — the other origins (never the claim’s own), max 6 by default. |
| verdicts[].firstSeenAt | string, ISO 8601 | Stateless endpoint → the request time. (Persistent first-seen requires running the engine with a store.) |
| verdicts[].signal | "confirmed" · "breaking" · "developing" · null | See spec §4.4. There is deliberately no “true” and no veracity score (G2). |
| engine | string | Engine identifier. |
| config | object, only if sent | Echo of the clamped overrides actually applied. |
Numeric keys only; anything else is ignored. Values are clamped to the ranges below. The defaults are the honesty contract — tighten freely, loosen knowingly (spec §5).
| Key | Default | Clamp | Meaning |
|---|---|---|---|
| mergeSimilarity | 0.60 | 0.3 – 1 | Jaccard floor for merging two events (gate 1). |
| minSharedTokens | 3 | 1 – 10 | Shared-token floor for merging (gate 2). |
| confirmedAt | 4 | 2 – 20 | Origins needed for “confirmed”. |
| corroboratedAt | 2 | 2 – 20 | Origins needed for “developing”/“breaking”. |
| breakingWindowMs | 1 800 000 | 1 min – 24 h | Publish-age window for “breaking”. |
| receiptsCap | 6 | 1 – 20 | Max receipts per verdict (display cap; the count is never capped). |
| keyTokens | 8 | 3 – 16 | Tokens in the deterministic event key. |
| Status | Body | When |
|---|---|---|
| 400 | { "error": "claims must be a non-empty array" } | Missing/empty claims. |
| 400 | { "error": "max 200 claims per call" } | Batch too large — split it. |
| 400 | { "error": "payload too large (200 kB max)" } / { "error": "invalid JSON" } | Body unreadable. |
| 405 | { "error": "POST a batch of claims" } | Any method other than GET (usage doc) / POST / OPTIONS. |
null verdicts (G7). Only a malformed envelope is a 400.GET /api/assess returns a machine-readable usage summary.curl -s https://sourced.run/api/assess \
-H "content-type: application/json" \
-d '{
"claims": [
{"id":"a","title":"Major dam breach reported upstream","origin":"reuters","publishedAt":"2026-07-10T21:00:00Z"},
{"id":"b","title":"Major dam breach reported upstream","origin":"bbc","publishedAt":"2026-07-10T21:04:00Z"}
]
}'
const res = await fetch("https://sourced.run/api/assess", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ claims }),
});
const { verdicts } = await res.json();
// verdicts[i] belongs to claims[i]
import requests
r = requests.post(
"https://sourced.run/api/assess",
json={"claims": [
{"id": "a", "title": "Major dam breach reported upstream",
"origin": "reuters", "publishedAt": "2026-07-10T21:00:00Z"},
{"id": "b", "title": "Major dam breach reported upstream",
"origin": "bbc", "publishedAt": "2026-07-10T21:04:00Z"},
]},
timeout=10,
)
verdicts = r.json()["verdicts"] # verdicts[i] belongs to claims[i]
# verdicts[1] -> {'corroboration': 2, 'corroboratingSources': ['reuters'], 'signal': 'breaking', ...}
Sourced ships as an MCP server — agents get the primitive
as native tools: assess (with session memory: corroboration
and first-seen accumulate across calls), verify_chain and
run_conformance. The server identifies itself as
sourced.
# Claude Code
claude mcp add sourced -- npx -y @sourcedhq/mcp
# any MCP client (config JSON)
{ "mcpServers": { "sourced": { "command": "npx", "args": ["-y", "@sourcedhq/mcp"] } } }
Machine-readable summaries of all three surfaces live at
/llms.txt on each domain. This endpoint itself is
agent-friendly by design: no auth, CORS open, JSON in/out,
GET returns usage.