Detection

Find brands in social videos. Send a video and a list of brands. Zelf reads the whole video and finds each scene where a brand is identifiable: its logo or name is visible, or the brand is named out loud. Every appearance comes back with its timestamp and what was detected. Detection is asynchronous: create a detection, then poll it for the results.

Detect brands in a video

POST/api/v1/detection/detection.create

Detects a list of brands in one video. Zelf Detection 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 (placement) comes back with its timestamp, what was detected and how confident the detection is. Send the brands in context.hints.brands.

Identify the video with content_id (a video already in Zelf) or content_url (a link to any supported video). Sending both, or neither, returns VALIDATION_ERROR. A content_url that is not in Zelf yet is added first, which makes the detection take longer.

Limits

  • One video per request, up to 90 minutes. YouTube, TikTok and Instagram videos. Photo posts are not supported.
  • A product with no visible logo or name, and no spoken mention, is not detected.
  • content_url: the full video link, not a short link (vm.tiktok.com, vt.tiktok.com, tiktok.com/t/). Supported forms: youtube.com/watch?v=<id>, youtu.be/<id>, youtube.com/shorts/<id>, tiktok.com/@<handle>/video/<id>, instagram.com/reel/<id>, /p/<id>, /tv/<id>.
  • context.hints.brands: 1 to 200 brands. No two names that differ only by punctuation or digits.
  • context.hints.brands[].name: up to 60 characters.
  • context.hints.brands[].identifiers: 2 to 10, each 2 to 200 characters, including the brand name.
  • context.hints.brands[].negatives: optional, up to 12, each up to 200 characters.

A request that breaks a rule returns VALIDATION_ERROR for the first rule it breaks. error.details.field names the field, for example context.hints.brands[3].identifiers, and error.details.code is one of INVALID_REQUEST, NO_BRANDS, TOO_MANY_BRANDS, INVALID_BRAND, IDENTIFIER_COUNT, IDENTIFIER_TOO_SHORT, UNSUPPORTED_URL, SHORT_LINK, PHOTO_POST or VIDEO_TOO_LONG.

Detection is asynchronous. This method returns immediately with the detection at status: SCHEDULED. Poll detection.get until status is SUCCESS or FAILED; both are final.

Request body

content_idType: stringmin length 1

Identifier of content Zelf already holds, obtained from content.list. Supply this or content_url, not both.

min length
1
content_urlType: string(uri)min length 1

Direct URL to the post or video to analyse. Content Zelf does not hold yet is indexed first. Supply this or content_id, not both.

min length
1
contextType: DetectionContextInputRequestRequired

The brands to detect and the evidence that counts for each.

Show child attributes
context.hintsType: DetectionHintsRequestRequired

The brands to detect and the evidence that counts for each.

Show child attributes
context.hints.brandsType: DetectionBrandHintRequest[]Requiredmin items 1 · max items 200

1 to 200 brands to detect, in the order the results report them.

Show child attributes
min items
1
max items
200
context.hints.brands[].nameType: stringRequiredmin length 1 · max length 60

Display name of the brand, at most 60 characters. Every placement names it verbatim.

min length
1
max length
60
context.hints.brands[].identifiersType: string[]Requiredmin items 2 · max items 10

2 to 10 words, logos and product names that count as evidence of this brand, the brand name included, each 2 to 200 characters. For example ['Nike', 'Jordan', 'Air Force 1'].

min items
2
max items
10
context.hints.brands[].negativesType: string[]max items 12

Up to 12 look-alikes that must not count, each at most 200 characters, for example ['Jordan (the country)'].

max items
12

Returns Detection

idType: stringread-only

Identifier of the detection. Use this value on follow-up calls.

created_atType: string(date-time)

Time the detection was created (UTC, ISO-8601).

updated_atType: string(date-time)

Time the detection was most recently modified (UTC, ISO-8601).

statusType: string

Where the detection has got to. One of SCHEDULED, IN_PROGRESS, SUCCESS, FAILED.

contextType: DetectionContext

The input this detection was created with.

Show child attributes
context.external_urlType: stringdefault

URL of the content being analysed. Read from the content this detection resolved to, falling back to the submitted content_url while that content is still being indexed.

default
context.hintsType: DetectionHints

The brands to detect and the evidence that counts for each.

Show child attributes
context.hints.brandsType: DetectionBrandHint[]min items 1 · max items 200

1 to 200 brands to detect, in the order the results report them.

Show child attributes
min items
1
max items
200
context.hints.brands[].nameType: stringmax length 60

Display name of the brand, at most 60 characters. Every placement names it verbatim.

max length
60
context.hints.brands[].identifiersType: string[]min items 2 · max items 10

2 to 10 words, logos and product names that count as evidence of this brand, the brand name included, each 2 to 200 characters. For example ['Nike', 'Jordan', 'Air Force 1'].

min items
2
max items
10
context.hints.brands[].negativesType: string[]max items 12

Up to 12 look-alikes that must not count, each at most 200 characters, for example ['Jordan (the country)'].

max items
12
resultType: DetectionResultor nullread-only

The placements document. null until status is SUCCESS or FAILED; on FAILED it carries the counts and brands: [].

Show child attributes
result.videoType: DetectionVideo

The content that was analysed.

Show child attributes
result.video.video_urlType: string(uri)

URL of the analysed content.

result.video.platformType: string

Platform the content was posted on.

result.video.duration_sType: number(double)

Length of the content in seconds.

result.summaryType: DetectionSummary

Counts across the whole run.

Show child attributes
result.summary.brands_requestedType: integer

How many brands the rubric asked for.

result.summary.brands_foundType: integer

How many of those brands were present.

result.summary.placementsType: integer

Total placements reported.

result.summary.confirmedType: integer

Placements that passed the final frame check.

result.summary.reviewType: integer

Placements that did not pass the final frame check, including every Audio placement.

result.brandsType: DetectionBrandResult[]

One entry per requested brand, in request order, including the brands that were not found.

Show child attributes
result.brands[].brand_nameType: string

The requested brand, named exactly as it was asked for.

result.brands[].foundType: boolean

Whether the brand was present at all.

result.brands[].summaryType: DetectionBrandSummary

Counts for this brand.

Show child attributes
result.brands[].summary.placementsType: integer

Placements for this brand.

result.brands[].summary.confirmedType: integer

Of those, confirmed.

result.brands[].summary.reviewType: integer

Of those, left for review.

result.brands[].placementsType: DetectionPlacement[]

Every placement for this brand, earliest first.

Show child attributes
result.brands[].placements[].timestampType: string

mm:ss-mm:ss, or mm:ss for a single moment. Past an hour the minutes keep counting (69:23-69:25).

result.brands[].placements[].video_url_at_timestampType: string(uri)or null

YouTube: opens the video 5 seconds before the placement. null on TikTok and Instagram.

result.brands[].placements[].modalityType: string

How it was detected. One of Visual, Audio, Text.

result.brands[].placements[].what_detectedType: string

What was observed, in the detector's own words.

result.brands[].placements[].reasoningType: string

Why the observation counts as this brand.

result.brands[].placements[].confidenceType: string

confirmed: passed a final check against the video's frames. review: detected but did not pass that final frame check. Audio placements are always review, because a spoken mention has no frames to check; they can be used as they are.

Responses

Detection

idType: stringread-only

Identifier of the detection. Use this value on follow-up calls.

created_atType: string(date-time)

Time the detection was created (UTC, ISO-8601).

updated_atType: string(date-time)

Time the detection was most recently modified (UTC, ISO-8601).

statusType: string

Where the detection has got to. One of SCHEDULED, IN_PROGRESS, SUCCESS, FAILED.

contextType: DetectionContext

The input this detection was created with.

Show child attributes
context.external_urlType: stringdefault

URL of the content being analysed. Read from the content this detection resolved to, falling back to the submitted content_url while that content is still being indexed.

default
context.hintsType: DetectionHints

The brands to detect and the evidence that counts for each.

Show child attributes
context.hints.brandsType: DetectionBrandHint[]min items 1 · max items 200

1 to 200 brands to detect, in the order the results report them.

Show child attributes
min items
1
max items
200
context.hints.brands[].nameType: stringmax length 60

Display name of the brand, at most 60 characters. Every placement names it verbatim.

max length
60
context.hints.brands[].identifiersType: string[]min items 2 · max items 10

2 to 10 words, logos and product names that count as evidence of this brand, the brand name included, each 2 to 200 characters. For example ['Nike', 'Jordan', 'Air Force 1'].

min items
2
max items
10
context.hints.brands[].negativesType: string[]max items 12

Up to 12 look-alikes that must not count, each at most 200 characters, for example ['Jordan (the country)'].

max items
12
resultType: DetectionResultor nullread-only

The placements document. null until status is SUCCESS or FAILED; on FAILED it carries the counts and brands: [].

Show child attributes
result.videoType: DetectionVideo

The content that was analysed.

Show child attributes
result.video.video_urlType: string(uri)

URL of the analysed content.

result.video.platformType: string

Platform the content was posted on.

result.video.duration_sType: number(double)

Length of the content in seconds.

result.summaryType: DetectionSummary

Counts across the whole run.

Show child attributes
result.summary.brands_requestedType: integer

How many brands the rubric asked for.

result.summary.brands_foundType: integer

How many of those brands were present.

result.summary.placementsType: integer

Total placements reported.

result.summary.confirmedType: integer

Placements that passed the final frame check.

result.summary.reviewType: integer

Placements that did not pass the final frame check, including every Audio placement.

result.brandsType: DetectionBrandResult[]

One entry per requested brand, in request order, including the brands that were not found.

Show child attributes
result.brands[].brand_nameType: string

The requested brand, named exactly as it was asked for.

result.brands[].foundType: boolean

Whether the brand was present at all.

result.brands[].summaryType: DetectionBrandSummary

Counts for this brand.

Show child attributes
result.brands[].summary.placementsType: integer

Placements for this brand.

result.brands[].summary.confirmedType: integer

Of those, confirmed.

result.brands[].summary.reviewType: integer

Of those, left for review.

result.brands[].placementsType: DetectionPlacement[]

Every placement for this brand, earliest first.

Show child attributes
result.brands[].placements[].timestampType: string

mm:ss-mm:ss, or mm:ss for a single moment. Past an hour the minutes keep counting (69:23-69:25).

result.brands[].placements[].video_url_at_timestampType: string(uri)or null

YouTube: opens the video 5 seconds before the placement. null on TikTok and Instagram.

result.brands[].placements[].modalityType: string

How it was detected. One of Visual, Audio, Text.

result.brands[].placements[].what_detectedType: string

What was observed, in the detector's own words.

result.brands[].placements[].reasoningType: string

Why the observation counts as this brand.

result.brands[].placements[].confidenceType: string

confirmed: passed a final check against the video's frames. review: detected but did not pass that final frame check. Audio placements are always review, because a spoken mention has no frames to check; they can be used as they are.

Request

string
string(uri)
DetectionContextInputRequestRequired
DetectionHintsRequestRequired
DetectionBrandHintRequest[]Required
from zelf import ZelfAPIClient

client = ZelfAPIClient(api_key="<your-api-key>")

result = client.detection.create(
    content_url="https://www.youtube.com/watch?v=PpAPhxsuCJw",
    context={
        "hints": {
            "brands": [{
                "name": "Nike",
                "identifiers": ["Nike", "Jordan", "Air Force 1"],
                "negatives": ["Jordan (the country)"],
            }, {
                "name": "Adidas",
                "identifiers": ["Adidas", "Samba"],
                "negatives": [],
            }],
        },
    },
)
print(result)
Response
{
  "log_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "timestamp": "2026-01-01T00:00:00Z",
  "results": [
    {
      "id": "aer_TqNpNLkEfxbokqbD",
      "status": "SCHEDULED",
      "context": {
        "external_url": "https://www.youtube.com/watch?v=PpAPhxsuCJw",
        "hints": {
          "brands": [
            {
              "name": "Nike",
              "identifiers": [
                "Nike",
                "Jordan",
                "Air Force 1"
              ],
              "negatives": [
                "Jordan (the country)"
              ]
            },
            {
              "name": "Adidas",
              "identifiers": [
                "Adidas",
                "Samba"
              ],
              "negatives": []
            }
          ]
        }
      },
      "result": null,
      "created_at": "2026-05-12T14:30:00Z",
      "updated_at": "2026-05-12T14:30:00Z"
    }
  ],
  "item_count": 1,
  "page": 1,
  "next": null,
  "previous": null
}

List detections and their status

GET/api/v1/detection/detection.list

Lists detections, newest first. Each detection has its current status and, once finished, its placements in result. Possible statuses are SCHEDULED, IN_PROGRESS, SUCCESS and FAILED. SUCCESS and FAILED are final.

Returns Detection

idType: stringread-only

Identifier of the detection. Use this value on follow-up calls.

created_atType: string(date-time)

Time the detection was created (UTC, ISO-8601).

updated_atType: string(date-time)

Time the detection was most recently modified (UTC, ISO-8601).

statusType: string

Where the detection has got to. One of SCHEDULED, IN_PROGRESS, SUCCESS, FAILED.

contextType: DetectionContext

The input this detection was created with.

Show child attributes
context.external_urlType: stringdefault

URL of the content being analysed. Read from the content this detection resolved to, falling back to the submitted content_url while that content is still being indexed.

default
context.hintsType: DetectionHints

The brands to detect and the evidence that counts for each.

Show child attributes
context.hints.brandsType: DetectionBrandHint[]min items 1 · max items 200

1 to 200 brands to detect, in the order the results report them.

Show child attributes
min items
1
max items
200
context.hints.brands[].nameType: stringmax length 60

Display name of the brand, at most 60 characters. Every placement names it verbatim.

max length
60
context.hints.brands[].identifiersType: string[]min items 2 · max items 10

2 to 10 words, logos and product names that count as evidence of this brand, the brand name included, each 2 to 200 characters. For example ['Nike', 'Jordan', 'Air Force 1'].

min items
2
max items
10
context.hints.brands[].negativesType: string[]max items 12

Up to 12 look-alikes that must not count, each at most 200 characters, for example ['Jordan (the country)'].

max items
12
resultType: DetectionResultor nullread-only

The placements document. null until status is SUCCESS or FAILED; on FAILED it carries the counts and brands: [].

Show child attributes
result.videoType: DetectionVideo

The content that was analysed.

Show child attributes
result.video.video_urlType: string(uri)

URL of the analysed content.

result.video.platformType: string

Platform the content was posted on.

result.video.duration_sType: number(double)

Length of the content in seconds.

result.summaryType: DetectionSummary

Counts across the whole run.

Show child attributes
result.summary.brands_requestedType: integer

How many brands the rubric asked for.

result.summary.brands_foundType: integer

How many of those brands were present.

result.summary.placementsType: integer

Total placements reported.

result.summary.confirmedType: integer

Placements that passed the final frame check.

result.summary.reviewType: integer

Placements that did not pass the final frame check, including every Audio placement.

result.brandsType: DetectionBrandResult[]

One entry per requested brand, in request order, including the brands that were not found.

Show child attributes
result.brands[].brand_nameType: string

The requested brand, named exactly as it was asked for.

result.brands[].foundType: boolean

Whether the brand was present at all.

result.brands[].summaryType: DetectionBrandSummary

Counts for this brand.

Show child attributes
result.brands[].summary.placementsType: integer

Placements for this brand.

result.brands[].summary.confirmedType: integer

Of those, confirmed.

result.brands[].summary.reviewType: integer

Of those, left for review.

result.brands[].placementsType: DetectionPlacement[]

Every placement for this brand, earliest first.

Show child attributes
result.brands[].placements[].timestampType: string

mm:ss-mm:ss, or mm:ss for a single moment. Past an hour the minutes keep counting (69:23-69:25).

result.brands[].placements[].video_url_at_timestampType: string(uri)or null

YouTube: opens the video 5 seconds before the placement. null on TikTok and Instagram.

result.brands[].placements[].modalityType: string

How it was detected. One of Visual, Audio, Text.

result.brands[].placements[].what_detectedType: string

What was observed, in the detector's own words.

result.brands[].placements[].reasoningType: string

Why the observation counts as this brand.

result.brands[].placements[].confidenceType: string

confirmed: passed a final check against the video's frames. review: detected but did not pass that final frame check. Audio placements are always review, because a spoken mention has no frames to check; they can be used as they are.

Responses

Detection

idType: stringread-only

Identifier of the detection. Use this value on follow-up calls.

created_atType: string(date-time)

Time the detection was created (UTC, ISO-8601).

updated_atType: string(date-time)

Time the detection was most recently modified (UTC, ISO-8601).

statusType: string

Where the detection has got to. One of SCHEDULED, IN_PROGRESS, SUCCESS, FAILED.

contextType: DetectionContext

The input this detection was created with.

Show child attributes
context.external_urlType: stringdefault

URL of the content being analysed. Read from the content this detection resolved to, falling back to the submitted content_url while that content is still being indexed.

default
context.hintsType: DetectionHints

The brands to detect and the evidence that counts for each.

Show child attributes
context.hints.brandsType: DetectionBrandHint[]min items 1 · max items 200

1 to 200 brands to detect, in the order the results report them.

Show child attributes
min items
1
max items
200
context.hints.brands[].nameType: stringmax length 60

Display name of the brand, at most 60 characters. Every placement names it verbatim.

max length
60
context.hints.brands[].identifiersType: string[]min items 2 · max items 10

2 to 10 words, logos and product names that count as evidence of this brand, the brand name included, each 2 to 200 characters. For example ['Nike', 'Jordan', 'Air Force 1'].

min items
2
max items
10
context.hints.brands[].negativesType: string[]max items 12

Up to 12 look-alikes that must not count, each at most 200 characters, for example ['Jordan (the country)'].

max items
12
resultType: DetectionResultor nullread-only

The placements document. null until status is SUCCESS or FAILED; on FAILED it carries the counts and brands: [].

Show child attributes
result.videoType: DetectionVideo

The content that was analysed.

Show child attributes
result.video.video_urlType: string(uri)

URL of the analysed content.

result.video.platformType: string

Platform the content was posted on.

result.video.duration_sType: number(double)

Length of the content in seconds.

result.summaryType: DetectionSummary

Counts across the whole run.

Show child attributes
result.summary.brands_requestedType: integer

How many brands the rubric asked for.

result.summary.brands_foundType: integer

How many of those brands were present.

result.summary.placementsType: integer

Total placements reported.

result.summary.confirmedType: integer

Placements that passed the final frame check.

result.summary.reviewType: integer

Placements that did not pass the final frame check, including every Audio placement.

result.brandsType: DetectionBrandResult[]

One entry per requested brand, in request order, including the brands that were not found.

Show child attributes
result.brands[].brand_nameType: string

The requested brand, named exactly as it was asked for.

result.brands[].foundType: boolean

Whether the brand was present at all.

result.brands[].summaryType: DetectionBrandSummary

Counts for this brand.

Show child attributes
result.brands[].summary.placementsType: integer

Placements for this brand.

result.brands[].summary.confirmedType: integer

Of those, confirmed.

result.brands[].summary.reviewType: integer

Of those, left for review.

result.brands[].placementsType: DetectionPlacement[]

Every placement for this brand, earliest first.

Show child attributes
result.brands[].placements[].timestampType: string

mm:ss-mm:ss, or mm:ss for a single moment. Past an hour the minutes keep counting (69:23-69:25).

result.brands[].placements[].video_url_at_timestampType: string(uri)or null

YouTube: opens the video 5 seconds before the placement. null on TikTok and Instagram.

result.brands[].placements[].modalityType: string

How it was detected. One of Visual, Audio, Text.

result.brands[].placements[].what_detectedType: string

What was observed, in the detector's own words.

result.brands[].placements[].reasoningType: string

Why the observation counts as this brand.

result.brands[].placements[].confidenceType: string

confirmed: passed a final check against the video's frames. review: detected but did not pass that final frame check. Audio placements are always review, because a spoken mention has no frames to check; they can be used as they are.

from zelf import ZelfAPIClient

client = ZelfAPIClient(api_key="<your-api-key>")

result = client.detection.list()
print(result)
Response
{
  "log_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "timestamp": "2026-01-01T00:00:00Z",
  "results": [
    {
      "id": "aer_TqNpNLkEfxbokqbD",
      "status": "SUCCESS",
      "context": {
        "external_url": "https://www.youtube.com/watch?v=PpAPhxsuCJw",
        "hints": {
          "brands": [
            {
              "name": "Nike",
              "identifiers": [
                "Nike",
                "Jordan",
                "Air Force 1"
              ],
              "negatives": [
                "Jordan (the country)"
              ]
            },
            {
              "name": "Adidas",
              "identifiers": [
                "Adidas",
                "Samba"
              ],
              "negatives": []
            }
          ]
        }
      },
      "result": {
        "video": {
          "video_url": "https://www.youtube.com/watch?v=PpAPhxsuCJw",
          "platform": "youtube",
          "duration_s": 65.1
        },
        "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": "00:12-00:15",
                "video_url_at_timestamp": "https://www.youtube.com/watch?v=PpAPhxsuCJw&t=7s",
                "modality": "Visual",
                "what_detected": "Nike swoosh on the left shoe",
                "reasoning": "The swoosh is fully visible on the left shoe.",
                "confidence": "confirmed"
              },
              {
                "timestamp": "00:34-00:37",
                "video_url_at_timestamp": "https://www.youtube.com/watch?v=PpAPhxsuCJw&t=29s",
                "modality": "Audio",
                "what_detected": "Spoken word 'Nike'",
                "reasoning": "The presenter says 'Nike' while introducing the hoop.",
                "confidence": "review"
              }
            ]
          },
          {
            "brand_name": "Adidas",
            "found": false,
            "summary": {
              "placements": 0,
              "confirmed": 0,
              "review": 0
            },
            "placements": []
          }
        ]
      },
      "created_at": "2026-05-12T14:30:00Z",
      "updated_at": "2026-05-12T15:00:00Z"
    }
  ],
  "item_count": 1,
  "page": 1,
  "next": null,
  "previous": null
}

Retrieve a detection and its placements

GET/api/v1/detection/{id}/detection.get

Retrieve one detection by id, the value returned by detection.create. Poll it until status is SUCCESS or FAILED; both are final. result is null until then. On FAILED, result carries the counts and brands: [].

Reading result

  • brands: one entry per requested brand, in request order. found: false means the brand was searched for and does not appear in the video.
  • timestamp: mm:ss-mm:ss, or mm:ss for a single moment. Past an hour the minutes keep counting (69:23-69:25).
  • modality: Visual (logo or product on screen), Text (brand name written on screen) or Audio (brand name spoken).
  • confidence: confirmed means the placement passed a final check against the video's frames; review means it was detected but did not pass that final frame check. Audio placements are always review, because a spoken mention has no frames to check. On Visual and Text placements, review means the final frame check did not confirm the placement.
  • video_url_at_timestamp: YouTube only, opens the video 5 seconds before the placement. null on TikTok and Instagram.

Parameters

idType: stringRequired

Returns Detection

idType: stringread-only

Identifier of the detection. Use this value on follow-up calls.

created_atType: string(date-time)

Time the detection was created (UTC, ISO-8601).

updated_atType: string(date-time)

Time the detection was most recently modified (UTC, ISO-8601).

statusType: string

Where the detection has got to. One of SCHEDULED, IN_PROGRESS, SUCCESS, FAILED.

contextType: DetectionContext

The input this detection was created with.

Show child attributes
context.external_urlType: stringdefault

URL of the content being analysed. Read from the content this detection resolved to, falling back to the submitted content_url while that content is still being indexed.

default
context.hintsType: DetectionHints

The brands to detect and the evidence that counts for each.

Show child attributes
context.hints.brandsType: DetectionBrandHint[]min items 1 · max items 200

1 to 200 brands to detect, in the order the results report them.

Show child attributes
min items
1
max items
200
context.hints.brands[].nameType: stringmax length 60

Display name of the brand, at most 60 characters. Every placement names it verbatim.

max length
60
context.hints.brands[].identifiersType: string[]min items 2 · max items 10

2 to 10 words, logos and product names that count as evidence of this brand, the brand name included, each 2 to 200 characters. For example ['Nike', 'Jordan', 'Air Force 1'].

min items
2
max items
10
context.hints.brands[].negativesType: string[]max items 12

Up to 12 look-alikes that must not count, each at most 200 characters, for example ['Jordan (the country)'].

max items
12
resultType: DetectionResultor nullread-only

The placements document. null until status is SUCCESS or FAILED; on FAILED it carries the counts and brands: [].

Show child attributes
result.videoType: DetectionVideo

The content that was analysed.

Show child attributes
result.video.video_urlType: string(uri)

URL of the analysed content.

result.video.platformType: string

Platform the content was posted on.

result.video.duration_sType: number(double)

Length of the content in seconds.

result.summaryType: DetectionSummary

Counts across the whole run.

Show child attributes
result.summary.brands_requestedType: integer

How many brands the rubric asked for.

result.summary.brands_foundType: integer

How many of those brands were present.

result.summary.placementsType: integer

Total placements reported.

result.summary.confirmedType: integer

Placements that passed the final frame check.

result.summary.reviewType: integer

Placements that did not pass the final frame check, including every Audio placement.

result.brandsType: DetectionBrandResult[]

One entry per requested brand, in request order, including the brands that were not found.

Show child attributes
result.brands[].brand_nameType: string

The requested brand, named exactly as it was asked for.

result.brands[].foundType: boolean

Whether the brand was present at all.

result.brands[].summaryType: DetectionBrandSummary

Counts for this brand.

Show child attributes
result.brands[].summary.placementsType: integer

Placements for this brand.

result.brands[].summary.confirmedType: integer

Of those, confirmed.

result.brands[].summary.reviewType: integer

Of those, left for review.

result.brands[].placementsType: DetectionPlacement[]

Every placement for this brand, earliest first.

Show child attributes
result.brands[].placements[].timestampType: string

mm:ss-mm:ss, or mm:ss for a single moment. Past an hour the minutes keep counting (69:23-69:25).

result.brands[].placements[].video_url_at_timestampType: string(uri)or null

YouTube: opens the video 5 seconds before the placement. null on TikTok and Instagram.

result.brands[].placements[].modalityType: string

How it was detected. One of Visual, Audio, Text.

result.brands[].placements[].what_detectedType: string

What was observed, in the detector's own words.

result.brands[].placements[].reasoningType: string

Why the observation counts as this brand.

result.brands[].placements[].confidenceType: string

confirmed: passed a final check against the video's frames. review: detected but did not pass that final frame check. Audio placements are always review, because a spoken mention has no frames to check; they can be used as they are.

Responses

Detection

idType: stringread-only

Identifier of the detection. Use this value on follow-up calls.

created_atType: string(date-time)

Time the detection was created (UTC, ISO-8601).

updated_atType: string(date-time)

Time the detection was most recently modified (UTC, ISO-8601).

statusType: string

Where the detection has got to. One of SCHEDULED, IN_PROGRESS, SUCCESS, FAILED.

contextType: DetectionContext

The input this detection was created with.

Show child attributes
context.external_urlType: stringdefault

URL of the content being analysed. Read from the content this detection resolved to, falling back to the submitted content_url while that content is still being indexed.

default
context.hintsType: DetectionHints

The brands to detect and the evidence that counts for each.

Show child attributes
context.hints.brandsType: DetectionBrandHint[]min items 1 · max items 200

1 to 200 brands to detect, in the order the results report them.

Show child attributes
min items
1
max items
200
context.hints.brands[].nameType: stringmax length 60

Display name of the brand, at most 60 characters. Every placement names it verbatim.

max length
60
context.hints.brands[].identifiersType: string[]min items 2 · max items 10

2 to 10 words, logos and product names that count as evidence of this brand, the brand name included, each 2 to 200 characters. For example ['Nike', 'Jordan', 'Air Force 1'].

min items
2
max items
10
context.hints.brands[].negativesType: string[]max items 12

Up to 12 look-alikes that must not count, each at most 200 characters, for example ['Jordan (the country)'].

max items
12
resultType: DetectionResultor nullread-only

The placements document. null until status is SUCCESS or FAILED; on FAILED it carries the counts and brands: [].

Show child attributes
result.videoType: DetectionVideo

The content that was analysed.

Show child attributes
result.video.video_urlType: string(uri)

URL of the analysed content.

result.video.platformType: string

Platform the content was posted on.

result.video.duration_sType: number(double)

Length of the content in seconds.

result.summaryType: DetectionSummary

Counts across the whole run.

Show child attributes
result.summary.brands_requestedType: integer

How many brands the rubric asked for.

result.summary.brands_foundType: integer

How many of those brands were present.

result.summary.placementsType: integer

Total placements reported.

result.summary.confirmedType: integer

Placements that passed the final frame check.

result.summary.reviewType: integer

Placements that did not pass the final frame check, including every Audio placement.

result.brandsType: DetectionBrandResult[]

One entry per requested brand, in request order, including the brands that were not found.

Show child attributes
result.brands[].brand_nameType: string

The requested brand, named exactly as it was asked for.

result.brands[].foundType: boolean

Whether the brand was present at all.

result.brands[].summaryType: DetectionBrandSummary

Counts for this brand.

Show child attributes
result.brands[].summary.placementsType: integer

Placements for this brand.

result.brands[].summary.confirmedType: integer

Of those, confirmed.

result.brands[].summary.reviewType: integer

Of those, left for review.

result.brands[].placementsType: DetectionPlacement[]

Every placement for this brand, earliest first.

Show child attributes
result.brands[].placements[].timestampType: string

mm:ss-mm:ss, or mm:ss for a single moment. Past an hour the minutes keep counting (69:23-69:25).

result.brands[].placements[].video_url_at_timestampType: string(uri)or null

YouTube: opens the video 5 seconds before the placement. null on TikTok and Instagram.

result.brands[].placements[].modalityType: string

How it was detected. One of Visual, Audio, Text.

result.brands[].placements[].what_detectedType: string

What was observed, in the detector's own words.

result.brands[].placements[].reasoningType: string

Why the observation counts as this brand.

result.brands[].placements[].confidenceType: string

confirmed: passed a final check against the video's frames. review: detected but did not pass that final frame check. Audio placements are always review, because a spoken mention has no frames to check; they can be used as they are.

Request

stringRequired
from zelf import ZelfAPIClient

client = ZelfAPIClient(api_key="<your-api-key>")

result = client.detection.get("<id>")
print(result)
Response
{
  "log_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "timestamp": "2026-01-01T00:00:00Z",
  "results": [
    {
      "id": "aer_TqNpNLkEfxbokqbD",
      "status": "SUCCESS",
      "context": {
        "external_url": "https://www.youtube.com/watch?v=PpAPhxsuCJw",
        "hints": {
          "brands": [
            {
              "name": "Nike",
              "identifiers": [
                "Nike",
                "Jordan",
                "Air Force 1"
              ],
              "negatives": [
                "Jordan (the country)"
              ]
            },
            {
              "name": "Adidas",
              "identifiers": [
                "Adidas",
                "Samba"
              ],
              "negatives": []
            }
          ]
        }
      },
      "result": {
        "video": {
          "video_url": "https://www.youtube.com/watch?v=PpAPhxsuCJw",
          "platform": "youtube",
          "duration_s": 65.1
        },
        "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": "00:12-00:15",
                "video_url_at_timestamp": "https://www.youtube.com/watch?v=PpAPhxsuCJw&t=7s",
                "modality": "Visual",
                "what_detected": "Nike swoosh on the left shoe",
                "reasoning": "The swoosh is fully visible on the left shoe.",
                "confidence": "confirmed"
              },
              {
                "timestamp": "00:34-00:37",
                "video_url_at_timestamp": "https://www.youtube.com/watch?v=PpAPhxsuCJw&t=29s",
                "modality": "Audio",
                "what_detected": "Spoken word 'Nike'",
                "reasoning": "The presenter says 'Nike' while introducing the hoop.",
                "confidence": "review"
              }
            ]
          },
          {
            "brand_name": "Adidas",
            "found": false,
            "summary": {
              "placements": 0,
              "confirmed": 0,
              "review": 0
            },
            "placements": []
          }
        ]
      },
      "created_at": "2026-05-12T14:30:00Z",
      "updated_at": "2026-05-12T15:00:00Z"
    }
  ],
  "item_count": 1,
  "page": 1,
  "next": null,
  "previous": null
}