A public instance of @sourcedhq/core. Paste claims
below and read the verdicts, or call POST /api/v1/assess from
your own code — full reference further down. Anonymous calls are
batch-scoped; an API key adds persistent memory and an own
transparency chain.
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.
An anonymous 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 the anonymous tier 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 requests — send an API key (§9), or run
@sourcedhq/core yourself with a store
(one load()/save() pair, any KV; see the
spec §3).
clusters and skip the guesswork; Sourced then only counts.
POST /api/v1/assess with content-type: application/json (optional Authorization: Bearer sk_src_…):
| 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. |
| 401 | { "error": "unknown or revoked key" } | A key was sent but is malformed/unknown. Requests without a key are never 401 — anonymous is a supported tier. |
| 405 | { "error": "POST a batch of claims" } | Any method other than GET (usage doc) / POST / OPTIONS. |
| 429 | { "error": "rate limit: 60 requests/minute (anon tier)" } | Per-minute budget exhausted. Headers: X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After. |
/api/v1/… paths are frozen — v1 never breaks. (/api/assess and /api/verify remain as aliases.) Every response carries X-Sourced-Api and X-Sourced-Engine.null verdicts (G7). Only a malformed envelope is a 400.GET /api/v1/assess returns a machine-readable usage summary; the full contract is OpenAPI 3.1.POST /api/v1/verify is also served on this host — verify transparency chains programmatically (schema in the OpenAPI file; human verification with receipts lives at sourced.network).curl -s https://sourced.run/api/v1/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/v1/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/v1/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 required, CORS open, JSON in/out,
GET returns usage.
The anonymous endpoint is a calculator: it answers for the batch you send and forgets you. A key gives the same endpoint a memory. Send it on any call:
Authorization: Bearer sk_src_…
What changes — precisely:
| anonymous | with key | |
|---|---|---|
| corroboration counted | within one request | across all your requests — an event reported by a new origin an hour later still corroborates |
| firstSeenAt | the request time | the true first sighting — “who reported this first, and when” survives across calls |
| signals over time | only within a batch | a story can go DEVELOPING at 09:00 and CONFIRMED at 11:00 — in different requests |
| rate limit | 60/min per IP | 600/min per key |
| transparency chain | — | your own hosted, publicly verifiable chain (§10) |
The event store keeps derived working state (event keys, counts, first-seen times), retires events after 36 h into nothing — this hosted tier stores no archive of your content. Keys are issued manually while Sourced is young: hello@tickwire.news. Same engine, same guarantees G1–G7 — a key never changes verdict semantics, only how long the engine remembers.
Every key owns a hash-chained transparency log — the same construction Tickwire runs in production (proof). Commit anything you want to be held to — e.g. a digest of each verdict batch you publish:
# append (writes need YOUR key)
curl -s -X POST https://sourced.run/api/v1/chain \
-H "authorization: Bearer sk_src_…" \
-H "content-type: application/json" \
-d '{"payload":{"batch":"2026-07-11T21:00Z","digest":"…"}}'
# read (public — that is the point)
curl -s "https://sourced.run/api/v1/chain?chain=<chainId>&full=1"
# directory of all hosted chains
curl -s https://sourced.run/api/v1/chains
Each append returns a LogRecord whose hash commits to the
entire history before it. Reads are public and CORS-open so anyone
can fetch your chain and recompute it (POST /api/v1/verify, or
in the browser at sourced.network).
Heads are anchored daily into public git history
(anchors/) —
after that, neither you nor we can rewrite a single record unnoticed.
Your chainId is derived one-way from your key: publishing it
is safe, writing stays yours alone.
Limits: 32 kB payload · 30 appends/min · 5000 records/month. This is what “Built on Sourced” means operationally: your claims history becomes checkable by strangers, not just asserted by you.