← Real or Render
Real or Render API
Tokens

Drive Real or Render from your own code

The one metered read is available over HTTP: give it a viral clip's description, the caption it came with, the tells ticked on the card and what has been checked so far, and it returns a verdict, the caption split into checkable claims (backed / contradicted / unchecked), every ticked tell read back, three to five next checks and a short reply to send. A natural use is a group-chat bot or a moderation queue that wants a calm second opinion before a clip is forwarded again.

The division of labour first: the page owns the card and the lean; the model does the investigation; the page re-checks the reply. The 24 tells, their families, the corroboration stamps, the bingo lines and the lean (by the same ordered rules the model gets) come from tellkit.js, which runs in node as well as in the browser. Its check function holds each reply to the card: one tell entry per tick and none invented, the verdict against the lean, "backed" claims only when something backs them, no internal field names in prose. The model never sees the clip.

Get a token on the token page (step 2 below also shows how to mint a guest token in code).

Base URL and the envelope

Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response uses one envelope:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "...", "status": 402, "details": { ... } } }

The token is minted for this app (the guest call takes {"slug":"real-or-render"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a required field such as task or clip is missing.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: the read is metered and needs a personal token.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. Retry with the same Idempotency-Key so a retry never double-bills.

The task field

Every run sends task: check - there is one lane. The verdict is decided by four ordered, non-overlapping rules; the first that matches wins:

OrderVerdictWhen
1real_but_miscaptionedA concrete fact contradicts the caption's time, place, event or people: an earlier upload, a different location, the same clip under another caption.
2likely_generatedOtherwise, two or more tells from the "looks generated" family.
3likely_realOtherwise, two or more independent corroborations (who filmed it, another angle, a news report, a statement, details that fit).
4not_enough_yetEverything else.

The declared input schema carries scalars only, so array-valued fields travel JSON-encoded as strings (TellKit.toWire does this). Every value is a string.

Input fields

FieldTypeNotes
taskstringcheck.
clipstringWhat happens in the clip, in the viewer's words. Past 1,500 characters the middle is cut with a marker.
captionstringThe caption or claim as posted; may be empty. Past 600 characters the middle is cut.
wherestringOne of short-video app, photo and video feed, group chat or message, forum or message board, news site comments, somewhere else, or empty.
posted_by, seen_onstringHow the viewer describes the account, and when they saw it; may be empty.
findingsstringWhat has been checked so far, free text; past 2,000 characters the middle is cut with a marker.
page_tellsstring (JSON)[{label, family}] in card order; family is looks generated, wrong caption or no source.
page_corroborationstring (JSON)Array of the corroboration labels ticked.
page_leanstringThe page's verdict from the ticks alone, by the rules above.
page_flagsstring (JSON)Array of the page's warnings.
retry_notestringOnly on a reformat retry.

The reply

The job's output.output is a string holding one JSON object: task, verdict, title, summary, claims ([{claim, status, basis}], status backed / contradicted / unchecked), tells ([{tell, reads_as, why}], one per tick, reads_as generated / miscaptioned / unsourced), checks ([{step, how, settles}], 3-5) and reply. Parse it with TellKit.parseReply, then normalize(reply), then check(reply, form), which returns {notes, lean}.

Worked examples

Replies produced against the app's prompt for the four sample clips (made-up clips, captions and notes; long fields and lists shortened; the full replies ship in example-result.js). Together they cover every verdict.

supermarket moose - likely generated

Input:

{
 "task": "check",
 "clip": "A moose walks through the automatic doors of a supermarket, wanders down the produce aisle and knocks over a tall pyrami ... (170 characters)",
 "caption": "Moose crashes the pumpkin sale at a small-town grocery store this morning. You can't make this up",
 "where": "short-video app",
 "posted_by": "a page that posts funny animal clips all day, no names or places",
 "seen_on": "Oct 18",
 "findings": "Watched it about five times. The price signs above the pumpkins look like letters but don't spell anything. One pumpkin seems to slide into  ... (279 characters)",
 "page_tells": "[{\"label\":\"Signs or text are gibberish\",\"family\":\"looks generated\"},{\"label\":\"No original uploader found\",\"family\":\"no source\"},{\"label\":\"Under 10 seconds, or cuts every few seconds\",\"family\":\"looks g ... (521 characters)",
 "page_corroboration": "[]",
 "page_lean": "likely_generated",
 "page_flags": "[]"
}

Reply:

{
 "task": "check",
 "verdict": "likely_generated",
 "title": "Supermarket pumpkin moose, likely generated",
 "summary": "This reads as likely generated: your card ticks five separate generated-video markers, including gibberish signage, objects merging and a clip under ten seconds, with no original source or uploader turning up either. Nothing in your notes points to a real incident with a different place or time attached - the caption's details are simply unconfirmed rather than disproven. Before sharing, it's worth running a reverse image search on a still frame rather than treating this as a true store visit.",
 "claims": [
  {
   "claim": "A moose crashed into the pumpkin display at a grocery store, as the clip shows.",
   "status": "contradicted",
   "basis": "Your card ticks several generated-video markers - gibberish signage, objects merging, a sub-ten-second clip and more - which point against the footage being genuine as shown."
  },
  {
   "claim": "This happened at a small-town grocery store.",
   "status": "unchecked",
   "basis": "Nothing in your notes names or rules out the store, so matching the background or signage to a real location would check this."
  },
  "... (3 in all)"
 ],
 "tells": [
  {
   "tell": "Signs or text are gibberish",
   "reads_as": "generated",
   "why": "Illegible signage, like the letters in your notes not spelling anything, is a common generated-video marker, though heavy compression can sometimes blur real text the same way."
  },
  {
   "tell": "No original uploader found",
   "reads_as": "unsourced",
   "why": "Not finding a first poster doesn't prove the clip is fake, but it does mean nothing yet backs up where or when it was actually filmed."
  },
  "... (7 in all)"
 ],
 "checks": [
  {
   "step": "Reverse image search a clear still frame",
   "how": "Pause on a sharp, non-blurry frame of the clip and run a reverse image search on it to look for an original or earlier upload.",
   "settles": "Would overturn the generated read if a genuine original turns up, or strengthen it if nothing but the same clip appears."
  },
  {
   "step": "Check the posting account's other clips",
   "how": "Look through what else the account has posted to see if it ever credits a source or only ever reposts other people's clips.",
   "settles": "Addresses whether no source can be found at all, and could surface a real place or time for the claims about where and when it happened."
  },
  "... (4 in all)"
 ],
 "reply": "Before you share this one - a few things point to it being generated rather than filmed: the price signs don't actually spell anything, one pumpkin seems to slide into the next instead of getting knocked over, and nobody's turned up where it was originally posted. It's also oddly short for everything that happens in it. Might be worth holding off on calling it a real store visit until a genuine original shows up."
}

forwarded hailstorm - real but miscaptioned

Input:

{
 "task": "check",
 "clip": "Hailstones the size of golf balls bounce off parked cars on a city street while someone films from a shop doorway; a car ... (144 characters)",
 "caption": "Insane hail in our city RIGHT NOW. Stay inside!!",
 "where": "group chat or message",
 "posted_by": "forwarded by my aunt, no idea who made it",
 "seen_on": "today",
 "findings": "Did a reverse image search on a still frame and found the same clip on a weather fans' page dated three summers ago, credited to a man who s ... (258 characters)",
 "page_tells": "[{\"label\":\"Found it posted before the claimed date\",\"family\":\"wrong caption\"},{\"label\":\"Signs, plates or landscape don't match the place\",\"family\":\"wrong caption\"},{\"label\":\"No date or place in the po ... (226 characters)",
 "page_corroboration": "[\"Found who filmed it, and they say so\"]",
 "page_lean": "real_but_miscaptioned",
 "page_flags": "[]"
}

Reply:

{
 "task": "check",
 "verdict": "real_but_miscaptioned",
 "title": "Hail clip, real but miscaptioned",
 "summary": "This hail footage looks real, but your notes show it isn't from today or your city: the same clip turns up three summers earlier credited to whoever filmed it, the street signs don't match your area, and it's clear outside where you are right now. That points to an old clip picked up with the wrong caption rather than anything faked or currently happening. Before sharing it further, it's worth noting it isn't describing today's weather where you are.",
 "claims": [
  {
   "claim": "The hail shown happened as filmed",
   "status": "backed",
   "basis": "your notes say you found who filmed it, and they say so"
  },
  {
   "claim": "The hail is happening in your city",
   "status": "contradicted",
   "basis": "your notes say the street signs in the clip are in a language that isn't yours"
  },
  "... (3 in all)"
 ],
 "tells": [
  {
   "tell": "Found it posted before the claimed date",
   "reads_as": "miscaptioned",
   "why": "This shows the clip already existed before today, so it can't be footage of something happening right now, though the hail itself may well be real from whenever it was filmed."
  },
  {
   "tell": "Signs, plates or landscape don't match the place",
   "reads_as": "miscaptioned",
   "why": "This shows the footage wasn't filmed where the caption says, not that the clip is fake - the hail could still be real, just somewhere else."
  },
  "... (3 in all)"
 ],
 "checks": [
  {
   "step": "Get the exact date from the earlier post",
   "how": "open the earlier post you found and check the exact date it gives, not just the year",
   "settles": "the claim that the hail is happening right now"
  },
  {
   "step": "Get the real location from the earlier post",
   "how": "check whether that earlier post names the actual place the footage was filmed",
   "settles": "the claim that the hail is happening in your city"
  },
  "... (4 in all)"
 ],
 "reply": "Heads up - this hail footage looks real, but it's not from today or here. The same clip was on a weather fans' page three summers ago, credited to the man who filmed it, and the street signs in it don't match this area. It's actually clear outside here right now, so it's not describing current weather - might be worth not passing it on as if it's happening now."
}

floating street piano - likely real

Input:

{
 "task": "check",
 "clip": "A street piano on a riverside promenade is lifted by rising water and floats a few metres before getting stuck against t ... (164 characters)",
 "caption": "The tide took the town's street piano last night - and it survived!",
 "where": "photo and video feed",
 "posted_by": "a local reporter's account",
 "seen_on": "Oct 19",
 "findings": "The woman who filmed it posted it first and answered comments with the time she took it. A second video from the bridge shows the same piano ... (293 characters)",
 "page_tells": "[{\"label\":\"The moment happens perfectly in frame\",\"family\":\"no source\"}]",
 "page_corroboration": "[\"Found who filmed it, and they say so\",\"Another angle from someone else\",\"A news outlet reported the same event\",\"Weather, signs and date fit the claim\"]",
 "page_lean": "likely_real",
 "page_flags": "[]"
}

Reply:

{
 "task": "check",
 "verdict": "likely_real",
 "title": "Street piano flood, likely real",
 "summary": "Your notes show strong, independent corroboration: the person who filmed it, a second angle from the bridge, a news report with a council quote, and the tide table all point to this being the real event. The one ticked tell is minor and does not outweigh that. Before sharing, it is worth checking the full news story for what happened to the piano afterward and lining up the exact timing.",
 "claims": [
  {
   "claim": "The tide lifted the piano, as the caption says",
   "status": "backed",
   "basis": "your notes say a second video from the bridge shows the same piano and a news outlet reported the same event"
  },
  {
   "claim": "This happened last night",
   "status": "backed",
   "basis": "your notes say the person who filmed it gave the time she filmed it, and the tide table shows a very high tide that night"
  },
  "... (3 in all)"
 ],
 "tells": [
  {
   "tell": "The moment happens perfectly in frame",
   "reads_as": "unsourced",
   "why": "Catching the exact moment can look suspicious alone, but someone who was already filming as the water rose could easily catch it, and your notes already confirm who filmed it, so this does not outweigh that."
  }
 ],
 "checks": [
  {
   "step": "Read the full news story",
   "how": "look at the local paper's article for what happened to the piano after it got stuck, such as whether it was retrieved undamaged",
   "settles": "the piano survived claim"
  },
  {
   "step": "Line up the timing",
   "how": "compare the time the filmer gave in her replies with the tide table's time for the high tide, and with when the caption says this happened",
   "settles": "the this happened last night claim"
  },
  "... (3 in all)"
 ],
 "reply": "This looks like the real deal - the person who filmed it confirmed when she shot it, there's a second angle from the bridge, and the local paper covered it with a quote from the council about the high tide. The tide table backs it up too. Nice catch!"
}

canal dolphins - not enough yet

Input:

{
 "task": "check",
 "clip": "A pod of dolphins swims up a narrow city canal past moored boats while people on the bank point and shout.",
 "caption": "Dolphins are back in the canals now that the water is clean!!! Nature is healing",
 "where": "short-video app",
 "posted_by": "an account with about 300 followers, made this month",
 "seen_on": "Oct 20",
 "findings": "",
 "page_tells": "[{\"label\":\"No original uploader found\",\"family\":\"no source\"},{\"label\":\"Posted by a new or repost-only account\",\"family\":\"no source\"},{\"label\":\"Water, hair, cloth or gravity move oddly\",\"family\":\"looks ... (344 characters)",
 "page_corroboration": "[]",
 "page_lean": "not_enough_yet",
 "page_flags": "[]"
}

Reply:

{
 "task": "check",
 "verdict": "not_enough_yet",
 "title": "Canal dolphins clip, not enough yet",
 "summary": "This dolphin-canal clip doesn't give you enough yet to call it real or generated. Your card shows no source for the footage, no other angle on it, and one flagged sign about how the water moves, but one sign alone isn't enough to call it generated, and nothing yet backs up the caption's story either. Hold off sharing it until you've traced the footage to its first post or found another angle.",
 "claims": [
  {
   "claim": "A pod of dolphins swam up the canal while people on the bank watched, as the clip shows",
   "status": "unchecked",
   "basis": "nothing in your notes yet confirms or disputes the footage itself; another angle or witness account turning up would settle it"
  },
  {
   "claim": "The canal water is now clean",
   "status": "unchecked",
   "basis": "your notes don't mention checking the water quality; a local water report or news coverage would confirm or disprove this"
  },
  "... (3 in all)"
 ],
 "tells": [
  {
   "tell": "No original uploader found",
   "reads_as": "unsourced",
   "why": "not finding where this first appeared means you can't yet trace it to a time or place, but plenty of real clips circulate a while before anyone locates the original post"
  },
  {
   "tell": "Posted by a new or repost-only account",
   "reads_as": "unsourced",
   "why": "a new or repost-only account doesn't tell you whether the footage is real or fake, only that this post can't vouch for itself"
  },
  "... (5 in all)"
 ],
 "checks": [
  {
   "step": "Search for the original upload",
   "how": "run a reverse image search on a still frame from the clip, or search the caption's most unusual phrase in quotes, to see if an earlier or differently captioned version turns up",
   "settles": "whether this is a repost of an older or differently captioned clip, or whether a first poster can be identified"
  },
  {
   "step": "Study the water and reflections closely",
   "how": "step through the clip frame by frame and watch the wake, ripples and reflections where the dolphins and boats meet, since these are hard for generated video to keep consistent",
   "settles": "whether the odd-movement tell holds up or has an innocent explanation"
  },
  "... (4 in all)"
 ],
 "reply": "Love this clip, but do you know where it was originally posted or filmed? I don't see a source for it, or another angle from anyone else, and nothing yet backs up the claim that the water's actually clean. If you know where it first showed up, or have any other footage of the same moment, could you share that?"
}

Build the input yourself

Load the script in node (global.window = global; require("./tellkit.js")), then:

const K = TellKit;
const form = { clip: "A moose walks into a supermarket and knocks over a pyramid of pumpkins.",
  caption: "Moose crashes the pumpkin sale this morning", where: "short-video app", posted_by: "a repost page",
  seen_on: "Oct 18", findings: "The price signs don't spell anything.", ticks: "text,merge,short", corro: "" };
console.log(K.lean(form), K.bingo(form).length);          // the page's lean and bingo lines
const wire = K.toWire(K.buildInput(form).input);           // POST this to /estimate and /run
// then: const read = K.normalize(K.parseReply(job.output.output));
// const { notes } = K.check(read, form);                  // the page's re-check of the reply
// tell ids: K.TELLS.filter(Boolean).map(t => t.id); corroboration ids: K.CORRO.map(c => c.id)

Step 1 — a tiny client helper

One function that sends JSON and unwraps the envelope. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here"   # from https://real-or-render.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
  curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
    ${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}

Step 2 — a token

A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.

# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"real-or-render"}' | jq -r '.data.token')

Step 3 — GET /me

Who the token is (subject_type: user or guest) and the credit balance.

api GET /me | jq '.data | {subject_type, credits}' 

Step 4 — POST /estimate

Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.

# input.json: the run input object - {"task": "check", "clip": "A moose walks through the automatic doors of a supermarket ...", "caption": "Moose crashes the pumpkin sale ...", "where": "short-video app", "posted_by": "...", "seen_on": "Oct 18", "findings": "...", "page_tells": "[{\"label\":\"Signs or text are gibberish\",\"family\":\"looks generated\"}, ...]", "page_corroboration": "[]", "page_lean": "likely_generated", "page_flags": "[]"}
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

Step 5 — POST /run, then poll the job

Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses real-or-render:<task>:<inputHash>:a<attempt> (there is one lane, check), and its one reformat retry (with retry_note added) is attempt 2, a new key and a second run. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="real-or-render:check:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
  J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
  case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json   # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}' 

Step 6 — POST /run-stream

The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.

# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: real-or-render:check:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json