Skip to main content

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 Search request_id) and the API:
  1. Matches the selfie against faces detected across the event’s source videos.
  2. Groups matched frames into continuous time segments.
  3. Asks the clipping service to produce one clip per matched source video.
  4. Returns publicly-resolvable clip URLs you can play, download, or hand to your UI.
The search is asynchronous. You start it once with 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 a request_id you can poll.
  • GET /video-clipping/{request_id} — reads the cached result and returns a structured status payload (with clip URLs once ready).
This lets your client stay responsive: trigger the search once, then poll for results without re-uploading the selfie. The request_id follows the same idempotency model as Face Search — see Conventions.
Reuse a request_id returned by Face Search when you start a video clipping search. The same selfie record is updated, so the user gets photo and video results from a single upload.

Endpoints

POST /api/v1/ext/{org_id}/event/{event_id}/video-clipping/search-by-selfie
GET  /api/v1/ext/{org_id}/event/{event_id}/video-clipping/{request_id}

POST /video-clipping/search-by-selfie — Start a Search

Accepts either a selfie image or a Face Search request_id, persists the request, and enqueues background clipping. Returns immediately with a request_id for polling.

Path Parameters

ParameterTypeRequiredDescription
org_idnumberYesYour organisation ID.
event_idnumberYesThe event to search within.

Multipart Form Fields

FieldTypeRequiredDescription
filefileNoSelfie 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_idstringNoUUID 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

curl -i \
  -H "X-API-Key: <your_9pic_api_key>" \
  -F "file=@selfie.jpg" \
  "https://api.9pic.ai/api/v1/ext/903/event/456/video-clipping/search-by-selfie"
To reuse an existing 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:
curl -i \
  -H "X-API-Key: <your_9pic_api_key>" \
  -F "request_id=ddc661a7-8861-4793-9437-af42a82d12f8" \
  "https://api.9pic.ai/api/v1/ext/903/event/456/video-clipping/search-by-selfie"

Example Response

{
  "responseType": "success",
  "message": "Video clipping search started. Poll GET /api/v1/ext/{org_id}/event/{event_id}/video-clipping/{request_id} for results.",
  "data": {
    "status": "processing",
    "request_id": "ddc661a7-8861-4793-9437-af42a82d12f8"
  }
}
On a successful POST the status is usually 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

ParameterTypeRequiredDescription
org_idnumberYesYour organisation ID.
event_idnumberYesThe event the search belongs to.
request_idstringYesUUID returned by a previous POST /video-clipping/search-by-selfie call.

Status Lifecycle

StatusTerminal?Meaning
processingNoThe job is still running. Continue polling (recommended interval: every 3–5 seconds, with backoff).
completedYesOne or more clips were generated. videos[] is populated.
no_matchesYesThe selfie did not match any frames in the event videos. No clips generated.
no_segmentsYesThe selfie matched frames, but the matches were too sparse or scattered to form a usable clip window.
clip_failedYesThe selfie matched and a window was selected, but the clipping service failed to produce one or more clips. clip_errors[] carries per-source detail.
failedYesThe job failed before any clips could be generated (for example: enqueue failed, internal error). error carries a human-readable message.
Use exponential backoff (e.g. 3s, 5s, 10s, 15s, then every 15s) and stop polling once the status is anything other than processing. End-to-end processing typically completes within tens of seconds, but heavy events can take longer.

Example Request

curl -i \
  -H "X-API-Key: <your_9pic_api_key>" \
  "https://api.9pic.ai/api/v1/ext/903/event/456/video-clipping/ddc661a7-8861-4793-9437-af42a82d12f8"

Example Response

{
  "responseType": "success",
  "message": "Video clipping completed",
  "data": {
    "request_id": "ddc661a7-8861-4793-9437-af42a82d12f8",
    "status": "completed",
    "videos": [
      {
        "uuid": "5e9a7cc1-0bcb-4a4d-b6e3-6c0b25c57a08",
        "video_url": "https://photos.9pic.ai/vids/456/motion/selfie/ddc661a7-8861-4793-9437-af42a82d12f8/5e9a7cc1-0bcb-4a4d-b6e3-6c0b25c57a08.mp4",
        "source_video_key": "12",
        "start_time": "00:00:42",
        "end_time": "00:00:52"
      }
    ],
    "error": null,
    "clip_errors": []
  }
}
1

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.
2

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.
3

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.
4

Render or fall back

On completed, render the clips from videos[].video_url. On no_matches / no_segments, show a friendly empty state (the selfie did not produce a clip). On clip_failed / failed, surface a retry CTA — a fresh POST (with a new selfie) is the most reliable next step.
A new POST (with a fresh request_id or no request_id) re-runs the full pipeline and is treated as a new billable search. Avoid issuing fresh POSTs on every refresh — GET polling is cheap, the POST is not.

Response Models

ModelDescription
VideoClippingStartResponseReturned by POST /video-clipping/search-by-selfie.
VideoClippingResultResponseReturned by GET /video-clipping/{request_id}.
VideoClipItemEach entry inside videos[].
VideoClipErrorItemEach entry inside clip_errors[].

Error Responses

StatusMeaning
400request_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).
401API key is missing.
403API key is invalid, inactive, token/event ownership mismatch, or video clipping (event-level or selfie-driven) is disabled for this event.
404Event configuration not found, video search configuration not found, or request_id is unknown (on GET, or on the file-less reuse POST).
429Rate limit exceeded. Back off and retry with the same request_id to hit the cache.
500Internal 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.
See Errors for canonical descriptions and retry guidance.