VSS API Client
Use this skill when writing clients or integration tests for the Video Search & Summarization (VSS) sample app. The API is asynchronous: uploading a video only creates a video record; summarization starts in a separate request; progress arrives over Socket.IO; final output is fetched from summary endpoints.
Environment setup (run first)
This skill drives the Video Search & Summarization app through its real source files, so the VSS application must be present and you must run commands from its app root. Do this before anything else, and it works whether or not the VSS source is already in your workspace.
Run the bundled bootstrap. It first tries to find an existing VSS checkout -
walking up from the current directory and inspecting the enclosing git repo - and
reuses it without ever re-cloning. Only when no checkout is found does it do a
shallow, single-branch, sparse checkout of just
sample-applications/video-search-and-summarization from main. It prints the
resolved app root on stdout:
# SKILL_DIR is THIS skill's own directory (shown to you when the skill loads);
# in-repo it is .github/skills/vss-api-client. Works the same if the skill is installed standalone.
SKILL_DIR=".github/skills/vss-api-client"
APP_ROOT="$(bash "$SKILL_DIR/scripts/vss-bootstrap.sh")"
cd "$APP_ROOT"
Every command below assumes the working directory is this APP_ROOT. To pull
from a fork/branch or reuse a specific checkout dir, override VSS_REPO_URL,
VSS_REPO_BRANCH, or VSS_CLONE_DIR before running it.
Verified base URLs and prefixes
- Default external nginx URL:
http://<HOST_IP>:12345 - Pipeline Manager through nginx:
http://<HOST_IP>:12345/manager - Pipeline Manager direct container host port:
http://<HOST_IP>:3001(PM_HOST_PORT) - Search microservice direct URL:
http://<HOST_IP>:7890(VS_HOST_PORT) - Socket.IO progress path through nginx:
http://<HOST_IP>:12345with Socket.IOpath: /ws/
When using nginx, REST paths are prefixed with /manager (for example /manager/videos). Direct Pipeline Manager calls omit that prefix.
Summary lifecycle: required order
- Check service health/features:
GET /manager/health, optionallyGET /manager/app/features. - Upload the video:
POST /manager/videosasmultipart/form-datafieldvideo; optionaltagsis comma-separated. Response containsvideoId. - Start processing:
POST /manager/summarywithvideoId,title,sampling, andevam. Response containssummaryPipelineId(the summarystateId). - Subscribe to progress with Socket.IO: connect using
path: /ws/, emitjoinwith thesummaryPipelineId, then listen forsummary:sync/{stateId}/status,/chunks,/frameSummary,/inferenceConfig,/summary, and/summaryStream. - Poll/fetch final summary:
GET /manager/summary/{stateId}for UI-shaped data, orGET /manager/summary/{stateId}/rawfor raw state. Completion usually meansvideoSummaryStatus: "complete"and/or non-emptysummary.
Ordering matters because /videos does not start summarization. The summaryPipelineId returned by /summary is also the Socket.IO room name and the ID used by summary retrieval endpoints.
Curl quick start
BASE=http://localhost:12345/manager
curl -f "$BASE/health"
VIDEO_ID=$(curl -sS -X POST "$BASE/videos" \
-F "video=@./sample.mp4" \
-F "tags=demo,api" | python3 -c 'import json,sys; print(json.load(sys.stdin)["videoId"])')
STATE_ID=$(curl -sS -X POST "$BASE/summary" \
-H 'Content-Type: application/json' \
-d "{\"videoId\":\"$VIDEO_ID\",\"title\":\"API smoke\",\"sampling\":{\"chunkDuration\":30,\"samplingFrame\":4,\"frameOverlap\":1,\"multiFrame\":5},\"evam\":{\"evamPipeline\":\"video_ingestion\"}}" \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["summaryPipelineId"])')
curl -sS "$BASE/summary/$STATE_ID" | python3 -m json.tool
Socket.IO progress snippet (JavaScript)
import { io } from "socket.io-client";
const appUrl = "http://localhost:12345";
const stateId = process.env.STATE_ID;
const socket = io(appUrl, { path: "/ws/" });
socket.on("connect", () => socket.emit("join", stateId));
for (const suffix of ["status", "chunks", "frameSummary", "inferenceConfig", "summary", "summaryStream"]) {
socket.on(`summary:sync/${stateId}/${suffix}`, (payload) => {
console.log(suffix, payload);
});
}
socket.on("search:update", (query) => console.log("search update", query.queryId));
Python REST snippet
import json, urllib.request
base = "http://localhost:12345/manager"
body = {
"videoId": "<videoId-from-POST-/videos>",
"title": "Programmatic summary",
"sampling": {"chunkDuration": 30, "samplingFrame": 4, "frameOverlap": 1, "multiFrame": 5},
"evam": {"evamPipeline": "video_ingestion"},
}
req = urllib.request.Request(
f"{base}/summary",
data=json.dumps(body).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
print(json.load(urllib.request.urlopen(req))) # {"summaryPipelineId": "..."}
Search queries with time filters
Pipeline Manager one-off search:
curl -sS -X POST http://localhost:12345/manager/search/query \
-H 'Content-Type: application/json' \
-d '{"query":"person walking","tags":"demo,api","timeFilter":{"value":24,"unit":"hours"}}' \
| python3 -m json.tool
Managed query lifecycle:
POST /manager/searchcreates and runs a saved query.GET /manager/search/{queryId}fetches it.POST /manager/search/{queryId}/refetchreruns it, optionally with{ "timeFilter": ... }.PATCH /manager/search/{queryId}/watchwith{ "watch": true }enables watch updates.GET /manager/search/watchedlists watched queries.
Direct search microservice (bypasses Pipeline Manager) accepts a list at POST http://localhost:7890/query:
[{"query_id":"q1","query":"person walking","tags":["demo"],"time_filter":{"start":"2026-01-01T00:00:00Z","end":"2026-12-31T23:59:59Z"}}]
More detail
See references/api-lifecycle.md for endpoint schemas, response shapes, and event names. Use scripts/api_smoke.py for a dependency-light smoke test.