Skip to main content
Moonbounce evaluates a video by splitting it into scenes and checking each scene against your policy. Each label hit says where in the video it happened. Video jobs run asynchronously. You create the job, wait for it to finish, then read the hits.
Video evaluation is in preview. The video request field is not yet shown in the Create a new job reference, so this page documents it. Request and response shapes may change before general availability.

How it Works

  1. Create a job for the video with POST /v1/jobs. The response returns a jobUuid straight away, with status JOB_STATUS_PENDING.
  2. Wait for the job to finish. It moves through JOB_STATUS_RUNNING to JOB_STATUS_COMPLETED or JOB_STATUS_FAILED. Get notified with a webhook, or poll.
  3. Read the label hits with GET /v1/jobs/{jobUuid}/labels. Each hit carries the label, its score, and where it happened in the video.
Every request authenticates with an API key, sent as a bearer token:
The examples use the HTTP/JSON API, whose field names are camelCase (for example jobUuid). The gRPC and protobuf names are the snake_case equivalents (job_uuid). In JSON, 64-bit integer fields such as startMs are encoded as strings. HTTP/JSON responses include every top-level field, even when it has no value: an empty string is "", an empty list is [], and an unset object is null. Check values, not whether a key is present.

Create a Video Job

Put a video object in a contentData entry. The video must be hosted at a publicly accessible HTTPS URL. Moonbounce downloads it from that URL, and you can’t upload the file bytes directly.
The response is the job, still pending. Save job.jobUuid.

Video requirements

  • One video per request. A video can’t be combined with other content in the same job. To evaluate several videos, create a job for each one.
  • Use POST /v1/jobs. The streaming endpoint (POST /v1/jobs/stream) doesn’t accept video.
  • Formats. The URL must serve one of these content types:
    • video/mp4 or application/mp4
    • video/webm
    • video/quicktime
    • video/x-matroska
    • video/x-msvideo
    • video/mpeg or video/MP2T
    • video/3gpp or video/3gpp2
    • video/ogg
    • video/x-flv
    HLS playlists are also accepted, if the URL serves them as application/vnd.apple.mpegurl or application/x-mpegurl. Moonbounce decides by the Content-Type header, not the .m3u8 extension, so a playlist served as binary/octet-stream or text/plain fails. The playlist must be video-on-demand (not live), unencrypted, and 3 hours or shorter.
  • Size. Up to 5 GiB.
  • Time. Downloading a video file can take up to 15 minutes. Retrieving and assembling an HLS stream can take up to 60 minutes. If a job is still running after 2 hours, it fails with JOB_ERROR_REASON_EXECUTION_DEADLINE_EXCEEDED.
Moonbounce checks the URL scheme and the request shape when you create the job. It checks the format, the size, and whether the URL is reachable later, while the job runs. Those problems show up as a failed job, not as an error on POST /v1/jobs. See Errors.

Know When It’s Done

Webhook

If you set webhook.url, Moonbounce sends a POST to that URL when the job completes or fails. That includes jobs that fail a precheck, where POST /v1/jobs itself returned an error (see Rejected requests), so you can receive a webhook for a job_id you never got back from the API. The URL must be HTTPS. Moonbounce also sends any webhook.extraHeaders you supply, adding an X-Webhook- prefix to each header name so it can’t collide with standard headers. The body names the job and its status, and includes the job itself:
The job object here is shortened. The top-level status is a short lowercase value (completed or failed). The job.status inside it uses the same JOB_STATUS_* values as the rest of the API. The webhook’s job is not encoded the same way as the GET /v1/jobs/{jobUuid} response. It leaves out fields that are unset or have default values, and it doesn’t include assertion reports. Don’t parse both with code that expects the same keys.
Each webhook is sent once, and Moonbounce waits up to 10 seconds for a response. If your endpoint is down, slow, or returns an error, Moonbounce doesn’t retry. Keep polling as a fallback for any job you haven’t heard about within the time you expect it to take.

Polling

Poll GET /v1/jobs/{jobUuid}, or call GET /v1/jobs/{jobUuid}/labels directly. Both return the job’s current status. Use an increasing interval between polls. Processing takes longer the longer the video is.

Read the Label Hits

The response lists every label the video hit, with the job’s status. This response was requested with pageSize=3:
Label hits appear only once the job is JOB_STATUS_COMPLETED. If you call this endpoint before then, it returns the current status with an empty labels list, so you can use it to poll.

How a video is evaluated

Moonbounce splits the video into scenes. Each scene is a frame segment, running from one detected frame to the next. Where there is speech, Moonbounce pairs the frame with the overlapping chunk of the audio transcript and evaluates the two together, so the model sees what is shown and what is said at the same time. Scenes without speech are evaluated from the frame alone. Every hit from a video job is reported as MODALITY_VIDEO, including hits that the transcript contributed to. A hit’s own range (startMs to endMs) is the part of the video where the frame segment and the transcript chunk overlap. For example:
  • The frame segment runs [6000, 15000) ms.
  • The transcript chunk runs [0, 10000) ms.
  • The hit’s range is their overlap, [6000, 10000) ms.
A frame segment that spans several transcript chunks is evaluated once with each chunk. So the same label can come back several times with the same frameStartMs, each hit with its own startMs/endMs. Don’t deduplicate hits by frame, or you’ll drop real hits. In the response above, the transcript chunk ([31, 152210) ms) contains each frame segment, so each hit’s range equals its frame segment. In a scene without speech, the hit’s range is also the whole frame segment, and the transcript fields are absent.

Label hit fields

Hit fields that have no value are left out of the hit; they are never sent as null. This applies only to fields inside a hit. The response’s top-level fields are always present (see above).
Video jobs processed before paired scene evaluation was introduced keep their original results. In those jobs, frame hits are MODALITY_VIDEO and transcript hits are reported separately as MODALITY_AUDIO. A frame hit may have only startMs, and the frame* and transcript* fields are absent. Code that reads older jobs should handle both shapes.

Ordering and pagination

Hits are returned sorted by startMs, then by label. Responses are limited to 1,000 hits. When more remain, nextPageToken is set. On the last page it is an empty string (""). To read every hit, repeat the request with pageToken set to the last nextPageToken, until nextPageToken comes back empty. Check for an empty value, not a missing key: the key is always there, and sending an empty pageToken fetches the first page again.
Pagination rules:
  • Tokens are tied to the request. A pageToken works only with the same jobUuid (and the same jobResultId, if you set one).
  • Only completed jobs are paginated. A job that hasn’t completed returns an empty nextPageToken. Sending a pageToken for it is rejected.
  • Tokens don’t expire. A completed job’s hits never change, so you can resume reading later.

Errors

Rejected requests

These come back straight away from POST /v1/jobs: A recorded job appears in GET /v1/jobs as JOB_STATUS_FAILED, but the error response doesn’t include its jobUuid. If you set a webhook, it is called for a precheck failure, but not for a quota failure or a failure to start. Don’t assume that an error from POST /v1/jobs means no job exists.

Failed jobs

Problems found while the job runs mark it JOB_STATUS_FAILED and fill in error on the job. For any other status, error is null in GET /v1/jobs/{jobUuid} and GET /v1/jobs/{jobUuid}/labels responses, so check status rather than whether error is present. You can read it from GET /v1/jobs/{jobUuid}, GET /v1/jobs/{jobUuid}/labels, or the webhook body:
Branch on reason. The message is fixed explanatory text for that reason. It is safe to show to users, but don’t parse it. New reasons may be added over time. Handle values you don’t recognize as a general failure.

Reading label hits

A jobResultId that isn’t part of the job is only reported once the job has completed. Until then the call returns 200 with the current status, so a poll with the wrong ID looks healthy.

Where to Next

Create a new job

The full POST /v1/jobs request and response schema.

Get job label hits

The GET /v1/jobs/{jobUuid}/labels reference.

Get job details

Read a job’s status, results, and failure details.

Submit job feedback

Tell Moonbounce whether a hit on a frame or transcript chunk was right.

We’d Love to Hear From You

Whether you have a suggestion, feedback, or a bug to report, here are the best ways to get in touch:
  • In the App: Use the Feedback button for direct suggestions.
  • On Slack: Reach out to the team in your shared channel.
  • With your AM: Talk to your dedicated account manager.
  • Via Email: Send a message to support@moonbounce.io.
  • Security, availability, or other incidents: Use the in-app Feedback button or email support@moonbounce.io. See Customer Feedback for what to include.