---
title: "Upload Video"
method: POST
path: "/v2/videos"
tags: ["Video Emotion API"]
---

# Upload Video

`POST /v2/videos`

## Request

- Base URL: `https://api.imentiv.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.imentiv.ai/v2/videos`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer
  - OAuth 2
    - Authorization code: `https://api.imentiv.ai/oauth/authorize`
    - Token URL: `https://api.imentiv.ai/oauth/token` scopes: `media.write`

Analyze emotions and facial expressions from video content.

Submit a video from multiple sources and receive detailed emotion analysis
of all detected faces throughout the video timeline.

**Input Sources:**
- **YouTube**: Provide any YouTube video URL
- **Cloud Storage**: Dropbox, Google Drive, or other cloud service links
- **Social Media**: Instagram, Facebook, Twitter (X) videos
- **Direct URL**: Any publicly accessible video link
- **File Upload**: Upload video files directly from your device

**Analyze Specific Segments:**
Focus on specific portions using `start_millis` and `end_millis` parameters.
This helps target key moments and optimize credit usage.

**How It Works:**
1. Submit your video and receive an immediate response with a video ID
2. Video is queued for processing (typically completes within minutes)
3. Optionally receive a webhook notification via `callback_url` when complete
4. Retrieve results using the video ID

**Supported Formats:**
MP4, AVI, MOV, WebM, MPEG

**Credit Usage:**
Billed on success; the amount is recorded after the run and is not quoted
on this request. Each video is billed at least 10 credits. A 402 is
returned before processing starts when admission is refused.

**Moments:**
Set `moments` to run Moments on this video as part of the same request. It
is billed with the analysis, and a single 402 covers both: when your balance
cannot cover them together, nothing is charged and the video is not queued.
If the video's first analysis fails, Moments is cancelled and you are not
billed for it; once the video has been reprocessed, a later failure does not
cancel it. Leave it off to request Moments later.

## Request body `multipart/form-data`

- BodyUploadVideoV2VideosPost
  - `title` string, required: A descriptive title for your video (e.g., 'Team Meeting' or 'Customer Interview').
  - `description` string: Optional notes about the video content or context.
  - `video_url` string: URL to your video. Supported sources: YouTube, Dropbox, Instagram, Facebook, Twitter (X), or any direct video link. Leave empty if uploading a file instead.
  - `start_millis` number: Start point in milliseconds to begin analysis. Use 0 to start from the beginning. If provided (>0), end_millis is required with minimum 1-second (1000ms) gap.
  - `end_millis` number: End point in milliseconds to stop analysis. Use 0 only when analyzing the full video (start_millis must also be 0). If start_millis is provided, end_millis is required and must be at least 1000ms greater than start_millis.
  - `callback_url` string: Your webhook URL to receive a notification when analysis completes. Must be an https URL on a publicly reachable host.
  - `video_file` string, binary: Upload a video file directly (mp4, avi, mov, webm, mpeg). File upload takes priority over video_url.
  - `project_id` string: Optional ID of a project you may modify (normally one you own). For an audience panel or user testing project, this video becomes its primary video. For a collection, this video is added to the collection; a delegated access token also needs the projects.write scope for that, and is refused with 403 insufficient_scope without it. An unknown project, or one you may not modify, returns 404. Any other project type returns 422: upload without project_id, then link the video to the project.
  - `processing_fps` 15 | 10 | 5 | 1, nullable: Reduced processing frame rate (15, 10, 5, or 1 fps) that lowers the credit cost. Omit (or send empty/0) for full/native fps (no discount).
  - `video_type` 'generic' | 'online_meeting' | 'auto' | 'in_vehicle': Processing mode for face analysis. 'generic' (the default) skips on-screen name OCR. 'online_meeting' OCRs the participant-name labels a meeting client draws on each tile. 'auto' asks the library to decide from a bounded probe of the opening and reports the resolved mode back; it is opt-in and never the default, because its precision on non-meeting video is not yet characterised. 'in_vehicle' runs EXPERIMENTAL driver-state analysis (drowsiness and distraction) and requires the driver_state feature; 'auto' never resolves to it. Omit (or send empty) for generic. Unrecognised values — including the retired spelling 'meeting' — are rejected.
  - `meeting_platform` 'zoom' | 'google_meet' | 'teams' | 'webex', nullable: Optional meeting-client hint (zoom, google_meet, teams, webex) that helps the OCR pipeline. Omit (or send empty) if unknown. Not required: the library is client-agnostic without it. Stored independently of video_type; the worker ignores the hint unless video_type is online_meeting.
  - `use_case` 'interview' | 'product_testing' | 'sales_call', nullable: Optional: what the recording is. 'interview': a question-and-answer session, where moments rank slow answers and drops in engagement first. 'product_testing': viewers reacting to things shown in the video, ranked by their reaction to each stimulus; it needs stimuli. 'sales_call': a sales conversation, where moments focus on the customer's reactions and shifts in momentum. Omit when unsure. A project whose type selects its own analysis, or video_type='in_vehicle', takes precedence.
  - `sales_call` boolean: Place this video in your sales calls, a sales_deal project, so it is left out of the Default Library. The project is created the first time you ask. A delegated token needs only media.write. Used when project_id is omitted. A project_id this upload can link is used instead; any other project_id is still refused.
  - `moments` boolean: Also run Moments on this video once its analysis finishes. If the video's first analysis fails, Moments is cancelled and you are not billed for it; once the video has been reprocessed, a later failure does not cancel it. Leave it off to request Moments later.
  - `driver_position` 'left' | 'right', nullable: Optional seat hint for video_type='in_vehicle': which half of the VIDEO FRAME the driver appears in ('left' or 'right') — not the vehicle's drive side. Answer it by looking at the footage; it stays correct whatever the camera orientation or mirroring, whereas a drive-side answer does not and a wrong one silently analyses the passenger. Omit if unsure: the pipeline then picks the driver from track persistence and face size alone. Ignored unless video_type is in_vehicle.
  - `driver_forward_gaze` string, nullable: Optional, video_type='in_vehicle' only: the direction the driver looks when looking straight ahead at the road, so driver attention can be measured from the first frame instead of being estimated from the footage. A JSON object {"yaw_deg": <number>, "pitch_deg": <number>} in degrees, camera-relative, in the same axes and sign as the gaze_yaw / gaze_pitch values returned for faces. yaw_deg must be within [-90, 90] and pitch_deg within [-60, 60]; both keys are required and no other keys are accepted. Example: {"yaw_deg": -20.0, "pitch_deg": 5.0}. Omit (or send empty) if unknown. Rejected with any other video_type.
  - `camera_mount` 'steering_column' | 'dashboard_center' | 'windshield' | 'unknown', nullable: Optional, video_type='in_vehicle' only: where the camera is mounted ('steering_column', 'dashboard_center', 'windshield' or 'unknown'). A hint for the driver's straight-ahead direction when driver_forward_gaze is not supplied; an explicit driver_forward_gaze takes precedence. 'unknown' is recorded as sent (distinct from omitting the field) and provides no hint. Omit (or send empty) if not known. Rejected with any other video_type.
  - `driver_forward_gaze_prior` string, nullable: Optional, video_type='in_vehicle' only: a prior for the direction the driver looks when looking straight ahead at the road — typically the driver_state forward_gaze estimate returned for an earlier video from the same camera. A JSON object {"yaw_deg": <number>, "pitch_deg": <number>, "confidence": <number>} in degrees, camera-relative, in the same axes and sign as the gaze_yaw / gaze_pitch values returned for faces. yaw_deg and pitch_deg must be within [-90, 90]; confidence must be greater than 0 and at most 1. The forward_gaze object returned for a face may be sent unchanged: its method is ignored and its convention must be 'public'; no other keys are accepted. An explicit driver_forward_gaze, or a camera_mount of 'steering_column', takes precedence over a prior, and a low-confidence prior may not be applied. Example: {"yaw_deg": -20.0, "pitch_deg": 5.0, "confidence": 0.9}. Omit (or send empty) if unknown. Rejected with any other video_type.
  - `diarization` boolean: Speaker diarization on the video's audio track. Enabled by default. Disabling this stage reduces the per-second credit cost.
  - `text_emotion` boolean: Text emotion analysis on the transcript. Enabled by default. Requires a transcript: ignored (forced off) when transcription is disabled and you supply no `transcript`. Supplying a `transcript` is the exception — the words already exist, so this is honoured as you sent it. Disabling this stage reduces the per-second credit cost.
  - `transcription` boolean: Transcript / ASR generation for the video's audio track. Enabled by default. When disabled, no transcript is produced and text_emotion is forced off. Supplying a `transcript` overrides this field entirely: our ASR is skipped either way, but your transcript is used and text_emotion still applies. Disabling this stage reduces the per-second credit cost.
  - `language` string, nullable: Language code for the audio track (e.g. 'en'). Auto-detected if not provided. With `expected_languages` it must be one of them.
  - `multilingual` boolean: Opt in to mixed-language transcription: the speech may switch language, and each stretch is transcribed in the language it is spoken in. Once the audio completes, the lines that are not in its main language are also translated into the main language and billed as a translation, on top of transcription (`auto_translation` reports it). This is input only; the languages actually detected are reported separately.
  - `expected_languages` string, nullable: Comma-separated codes of the languages the audio may contain, the main one included (e.g. `en,ja`), 1 to 5 after duplicates are removed. Case and whitespace are ignored, a region is dropped (`pt-BR` is `pt`) and an alias maps to the Whisper spelling (`iw` is `he`); Cantonese is `yue`. Language detection is restricted to the set, with or without `multilingual`. If `language` is also sent it must be one of them (422 `invalid_language` otherwise). If `language` is omitted, detection still runs, restricted to the set. An unknown code, or more than 5, is refused with 422 `invalid_language` before anything is charged.
  - `transcript` string: Optional: a transcript you already have for this video. When supplied we skip our own speech-to-text, use your transcript's speaker turns as the diarization, and name the speakers from it — so results come back with real participant names. Supplying one overrides `transcription`: our ASR is skipped either way, and you are charged less because that stage is not billed.
  - `transcript_format` string: Format of `transcript`. 'auto' (the default) detects it. Supported: 'auto', 'text', 'tldv_json', 'teams_vtt', 'zoom_vtt', 'vtt', 'srt'. Works with tl;dv, Zoom, Microsoft Teams, Otter, Fathom, Tactiq and most .vtt/.srt exports. Google Meet's own transcript is not supported yet — it timestamps only every 5 minutes, which cannot be aligned to the audio.
  - `transcript_offset_millis` integer: Milliseconds added to every timestamp in `transcript` to place it on the media timeline. Positive means the transcript starts later than the recording (transcription began after record). Negative means it starts earlier (transcription running before record). 0 (the default) is today's behaviour. A negative offset that places any turn before 0 ms is rejected. Ignored when no `transcript` is supplied.
  - `stimuli` string, nullable: Optional: when things the viewer reacts to appear in this video, for product testing, ad testing and reaction videos. A JSON array of objects {"label": str, "kind": "ad_start"|"scene"|"product_reveal"|"price"|"logo"|"question"|"cta"|"custom", "start_ms": int, "end_ms": int (optional), "id": str (optional), "meta": object (optional), "category": str (optional), "variant": str (optional), "response_window_ms": [int, int] (optional), "attribute_during_span": bool (optional)}, in milliseconds of video time. category is a grouping label of your own, 1-32 characters of A-Z, a-z, 0-9, '_', '.' or '-'. variant is a label for comparing versions of the same stimulus, 1-64 characters. For both, surrounding spaces are removed and case is kept, so " Cedar " is stored as "Cedar" and is a different value from "cedar". response_window_ms is [start, end] in milliseconds after the stimulus starts, 0 <= start < end <= 600000: a reaction that begins inside it is attributed to the stimulus; when omitted, the default window applies. attribute_during_span true also attributes a reaction that begins while the stimulus is on screen, and needs an end_ms after start_ms; false is the same as omitting it, is not stored and is returned as null. Every stimulus must start within the video and end no later than its end. At most 500 stimuli. Not accepted together with start_millis > 0. Example: [{"label": "Price screen", "kind": "price", "start_ms": 72000, "end_ms": 80000}].
  - `non_human_spans` string, nullable: Optional: known non-human audio, such as a voice assistant or music. A JSON array of {"start_ms": int, "end_ms": int, "role": "system"|"media", "label": str (optional)}, in milliseconds of the analysed video. end_ms is after start_ms and no later than the video's end. label, when present, is 1-80 characters after surrounding spaces are removed. At most 1000 spans. Not accepted together with start_millis > 0. Omitted, blank or [] means none. Example: [{"start_ms": 0, "end_ms": 4000, "role": "system", "label": "Voice assistant"}].

## Response `200`

Successful Response

- VideoEmotionResponseV2: V2 response model for video emotion detection with URL source tracking. Supports: - YouTube URLs - Cloud storage URLs (Dropbox, direct URLs, etc.) - Social Media URLs (Instagram Reels, Facebook videos/reels, Twitter/X status videos) - Time parameters for all URL types - URL source tracking Attributes: id (str): Unique ID of the video. title (str): Title of the video. status (str): Processing status (queue, processing, done, etc.). duration (int): Duration in seconds (trimmed duration if time params provided). media_source (str): Source type ("youtube", "dropbox", "instagram", "facebook", "twitter", "public_url", "file_upload"). media_source_url (str): Full URL as provided by user. user_id (str): ID of the user who uploaded the video. created_at (Optional[str]): Timestamp when video was created.
  - `id` string, required
  - `title` string, required
  - `status` string, required
  - `duration` integer, required
  - `media_source` string, required
  - `media_source_url` string, required
  - `user_id` string, required
  - `created_at` string, nullable
  - `processing_fps` integer, nullable
  - `processing_fps_auto` boolean, nullable
  - `processing_fps_estimate_s` integer, nullable
  - `video_type` 'generic' | 'online_meeting' | 'auto' | 'in_vehicle', nullable
  - `meeting_platform` 'zoom' | 'google_meet' | 'teams' | 'webex', nullable
  - `use_case` 'interview' | 'product_testing' | 'sales_call', nullable
  - `driver_position` 'left' | 'right', nullable
  - `driver_forward_gaze` DriverForwardGaze: The driver's straight-ahead gaze direction in camera-relative degrees, in the same axes and sign as the gaze_yaw / gaze_pitch values returned for faces.
    - `yaw_deg` number, required: Horizontal angle in degrees, camera-relative, in the same axes and sign as the gaze_yaw values returned for faces. Range [-90, 90].
    - `pitch_deg` number, required: Vertical angle in degrees, camera-relative, in the same axes and sign as the gaze_pitch values returned for faces. Range [-60, 60].
  - `camera_mount` 'steering_column' | 'dashboard_center' | 'windshield' | 'unknown', nullable
  - `driver_forward_gaze_prior` DriverForwardGazePrior: A prior for the driver's straight-ahead gaze direction in camera-relative degrees, in the same axes and sign as the gaze_yaw / gaze_pitch values returned for faces, with how confident you are in it.
    - `yaw_deg` number, required: Horizontal angle in degrees, camera-relative, in the same axes and sign as the gaze_yaw values returned for faces. Range [-90, 90].
    - `pitch_deg` number, required: Vertical angle in degrees, camera-relative, in the same axes and sign as the gaze_pitch values returned for faces. Range [-90, 90].
    - `confidence` number, required: How confident you are in this prior, greater than 0 and at most 1. A low-confidence prior may not be applied.
  - `diarization` boolean, nullable
  - `text_emotion` boolean, nullable
  - `transcription` boolean, nullable
  - `speaker_count` integer, nullable

## Other responses

- `422`: Request validation failed (`{"detail": str}` only), or `processing_fps_over_budget`: the video is too long to process at its frame rate. Nothing was charged. The body then also offers the frame rates that fit (`recommended_processing_fps`, `fits_processing_fps`) and each tier's longest video (`duration_s`, `max_duration_by_fps`), so the client can suggest a lower frame rate or a trim.

## Changes

- **2026-11-06** `bc15cec7e794`: 1 breaking, 9 info
  - the `detail` response's property type changed from `array` to `string` for status `422`
  - added the optional property `code` to the response with the `422` status
  - added the optional property `duration_s` to the response with the `422` status
  - added the optional property `fits_processing_fps` to the response with the `422` status
  - …6 more
- **2026-10-31** `bd825cd86e8c`: 1 warning, 5 info
  - added the new `sales_call` enum value to the `use_case/anyOf[subschema #1]/` response property for the response status `200`
  - added the new optional request property `expected_languages`
  - added the new optional request property `moments`
  - added the new optional request property `multilingual`
  - …2 more
- **2026-10-07** `45bdd041d683`: 2 breaking, 2 warning, 3 info
  - request property `use_case/anyOf[subschema #1]/` was restricted to a list of enum values
  - the `` response property const value `interview` was removed for the status `200`
  - added the new `interview` enum value to the `use_case/anyOf[subschema #1]/` response property for the response status `200`
  - added the new `product_testing` enum value to the `use_case/anyOf[subschema #1]/` response property for the response status `200`
  - …3 more
- …earlier changes not shown

[Operation history](https://skmtc.dev/imentiv/apis/welcome-to-imentiv-ai/changes/v2/videos/post.md)

---

[API](https://skmtc.dev/imentiv/apis/welcome-to-imentiv-ai.md) - [All operations](https://skmtc.dev/imentiv/apis/welcome-to-imentiv-ai/llms.txt) - [OpenAPI document](https://skmtc.dev/imentiv/apis/welcome-to-imentiv-ai/revisions/bc15cec7e794?raw)
