UGC video analyser: API for agents

Send one UGC video, get back a score and a rule-by-rule report against the Ryze filming guide (https://ugc-filming-guide.vercel.app). Each video is checked by a lead agent plus seven check agents. The report counts 14 scored rules; good-to-have and alternative rules are reported but never change the score. Every report has a share link a person can open in the browser and a clean JSON file for you.

Base URL: https://analyzer-production-1bcb.up.railway.app

API key

Send your key in a header. $UGC_API_KEY below is a placeholder; ask the Ryze team for a key.

Authorization: Bearer $UGC_API_KEY

X-API-Key: $UGC_API_KEY works too. Without a key you get the public limit (30 videos per hour per IP).

1. Send a video

By file (multipart, field video):

curl -s https://analyzer-production-1bcb.up.railway.app/api/jobs \
  -H "Authorization: Bearer $UGC_API_KEY" \
  -F video=@file.mp4 \
  -F ref=creator-042/take-3

By URL (JSON, video_url must be a public https link to the video file itself):

curl -s https://analyzer-production-1bcb.up.railway.app/api/jobs \
  -H "Authorization: Bearer $UGC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"video_url": "https://example.com/videos/take-3.mp4", "ref": "creator-042/take-3", "callback_url": "https://example.com/hooks/ugc"}'

Optional fields (multipart field or JSON key):

field what it does
ref Free text to match results on your side. Echoed in the report as ref.
name Display name of the video. Defaults to the file name.
callback_url https. When the check ends, the server POSTs the report JSON there (3 tries with backoff).

Answer 202:

{
  "id": "Qm7tX2c9LpZa",
  "status": "queued",
  "share_url": "https://ugc-filming-guide.vercel.app/r/Qm7tX2c9LpZa",
  "json_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/report.json",
  "status_url": "/api/jobs/Qm7tX2c9LpZa",
  "poll_after_s": 15
}

Errors come as {"error": "plain message"}: 400 wrong type, too long, too big or a bad URL; 429 limit reached; 503 analyser offline.

2. Wait for the report

Poll GET /api/jobs/<id>/report.json every 15 seconds.

ID=Qm7tX2c9LpZa
while :; do
  code=$(curl -s -o report.json -w '%{http_code}' \
    https://analyzer-production-1bcb.up.railway.app/api/jobs/$ID/report.json)
  [ "$code" = 200 ] && break
  [ "$code" = 202 ] || { cat report.json; exit 1; }
  sleep 15
done
jq '{status, score, verdict, to_fix}' report.json

If you passed callback_url you can skip polling, but keep a slow poll as a fallback. A finished report never changes: opening it again does not re-run the check.

3. Report JSON (schema ugc-report/1)

field meaning
schema "ugc-report/1"
id, ref job id; your ref if you sent one
status, error "done" or "error"; plain message or null
created_at, finished_at, duration_s ISO times; how long the check took
video {"name", "length_s"}
format "switch" (cuts between face and filmed laptop), "split" (face and screen share the frame) or "other"
share_url, json_url page for people; this file
score {"hit", "total", "percent"}. total = scored rules that apply to this video (max 14)
verdict, verdict_text "ready" (hit >= total - 1), "close" (hit >= total - 3), "work"
to_fix scored rules that failed, guide order: {"number", "rule", "at", "note"}
scored the 14 scored rules, guide order
optional good-to-have and alternative rules. Never affect the score.

Each item in scored and optional:

field meaning
number, id guide number (1-21) and rule id
section, group, rule guide section, group and rule text
kind "required", "good_to_have" or "alternative"
counts_toward_score true only for required
status "pass", "fail", "na" (does not apply, left out of the total), "unknown" (check failed, left out)
at_s, at moment in the video that shows it, seconds and "m:ss", or null
note one or two plain sentences: what was seen and what to change
snapshot_url jpeg frame at that moment, or null

For optional items pass means the creator used it and fail means they did not. Do not treat them as mistakes.

Full example: 14 scored rules, optional block, to_fix
{
  "schema": "ugc-report/1",
  "id": "Qm7tX2c9LpZa",
  "status": "done",
  "error": null,
  "ref": "creator-042/take-3",
  "created_at": "2026-10-11T18:02:11Z",
  "finished_at": "2026-10-11T18:03:55Z",
  "duration_s": 104,
  "video": {
    "name": "ryze-demo-take3.mp4",
    "length_s": 59.4
  },
  "format": "switch",
  "share_url": "https://ugc-filming-guide.vercel.app/r/Qm7tX2c9LpZa",
  "json_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/report.json",
  "score": {
    "hit": 12,
    "total": 14,
    "percent": 86
  },
  "verdict": "close",
  "verdict_text": "Almost there. Fix the points below before you submit.",
  "to_fix": [
    {
      "number": 14,
      "rule": "Not too close, not too far. Show a vertical slice of the screen and part of the keyboard, with the backlight on.",
      "at": "0:44",
      "note": "Most screen shots are close-ups of one line. Step back so a vertical slice of the screen and part of the keyboard are in frame."
    },
    {
      "number": 20,
      "rule": "Start every new screen with a zoom in or a zoom out in the first two seconds.",
      "at": "0:03",
      "note": "Only 3 of 9 new screens open with a zoom. Start each new screen with a quick zoom in or out."
    }
  ],
  "scored": [
    {
      "number": 1,
      "id": "open-emotion",
      "section": "Opening",
      "group": "You on camera",
      "rule": "Say your first line with real emotion. Shock, anger or excitement is what stops the scroll.",
      "kind": "required",
      "counts_toward_score": true,
      "status": "pass",
      "at_s": 0.4,
      "at": "0:00",
      "note": "You open on your face with a shocked look and a raised hand on the first line.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/open-emotion.jpg"
    },
    {
      "number": 2,
      "id": "open-selfie",
      "section": "Opening",
      "group": "You on camera",
      "rule": "Hold the phone yourself and film your face on the front camera. No tripod. A slightly low angle is fine.",
      "kind": "required",
      "counts_toward_score": true,
      "status": "pass",
      "at_s": 1.2,
      "at": "0:01",
      "note": "Handheld front camera, your face fills most of the frame.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/open-selfie.jpg"
    },
    {
      "number": 4,
      "id": "open-glow",
      "section": "Opening",
      "group": "You on camera",
      "rule": "Let the glow of your laptop or phone light your face. The camera must be sharp.",
      "kind": "required",
      "counts_toward_score": true,
      "status": "pass",
      "at_s": 2.6,
      "at": "0:02",
      "note": "Soft light from the screen on your face, and the face is sharp.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/open-glow.jpg"
    },
    {
      "number": 6,
      "id": "open-title",
      "section": "Opening",
      "group": "On screen",
      "rule": "Put the main title at the top of the frame, above your head.",
      "kind": "required",
      "counts_toward_score": true,
      "status": "pass",
      "at_s": 0.8,
      "at": "0:00",
      "note": "The main title sits at the top of the frame, above your head.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/open-title.jpg"
    },
    {
      "number": 8,
      "id": "open-return",
      "section": "Opening",
      "group": "On screen",
      "rule": "When you are just talking on screen, keep your hands moving in the frame.",
      "kind": "required",
      "counts_toward_score": true,
      "status": "pass",
      "at_s": 31.5,
      "at": "0:31",
      "note": "Your hands move in the frame each time you talk to camera.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/open-return.jpg"
    },
    {
      "number": 9,
      "id": "open-captions",
      "section": "Opening",
      "group": "On screen",
      "rule": "Captions go at the bottom: white text, black outline, one line at a time. Words fill the line instead of flashing one by one.",
      "kind": "required",
      "counts_toward_score": true,
      "status": "pass",
      "at_s": 4.1,
      "at": "0:04",
      "note": "White captions with a black outline in the lower half, words build up on one line.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/open-captions.jpg"
    },
    {
      "number": 11,
      "id": "screen-clean",
      "section": "Filming the screen",
      "group": "Setup",
      "rule": "Keep the desk empty: laptop, second screen, cables. Nothing else.",
      "kind": "required",
      "counts_toward_score": true,
      "status": "pass",
      "at_s": 10.5,
      "at": "0:10",
      "note": "Only the laptop and a second screen are on the desk.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/screen-clean.jpg"
    },
    {
      "number": 13,
      "id": "screen-lens",
      "section": "Filming the screen",
      "group": "Setup",
      "rule": "Wipe the camera lens before you film. Every frame must be sharp.",
      "kind": "required",
      "counts_toward_score": true,
      "status": "pass",
      "at_s": 28.3,
      "at": "0:28",
      "note": "Screen text is sharp and readable all the way through.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/screen-lens.jpg"
    },
    {
      "number": 14,
      "id": "screen-framing",
      "section": "Filming the screen",
      "group": "Framing",
      "rule": "Not too close, not too far. Show a vertical slice of the screen and part of the keyboard, with the backlight on.",
      "kind": "required",
      "counts_toward_score": true,
      "status": "fail",
      "at_s": 44,
      "at": "0:44",
      "note": "Most screen shots are close-ups of one line. Step back so a vertical slice of the screen and part of the keyboard are in frame.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/screen-framing.jpg"
    },
    {
      "number": 15,
      "id": "move-hand",
      "section": "Movement",
      "group": "",
      "rule": "Glide a relaxed hand over the part of the screen you are talking about.",
      "kind": "required",
      "counts_toward_score": true,
      "status": "pass",
      "at_s": 8.4,
      "at": "0:08",
      "note": "A relaxed hand glides over the dashboard while you explain it.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/move-hand.jpg"
    },
    {
      "number": 17,
      "id": "move-point",
      "section": "Movement",
      "group": "",
      "rule": "When you need to show something specific, point right at it with the pen or your finger.",
      "kind": "required",
      "counts_toward_score": true,
      "status": "pass",
      "at_s": 20.4,
      "at": "0:20",
      "note": "The pen tip lands on the cost number and holds.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/move-point.jpg"
    },
    {
      "number": 19,
      "id": "screen-closeup",
      "section": "Movement",
      "group": "",
      "rule": "For the key detail, go very close and point at it with a pen or anything else that catches the eye.",
      "kind": "required",
      "counts_toward_score": true,
      "status": "pass",
      "at_s": 39.6,
      "at": "0:39",
      "note": "A very close shot of the key number, with the pen on it.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/screen-closeup.jpg"
    },
    {
      "number": 20,
      "id": "move-zoom",
      "section": "Movement",
      "group": "",
      "rule": "Start every new screen with a zoom in or a zoom out in the first two seconds.",
      "kind": "required",
      "counts_toward_score": true,
      "status": "fail",
      "at_s": 3,
      "at": "0:03",
      "note": "Only 3 of 9 new screens open with a zoom. Start each new screen with a quick zoom in or out.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/move-zoom.jpg"
    },
    {
      "number": 21,
      "id": "move-camera",
      "section": "Movement",
      "group": "",
      "rule": "Hold the phone still only while the screen animates. Otherwise glide the phone to each part as you explain it.",
      "kind": "required",
      "counts_toward_score": true,
      "status": "pass",
      "at_s": 12.2,
      "at": "0:12",
      "note": "The camera glides between parts of the screen and stays still while the page animates.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/move-camera.jpg"
    }
  ],
  "optional": [
    {
      "number": 3,
      "id": "open-dark",
      "section": "Opening",
      "group": "You on camera",
      "rule": "Film in a dark or dim room.",
      "kind": "good_to_have",
      "counts_toward_score": false,
      "status": "pass",
      "at_s": 2,
      "at": "0:02",
      "note": "The room behind you is dim.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/open-dark.jpg"
    },
    {
      "number": 5,
      "id": "open-distance",
      "section": "Opening",
      "group": "You on camera",
      "rule": "Film from further away, holding the laptop with the demo playing on it.",
      "kind": "alternative",
      "counts_toward_score": false,
      "status": "fail",
      "at_s": null,
      "at": null,
      "note": "You film close to your face, not from further away with the laptop.",
      "snapshot_url": null
    },
    {
      "number": 7,
      "id": "open-gesture",
      "section": "Opening",
      "group": "On screen",
      "rule": "Hook with a gesture in the first second: point up at the title, drop a pen, or turn your phone toward the laptop.",
      "kind": "good_to_have",
      "counts_toward_score": false,
      "status": "fail",
      "at_s": null,
      "at": null,
      "note": "No gesture in the first 2 seconds.",
      "snapshot_url": null
    },
    {
      "number": 10,
      "id": "screen-dark",
      "section": "Filming the screen",
      "group": "Setup",
      "rule": "Film the laptop in a dark room with a dim background.",
      "kind": "good_to_have",
      "counts_toward_score": false,
      "status": "pass",
      "at_s": 9.5,
      "at": "0:09",
      "note": "Screen shots are filmed in a dim room.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/screen-dark.jpg"
    },
    {
      "number": 12,
      "id": "screen-second",
      "section": "Filming the screen",
      "group": "Setup",
      "rule": "Put a related image from another demo on the second screen behind the laptop.",
      "kind": "good_to_have",
      "counts_toward_score": false,
      "status": "fail",
      "at_s": 12,
      "at": "0:12",
      "note": "The second screen is blank.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/screen-second.jpg"
    },
    {
      "number": 16,
      "id": "move-pen",
      "section": "Movement",
      "group": "",
      "rule": "Switch from your hand to a pen now and then, and hover over the screen with the pen.",
      "kind": "good_to_have",
      "counts_toward_score": false,
      "status": "pass",
      "at_s": 14.1,
      "at": "0:14",
      "note": "You switch to a pen and hover over the chart.",
      "snapshot_url": "https://analyzer-production-1bcb.up.railway.app/api/jobs/Qm7tX2c9LpZa/snap/move-pen.jpg"
    },
    {
      "number": 18,
      "id": "move-object",
      "section": "Movement",
      "group": "",
      "rule": "Swap the pen for something unexpected, like a watch.",
      "kind": "good_to_have",
      "counts_toward_score": false,
      "status": "fail",
      "at_s": null,
      "at": null,
      "note": "Only a hand and a pen are used.",
      "snapshot_url": null
    }
  ]
}

share_url (https://ugc-filming-guide.vercel.app/r/<id>) opens the same report as a page: score, verdict, every rule with timestamps and snapshots. Send it to the creator or a reviewer. Anyone with the link can see the report; the id is random and works as the access token. Reports are kept for 365 days. The uploaded video is deleted as soon as the check ends.

5. Limits

Other endpoints

Errors