Overview
Video Clipping Search is the API surface for 9Pic Motion, our selfie + BIB-driven video clipping pipeline. It finds the moments where a participant appears in the event’s videos and returns short, downloadable MP4 clips of those moments. The participant uploads a selfie (or reuses a Face Searchrequest_id) and the API:
- Matches the selfie against faces detected across the event’s source videos.
- Groups matched frames into continuous time segments.
- Asks the clipping service to produce one clip per matched source video.
- Returns publicly-resolvable clip URLs you can play, download, or hand to your UI.
POST and then GET the result by request_id until it reaches a terminal status. The same request_id is shared with Face Search, so a single selfie upload can power both photo and video discovery — and once Face Search has run, you can start a clip with just that request_id and no file upload, because the matching frames were already computed.
Video Clipping Search must be enabled for the event. Both
video_search (the event-level toggle) and video_selfie_search (the selfie-driven video toggle) returned by Event Details must be true.Why Two Endpoints?
Producing a video clip is substantially more expensive than searching for photos: it has to scan every video frame index, run face matching, decide on the best time window per source video, and finally invoke the clipping service. We split this into:POST /video-clipping/search-by-selfie— accepts the selfie, persists a record, and immediately enqueues background processing. Returns arequest_idyou can poll.GET /video-clipping/{request_id}— reads the cached result and returns a structured status payload (with clip URLs once ready).
request_id follows the same idempotency model as Face Search — see Conventions.
Endpoints
POST /video-clipping/search-by-selfie — Start a Search
Accepts either a selfie image or a Face Searchrequest_id, persists the request, and enqueues background clipping. Returns immediately with a request_id for polling.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
org_id | number | Yes | Your organisation ID. |
event_id | number | Yes | The event to search within. |
Multipart Form Fields
| Field | Type | Required | Description |
|---|---|---|---|
file | file | No | Selfie image (JPEG/PNG). The clearest single-face image gives best results. Optional — omit it and pass a request_id from a completed Face Search to reuse the segments already computed for that selfie. |
request_id | string | No | UUID of the selfie record. Pass a Face Search request_id to reuse the same record. Required when no file is uploaded — sending it alone reuses the precomputed segments and clips immediately. If omitted (with a file), 9Pic generates one and returns it. |
You must provide a
file, a request_id, or both. A request_id with no file only works after a Face Search has run for that request_id (so the matching frames exist); otherwise the call returns 400.Example Request
request_id (for example one returned by Face Search) while still uploading a selfie, add -F "request_id=<uuid>" (cURL), data={"request_id": "<uuid>"} (Python), or formData.append("request_id", "<uuid>") (JavaScript).
To clip without re-uploading — the recommended flow right after a Face Search — send only the request_id and omit the file. 9Pic reuses the matching frames it already computed and starts clipping immediately:
Example Response
processing — use the request_id with the GET endpoint to wait for the terminal status. If the selfie matched no video frames at all, the POST can instead return a terminal no_matches straight away (no polling needed). Other terminal outcomes such as no_segments are determined later during clipping and surface only via the GET endpoint. Treat any processing response as “keep polling”.
GET /video-clipping/ — Poll for Results
Returns the current status of the search and any generated clips. Safe to call repeatedly until the status is terminal.Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
org_id | number | Yes | Your organisation ID. |
event_id | number | Yes | The event the search belongs to. |
request_id | string | Yes | UUID returned by a previous POST /video-clipping/search-by-selfie call. |
Status Lifecycle
| Status | Terminal? | Meaning |
|---|---|---|
processing | No | The job is still running. Continue polling (recommended interval: every 3–5 seconds, with backoff). |
completed | Yes | One or more clips were generated. videos[] is populated. |
no_matches | Yes | The selfie did not match any frames in the event videos. No clips generated. |
no_segments | Yes | The selfie matched frames, but the matches were too sparse or scattered to form a usable clip window. |
clip_failed | Yes | The selfie matched and a window was selected, but the clipping service failed to produce one or more clips. clip_errors[] carries per-source detail. |
failed | Yes | The job failed before any clips could be generated (for example: enqueue failed, internal error). error carries a human-readable message. |
Example Request
Example Response
- completed
- processing
- no_matches
- no_segments
- clip_failed
- failed
Recommended Integration Sequence — “POST once, GET many”
Confirm video clipping is enabled
Call Event Details and check that both
video_search and video_selfie_search are true. If either is false, do not show the video clipping CTA.POST once
Upload the selfie to
POST /video-clipping/search-by-selfie. Persist the request_id from the response (URL state, local storage, or your DB).
If you already have a request_id from Face Search, send only that request_id (no file) to clip from the segments Face Search already computed.Poll the GET endpoint
Call
GET /video-clipping/{request_id} until status is anything other than processing. Use backoff: 3s, 5s, 10s, 15s, then every 15s.Response Models
| Model | Description |
|---|---|
| VideoClippingStartResponse | Returned by POST /video-clipping/search-by-selfie. |
| VideoClippingResultResponse | Returned by GET /video-clipping/{request_id}. |
| VideoClipItem | Each entry inside videos[]. |
| VideoClipErrorItem | Each entry inside clip_errors[]. |
Error Responses
| Status | Meaning |
|---|---|
400 | request_id is not a valid UUID; the uploaded selfie file is empty; neither a file nor a request_id was provided; or a request_id was sent with no file but has no precomputed segments (run Face Search first). |
401 | API key is missing. |
403 | API key is invalid, inactive, token/event ownership mismatch, or video clipping (event-level or selfie-driven) is disabled for this event. |
404 | Event configuration not found, video search configuration not found, or request_id is unknown (on GET, or on the file-less reuse POST). |
429 | Rate limit exceeded. Back off and retry with the same request_id to hit the cache. |
500 | Internal failure that prevented the request from being recorded, or the background pipeline could not be enqueued. The selfie record is marked failed; a fresh POST is the safest retry. |

