역할
당신은 AppLovin에서 광고 운영 책임자를 대신해 creative set을 대량 생성합니다. API 호출과 자격 증명 재사용은 이 스킬과 동일한 디렉토리의 scripts/creative_tuner.py를 통해 이루어집니다(ct.set_account() / ct.APL_MGMT_KEY / ct.APL_ACCOUNT_ID / ct.APL_MGMT_URL). 모든 도구 스크립트는 scripts/ 디렉토리에 있으며, 파일명은 _cs_*.py 형식입니다.
설치 / 설정 (최초 필독)
apl-creative-set/디렉토리 전체를~/.claude/skills/(또는 Windows%USERPROFILE%\.claude\skills\)에 넣으세요.scripts/디렉토리에서cp config.example.json config.json을 실행하고, 자신의 AppLovin 자격 증명을 입력하세요:accounts.<계정ID>.report_key= AppLovin Report Key;.mgmt_key= Management API Key.account_aliases에서 계정에 별칭을 부여할 수 있습니다(예:"myapp": "당신의계정ID"). 그러면 스크립트에서ct.set_account("myapp")로 사용 가능합니다.feishu섹션은 선택 사항이며,creative_tuner.py의 최적화 보고서에만 사용됩니다. creative set만 생성한다면 필요 없으며, 비워두면 됩니다.
config.json에는 실제 키가 포함되어 있으므로 절대 git에 커밋하거나 외부에 공유하지 마세요 (.gitignore에 추가하는 것을 권장).- 스크립트에서
ct.set_account("YOUR_ACCOUNT"), 상단의CAMP=""/PA_IDS/VIDS/PREFIX/PP/SUFFIX등 대문자 상수는 TODO 플레이스홀더입니다. 이번 작업 목표에 맞게 수정하세요.
이 스킬은 Claude가 SKILL.md를 읽고 실행합니다: 사용자가 캠페인, 생성할 동영상, 네이밍, CPP 등을 Claude에게 알리면, Claude는 아래 절차에 따라 스크립트 상단의 상수를 수정하고 호출합니다.
철칙
- ★ 작업 시작 전 반드시 사용자에게 다음 세 가지를 항목별로 확인하세요 (iOS/Android 양쪽이 다를 수 있으므로 이전 설정을 재사용하지 마세요):
① set 네이밍 규칙; ② CPP 접미사 추가 여부; ③ CPP
product_page에 입력할 값. 동일한 제품 라인이라도 반드시 질문하고, 기본값을 적용하지 마세요. - 자격 증명 안전: config.json의 어떤 key/secret/token/open_id도 절대 출력/표시/에코하지 마세요. 스크립트는 자격 증명을 print하지 않습니다.
- 기본 PAUSED: 새로 생성하는 set은 항상
status:"PAUSED"상태이며, 사용자가 확인한 후 활성화합니다(사용자가 "활성화"라고 말하면 →LIVE). - 요청된 소재 범위만 처리: 사용자가 "특정 접두사만", "최근 업로드된 것만" 등으로 제한하는 경우가 많으므로, 해당 조건을 엄격히 필터링하고 초과 생성하지 마세요.
- 각 단계 후 검증: 생성/연결/CPP/활성화 후에는 반드시
creative_set/list로 재확인하고, 응답 코드만 신뢰하지 마세요. - 숫자는 출처를 명시하고, 소수점 둘째 자리까지 정확히 표기하며, 의문점이 있으면 먼저 질문하고, 쓰기 작업 전에는 확인 후 실행하세요.
★ API 핵심 메커니즘 (시행착오 경험, 반드시 준수)
- 캠페인 연결은 add-to-campaigns를 기준으로 하세요. create가 자동으로 연결한다고 기대하지 마세요.
POST /creative_set/create본문에서 캠페인 관련 필드에는 두 가지 표기법이 있지만(단수campaign_id:"<내부id>"/ 복수campaign_ids:["<내부id>"]), 어느 것도 완전히 연결됨을 보장하지 않습니다 (복수형은 자동으로 무시되는 경우가 많음). 따라서 절차는 고정입니다: create로 생성 → 즉시add-to-campaigns로 안전하게 연결 →campaign_ids재확인. create 본문 (실제 사용):{"account_id":AID,"type":"APP","name":..,"status":"PAUSED","campaign_ids":["<내부id>"],"product_page":"","languages":[],"countries":[],"assets":[{"id":".."},..]}→ 응답{"id":..,"version":"V2"} - 캠페인 추가 연결:
POST /creative_set/add-to-campaigns, 복수 정수 배열 사용:{"campaign_ids":[<내부id>],"creative_set_ids":[..]}(campaign≤20, set≤50/배치). 가장 안정적입니다. 재확인 시campaign_ids에 대상 캠페인이 포함되어야 성공입니다. - set 수정 (이름/CPP/켜기끄기/소재 추가 제거):
POST /creative_set/update{"id":..,"type":"APP","status":"LIVE|PAUSED","name":..,"product_page":..,"assets":[..]}- update는 새 버전을 생성합니다.
assets를 전달하면 전체 덮어쓰기입니다 (추가가 아님): 삭제=해당 id를 전달하지 않음, 추가=전체 목록에 id 포함, 누락된 항목은 삭제됩니다 (동영상과 유지할 플레이어블을 반드시 포함). - 전달되지 않은 필드(name/product_page/campaign 연결 등)는 실제로 유지되며 손실되지 않습니다. 안전을 위해 켜기끄기/소재 수정 시 name+product_page+languages+countries를 함께 전달하는 것이 좋습니다.
- update에서
campaign_ids를 전달하는 것은 무효입니다 (200을 반환하지만 연결되지 않음) — 연결은 add-to-campaigns만 사용하세요.
- update는 새 버전을 생성합니다.
- Custom Product Page: 필드
product_page.- 기본 스토어 페이지 (Use default product page) =
product_page:""빈 문자열 (CPP 없음, 이름에 접미사 없음). - CPP 설정 시 해당하는 product_page 값 (문자열 slug, UUID 아님) 을 입력합니다. 목록 조회 엔드포인트는 없습니다. 특정 이름의 값을 확인하려면 전체 계정의 set을 스캔하여
product_page와 이름 접미사를 매핑합니다 (_cpp_map.py참조).
- 기본 스토어 페이지 (Use default product page) =
- 소재 유형 필드는 두 인터페이스에서 이름이 다릅니다 (혼동 주의):
asset/list(미디어 라이브러리)에서 필드는asset_type;creative_set의assets[]요소에서 필드는type.- 값: 동영상 =
VID_LONG_P/VID_SHORT_P(또는startswith("VID")); 플레이어블 =HOSTED_HTML.
- ★ 단일 creative set의 최대 소재 수 = 11: assets가 11개에 도달한 후 update로 추가하면 자동으로 거부됩니다 (에러 없이 200을 반환하지만 추가되지 않으며, 재확인으로만 발견 가능). 플레이어블을 추가하기 전에
len(assets)를 확인하고, 11개가 찼으면 1개를 먼저 삭제하여 공간을 확보한 후 추가하세요 (교체). creative_set/list및asset/list의 페이지 크기는 ≤100/200이며, 일부 페이지는 원시 리스트를 반환합니다 ({results:[]}아님). 파싱 시isinstance로 호환해야 합니다.asset/list의 페이지당 최대 100개: 페이지네이션 판단은len(res)<100으로 break해야 합니다 (200을 사용하면 첫 페이지에서 잘못 중단되어 처음 100개의 미디어만 가져옴).
시작 전 확인 사항 (사용자가 이미 제공하지 않은 경우)
- 대상 캠페인 (이름/ID) → config에 설정된 계정을 검색하여 내부 ID를 가져옵니다.
- 소재 범위: 어떤 동영상? 접두사 필터 (예: 특정 제품 라인), 업로드 시간 기준 (upload_time≥특정일), 상태 (일반적으로 ACTIVE 및 다른 set에서 사용되지 않음).
- 고정 플레이어블: 연결할 HTML 플레이어블 (사용자가 정확한 파일 이름 제공).
- 네이밍 규칙: 기본값
EN_동영상유형(AI/3D/JJ… 소재명에서 추출)_동영상명_top플레이어블그룹PA; 시리즈로 여러 동영상이 하나의 set에 들어가는 경우 →EN_유형_기본명그룹_top플레이어블그룹PA(기본명=끝의 일련번호 제거). - 시리즈 판단: 동일 개념+일련번호 (반복 1/4)를 하나의 그룹으로 합침; 혼동될 경우 먼저 질문 (예: "기본형/고급형"을 분리할지).
- CPP: product_page 설정 여부, 입력 값, 이름에 접미사 추가 여부.
표준 절차 (스크립트 파이프라인, 모두 scripts/ 디렉토리)
_apl_active_assets.py→ ACTIVE 소재를 가져와 중복 제거 (_apl_active_assets.json)._cs_plan_scan.py→ 고정 플레이어블 ID + 후보 동영상 찾기 (접두사+is_video+기존 set에서 사용되지 않음+시간 필터) →_cs_scan.json. 먼저 상단의PREFIX/CAMP/targets를 수정하세요._cs_group.py→ 후보를 시리즈/단일 소재별로 그룹화, 이름 생성 →_cs_build_plan.json._cs_plan_show.py로 출력하여 사용자 확인 가능._cs_build_all.py(멱등성: 이름으로 중복 확인 후 이미 생성된 set 건너뜀) → 각각 create(PAUSED, campaign_id 포함) + add-to-campaigns 안전 연결 + 재확인 →_cs_create_result.json.- (선택 사항)
_cpp_apply.py all→product_page설정 + 이름 접미사 추가 (먼저 상단PP/SUFFIX수정). - 사용자 확인 후
_cs_enable.py→ status→LIVE (name+product_page 포함하여 손실 방지) + 재확인. - 샘플 검사:
_cs_spotcheck.py <id..>로 소재 구성 확인 (동영상 수 + 플레이어블 수).
간소화 경로: 소량 지정 동영상 직접 그룹화 (동영상명을 알고 있음, 전체 스캔 불필요)
적용: 사용자가 새로 몇 개의 동영상을 업로드하고, 직접 그룹화를 요청한 경우.
_cs_find_new.py <키워드..>→ 미디어 라이브러리에서 이름으로 동영상 검색 (전체 이름+id+상태+업로드 시간, 읽기 전용); 해당 동영상이 맞는지, 상태가 ACTIVE인지 확인._cs_en_new_create.py상단의VIDS=[(핵심명,동영상id),..]/PA_IDS(고정 플레이어블) /CAMP수정; 먼저 철칙 0에 따라 사용자에게 네이밍+CPP 확인.python _cs_en_new_create.py(dry-run으로 중복 확인+계획 출력) →go(PAUSED 생성 + 기본 스토어 페이지""+ 언어를 캠페인에 따르도록 설정하지 않음 + add-to-campaigns + 재확인: 연결/assets 수/pp/status).- 사용자가 활성화 요청 →
_cs_en_new_enable.py(_cs_en_new_ids.json읽기, name/product_page/languages/countries 명시적 포함하여 손실 방지, status→LIVE + 재확인).
set의 플레이어블 추가 / 제거 / 교체
set 이름 키워드로 특정 플레이어블을 일괄 추가/제거 (update로 전체 assets 덮어쓰기, name/product_page/languages/countries/status 유지, 실행 후 재확인):
- 추가:
_cs_add_pa.py <플레이어블id> <set이름키워드> [enonly](dry-run) →go실행.enonly=EN_으로 시작하는 그룹에만 적용 (다국어 타겟팅 그룹 제외). 멱등성: 이미 포함된 경우 건너뜀; 소재가 11개인 그룹은 경고 후 건너뜀 (먼저 1개를 삭제하여 공간 확보 필요). - 제거:
_cs_rm_pa.py <플레이어블id>(dry-run) →go. 안전 장치: 제거 후 해당 set에 최소 1개의 동영상과 1개의 플레이어블이 있어야 하며, 그렇지 않으면 빨간색 경고 후 수정을 건너뜀. - 교체 = 전체 assets 목록에서 기존 id를 새 id로 교체한 후 update.
- 플레이어블 id는
_cs_find_new.py <키워드>를 사용하여 미디어 라이브러리에서 이름으로 검색 (HOSTED_HTML).
전달 멘트
보고: 몇 개의 그룹(시리즈/단일)을 생성했는지, 포함된 동영상 수, 각 그룹 = 해당 동영상 + N개의 플레이어블, 모두 PAUSED + 캠페인 연결 + CPP 설정, "그룹명→동영상" 목록 첨부; 검토되지 않았거나 업로드되지 않은 소재에 대해 알림 (_b8_leftover.py 방식을 사용하여 REJECTED/미업로드 등 상태별 분류 가능). 사용자가 "활성화"라고 말할 때까지 기다렸다가 6단계 실행.