본문으로 건너뛰기

브랜드 세이프티

광고 차단은 광고 소재 하나를 대상으로 하는 프로그래매틱 브랜드 세이프티 심사입니다. 소재와 자연어 정책 규칙 목록(선택)을 보내면, 서비스가 결정적인 block / allow 판정과 함께 걸린 GARM 카테고리, 일치한 규칙을 반환합니다. 동기 호출 한 번으로 끝나며 폴링이나 콜백이 없습니다.

심사 방식​

심사 요청은 동기 호출 한 번 안에서 다음을 수행합니다.

  1. 소재를 저장하고, 필요하면 분석기에 맞게 트랜스코딩합니다.
  2. 오디오와 키프레임을 추출합니다.
  3. 멀티모달 분석을 한 번 실행해 11개 GARM 브랜드 세이프티 카테고리와 모든 정책 규칙을 함께 평가합니다. 정책 규칙은 팀에 저장된 차단 목록과 요청에 담긴 임시 rules입니다.
  4. 근거로부터 코드에서 판정을 계산합니다. GARM 카테고리가 하나 이상 걸렸거나 또는 정책 규칙이 하나 이상 일치한 경우에만 block_decision이 block입니다. 그 외에는 allow입니다.
  5. 결과를 저장하고 반환합니다.

설계 시 고려할 특성은 다음과 같습니다.

  • 동기 방식. 일반적인 지연 시간은 몇 초이며, 대부분 멀티모달 분석에 걸립니다.
  • 결정적인 판정. 추론은 모델이 하지만, 판정은 garm과 blocklist.hits로부터 코드에서 계산합니다. 따라서 판정은 함께 반환되는 근거와 항상 일치합니다. 근거는 AI 출력이라 어느 방향으로든 틀릴 수 있습니다. block은 "검토 대기"로 다루고, 중요한 건에는 반드시 사람이 개입하세요. 데이터 처리를 참고하세요.
  • 페일 클로즈드. 분석 자체에서 오류가 나도 block_decision: "block"과 screening_failed: true를 담은 200을 받습니다. 따라서 block_decision만 보고 분기하는 연동도 소재를 송출하지 않고 보류합니다.
  • GARM은 항상 평가합니다. 규칙이 없어도 마찬가지입니다. 규칙은 그 위에 더하는 정책입니다.
  • 길이 상한. 소재는 약 120초로 제한됩니다.

업로드한 파일 심사​

POST /api/v1/ad-blocking은 multipart/form-data를 받습니다.

filefilebodyrequired

광고 영상 파일(mp4 / mov / ...)이며, 최대 ~120초입니다.

titlestringbody

감사 기록과 목록에 표시할 제목입니다.

rulesstringbody

자연어로 작성한 임시 정책 규칙으로, 쉼표나 줄바꿈으로 구분합니다. 팀에 저장된 차단 목록(대시보드 Ad Blocking 탭에서 관리)에 더해 평가합니다.

curl -X POST https://video.pyler.ai/api/v1/ad-blocking \
-H "Authorization: Bearer $VUF_API_KEY" \
-F "file=@./ads/crunchy-sticks-q4.mp4" \
-F "title=Crunchy Sticks Q4 brand campaign" \
-F "rules=no on-screen consumption of alcohol, no minors holding weapons"

응답 — 200 OK:

{
"id": "9f3c2a4d-0000-0000-0000-000000000000",
"ad_id": null,
"title": "Crunchy Sticks Q4 brand campaign",
"duration_ms": 18000,
"one_line": "Chocolate-stick snack; friends share boxes at a party.",
"garm": [
{
"category": "illegal_drugs_alcohol_tobacco_vaping",
"reasoning": "Bar scene at 0:12 shows a beer pour and a toast."
}
],
"blocklist": {
"rules": [
"no on-screen consumption of alcohol",
"no minors holding weapons"
],
"hits": [
{
"rule": "no on-screen consumption of alcohol",
"matched": true,
"evidence": "Characters drink beer at 0:14."
}
]
},
"block_decision": "block",
"explanation": "BLOCKED — GARM illegal_drugs_alcohol_tobacco_vaping; blocklist rule 'no on-screen consumption of alcohol'. Bar scene shows pour and toast.",
"screening_failed": false,
"created_at": "2026-06-16T10:23:01Z"
}

업로드한 파일로 실행한 심사는 ad_id: null입니다. 이 값은 아래 v1.1 경로에서만 채워집니다.

등록된 소재 심사(v1.1)​

소재가 이미 광고 레지스트리에 있다면 다시 업로드할 필요가 없습니다. POST /api/v1.1/ad-blocking은 ad_id(status: ready여야 함)와 선택적 rules를 담은 JSON 본문을 받습니다. 코덱 정규화와 오디오 트랙 분석을 포함한 심사 파이프라인은 파일 경로와 같으므로, 같은 소재는 어느 경로로든 같은 판정을 받습니다. 판정 형태도 같으며 ad_id만 채워집니다.

ad_idstringbodyrequired

status: ready인 등록된 광고 소재 UUID입니다.

rulesstring | nullbody

선택적 임시 정책 규칙으로, 쉼표나 줄바꿈으로 구분합니다.

curl -X POST https://video.pyler.ai/api/v1.1/ad-blocking \
-H "Authorization: Bearer $VUF_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"ad_id\": \"$AD_ID\", \"rules\": \"no on-screen consumption of alcohol\"}"
HTTP발생 조건
200판정을 반환했습니다.
404알 수 없는 ad_id, 다른 팀 소유, 또는 유효하지 않은 UUID입니다(존재 여부가 드러나지 않도록 잘못된 형식의 id도 404로 정규화합니다).
409소재가 status: ready가 아닙니다.
노트

/api/v1.1은 등록된 ad_id를 입력으로 받는 경로를 위해 /api/v1과 나란히 있는 별도 버전 루트입니다. 두 루트 모두 같은 API 키를 받습니다. v1 multipart 경로는 바뀌지 않았습니다.

지난 심사 결과 조회​

모든 심사 결과는 저장되므로, 클라이언트 타임아웃으로 응답을 놓쳐도 작업이 사라지지 않습니다. GET /api/v1.1/ad-blocking은 팀의 결과를 최신순으로 나열합니다. 다시 제출하지 말고 ad_id로 필터링해 놓친 결과를 가져오세요.

쿼리타입비고
ad_idUUID선택입니다. 알 수 없는 id는 빈 목록을 반환하고, UUID가 아닌 값은 422를 반환합니다.
limitinteger1~200, 기본값 50입니다.
offsetinteger기본값 0입니다.
curl "https://video.pyler.ai/api/v1.1/ad-blocking?ad_id=$AD_ID" \
-H "Authorization: Bearer $VUF_API_KEY"
{
"items": [
{
"id": "1a2b3c4d-0000-0000-0000-000000000000",
"ad_id": "c54eff39-0000-0000-0000-000000000000",
"block_decision": "allow",
"screening_failed": false,
"created_at": "2026-06-16T10:25:14Z"
}
],
"total": 1
}

항목에는 아래의 전체 판정 형태가 담깁니다. 업로드한 파일로 실행한 심사(v1 multipart 경로)는 ad_id: null이라 이 방법으로 조회할 수 없습니다. ad_id는 광고 레지스트리에 등록한 소재에만 지정하세요. 지난 심사 결과는 대시보드의 Ad Blocking 탭에서도 볼 수 있습니다.

판정 형태​

두 심사 경로와 조회는 모두 같은 AdBlockingResult를 반환합니다.

idstringrequired

서버가 발급한 이 심사의 id입니다.

ad_idstring | null

심사한 등록 소재입니다. 파일 업로드는 null입니다. GET /api/v1.1/ad-blocking?ad_id=...에 사용해 결과를 다시 찾을 수 있습니다.

titlestring | null

표시용 제목입니다. 요청의 title, 없으면 파일 이름입니다.

duration_msinteger | null

소재 길이(밀리초)입니다.

one_linestring | null

광고 내용을 한 줄로 요약합니다.

garmobject[]

걸린 GARM 카테고리이며, 문제가 없으면 빈 배열입니다. 각 항목은 { category, reasoning }입니다. category는 아래 표의 고정 슬러그라 분기에 써도 안전합니다. reasoning은 판단의 근거가 된 단서와 발생 시점을 인용합니다(예: "Bar scene at 0:12").

blocklistobject

{ rules, hits }입니다. rules는 평가한 전체 규칙(팀 차단 목록 + 임시 규칙)입니다. hits는 [{ rule, matched, evidence }]이며, matched: true인 항목이 걸린 규칙입니다.

block_decisionstringdefault: allow

판정입니다. garm이 비어 있지 않거나 blocklist.hits[i].matched 중 하나라도 true이면 block, 그 외에는 allow입니다.

explanationstring | null

걸린 카테고리와 규칙을 앞에 붙인 사람이 읽기 위한 설명입니다. 표시용이며 파싱용이 아닙니다. 분기는 garm과 blocklist.hits로 하세요.

screening_failedbooleandefault: false

심사 자체가 실패해 block이 콘텐츠 판정이 아닌 예방 조치(페일 클로즈드)일 때 true입니다. 호출을 재시도하세요. 최종 사용자에게 실제 차단처럼 보여 주면 안 됩니다.

created_atstringrequired

ISO 8601 UTC 타임스탬프입니다.

경고

설명 텍스트가 아니라 screening_failed로 분기하세요. 실제 차단과 실패한 심사 모두 BLOCKED —로 시작합니다.

GARM 카테고리​

심사기는 11개 GARM Brand Safety Floor 카테고리를 평가합니다. 슬러그는 고정이며, 제목은 GARM 공식 명칭입니다.

슬러그GARM 제목
adult_and_explicit_sexual_contentAdult & Explicit Sexual Content
arms_and_ammunitionArms & Ammunition
crime_human_rights_violationsCrime & Harmful Acts, Human Rights Violations
death_injury_or_military_conflictDeath, Injury, or Military Conflict
online_piracyOnline Piracy
hate_speech_and_acts_of_aggressionHate Speech & Acts of Aggression
obscenity_and_profanityObscenity & Profanity
illegal_drugs_alcohol_tobacco_vapingIllegal Drugs / Tobacco / E-cigarettes / Vaping / Alcohol
spam_or_harmful_contentSpam or Harmful Content
terrorismTerrorism
debated_sensitive_social_issueDebated Sensitive Social Issue
정보

광고 차단의 garm 항목에는 카테고리와 추론만 담기며, 심사 판정에는 심각도 수준이나 장면 구간이 없습니다. 심각도(level: high / medium / low)와 time_ranges는 색인 생성, 검색, GET /api/v1/ads/{ad_id}/analysis가 반환하는 영상 측 garm_video 항목에 있습니다. 색인 생성 측 분류 체계는 12개 카테고리입니다(misinformation 추가). 분류 체계와 판정을 참고하세요.

레시피: 제출하고 판정에 따라 조치하기​

일반적인 연동 흐름은 심사하고, block_decision으로 분기하고, 차단되면 설명을 사람에게 넘기는 것입니다.

  1. 소재 심사

    파일을 업로드하거나(v1) 등록된 ad_id를 넘기고(v1.1), 임시 rules를 함께 보냅니다.

  2. 먼저 `screening_failed` 확인

    true이면 block은 예방 조치입니다. 마감 시간 안에서 한 번 재시도하고, 콘텐츠 차단으로 표시하지 마세요.

  3. `block_decision`으로 분기

    allow → 송출할 수 있습니다. block → explanation, garm[].category 슬러그, 일치한 규칙을 첨부해 검토 대기로 둡니다.

response=$(curl -s -X POST https://video.pyler.ai/api/v1/ad-blocking \
-H "Authorization: Bearer $VUF_API_KEY" \
-F "file=@./ads/new-creative.mp4" \
-F "title=spring-2026-launch" \
-F "rules=no lingerie or swimwear modeling, no on-screen consumption of alcohol")

if [ "$(echo "$response" | jq -r .screening_failed)" = "true" ]; then
echo "Screening failed — retry"; exit 1
fi

if [ "$(echo "$response" | jq -r .block_decision)" = "block" ]; then
echo "BLOCKED — hold for human review"
echo "$response" | jq -r .explanation
echo "GARM:"; echo "$response" | jq -r '.garm[].category'
echo "Matched rules:"; echo "$response" | jq -r '.blocklist.hits[] | select(.matched) | .rule'
else
echo "Cleared for delivery"
fi

allow 응답은 다음과 같습니다.

{
"id": "1a2b3c4d-0000-0000-0000-000000000000",
"ad_id": null,
"title": "spring-2026-launch",
"duration_ms": 15000,
"one_line": "Unbranded running-shoe ad; runner on a coastal road at sunrise.",
"garm": [],
"blocklist": { "rules": ["no on-screen consumption of alcohol"], "hits": [] },
"block_decision": "allow",
"explanation": "ALLOWED — no GARM or blocklist violation. Standard product showcase with voiceover.",
"screening_failed": false,
"created_at": "2026-06-16T10:25:14Z"
}

타임아웃과 재시도​

타임아웃은 실패가 아니라 결과를 알 수 없는 상태입니다. 요청이 수락되면 연결이 유지되는지와 관계없이 심사는 끝까지 실행되고 결과가 저장됩니다. 잃는 것은 응답뿐이므로 광고가 심사되지 않았다고 가정하지 마세요.

  • 권장 클라이언트 타임아웃: 120초. 심사는 보통 몇 초 안에 반환됩니다. 이 상한은 드문 지연에 대비한 값입니다.

  • 5분 넘게 올려도 효과가 없습니다. 5분 동안 데이터를 내보내지 않은 요청은 504로 닫힙니다. 스트리밍 엔드포인트(/api/v1/ads/match, /api/v1/videos/{video_id}/scenes/stream)는 계속 데이터를 내보내므로 더 오래 실행될 수 있지만, 조용한 요청은 5분 시점에 끝납니다.

  • 다시 제출하지 말고 복구하세요. v1.1 심사라면 다시 심사하는 대신 ad_id로 결과를 조회하세요. 비용이 적고 원래 판정을 반환합니다.

    curl "https://video.pyler.ai/api/v1.1/ad-blocking?ad_id=$AD_ID" \
    -H "Authorization: Bearer $VUF_API_KEY"

    같은 ad_id를 다시 심사할 수는 있지만 멱등하지 않습니다. 다시 실행되고 두 번째 결과가 기록됩니다.

  • 백오프. 전체 마감 시간 안에서 한두 번 재시도하면 충분합니다. 재시도는 다른 요청에 쓰일 심사 처리량을 차지하며, 요청 한도가 적용될 수 있습니다. 사용량과 한도를 참고하세요.

재시도할 오류는 다음과 같습니다.

HTTP의미재시도
400잘못된 업로드입니다. 파일 누락, 분석 불가, 또는 길이 초과입니다(v1 경로).아니요
401 / 403키가 없거나 유효하지 않거나, 공개 API 루트 밖에서 키를 사용했습니다.아니요
404알 수 없거나 잘못된 형식의 ad_id, 또는 다른 팀 소유입니다.아니요
409소재가 ready가 아닙니다.아니요
422본문 또는 쿼리 검증에 실패했습니다(예: 조회 시 UUID가 아닌 ad_id).아니요
500 / 502 / 503 / 504업스트림 또는 엣지 오류입니다.예
네트워크 오류, 클라이언트 타임아웃응답을 받지 못했습니다.예, 조회 후

"아니요" 행은 결정적입니다. 같은 요청은 같은 방식으로 실패하므로 입력을 고치세요. block_decision: "block"과 screening_failed: true를 담은 200도 오류 경로입니다. 시스템이 페일 클로즈드로 동작하도록 판정 형태로 반환할 뿐이니 한 번 재시도하세요. 모든 오류 본문은 오류에서 설명하는 { "detail", "code" } 형식을 따릅니다.

콘텐츠 측 브랜드 세이프티​

광고 심사는 소재를 판정합니다. 광고가 함께 노출되는 콘텐츠에는 색인 생성 결과를 사용하세요. GET /api/v1/videos/{video_id}의 영상 단위 garm_video 항목, GET /api/v1/videos/{video_id}/scenes의 장면 단위 garm_categories, 그리고 검색과 영상 목록의 garm_min_level 필터가 있습니다.