Quickstart
Upload one small video, wait for indexing, and make your first evidence-backed search.
This guide takes you through the shortest complete path through the DeepFrame API: upload one MP4 file, wait until processing completes, then find a moment in it. Run the commands in Bash or Zsh. It uses a direct upload, so choose a file no larger than 200 MiB.
Before You Start
You need curl, jq, an alpha-enabled workspace, and a key with UPLOAD_VIDEOS, READ_VIDEOS, and SEARCH_CONTENT. Run these commands only in a trusted shell. Never put an API key in browser code or source control.
Set Your Inputs
Replace the key, local file path, and search question. Ask about something you know appears in the video so the result is easy to verify.
export BASE_URL="https://api.deepframe.cloud"
export DEEPFRAME_API_KEY="df_live_..."
export VIDEO_FILE="/absolute/path/to/your-video.mp4"
export SEARCH_QUERY="Describe the person entering through the door"
export QUICKSTART_RUN_ID=$(date -u +%Y%m%dT%H%M%SZ)
command -v curl >/dev/null || {
echo "Install curl before continuing."
exit 1
}
command -v jq >/dev/null || {
echo "Install jq before continuing."
exit 1
}
test -r "$VIDEO_FILE" || {
echo "VIDEO_FILE must point to a readable MP4 file."
exit 1
}
FILE_NAME=$(basename "$VIDEO_FILE")
FILE_SIZE=$(wc -c < "$VIDEO_FILE" | tr -d ' ')
test "$FILE_SIZE" -le 209715200 || {
echo "This Quickstart supports files up to 200 MiB. Use the multipart upload guide for larger files."
exit 1
}Success means the checks print no error. The full Uploads guide covers files up to 10 GiB and resumable multipart uploads.
Create An Upload Session
The API chooses the upload strategy and returns a short-lived storage target. The commands below save every value used later.
UPLOAD_RESPONSE=$(curl --silent --show-error --fail-with-body \
-X POST "$BASE_URL/api/v1alpha1/videos" \
-H "Authorization: Bearer $DEEPFRAME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: quickstart-upload-$QUICKSTART_RUN_ID" \
-d "$(jq -n \
--arg filename "$FILE_NAME" \
--argjson file_size "$FILE_SIZE" \
'{
filename: $filename,
file_size: $file_size
}')")
VIDEO_ID=$(jq -er '.video_id' <<<"$UPLOAD_RESPONSE")
UPLOAD_STRATEGY=$(jq -er '.strategy' <<<"$UPLOAD_RESPONSE")
TARGET_URL=$(jq -er '.target.url' <<<"$UPLOAD_RESPONSE")
TARGET_CONTENT_TYPE=$(jq -er '.target.headers["Content-Type"]' <<<"$UPLOAD_RESPONSE")
printf 'video_id=%s\nstrategy=%s\n' "$VIDEO_ID" "$UPLOAD_STRATEGY"Expected output:
video_id=vid_...
strategy=directThe request contains only the filename and exact file size. The server owns content type and duration, and it selects direct or multipart upload automatically. Use Multipart Upload for any resumable session.
Upload The Video Bytes
Send the file directly to the returned storage URL with the returned content type.
curl --silent --show-error --fail-with-body \
-X PUT "$TARGET_URL" \
-H "Content-Type: $TARGET_CONTENT_TYPE" \
--data-binary "@$VIDEO_FILE"A successful direct upload returns an HTTP success response. If the target expired, request a fresh one with POST /api/v1alpha1/videos/{video_id}/upload/targets; do not create a second video.
Complete The Upload
Completion verifies the stored byte count and starts video processing.
curl --silent --show-error --fail-with-body \
-X POST "$BASE_URL/api/v1alpha1/videos/$VIDEO_ID/upload/complete" \
-H "Authorization: Bearer $DEEPFRAME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: quickstart-complete-$QUICKSTART_RUN_ID" \
-d '{}' |
jq '{id, status, progress}'The API returns 202 Accepted. This means processing started; it does not mean processing has completed yet.
Wait Until Processing Completes
Processing time depends on the video. This loop checks every 10 seconds and stops clearly on success or failure.
while true; do
VIDEO_RESPONSE=$(curl --silent --show-error --fail-with-body \
"$BASE_URL/api/v1alpha1/videos/$VIDEO_ID" \
-H "Authorization: Bearer $DEEPFRAME_API_KEY")
VIDEO_STATUS=$(jq -er '.status' <<<"$VIDEO_RESPONSE")
printf 'status=%s\n' "$VIDEO_STATUS"
if [ "$VIDEO_STATUS" = "completed" ]; then
break
fi
if [ "$VIDEO_STATUS" = "failed" ]; then
echo "Video processing failed."
jq '{status, error, progress}' <<<"$VIDEO_RESPONSE"
exit 1
fi
sleep 10
doneSuccess is exactly status=completed. Search is available only once processing completes. A failed video is never searchable, so inspect the returned error before retrying.
Make Your First Search
Search only the video you just uploaded. A low threshold makes this first exploration less restrictive; tune it later for your use case.
SEARCH_RESPONSE=$(curl --silent --show-error --fail-with-body \
-X POST "$BASE_URL/api/v1alpha1/search" \
-H "Authorization: Bearer $DEEPFRAME_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg query "$SEARCH_QUERY" \
--arg video_id "$VIDEO_ID" \
'{
query: $query,
video_ids: [$video_id],
limit: 5,
threshold: 0
}')")
jq '{
match_count: (.data | length),
matches: [.data[] | {
video_id,
time_position,
score,
scene_description,
image,
video_url
}]
}' <<<"$SEARCH_RESPONSE"Success means the request returns a data array. An empty array is a valid result: it means no indexed evidence met the request. For each match, check time_position and scene_description, then open image or video_url when present before relying on the match.
Finish Safely
If you stop before completing the upload, you can abort that incomplete upload while the API key is still available:
curl --silent --show-error --fail-with-body \
-X DELETE "$BASE_URL/api/v1alpha1/videos/$VIDEO_ID/upload" \
-H "Authorization: Bearer $DEEPFRAME_API_KEY"Do not run that command after successful completion. The DeepFrame API does not currently expose deletion of a completed video. Plan test data accordingly.
After you finish, remove the secret and temporary values from the current shell:
unset DEEPFRAME_API_KEY UPLOAD_RESPONSE VIDEO_RESPONSE SEARCH_RESPONSE
unset TARGET_URL TARGET_CONTENT_TYPE QUICKSTART_RUN_IDNext, choose the outcome you need:
- Search for ranked moments across indexed video.
- Runs And Status for typed analysis or repeatable rulebook review.