Find brands in one video
Search one video for your brands, by content id or by link.
Zelf Detection finds brands in social videos. It reads the whole video and finds each scene where a brand is identifiable: its logo or name is visible (on a product, on packaging or in on-screen text) or the brand is named out loud. Every appearance comes back with its timestamp.
This guide covers the smallest case: one video, one detection, one result. Identify the video with content_id (a video already in Zelf) or content_url (a link to any supported video). Send one or the other, never both.
Every step shows the same call in all six Zelf clients and in cURL. Pick a language and the install line above the snippet switches with it. Each client reads your key from ZELF_API_KEY.
Key concepts
- Detection
- A search for a list of brands in one video. Detection is asynchronous: you create a detection, then poll it until it finishes.
- Placement
- One appearance of a brand in a video, with its timestamp.
- Identifiers
- Alternative names that count as the brand, such as product lines, characters and slogans.
- Negatives
- Terms that resemble the brand and should not count.
- 01Write the brand list
- 02Find the video id
- 03Create a detection
- 04Create a detection from a video link
- 05Retrieve the results
- 06Read the placements
Write the brand list
List each brand to search for: a name, its identifiers and optional negatives. You send this list with the detection in step 3. Results depend on the list, so spend a few minutes on each brand.
identifiers2 to 10 | Names that count as the brand when they are visible on screen or spoken. Include the brand name, sub-brands, product lines, hero products, characters and slogans. Example for Nintendo: Nintendo, Switch, Mario, Pokemon, Zelda. Detection can miss a product or character that is not on the list, so use the 10 slots for the ones most likely to appear in videos. |
|---|---|
negativesup to 12 | Terms that look or sound like the brand but are not it, usually common words. Example for Nike: "Jordan (the country)", because "Jordan" is one of its identifiers. Example for Apple: "apple (the fruit)". |
namethe brand | The name the brand has in the results. See the limits in step 3. |
Find the video id
Call content.list to find the video. Each result has an id (prefixed con_). Send it to detection.create in the next step. If you know the post id on the platform, pass it as external_id for an exact match. Skip this step if you send the video link instead.
external_idstring | The post id on the platform. Exact match. A content search needs at least one of search, brand_id, creator_id, creator_username or external_id, and external_id alone is enough. |
|---|---|
creator_usernamestring | Partial match, case insensitive. Add content_form=VIDEO to list only videos. |
results[].idcon_... | The video id. Send it to detection.create. |
results[].media_durationfloat | Seconds. Detection refuses anything over 5400 (90 minutes). |
pip install zelf-sdkimport os
from zelf import ZelfAPIClient
client = ZelfAPIClient(api_key=os.environ["ZELF_API_KEY"])
result = client.content.list(
external_id="Cx12ab34cd",
content_form="VIDEO",
)
print(result)Create a detection
Send a video id and your brand list to detection.create. The response contains the detection's id (prefixed aer_). Use that id to retrieve the results in the next step. Each request covers one video.
content_idcon_... | The id of a video already in Zelf. Send this or content_url, never both and never neither. |
|---|---|
Videolimit | One per request, up to 90 minutes long. YouTube, TikTok and Instagram videos (not photo posts). |
What countsscope | A brand counts when its logo or name is visible or the brand is named out loud. A product with no visible logo or name, and no spoken mention, is not detected. |
Brandslimit | Up to 200 per request. No two brand names that differ only by punctuation or digits (for example "Brand" and "Brand 2"). |
Identifierslimit | 2 to 10 per brand, each at least 2 characters. Include the brand name itself. |
Negativeslimit | Optional, up to 12 per brand. |
pip install zelf-sdkimport os
from zelf import ZelfAPIClient
client = ZelfAPIClient(api_key=os.environ["ZELF_API_KEY"])
result = client.detection.create(
content_id="con_8Qd2Vx7mLp",
context={
"hints": {
"brands": [{
"name": "Nike",
"identifiers": ["Nike", "Jordan", "Air Force 1"],
"negatives": ["Jordan (the country)"],
}, {
"name": "Starbucks",
"identifiers": ["Starbucks", "Frappuccino", "Pumpkin Spice Latte"],
"negatives": [],
}],
},
},
)
print(result)Create a detection from a video link
Send content_url instead of content_id when the video is not in Zelf yet, or when you only have its link. The rest of the request is the same.
YouTubeaccepted | youtube.com/watch?v=..., youtu.be/..., youtube.com/shorts/... |
|---|---|
TikTokaccepted | tiktok.com/@handle/video/... |
Instagramaccepted | instagram.com/reel/..., instagram.com/p/..., instagram.com/tv/... |
Short linksrefused | vm.tiktok.com, vt.tiktok.com and tiktok.com/t links are refused. Open the link and send the full one it lands on. |
pip install zelf-sdkimport os
from zelf import ZelfAPIClient
client = ZelfAPIClient(api_key=os.environ["ZELF_API_KEY"])
result = client.detection.create(
content_url="https://www.youtube.com/watch?v=dQw4w9WgXcQ",
context={
"hints": {
"brands": [{
"name": "Nike",
"identifiers": ["Nike", "Jordan", "Air Force 1"],
"negatives": ["Jordan (the country)"],
}],
},
},
)
print(result)To run a different brand list on the same video, create a new detection.
Retrieve the results
Detection is asynchronous. Poll detection.get with the detection id until status is SUCCESS or FAILED. A finished detection lists each brand with its placements.
SUCCESSfinal | The detection finished. result lists each brand with its placements. |
|---|---|
FAILEDfinal | The detection finished without placements. result.brands is an empty list. |
pip install zelf-sdkimport os
from zelf import ZelfAPIClient
client = ZelfAPIClient(api_key=os.environ["ZELF_API_KEY"])
result = client.detection.get("aer_6Pz4Jb8n")
print(result)Read the placements
A finished detection has a result with three parts. video: {video_url, platform, duration_s}. summary: {brands_requested, brands_found, placements, confirmed, review}. brands: one entry per requested brand, in the order requested, each with its placements. A brand with found: false was searched for and does not appear in the video.
timestampplacement | A range (mm:ss-mm:ss) or a single moment (mm:ss). Past an hour the minutes keep counting, so 69:23 is an hour and nine minutes in. |
|---|---|
modalityplacement | Visual (logo or product on screen), Text (brand name written on screen) or Audio (brand name spoken). |
what_detected and reasoningplacement | What appears in the video, and why it counts as the brand. |
confidenceplacement | confirmed: the brand was visually confirmed in the video. review: detected, but not visually confirmed. Audio placements are always review, because a spoken mention has nothing visual to confirm. Use audio placements as they are. Check Visual and Text placements marked review before relying on them. |
video_url_at_timestampplacement | For YouTube, a link that opens the video just before the placement. Null on TikTok and Instagram. |
pip install zelf-sdkimport os
from zelf import ZelfAPIClient
client = ZelfAPIClient(api_key=os.environ["ZELF_API_KEY"])
result = client.detection.get("aer_6Pz4Jb8n")
print(result){
"id": "aer_6Pz4Jb8n",
"status": "SUCCESS",
"created_at": "2026-10-07T09:44:10Z",
"updated_at": "2026-10-07T09:52:38Z",
"context": {
"external_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"hints": { "brands": [{ "name": "Nike", "identifiers": ["Nike", "Jordan", "Air Force 1"], "negatives": ["Jordan (the country)"] }] }
},
"result": {
"video": {
"video_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"platform": "youtube",
"duration_s": 742
},
"summary": {
"brands_requested": 2,
"brands_found": 1,
"placements": 2,
"confirmed": 1,
"review": 1
},
"brands": [
{
"brand_name": "Nike",
"found": true,
"summary": { "placements": 2, "confirmed": 1, "review": 1 },
"placements": [
{
"timestamp": "01:12-01:19",
"video_url_at_timestamp": "https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=67s",
"modality": "Visual",
"what_detected": "Nike swoosh on the right shoe during the lacing shot",
"reasoning": "The swoosh is unobstructed and fills most of the frame.",
"confidence": "confirmed"
},
{
"timestamp": "04:03",
"video_url_at_timestamp": "https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=238s",
"modality": "Audio",
"what_detected": "Says \"these are the new Nikes\"",
"reasoning": "The brand is named directly in the voiceover.",
"confidence": "review"
}
]
},
{ "brand_name": "Starbucks", "found": false, "summary": { "placements": 0, "confirmed": 0, "review": 0 }, "placements": [] }
]
}
}Summary
| Step | Request | Input | Output |
|---|---|---|---|
| 1 | GET /api/v1/content/ | external_id or creator_username | content id (con_...) |
| 2 | POST /api/v1/detection/ | content_id or content_url, and the brand list | detection id (aer_...) |
| 3 | GET /api/v1/detection/{id}/ | detection id | status, result |
| 4 | read result | detection result | placements per brand |