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.
Two things your backend needs: a provision-scoped token, and somewhere to store each user's token encrypted.
export RTFC=https://api.faktolumo.eu/v1
export PROV=rtfc_your_provisioner_token1. A user signs up
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:
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=…"}'{"platform": "youtube", "external_id": "…", "title": "…", "duration_s": 812,
"is_public": true, "reject_reason": "", "existing_debate_id": null}Two fields decide what happens next:
reject_reasonnon-empty → stop and tell the user why. It is one ofunsupported_platform,not_a_single_video,unavailable,private,needs_auth,geo_blocked,live_in_progress.existing_debate_idnon-null → RTFC has declared an analysis of this exact video shared. Claim access and skip ingest entirely - it is free and instant. Otherwise (null) create your own analysis: if the video was analysed before, its transcript and speakers are reused automatically.
curl -sX POST $RTFC/media/youtube/$EXTERNAL_ID/claim-access -H "X-Auth-Token: $USER_TOKEN"3. Otherwise, ingest it
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": "us", "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 lets an advisory judge select the agreement panel. Disagreements are adjudicated into one claim verdict. language: "auto" handles code-switching.
Set detection_filter_tier on POST /v1/debates (or PATCH /v1/debates/{id}): 1 requires a model accuracy score ≥ 75%, 2 ≥ 80%, and 3 ≥ 85%. The default is 3, including debates created automatically for followed accounts and alert groups. The dashboard exposes these three tiers under New debate → Advanced settings → Detection filter strength. Both realtime and batch claim detection apply the threshold. The gateway uses the measured detector benchmark pass rate when available, otherwise its catalog quality score; models without a known score are excluded. Response time does not filter eligibility, and detection disables latency scoring and group speed ceilings. Research worker selection is unchanged. If no eligible model is available, detection retains the threshold instead of falling back to a lower score.
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.
curl -s "$RTFC/debates/$DEBATE/transcript?limit=1" -H "X-Auth-Token: $USER_TOKEN" | jq .ingest_status{"stage": "transcribing", "since": "2026-09-25T08:44:17Z",
"provider": "elevenlabs/scribe_v2", "audio_seconds": 2512}A video is transcribed in one call once it is downloaded, so no segment appears for several minutes. Poll ingest_status on the transcript endpoint to show progress meanwhile: stage goes downloading → queued → decoding → transcribing → done (or failed, with reason download_failed or stt_failed). since is when the current stage started; audio_seconds is the length being transcribed; fallback: true means the first speech-to-text provider failed and another one took over. The field is null for live sessions.
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:
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-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:
STREAM=$(curl -sX POST $RTFC/stream/token -H "X-Auth-Token: $USER_TOKEN" | jq -r .stream_token)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
curl -s "$RTFC/claims/$CLAIM/results?debate_id=$DEBATE&lang=en" -H "X-Auth-Token: $USER_TOKEN"panels[]- all eligible method results for every authorized caller, including API accountsbest- the single claim verdict (null while pending)conflict- false: method disagreements are resolved before publicationelection_method- the claim verdict basis:operator,agreement,adjudicated,fallback, orpending
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
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:
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
curl -sX DELETE $RTFC/provision/user/$USER_ID -H "X-Auth-Token: $PROV"