OriginAI API

For developers: the same requests this website makes to scan a video. It's a test API for our thesis demo, open without keys, so it may be busy, offline or changed without notice.

Before you start

Every address below starts with the address of this website. Replace https://<your-originai-site> in the examples with it.

A test API

It runs on the same rented GPU as the website, with a daily limit on checks. Don't build anything important on it.

No keys

There are no accounts or API keys. Anyone with a check's id can read its result, so treat ids like private links.

Kept for 1 hour

Results and their evidence images are deleted an hour after a check starts. After that, asking for them returns 404.

Scan a video in three requests

Checks run in the background. Start one, then ask for the result every few seconds until it's ready, without keeping a connection open.

  1. Pick a detector

    Lists the four detectors with their id, name and the collection they learned from: veni-hq, veni-lq, vidi and vici.

    GET /api/models

  2. Start a check

    Send one video file as video and a detector id as model, as multipart form data. The answer is 202 with the check's id and "status": "processing".

    POST /api/analyses

  3. Get the result

    Ask about every 3 seconds. status stays processing until it becomes completed or failed. The first check after a quiet spell can take 5 to 10 minutes while a GPU starts.

    GET /api/analyses/{id}

An example

Vidi's check of the example clip on the home page. The finished result is shortened: clips and heatmaps hold one entry per part of the video.

Start a check
curl -X POST "https://<your-originai-site>/api/analyses" \
  -F "video=@clip.mp4" \
  -F "model=vidi"
Answer: 202 Accepted
{
  "id": "8c1f3e2a9b7d4f60a5e2c9d1b3f47a68",
  "status": "processing",
  "model": {
    "id": "vidi",
    "name": "Vidi",
    "dataset": "Celeb-DF v2"
  },
  "created": 1791085773.32
}
Ask for the result
curl "https://<your-originai-site>/api/analyses/8c1f3e2a9b7d4f60a5e2c9d1b3f47a68"
Failed check
{
  "id": "2d7a9e41c0b84f3fa6e15c8b9d0f2a73",
  "status": "failed",
  "model": {
    "id": "veni-hq",
    "name": "Veni HQ",
    "dataset": "FaceForensics++ C23 (HQ)"
  },
  "reason": "no_face",
  "error": "No face was detected in the video."
}
Finished result (shortened)
{
  "id": "8c1f3e2a9b7d4f60a5e2c9d1b3f47a68",
  "status": "completed",
  "model": { "id": "vidi", "name": "Vidi", "dataset": "Celeb-DF v2" },
  "created": 1791085773.32,
  "pred": "DEEPFAKE",
  "prob_fake": 0.9059,
  "raw_prob_fake": 0.99999,
  "threshold": 0.2286,
  "video_logit": 11.357,
  "aggregation": "mean_clip_logit",
  "meta": { "width": 944, "height": 500, "fps": 30.0, "duration": 15.633 },
  "coverage": {
    "mode": "every_frame_phased",
    "duration": 15.633,
    "frames_analyzed": 469,
    "analyzed_start_s": 0.0,
    "analyzed_end_s": 15.6,
    "fallback": null
  },
  "clips": [
    { "index": 0, "start_s": 1.75, "end_s": 3.5, "prob": 0.8797 }
  ],
  "protocol": { "available": true, "prob_fake": 0.8871, "pred": "DEEPFAKE" },
  "heatmaps": {
    "target": "DEEPFAKE",
    "clips": [
      {
        "clip_index": 4,
        "clip_start_s": 7.2,
        "clip_end_s": 10.7,
        "clip_prob": 0.9695,
        "frames": [
          {
            "t": 7.2,
            "frame": "/api/analyses/8c1f3e2a9b7d4f60a5e2c9d1b3f47a68/media/c4_f0_frame.jpg",
            "frame_heat": "/api/analyses/8c1f3e2a9b7d4f60a5e2c9d1b3f47a68/media/c4_f0_frame_heat.jpg"
          }
        ]
      }
    ]
  },
  "timings": { "total": 1.51 }
}

What a result contains

Every answer from GET /api/analyses/{id} has id, status, model and created. The rest depends on the status.

FieldWhat it means
statusprocessing, then completed or failed.
stageWhile processing: fetching (a link's video is being downloaded) or analyzing. Then completed or failed.
predThe result: DEEPFAKE or REAL.
prob_fakeThe calibrated score from 0 to 1. pred is DEEPFAKE when it's at or above threshold.
thresholdThe detector's own line: veni-hq 0.5248, veni-lq 0.4893, vidi 0.2286, vici 0.8322. Compare scores only with the line of the detector that made them.
raw_prob_fake, video_logit, aggregationThe score before calibration, the combined raw score, and how the clip scores were combined (mean_clip_logit).
metaThe video's width, height, fps and duration in seconds.
coverageWhat was checked: how many frames and which seconds. fallback is not null (and mode is protocol_fallback) when too few frames showed a usable face and only a few short clips were checked. Such a result is less reliable.
clipsThe timeline: the video in equal parts, each with start_s, end_s and its score prob, averaged over every check that overlaps it.
heatmapsThe moments with evidence images. Each has its own clip_start_s, clip_end_s and score clip_prob, and frames with image paths. Its clip_index is not an index into clips.
protocolThe second check: three short clips scored the way the detector was tested. It looks at less of the video, so it can disagree with pred.
timingsHow long each step took on the GPU, in seconds.
error, reasonWhen the check failed: a message for people, and a short code such as no_face, insufficient_valid_frames or decode_failed.

The other requests

The website also uses these. Links work only for public videos from the platforms the server lists.

RequestWhat it does
GET /api/capacityWhether a new check is accepted right now (accepting, and a detail message and code when it isn't), how long a check may run (scan_timeout_seconds) and the file limits.
POST /api/analyses/linkStarts a check of a video link. Send JSON {"url": "…", "model": "vidi"}. The answer is 202 like an upload, with "stage": "fetching" and a source naming the platform.
GET /api/link-sourcesWhether links can be checked, the accepted platforms, and the known off_platforms that can't be checked yet.
POST /api/links/inspectLooks up a link's title, length and quality without checking it. Send JSON {"url": "…"}.
GET /api/analyses/{id}/media/{name}An evidence image named in heatmaps. Kept for 1 hour, like the result.

Limits and errors

A refused request answers with JSON: a detail message you can show to people, and a code your app can check.

What can be checked

  • MP4, MOV, WebM or MKV
  • 10 seconds to 2 minutes long
  • 360p to 1080p (the shorter side)
  • Up to 150 MB
  • Results expire after 1 hour
StatusCodes
400unknown_model, bad_format, empty, not_video, too_short, too_long, resolution_low, resolution_high, invalid_request. Link requests can also answer unsupported, private or platform_off.
404The check doesn't exist or has expired.
413too_large
429busy, daily_limit, visitor_busy, visitor_daily_limit. Try again later.
503not_configured: the detectors aren't connected right now.

Showing results to your users

Most of your users won't be experts. A few rules keep the results honest.

Show every state

Show when a check is waiting, running, done or failed, and let people retry without uploading again.

Keep the context

Show the detector, what it learned from and its threshold next to every result.

Don't call it proof

Describe the result as one clue, and point people to ways of checking further.

Want to build with OriginAI?

This test API is here for the thesis demo. If you'd like to use it for something, tell us what you're building first.