Fact It
Developers

Streaming

Render verdicts as they land instead of waiting for the whole check. Same input as /api/check, streamed as Server-Sent Events.

The stream

POST the same body to https://fact-it-web-412159981305.us-central1.run.app/api/check/stream. The response has Content-Type: text/event-stream; each event arrives as data: <json>\n\n, where the JSON is a PipelineEvent. The terminal event is either { "type": "done", "check": Check } (carrying the full Check) or { "type": "error", ... }.

Event types

PipelineEvent is a discriminated union on type:

type: "extracted"

FieldTypeRequiredDetails
type"extracted"required
claimsarray<object>required

claims[]

FieldTypeRequiredDetails
idstringrequired
textstringrequired
spanobjectrequired
typeenumrequirednumeric | historical | current_event | scientific | biographical | definition | opinion
importanceenumrequiredcore | supporting | aside
freshnessNeedenumrequiredlive | recent | evergreen
searchQuerystringoptional
domainenumrequiredgeneral | medical | legal | scientific | financial | news | personaldefault: general

span

FieldTypeRequiredDetails
startintegerrequiredmin: 0
endintegerrequiredmin: 0

type: "retrieving"

FieldTypeRequiredDetails
type"retrieving"required
claimIdstringrequired

type: "retrieved"

FieldTypeRequiredDetails
type"retrieved"required
claimIdstringrequired
evidencearray<object>required

evidence[]

FieldTypeRequiredDetails
urlstring (uri)required
titlestringrequired
snippetstringrequired
lastModifiedAtstring (date-time)optional
authorityScorenumberrequiredmin: 0, max: 1
recencyScorenumberrequiredmin: 0, max: 1
relevanceScorenumberrequiredmin: 0, max: 1
providerIdstringrequired

type: "verdict"

FieldTypeRequiredDetails
type"verdict"required
verdictobjectrequired

verdict

FieldTypeRequiredDetails
claimIdstringrequired
statusenumrequiredsupported | partial | contradicted | unsupported | pending
confidencenumberrequiredmin: 0, max: 1
evidencearray<object>required
reasoningstringrequired

evidence[]

FieldTypeRequiredDetails
urlstring (uri)required
titlestringrequired
snippetstringrequired
lastModifiedAtstring (date-time)optional
authorityScorenumberrequiredmin: 0, max: 1
recencyScorenumberrequiredmin: 0, max: 1
relevanceScorenumberrequiredmin: 0, max: 1
providerIdstringrequired

type: "done"

FieldTypeRequiredDetails
type"done"required
checkobjectrequired

check

FieldTypeRequiredDetails
idstringrequired
sourceAppenumrequiredchatgpt-web | claude-web | gemini-web | perplexity-web | chatgpt-desktop | claude-desktop | dashboard | cli | other
promptstringoptional
responsestringrequired
verdictsarray<object>required
degradedProvidersarray<object>optional
overallConfidencenumberrequiredmin: 0, max: 1
modelobjectrequired
costobjectoptional
createdAtstring (date-time)required

verdicts[]

FieldTypeRequiredDetails
claimIdstringrequired
statusenumrequiredsupported | partial | contradicted | unsupported | pending
confidencenumberrequiredmin: 0, max: 1
evidencearray<object>required
reasoningstringrequired

evidence[]

FieldTypeRequiredDetails
urlstring (uri)required
titlestringrequired
snippetstringrequired
lastModifiedAtstring (date-time)optional
authorityScorenumberrequiredmin: 0, max: 1
recencyScorenumberrequiredmin: 0, max: 1
relevanceScorenumberrequiredmin: 0, max: 1
providerIdstringrequired

degradedProviders[]

FieldTypeRequiredDetails
providerIdstringrequired
kindenumrequiredquota | auth | rate-limit | upstream | network
statusintegeroptionalmin: -9007199254740991
messagestringrequired

model

FieldTypeRequiredDetails
extractorstringrequired
verifierstringrequired

cost

FieldTypeRequiredDetails
usdnumberrequiredmin: 0
inputTokensintegerrequiredmin: 0
outputTokensintegerrequiredmin: 0
cacheReadTokensintegerrequiredmin: 0
cacheWriteTokensintegerrequiredmin: 0
searchCallsintegerrequiredmin: 0

type: "error"

FieldTypeRequiredDetails
type"error"required
messagestringrequired
claimIdstringoptional

Consuming the stream

Read the body as a stream, buffer partial chunks, split on the blank-line delimiter, and JSON-parse the data: payload of each event:

const res = await fetch('https://fact-it-web-412159981305.us-central1.run.app/api/check/stream', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer fk_your_token',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ response: 'The Eiffel Tower is 984 feet tall.' }),
});

if (!res.ok || !res.body) throw new Error(`stream failed: ${res.status}`);

const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
let buf = '';

while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  buf += value;

  // Events are separated by a blank line; keep the trailing partial in the buffer.
  const blocks = buf.split('\n\n');
  buf = blocks.pop() ?? '';

  for (const block of blocks) {
    const line = block.split('\n').find((l) => l.startsWith('data:'));
    if (!line) continue;
    const event = JSON.parse(line.slice(5).trim());

    switch (event.type) {
      case 'extracted':
        console.log('claims:', event.claims.length);
        break;
      case 'verdict':
        console.log('verdict:', event.verdict.status);
        break;
      case 'done':
        console.log('trust score:', event.check.overallConfidence);
        break;
      case 'error':
        console.error('stream error:', event.message);
        break;
    }
  }
}

This mirrors the parsing loop in @fact-it/sdk's FactItClient.stream() — inside the monorepo you can iterate that async generator directly instead of writing the reader by hand.

Next

See the full field-by-field breakdown of every event and data type in the API reference.