sourced.run — playground & hosted API

Run the engine

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.

endpoint POST https://sourced.run/api/v1/assess auth optional CORS open limits 200 claims · 200 kB · 60/min anon dashboard Control Deck →

1Playground

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.

2What a call computes

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).

Grouping hint: the engine merges events by title similarity (spec §4). If you already know which claims describe the same event — from your own clustering — pass clusters and skip the guesswork; Sourced then only counts.

3Request schema

POST /api/v1/assess with content-type: application/json (optional Authorization: Bearer sk_src_…):

FieldTypeSemantics
claims requiredClaim[] (1–200)The batch to assess. Order is preserved in the response.
claims[].id requiredstringStable report id; used to look up clusters entries.
claims[].title requiredstringThe claim in words — event identity derives from it.
claims[].origin requiredstringThe source; the unit of independence. Normalize consistently.
claims[].publishedAt requiredstring, ISO 8601Upstream 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 optionalobjectThreshold overrides — whitelisted numeric keys only, clamped to sane ranges (§5).

4Response schema

200 OK, JSON:

FieldTypeSemantics
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[].corroborationnumber ≥ 1Distinct independent origins for the claim’s event within this batch.
verdicts[].corroboratingSourcesstring[]Receipts — the other origins (never the claim’s own), max 6 by default.
verdicts[].firstSeenAtstring, ISO 8601Stateless endpoint → the request time. (Persistent first-seen requires running the engine with a store.)
verdicts[].signal"confirmed" · "breaking" · "developing" · nullSee spec §4.4. There is deliberately no “true” and no veracity score (G2).
enginestringEngine identifier.
configobject, only if sentEcho of the clamped overrides actually applied.

5Config keys

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).

KeyDefaultClampMeaning
mergeSimilarity0.600.3 – 1Jaccard floor for merging two events (gate 1).
minSharedTokens31 – 10Shared-token floor for merging (gate 2).
confirmedAt42 – 20Origins needed for “confirmed”.
corroboratedAt22 – 20Origins needed for “developing”/“breaking”.
breakingWindowMs1 800 0001 min – 24 hPublish-age window for “breaking”.
receiptsCap61 – 20Max receipts per verdict (display cap; the count is never capped).
keyTokens83 – 16Tokens in the deterministic event key.

6Errors & limits

StatusBodyWhen
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.

7Examples

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', ...}

8For AI agents (MCP)

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.

9API keys — persistent memory

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:

anonymouswith key
corroboration countedwithin one requestacross all your requests — an event reported by a new origin an hour later still corroborates
firstSeenAtthe request timethe true first sighting — “who reported this first, and when” survives across calls
signals over timeonly within a batcha story can go DEVELOPING at 09:00 and CONFIRMED at 11:00 — in different requests
rate limit60/min per IP600/min per key
transparency chainyour 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.

10Chain-as-a-service

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.