Brands
Detect brand appearances in video with confidence scores, on-screen intervals, and visual evidence.
Brand detection finds brands three ways: visible on screen, mentioned in speech, and named in the video's own metadata (title, description, tags). Each brand comes back with a confidence score and a list of appearances.
On the Free and Starter plans, a brands job must be targeted — pass brands (names to look for), surfaces
(where to look), or both. An untargeted brands job is rejected with PLAN_REQUIRED (403). Pro and Scale can run
open-ended detection.
curl -X POST https://api.netraflow.com/v1/jobs \
-H "Content-Type: application/json" \
-H "X-Api-Key: sk_live_your_key_here" \
-d '{
"url": "https://www.youtube.com/watch?v=example",
"capabilities": ["brands"],
"brands": ["Nike", "Adidas"]
}'{
"data": {
"job_id": "job_abc123",
"status": "completed",
"results": {
"brands": [
{
"name": "nike",
"raw_name": "Nike",
"confidence": 0.95,
"total_appearances": 3,
"total_visible_duration": 8.4,
"first_seen": 12.5,
"last_seen": 145.0,
"appearances": [
{
"timestamp": 12.5,
"timestamp_start": 12.5,
"timestamp_end": 16.0,
"visible_duration": 3.5,
"confidence": 0.93,
"verified": true,
"source": "visual",
"bounding_box": { "x": 0.3, "y": 0.2, "width": 0.15, "height": 0.1 }
},
{
"timestamp": 89.0,
"timestamp_start": 89.0,
"timestamp_end": 89.6,
"confidence": 0.88,
"source": "audio",
"context": "Mentioned in speech",
"speech_context": "We partnered with Nike for this campaign."
},
{
"source": "metadata",
"confidence": 0.5,
"context": "Mentioned in video metadata"
}
]
}
],
"confidence_summary": { "high": 1, "medium": 0, "low": 0, "total": 1 }
}
}
}Targeting
Two optional request fields narrow the search. Both are recommended even where they aren't required — a targeted job is faster and more accurate than open-ended detection.
| Prop | Type | Description |
|---|---|---|
| brands | string[] | Brand names to look for. Max 50 names, 100 characters each. |
| surfaces | string[] | Where the brand should appear, as surface slugs (e.g. "jersey"). Max 10. An unknown or unavailable slug is rejected with INVALID_SURFACE (422). |
When you pass surfaces, each kept appearance carries the surface it was found on, and each brand gets a surfaces rollup of the distinct surfaces it appeared on. This is what answers a find {brand} on {surface} question.
Brand fields
| Prop | Type | Description |
|---|---|---|
| name | string | Canonical brand identity, always lowercase ("nike"). Stable across runs and across the sources that found the brand, so it is what to key on — not what to display. |
| raw_name | string | The human-readable label to render ("Nike", "AT&T", "npm"). Always present on new results; older results may omit it, in which case fall back to `name`. |
| confidence | number (0-1) | Overall confidence for this brand. |
| low_confidence | boolean (optional) | True when the detection sits below the high-confidence band — treat it as a candidate. |
| total_appearances | integer | Number of distinct appearances: on-screen intervals plus speech and metadata mentions. Not a count of frames. |
| total_visible_duration | number (optional) | Combined seconds the brand was on screen. Derived from visual appearances only. |
| first_seen | number (optional) | Seconds. First timed appearance. Absent when the brand was only found in metadata. |
| last_seen | number (optional) | Seconds. Last timed appearance. |
| surfaces | string[] (optional) | Distinct surfaces this brand appeared on. Present only on surface-targeted jobs. |
| appearances | Appearance[] | The individual appearances. |
Appearance fields
Appearances are not uniform — branch on source first. A metadata appearance is timeless: it carries no
timestamp, no window, no duration, and no box, because a mention in a video's title isn't tied to a moment.
| Prop | Type | Description |
|---|---|---|
| source | "visual" | "audio" | "metadata" | How the brand was found: on screen, in speech, or in the video's title/description/tags. |
| timestamp | number (optional) | Seconds. Start of the appearance. Absent on metadata appearances. |
| timestamp_start | number (optional) | Seconds. Start of the interval. |
| timestamp_end | number (optional) | Seconds. End of the interval. |
| visible_duration | number (optional) | Seconds the brand was on screen for this interval. Frame-coverage based: a detection on a static shot covers the stable stretch up to the next analyzed frame, so a single detection can yield several seconds. |
| confidence | number (0-1, optional) | Confidence for this individual appearance, distinct from the brand-level score. |
| verified | boolean (optional) | Set on visual appearances that completed brand resolution. |
| bounding_box | { x, y, width, height } (optional) | Normalized 0-1 coordinates. One representative box for the whole interval. |
| frame_detections | FrameDetection[] (optional) | Per-frame boxes across the interval, so a box can track the logo as it moves. Visual only. |
| surface | string (optional) | The surface the brand was found on. Present only on surface-targeted jobs. |
| context | string (optional) | Short fixed label on audio and metadata appearances ("Mentioned in speech"). Not emitted on visual appearances. |
| speech_context | string (optional) | Transcript excerpt around an audio mention. Requires return_proof. |
Frame detection fields
| Prop | Type | Description |
|---|---|---|
| timestamp | number | Seconds. The analyzed frame this box came from. |
| bounding_box | { x, y, width, height } | Normalized 0-1 coordinates of the brand in that frame. |
| confidence | number (0-1, optional) | Score for this box. |
| confidence_kind | "detector" | "match" (optional) | How to read confidence. "detector" (or absent) is a recognition score. "match" is a visual-similarity score rather than a recognition score — a low value there does not mean the detection is wrong. |
| mask | { size, counts } (optional) | Run-length silhouette of the surface region the brand sits on, for overlay rendering. Surface-targeted detections only. |
Visual evidence
return_proof is on by default. On brands jobs it adds a storyboard to results: the analyzed frames composited into tiled sheets, which — combined with frame_detections — lets you show the exact frame a brand was detected in, with the box drawn on it. Sheets are fetched through GET /v1/frames/{key} using the same X-Api-Key header.
return_proof also controls whether audio appearances carry speech_context transcript excerpts. Set return_proof: false to skip both and reduce response size.
| Prop | Type | Description |
|---|---|---|
| interval_ms | integer | Nominal spacing between tiles, in milliseconds. |
| timestamps_ms | integer[] (optional) | True timestamp of each tile, ascending, one per tile. Frames are analyzed non-uniformly, so prefer this over interval_ms when present. |
| tile | { w, h } | Tile dimensions in pixels. |
| grid | { cols, rows } | Tile layout per sheet. Tile k is on sheet floor(k / (cols * rows)), row-major within it. |
| count | integer | Total tiles across all sheets. |
| duration_ms | number | Video duration in milliseconds. |
| sheets | string[] | Sheet keys to fetch via GET /v1/frames/{key}. |
The storyboard manifest is the one place the API reports milliseconds. Every other timestamp in a result is in seconds.
Result-level signals
Brands jobs also populate two top-level fields alongside results.brands:
| Prop | Type | Description |
|---|---|---|
| confidence_summary | { high, medium, low, total } | Detection counts bucketed by confidence band — high >= 0.8, low < 0.5, medium in between. |
| meta.brand_detection_degraded | boolean (optional) | True when the visual detection stage ran degraded and results may be incomplete. Check this before treating an empty result as "no brands present". |
Duration limits
Brands jobs analyze frames, so they use your plan's standard video ceiling: 1 minute on Free, 3 on Starter, 10 on Pro, 20 on Scale. A longer video fails with VIDEO_TOO_LONG after ingestion.