RTFC API

Build your own fact-checking app

The full flow, end to end: provision a user, take a video or some text, stream results back, and account for the cost. This is the same shape the Esploro mobile app uses.

Two things your backend needs: a provision-scoped token, and somewhere to store each user's token encrypted.

bash
export RTFC=https://api.faktolumo.eu/v1
export PROV=rtfc_your_provisioner_token

1. A user signs up

bash
USER=$(curl -sX POST $RTFC/provision/user \
  -H "X-Auth-Token: $PROV" -H 'Content-Type: application/json' -d '{}')
USER_TOKEN=$(echo "$USER" | jq -r .token)
USER_ID=$(echo "$USER" | jq -r .user_id)

Store both. Everything below uses $USER_TOKEN - the provisioner token is not touched again until you delete the account.

2. They share a video

Check it before spending anything. The probe is anonymous and creates nothing:

bash
PROBE=$(curl -sX POST $RTFC/media/probe \
  -H "X-Auth-Token: $USER_TOKEN" -H 'Content-Type: application/json' \
  -d '{"url": "https://www.youtube.com/watch?v=…"}')
json
{"platform": "youtube", "external_id": "…", "title": "…", "duration_s": 812,
 "is_public": true, "reject_reason": "", "existing_debate_id": null}

Two fields decide what happens next:

unsupported_platform, not_a_single_video, unavailable, private, needs_auth, geo_blocked, live_in_progress.

publicly. Claim access and skip ingest entirely - it is free and instant:

bash
curl -sX POST $RTFC/media/youtube/$EXTERNAL_ID/claim-access -H "X-Auth-Token: $USER_TOKEN"

3. Otherwise, ingest it

bash
DEBATE=$(curl -sX POST $RTFC/debates \
  -H "X-Auth-Token: $USER_TOKEN" -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"title": "…", "research_modes": ["two_step","decompose"],
       "language": "auto", "country": "fr", "elector_enabled": true}' | jq -r .id)

curl -sX POST $RTFC/debates/$DEBATE/upload-media \
  -H "X-Auth-Token: $USER_TOKEN" -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"url": "https://www.youtube.com/watch?v=…", "detector_mode": "batch"}'

elector_enabled: true means an LLM judge picks the single best panel per claim once every method has finished - so /best returns one confident answer rather than leaving your UI to choose. language: "auto" handles code-switching.

Watch the response for matched_existing: true: it means nothing was ingested and the returned debate_id is a pre-existing analysis you were granted access to.

If you transcribe on-device instead

Skip the media call. Start a detector-only session and push text - no audio leaves the device and there is no transcription cost:

bash
curl -sX POST $RTFC/session/client-start -H "X-Auth-Token: $USER_TOKEN" \
  -H 'Content-Type: application/json' -d "{\"debate_id\":\"$DEBATE\",\"language\":\"en\"}"

curl -sX POST $RTFC/debates/$DEBATE/transcript-segment -H "X-Auth-Token: $USER_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"segments":[{"text":"…","language":"en","segment_id":"your-own-uuid"}]}'

Passing your own segment_id is worth doing: it comes back in each claim's source_segment_ids, so you can highlight exactly which line produced a claim.

4. Stream results to the user

Mint a short-lived stream token server-side and hand it to the client:

bash
STREAM=$(curl -sX POST $RTFC/stream/token -H "X-Auth-Token: $USER_TOKEN" | jq -r .stream_token)
js
const es = new EventSource(`${RTFC}/stream?token=${STREAM}&debate_id=${debateId}`);
es.onmessage = (m) => {
  const ev = JSON.parse(m.data);          // note: data line, no event: field
  if (ev.type === "ping") return;
  if (ev.type === "new_claim") addPendingCard(ev);
  if (ev.type === "result")   showVerdict(ev);
};

Events arrive as JSON on the data: line - RTFC never sets the SSE event: field. A ping every 20s keeps intermediaries from closing an idle connection. Tokens last 5 minutes; mint a new one and reconnect.

Prefer not to hold a connection? Use webhooks instead.

5. Show the answer

bash
curl -s "$RTFC/claims/$CLAIM/results?debate_id=$DEBATE&lang=en" -H "X-Auth-Token: $USER_TOKEN"

Pass ?debate_id= whenever you have it. For a claim shared with another debate it scopes source_segment_ids and media_offset_ms to yours. ?lang= translates on demand and caches, so re-reads are free.

media_offset_ms is where the claim occurs in the video - enough to sync verdicts to playback.

6. Account for the cost

bash
curl -s $RTFC/claims/$CLAIM/cost -H "X-Auth-Token: $USER_TOKEN"
curl -s $RTFC/debates/$DEBATE/transcription-cost -H "X-Auth-Token: $USER_TOKEN"
curl -s $RTFC/usage/me/summary -H "X-Auth-Token: $USER_TOKEN"

Costs are already marked up per your plan - debit your user 1:1 against total_cost_usd and you cannot drift from what you are charged.

A reused claim costs $0. Do not treat that as an error.

7. Pause and resume

If your user runs out of credit mid-video, stop the spend without losing progress:

bash
curl -sX POST $RTFC/debates/$DEBATE/pause  -H "X-Auth-Token: $USER_TOKEN"
curl -sX POST $RTFC/debates/$DEBATE/resume -H "X-Auth-Token: $USER_TOKEN"

Pause records an exact resume offset and holds already-detected claims rather than dropping them. Resume replays ingest from that offset and re-queues the held claims, oldest first. Resume is always explicit - nothing restarts on its own.

8. They ask to be forgotten

bash
curl -sX DELETE $RTFC/provision/user/$USER_ID -H "X-Auth-Token: $PROV"