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.