CommunityResearch & Data Analysisgithub.com

AppLovin Creative Set Batch Creator

Batch-creates AppLovin creative sets with video + playable grouping, campaign linking, CPP configuration, all defaulting to PAUSED.

What is AppLovin Creative Set Batch Creator?

This skill enables an AI agent (e.g., Claude Code, Cursor) to automate the bulk creation of AppLovin creative sets. It helps media buyers and UA managers efficiently group video and playable assets into creative sets, attach them to campaigns, set Custom Product Pages (CPP), and ensure all new sets start in PAUSED status for safety. The skill supports multiple AppLovin accounts via a reusable configuration file (`config.json`) and leverages a set of Python scripts under `scripts/` prefixed with `_cs_*.py`. Key workflows include: scanning active assets, planning set groupings (by video series or single assets), creating sets with campaign attachment using the `add-to-campaigns` endpoint (avoiding a known API pitfall), and applying CPP. The agent strictly follows a checklist before each run: confirming naming rules, CPP suffix usage, and the `product_page` value. It also enforces security rules (never exposing credentials), validates each step by re-reading data, and prompts the user before any write operation. The skill is designed for platforms like Claude Code and can be extended to other AI coding agents.

Works with✓Claude Code~Codex CLI~Cursor
npx skills add 2462268949-hue/apl-creative-set

Installed? Explore more Research & Data Analysis skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

Role

You help the media buyer lead batch-create creative sets in AppLovin. All API encapsulation and credential reuse are handled by the scripts/creative_tuner.py script in the same directory (ct.set_account() / ct.APL_MGMT_KEY / ct.APL_ACCOUNT_ID / ct.APL_MGMT_URL). All tool scripts are located in scripts/ and are named _cs_*.py.

Installation / Configuration (Read First)

  1. Place the entire apl-creative-set/ directory into ~/.claude/skills/ (or %USERPROFILE%\.claude\skills\ on Windows).
  2. In scripts/, run cp config.example.json config.json, then fill in your own AppLovin credentials:
    • accounts.<accountID>.report_key = AppLovin Report Key; .mgmt_key = Management API Key.
    • account_aliases allows you to give accounts nicknames (e.g., "myapp": "your-account-id"), so scripts can use ct.set_account("myapp").
    • The feishu section is optional and only used by the tuning daily report in creative_tuner.py; you don't need it just to create creative sets—leave it empty.
  3. config.json contains real secrets, never commit it to git / share it (add it to .gitignore).
  4. In the scripts, anywhere you see ct.set_account("YOUR_ACCOUNT") or uppercase constants like CAMP="" / PA_IDS / VIDS / PREFIX / PP / SUFFIX at the top are TODO placeholders; modify them according to your current goal.

This skill is driven by Claude reading SKILL.md: you tell Claude your goal (campaign, videos to build, naming, CPP), and Claude modifies the constants at the top of the scripts and runs them following the flow below.

Hard Rules

  1. ★ Before each session, must confirm these three items with the user, item by item (iOS/Android differ, never reuse previous answers): ① Set naming rule; ② Whether to add CPP suffix; ③ What value CPP product_page should take. Even if it's the same product line as last time, ask again; do not assume defaults.
  2. Credential Security: Never output/print/echo any key/secret/token/open_id from config.json. Scripts must never print credentials.
  3. Default PAUSED: All newly created sets must have status:"PAUSED". Only enable them after user confirms everything is correct (user says "enable" → then LIVE).
  4. Only the requested asset scope: Users often limit to "only certain prefixes" or "only recently uploaded" — strictly follow the filter; don't create extra.
  5. Re-read to verify every step: After create/mount/CPP/enable, must re-read via creative_set/list to confirm, not rely on response codes.
  6. Numbers should state source, precise to 2 decimal places. If unsure, ask first. Before write operations, confirm then execute.

★ API Key Mechanics (Pitfalls encountered, must follow)

  • Always attach campaigns via add-to-campaigns; do not rely on create to attach them reliably. The POST /creative_set/create body can use either singular campaign_id:"<internal_id>" or plural campaign_ids:["<internal_id>"], but neither guarantees attachment (the plural is often silently ignored). So the fixed flow is: create → immediately add-to-campaigns to backfill → re-read campaign_ids to verify. create body (production version): {"account_id":AID,"type":"APP","name":..,"status":"PAUSED","campaign_ids":["<internal_id>"],"product_page":"","languages":[],"countries":[],"assets":[{"id":".."},..]} → returns {"id":..,"version":"V2"}
  • Backfill campaign: POST /creative_set/add-to-campaigns, uses integer array: {"campaign_ids":[<internal_id>],"creative_set_ids":[..]} (max 20 campaigns / 50 sets per batch). Most reliable. Success = target campaign appears in the re-read campaign_ids.
  • Modify set (rename/CPP/toggle/assets add/remove): POST /creative_set/update {"id":..,"type":"APP","status":"LIVE|PAUSED","name":..,"product_page":..,"assets":[..]}
    • update generates a new version. Passing assets is a full replacement (not append): to remove, don't include that id; to add, include the id in the full list; any omitted id will be deleted (ensure you keep the video + playables you want to retain).
    • Fields not passed (e.g., name/product_page/campaign association) are observed to be preserved in practice; to be safe, when toggling or changing assets, include name + product_page + languages + countries to avoid loss.
    • Passing campaign_ids in update does not work (returns 200 but doesn't attach) — use only add-to-campaigns.
  • Custom Product Page: Field product_page.
    • Use default store page (no CPP) = product_page:"" (empty string, no suffix in name).
    • When setting CPP, fill the corresponding product_page value (string slug, not UUID). There is no listing endpoint; to confirm the value for a given name, scan all sets in the account and correlate product_page vs name suffix (see _cpp_map.py).
  • Asset type field differs between two endpoints (easy to trip):
    • In asset/list (media library) the field is asset_type;
    • In creative_set's assets[] elements the field is type.
    • Values: Video = VID_LONG_P / VID_SHORT_P (or starts with "VID"); Playable = HOSTED_HTML.
  • ★ Max assets per creative set = 11: Once assets reach 11, any update to add more is silently rejected (returns 200 but doesn't add; you can only detect it by re-reading). Before adding a playable, check len(assets); for a full set of 11, you must first delete one to make room (replace), otherwise the addition fails.
  • creative_set/list and asset/list pagination size ≤100/200, and some pages return bare list (not {results:[]}); parsing must use isinstance to be compatible. asset/list page limit = 100: for pagination, break when len(res)<100 (using 200 would stop at first page, returning only the first 100 media).

Before Starting, Ask (unless user has already provided)

  1. Target campaign (name/ID) → locate across configured accounts, get internal id.
  2. Asset scope: Which videos? Prefix filter (e.g., a product line prefix), upload time boundary (upload_time ≥ date), status (usually ACTIVE and not already used by another set).
  3. Fixed playables: Which HTML playables to attach (user will give exact filenames).
  4. Naming rule: Default: EN_videotype(AI/3D/JJ…takenfromassetname_slot)_videoname_topplayablegroupPA; if multiple videos from a series go into one set → EN_type_basenamegroup_topplayablegroupPA (basename = remove trailing index).
  5. Series detection: Same concept + trailing number (iteration 1/4) → group together. If ambiguous, ask first (e.g., "basic/advanced" — should they be split?).
  6. CPP: Whether to set product_page, what value, and whether to add a suffix to the name.

Standard Flow (Script Pipeline, all in scripts/)

  1. _apl_active_assets.py → Pull ACTIVE assets, deduplicate → _apl_active_assets.json.
  2. _cs_plan_scan.py → Find fixed playable ids + candidate videos (prefix + is_video + not already used by existing sets + time filter) → _cs_scan.json. First modify PREFIX / CAMP / targets at the top.
  3. _cs_group.py → Group candidates by series/single asset, generate names → _cs_build_plan.json. Can use _cs_plan_show.py to print for user confirmation.
  4. _cs_build_all.py (Idempotent: check by name, skip duplicates) → Create each (PAUSED, with campaign_id) + backfill add-to-campaigns + re-read verify → _cs_create_result.json.
  5. (Optional) _cpp_apply.py all → Set product_page + add name suffix (first modify PP / SUFFIX at top).
  6. After user confirms, _cs_enable.py → status→LIVE (include name+product_page to avoid loss) + re-read.
  7. Spot check: _cs_spotcheck.py <id..> to inspect asset composition (video count + playable count).

Lightweight Path: Create Sets for a Few Specified Videos Directly (known video names, skip full scan)

Use when user just uploaded a few videos and wants sets built from them.

  1. _cs_find_new.py <keyword..> → Search media library by name for videos (full name+id+status+upload time; read-only). Verify it's the right one and status is ACTIVE.
  2. Modify _cs_en_new_create.py top: VIDS=[(core_name,video_id),..] / PA_IDS(fixed playables) / CAMP; first confirm naming+CPP per hard rule 0. python _cs_en_new_create.py (dry-run: check duplicates + list plan) → go (create PAUSED + default store page "" + no language settings (follows campaign) + add-to-campaigns + re-read verify: campaign attachment/assets count/pp/status).
  3. User says enable → _cs_en_new_enable.py (reads _cs_en_new_ids.json, explicitly includes name/product_page/languages/countries to prevent loss, status→LIVE + re-read).

Add / Remove / Replace Playables in a Set

Batch add/remove a specific playable by set name keyword (via update with full asset replacement, preserve name/product_page/languages/countries/status, re-read after execution):

  • Add: _cs_add_pa.py <playable_id> <set_name_keyword> [enonly] (dry-run) → then go. enonly = only apply to sets starting with EN_ (excludes localization-targeted sets). Idempotent (skips if already present); sets with 11 assets are warned and skipped automatically (you must first delete one to make room).
  • Remove: _cs_rm_pa.py <playable_id> (dry-run) → go. Safety guard: after removal, the set must still have ≥1 video and ≥1 playable; otherwise it's flagged red and skipped.
  • Replace = swap old id for new id in the full asset list, then update.
  • To find playable id, use _cs_find_new.py <keyword> to search media library by name (HOSTED_HTML).

Delivery Script

Report: How many sets created (series/singles), how many videos covered, each set = corresponding video + N playables, all PAUSED + campaign-attached + CPP, plus a "set name → video" mapping; flag any assets that are rejected/not uploaded (can use full-status scan like _b8_leftover.py to classify REJECTED/not uploaded). Wait for user to say "enable" before running step 6.

Related Skills