Skip to main content
A moderation check runs on every video after it is ready. It looks at the review frames our encoder writes (1 frame every 10 seconds). If the check flags a video, or a free account’s first videos still wait for a check, the video is held: it does not play. Our team releases it from an internal queue. This page shows the moderation states, what your app sees when a video is held, and how a held video is released.
Status. This page is TEST only. See the “Tested in TEST” section below.
This guide is written in simple English.

Before you start

  • API in TEST: https://test-api.segmnt.cloud
  • Playback uses POST /v1/projects/{projectId}/videos/{videoId}/playback-tokens.
  • A held video is refused with 409 code: video_not_ready. The detail is The video is held for review. for a free hold, and The video is not ready to play yet. for a flagged or team-held video.
  • Times are UTC.

1. What the check does

  • The encoder writes 1 review frame every 10 seconds to our storage.
  • A moderation job reads up to 20 frames and asks the AI provider.
  • The job runs on our 15-minute cron. Our team can also run it on demand.
  • A flagged video is held. A clean check on a video that waits for a check releases it.
  • Every decision is audited: what decided (the check or a person), the decision, the reason and the time.
The check is safe to repeat: a video has one claim, so a second scan does not check it twice. A check that fails is retried by a later scan.

2. Moderation states

Each video has one moderation state: Notes:
  • The state is for our team. Your API sees the effect: the video status becomes held, and a playback token is refused (section 3).
  • A free account’s first videos are pending while the video status may still be ready. So status: ready alone does not mean the video plays. Ask for a playback token to be sure, or wait for video.ready.

3. What your app sees when a video is held

  • POST /v1/projects/{projectId}/videos/{videoId}/playback-tokens answers 409 and issues no token. The code is always video_not_ready; the detail depends on the state: Example (a free hold):
  • A flagged video, and a video our team holds, gets status: held, and we send the video.held webhook (if your endpoint subscribed to it).
  • A playback link that was issued before the hold stops working within about a minute. The CDN answers 403 with the header X-Segmnt-Error: video_unavailable.
  • GET /v1/projects/{projectId}/videos/{videoId} still returns the video. Its status is held for a check or a team hold; it can be ready for a free account’s first videos that still wait for a check.
Handle a held video in your app: hide the player, or show a short message. Do not retry the playback-token call in a loop; the state changes only when a person acts (section 5) or a check releases the video.

4. Free accounts: the first videos

  • A free account’s first videos are held until checked. The default is 3, and our config allows 0 to 5.
  • A paid account’s first video is not held.
  • Phone verification is available but not required in this version, so it does not hold or block the first upload. See Phone verification.
  • Until the AI check runs, these videos wait for a person in our review queue.
The count is per account, across all of its projects, and it counts videos that are not deleted. After the first videos, new uploads start none (playable when ready).

5. How a held video is released

Our team reviews held videos on an internal page at /admin on the dashboard host, in the Moderation tab:
  • Every Approve and Hold is audited as a human decision, with the reason.
  • Our team’s release sends the video.ready webhook; our team’s hold sends video.held. A clean check that clears a free hold makes the video playable and sends no webhook.
  • You cannot release a video yourself. /v1/admin/** is for our team only. If you think a hold is wrong, contact us.

6. The AI provider (our side)

The AI provider costs money, so choosing or using one needs board approval first. Today only the no-cost stub provider can be enabled (MODERATION_PROVIDER=stub). Any other value is ignored, so nothing paid can be switched on by config alone. Until a paid provider is approved, the pipeline still works: a free account’s first videos are held and wait for a person in the review queue, exactly as “held until checked” requires. Config (our side): The state lives in migration 0013_moderation.sql. Admin API (our team only, under the Admin tag in the API reference): The admin page /admin has a Moderation tab that lists this queue and can Approve or Hold a video. These routes sit behind Cloudflare Access and are not for customers.

7. Errors

Errors use application/problem+json. Use the code field in your code, not the text.

Tested in TEST

It is TEST only; no paid provider is called. TEST deploys from main. The behaviour is proved by our API test suite:
  • api/src/moderation.test.ts — only none and cleared play; the free hold counts the account’s videos and is clamped to 0-5; the check flags and holds a video and audits it; a clean check releases a free hold; a second run is skipped; a failed check is retried; only stub can be enabled; the scan is a no-op with no provider.
  • api/src/uploads.test.ts — a free account’s first 3 videos are pending (and each hold is audited); the 4th is none; a paid account’s first video is none.
  • api/src/playback-tokens.test.ts — 409 for pending and for flagged, 201 for cleared. (The test asserts the status only; the detail difference in section 3 comes from the guard order in playback-tokens.ts.)
  • api/src/admin.test.ts — the review queue lists pending/held videos, a release clears the hold and writes the audit row, and the scan route answers.
A live check against https://test-api.segmnt.cloud needs a held video and a verified domain. The API tests above cover the behaviour; we re-run them after every change.