Skip to content

t1k:marketing:analytics:applovin-exports

FieldValue
Moduleanalytics
Version1.16.0
Effortmedium
Tools—

Keywords: ad format mix, analyze them for me, analyze this, applovin campaign export, arpdau, axon ua report, campaign name taxonomy, clone decision, cohort maturity, compare competitor games, competitive intelligence ad export, competitor ua analysis, competitor ua data, competitor ua teardown, creative concept ranking, d28 retention filtering, d28 total roas, d7 retention filtering, dau dedup, days to peak spend, drop-a-csv, follow decision framework, follow or not, imp/dau, is this game worth copying, matured roas, max activity report, max mediation export, retention shape vs level, roas goal, scaling velocity, should we build something like this competitor, should we follow this game, size/type, strip the iap test, user_activity_mediation_report, which game has more potential

/t1k:marketing:analytics:applovin-exports

Read a manually-exported AppLovin AXON UA campaign CSV or MAX mediation/activity CSV correctly — for your own apps or for a competitor’s. Both exports look like flat rows you could average straight down a column. Doing that is silently wrong: cohort columns report partial values until they mature, DAU repeats across format rows, and the interesting signal (geo, creative concept, optimization window) lives only in free-text campaign names.

Only part of this skill is cheap-tier work — the mechanical column math (§1–2, §5) is fully specified before it starts, but the creative ranking, scaling classification, and follow/clone recommendation (§3–4, §6) require reading the result and forming a judgment, and stay on a reasoning tier (routing SSOT: rules/orchestration-rules.md § “Task-Type → Agent Routing”):

StepAgent
Recompute a grouped ratio (CPI/CTR/IR/CPM, ARPDAU) from Σnumerator/Σdenominator per the stated formulat1k-metrics-reporter
Apply the §2 maturity filter (age >= K + 1) and report matured-spend coverage per metrict1k-metrics-reporter
Dedupe DAU/DAV on (package, platform, day) before a DAU-denominated figuret1k-metrics-reporter
Decode campaign-name tokens against Goal type / Day target / Bidding strategy (§3)t1k-list-classifier — the token-classification rubric is stated in §3
Rank creative concepts, classify scaling status, or make a follow/clone call (§3–4, §6)stays on a reasoning tier — these read the computed numbers and form a judgment

The maturity/dedup/ratio math is where this skill’s correctness rules live — delegate exactly that arithmetic and keep the interpretive axes (§6) on a reasoning tier; a cheap agent should never be asked to decide whether a game is worth cloning.

  • Analyzing a dropped AXON UA campaign CSV (own app or competitor) — spend, installs, CPI, ROAS, retention
  • Analyzing a dropped MAX mediation/activity CSV — DAU, ARPDAU, eCPM, fill rate per ad format
  • Decoding what a competitor’s campaign names encode (geo / creative concept / optimization window) and ranking creative concepts by performance
  • Classifying a campaign’s scaling status (ramping / live / throttling / ended) from its daily spend curve
  • Building competitive-intel or portfolio recommendations that combine the UA side (what they buy) with the monetization side (what they earn)
  • Deciding whether to follow/clone a competitor game from its exports — “which of these games has more potential”, “should we build something like this”, “is this worth copying”

Not this skill: live API pulls across networks (→ t1k-marketing-analytics-crawldata) or MAX SDK/mediation setup and the Reporting/Management APIs (→ t1k-marketing-monetization-applovin-max) — see Related skills below.

Recognizing this data with little or no prose

Section titled “Recognizing this data with little or no prose”

In practice a CSV shows up as a bare path or “analyze this for me” with no other context — match on the file, not the sentence:

SignalAXON UA exportMAX mediation/activity export
Default export filenamereport_<timestamp>.csv (e.g. report_2026-08-11_11_11_14_7714.csv)user_activity_mediation_report_<timestamp>.csv
Distinctive header columnsCampaign name, Spend, ROAS goal, Goal type, Day target, D7 retention, D28 total ROASSize/Type, DAU, DAV, ARPDAU, Fill Rate, Display Rate, Imp/dau

If the filename alone is ambiguous (AXON’s report_*.csv is generic), open the file and check the header row against the column list above — either table (§1 / §5) confirms which export you’re holding within the first line.

One row per campaign per day. Core columns: Date, Package name, Account Name, Campaign name, Platform, Spend, Installs, Impressions, Clicks, CPI, CPM, CTR, IR, Goal type, Day target, Bidding strategy, ROAS goal. Then, per K in {1,3,7,14,28}: DK retention; per K in {0,1,3,7,14,28,60,90,180,1Y}: DK IAA ROAS, DK IAP ROAS, DK total ROAS. Full 68-column reference: references/axon-ua-columns.md.

Recompute a grouped ratio column from its numerator and denominator — never average the per-row value. CPI, CTR, IR, and CPM are all per-row ratios, correct on every row — the trap is purely in aggregation. Verified across three corpus apps: excluding zero-install rows (uninformative — every candidate formula trivially reads 0% there), 1,076 of 1,080 rows remain. On those, IR = installs / impressions matches 1,076/1,076 at ±0.01pp; IR = installs / clicks matches 0/1,076 at that tolerance but a spurious 4/1,076 at a looser ±0.02pp, because IR values near 0.07% turn a 0.02pp absolute window into a ~25% relative window — state your tolerance, or “close enough” silently depends on how tight it is. CTR = clicks / impressions matches 1,080/1,080. Averaging the per-row column instead of Σnumerator / Σdenominator isn’t rounding noise either: on this corpus it overstates CPI by +25.5% ($4.05 vs $3.23) and CPM by +26.3% ($51.35 vs $40.66), and shifts IR by +5.4%. Recompute: CPI = Σspend / Σinstalls, CTR = Σclicks / Σimpressions, IR = Σinstalls / Σimpressions, CPM = Σspend / Σimpressions × 1000. The MAX mediation export has the identical trap for ARPDAU — see §5.

2. Cohort maturity filtering — the correctness rule

Section titled “2. Cohort maturity filtering — the correctness rule”

DK retention and DK ROAS are cohort metrics: a row for installs acquired on day D only carries a trustworthy DK value once the cohort has actually aged that long. The threshold is age >= K + 1, not age >= K — a cohort acquired on D is measured on D+K, and at exactly age == K that measurement day is still in progress, so the export reports a fraction of the true value. The bias is always downward.

Retention and ROAS fail differently when immature — only one looks obviously wrong. Retention zero-fills (an immature row reads a literal 0%). ROAS carries a running cumulative-to-date, stamped into every window K >= age and frozen at the true DK only once day K has elapsed — so an immature DK cell is a live partial accrual, not a stale copy of the last completed window. Averaging either over unfiltered rows is wrong (toward zero for retention, understated-and-drifting for ROAS), but only the zero-fill is visually suspicious.

Consequence — the immature cell is a usable lower bound. Because it is a live partial rather than a stale value, an unmatured DK reads below the eventual DK and converges upward, so it bounds an in-flight cohort from below. Never average these cells as if matured; do use them to bracket.

Filter to matured rows PER METRIC, independently, then spend-weight. D7 and D28 have different matured row sets from the same export — don’t reuse one filter for both.

matured(row, k) := (export_max_date - row.date).days >= k + 1
d28_roas = spend_weighted_avg(row.d28_total_roas for row in rows if matured(row, 28))

Always report matured-spend coverage next to any DK claim. State the coverage percentage every time you state a DK figure past D7 — recently-launched apps skew this hardest, since the covered slice is also the earliest and best-performing.

Below ~5% of matured spend, emit n/a (coverage X%) instead of a number. A coverage footnote is not enough — readers compare adjacent table cells and skip footnotes.

Coverage % is necessary but not sufficient — check segment coverage independently. A figure can clear the floor and still be drawn from one platform, one campaign, or one geo. Gate: if any segment holding ≥10% of app spend has <10% of its own spend matured, the figure is segment-biased — label it or split it, whatever the headline coverage says.

Exception: a ratio or accrual multiple between two cohort horizons needs ONE row-set, not two. Per-metric filtering (above) is correct for a level, but a DK/D1 decay ratio or a DK→DJ accrual multiple divides two levels drawn from different populations — the longer horizon’s matured set is a strict subset skewed toward the earliest, usually best-performing cohorts. Rule: filter per metric for a level; filter BOTH metrics on the longer horizon’s matured set for a ratio or multiple.

Detection, when you are reading a table rather than computing one: compute the implied multiple and compare it against the per-cohort-week distribution of that same multiple measured on rows matured for the longer horizon. A value outside the observed band is a population-mismatch suspect, not a discovery. The band also drifts down as cohorts get more expensive, so apply the low end when extrapolating a scaled cohort.

The band is genre- and mix-specific — never transplant it. A pure-IAA title’s D28→D60 tail can be ~25%+ off an IAP-heavy title’s in the same corpus. Measure the tail on the app in front of you, or state that you assumed it.

The export’s final day is a partial day. It was pulled mid-day, so the last calendar date always under-reports spend. Exclude it from status classification, peak-spend detection, and trailing-window comparisons — including it falsely reads every live campaign as “throttling” (see §4).

Every rule above is measured against real exports — the retention-age comparisons, the D28/D60 monotonic-gap corpus stats, the 5%-coverage Arrow Puzzle case study, and the D28→D60 population-mismatch worked examples are in references/cohort-maturity-methodology.md.

Geo, creative concept, and optimization window usually have no dedicated column — they exist only as tokens inside Campaign name, and every buyer invents their own grammar. Decode by tokenizing on the delimiter (_, -, or mixed) and classifying each token against the columns that already disambiguate most of them: a token matching Goal type (e.g. iaa, bldroas) or Day target (d7, d28) is confirmed, not guessed; a token matching Bidding strategy (cpi, cpm) is confirmed; a token that is 6 digits is almost always a creation datestamp (check both YYMMDD and DDMMYY — different buyers use opposite conventions in the same dataset); tokens present on every campaign from one account carry no discriminating information (buyer/team handles) and should be dropped from ranking, not ranked as if they were concepts. Whatever tokens remain after removing app/platform/network/goal/window/geo/date/ billing are the creative-concept slot.

Worked example (one buyer’s real grammar, tokenized and classified end to end): references/campaign-taxonomy-worked-example.md.

Rank decoded creative concepts by spend, then by matured, spend-weighted ROAS (§2) within each concept group — never on immature rows, and never on CTR as a quality signal (AppLovin counts near any interaction with playable/interactive-endcard inventory as a click; CTR here correlates with ad-format mix, not creative quality — cross-check against IR (installs ÷ impressions) instead). A concept with a lower CTR than the account baseline but a higher matured ROAS is common and is not a data error.

Before ranking or contrasting on two dimensions at once — decoded or columned — print the spend cross-tab first. Goal type (a real column) and geo (a decoded taxonomy token, above) can be perfectly confounded rather than merely correlated: in one account, ROAS-IAA/ ROAS-Hybrid campaigns were 100% WW and ROAS-IAP/CPP campaigns were 100% US_T1 — the off-diagonal cell was exactly $0, not merely thin. On that data, “IAP retains better than IAA” and “US tier-1 retains better than WW” are the identical statement on every row; no re-slicing separates them, and recommending a budget shift on either reading risks moving spend on the wrong lever. Rule: cross-tab spend by both factors before any two-factor cut — an empty off-diagonal means the comparison is unanswerable from this export, not merely underpowered.

Classify a campaign from its daily spend series, relative to the export’s last complete day — never wall-clock “today” and never the partial final day (§2):

  1. launch = first day with spend > 0.
  2. peak_spend, peak_date = max daily spend and the day it occurred.
  3. days_to_peak = (peak_date - launch).days + 1.
  4. status, evaluated against the last complete day’s spend vs peak_spend:
    • ended — no spend for 2+ consecutive days before the last complete day
    • throttling — last complete day’s spend < 60% of peak
    • live — otherwise (holding or still climbing)

A short days_to_peak (1–2 days) on a large budget is a burst test, not organic growth — flag it separately; it behaves differently in cohort-quality trend analysis (§2) because its early cohorts are disproportionately weighted. Compare days_to_peak across a buyer’s campaigns to infer their testing cadence — one buyer that reached 50%/80% of eventual peak in 25/36 days on one campaign and 1/9 days on another is telling you it graduated a validated campaign into an aggressive scale, not that its process is inconsistent.

5. MAX mediation CSV column reference + traps

Section titled “5. MAX mediation CSV column reference + traps”

One row per ad format per app-day (typically 7 formats × N days). Core columns: Day, Package Name, Platform, Country, Size/Type (ad format), DAU, DAV, Requests, Impressions, Clicks, Est. Revenue, eCPM, No Fills, Loads, Load Errors, Fill Rate, Display Rate, Req/dau, Imp/dau. Full column reference + the four verified derivation formulas: references/max-mediation-columns.md.

  • DAU/DAV repeat the app-level total on every per-format row. Summing the column inflates DAU by roughly the number of formats reported (typically 7×). Deduplicate on (package, platform, day) before any DAU-denominated figure (ARPDAU, imp/DAU) — take the value once per app-day, never sum across format rows.
  • Platform carries trailing whitespace ("android ", not "android"). Strip every key before grouping or joining, or one platform silently becomes two.
  • Recompute ARPDAU yourself: Σ Est. Revenue / Σ deduplicated DAU. The provided ARPDAU column is per-format (format revenue / app DAU) and reads as a plausible per-app number if you don’t check — trust the recomputation, not the column.
  • A zero row means “format not running,” not “zero performance.” Averaging an inactive format’s zero row into a per-format metric understates every active format. Omit inactive rows (all-zero impressions and revenue) and report the absence as a finding — which formats a competitor doesn’t run is itself signal.
  • Banner imp/DAU in the hundreds is refresh, not an anomaly. Banners auto-refresh every 15–30s, so a banner-on app can show 200–300+ impressions per DAU per day. Never compare banner imp/DAU to interstitial/rewarded imp/DAU directly — compare banner to banner, and use revenue per DAU (not eCPM) as the cross-format comparison metric, since banner eCPM is structurally tiny (refresh-diluted) next to full-screen formats.
  • Requests may be filled-requests-only, not total demand — verify before relying on it. In one export, Fill Rate matched Loads / (Requests + No Fills) on every active row (not 1 - No Fills/Requests, which produced negative rates on ~27% of rows) — implying total demand is Requests + No Fills, not Requests alone. This isn’t documented by AppLovin and hasn’t been cross-checked against a second export or account; re-derive the identity above against your own export before trusting Requests or Fill Rate at face value.

Join the decoded UA side (§1–4: what a title buys, at what efficiency, with what creative/geo mix) against the decoded monetization side (§5: what it earns per user, per format) to produce recommendations — a target ROAS goal and CPI ceiling from the UA side’s payback curve, a creative-concept starting point from the taxonomy ranking, an ad-format mix from the mediation side’s revenue-per-DAU split. This is read/analysis-only — nothing here calls a live ad-network API or mutates a campaign, bid, budget, or placement. See rules/ad-network-mcp-write-safety.md.

Follow/clone decision — “should we build something like this?”

Section titled “Follow/clone decision — “should we build something like this?””

Exports also answer the question they’re actually pulled for: given several competitors’ exports, which one has more potential, and which should this studio build something like? Eight axes, each independently decisive enough to flip a recommendation:

  1. Retention SHAPE (D28/D1 ratio) beats retention LEVEL — a great D1 with a collapsed D28 is a good hook, not a good game.
  2. Strip the IAP. Recompute D28 from the IAA column alone against CPI — if IAA-only doesn’t clear the bar, following this game means committing to build an economy, not a monetization setting.
  3. Matured-spend coverage (§2) is a confidence gate, and thin coverage skews optimistic — the earliest-maturing cohorts are usually the cheapest and best-performing, and check it per segment, not just in aggregate.
  4. Revenue-per-install required to clear is set by CPI — a higher ROAS at a much higher CPI is a more fragile business, and CPI tends to rise as spend scales, so never rank candidates (or campaigns within one) on ROAS% alone — rank by profit contribution.
  5. Observed payback (§2 matured rows) beats projected payback — prefer a candidate whose D60+ is actually readable over one that’s still thin.
  6. Category TAM evidence — one game’s ROAS says nothing about category size; look for aggregate category spend and its trend.
  7. Genre adjacency to your existing portfolio — same-genre candidates reuse funnel, creative library, and audience.
  8. What to steal even from a rejected candidate — separate “don’t build this game” from “don’t copy anything about it” (e.g. a rejected game’s scaling cadence, per §4, may still be worth copying).

A follow decision from these exports alone is under-informed — always state which of these apply: single network only (AppLovin, not Meta/Google/TikTok), no organics (paid installs only, no k-factor), no geo dimension, no ARPPU, no monetization data unless a MAX export is also present, and long windows (D60+) unreadable on recently-launched apps. Cheapest gap-closer: re-pull in ~30 days once matured-spend coverage (§2) would be near-full.

Full worked example (real numbers), the eight axes in depth, the limits checklist, and the cross-candidate comparison table shape: references/follow-decision-framework.md.

  • age >= K (not K + 1) silently understates every DK metric — always add the +1.
  • Averaging a DK column with immature rows still in it drags the number toward zero (retention zero-fill) or understates it (ROAS carries a running cumulative-to-date, not a stale forward-fill) — filter first, per metric, then spend-weight.
  • An immature DK cell is a valid lower bound, not noise — usable for bounding an in-flight cohort, just never averaged in as a completed read.
  • A DK/D1 ratio or DK→DJ accrual multiple is the one case where per-metric filtering is wrong — filter both metrics on the longer horizon’s matured set, or the ratio compares two different cohort populations. Then sanity-check the resulting multiple against the per-cohort- week distribution — an outlier is a population-mismatch suspect, not a discovery.
  • Below ~5% matured-spend coverage, print n/a (coverage X%) rather than a number — a footnote does not survive contact with a reader comparing adjacent cells.
  • Coverage % is not sufficient: if a segment holding ≥10% of app spend has <10% of its own spend matured, the figure is segment-biased even at healthy headline coverage.
  • A ROAS percentage without its spend is not comparable to another — rank tables by profit contribution (spend × (ROAS − 1)), never by ROAS% alone; treat a high ROAS at low spend as a hypothesis until it’s held up as CPI rose — and never carry a fixed pp-per-dollar decay coefficient between datasets, the relationship is non-linear even within one.
  • Coverage answers “can I trust this number”; spend-relevance and scale-durability answer “does this number matter, and will it hold” — clearing one does not mean the other clears too.
  • Never average a per-row ratio column (CPI, CTR, IR, CPM) — each is correct per row, but averaging overstates CPI/CPM by ~25% on this corpus. Recompute from Σnumerator / Σdenominator over the group; IR = installs/impressions, not installs/clicks despite the name. Same principle as the ARPDAU trap below (§5).
  • ROAS goal is a target, never an observation; Goal type (IAA/IAP/Blended) + Day target tell you which of the three ROAS columns the campaign is actually optimizing toward — an IAA/D28 campaign and a Blended/D7 campaign are not comparable at face value.
  • The export’s final calendar day is always partial — exclude it from status/peak/trend calls.
  • Buyer/team-handle tokens that appear on every campaign from one account are not creative concepts — drop them from concept ranking.
  • CTR is not a creative-quality signal on AppLovin exports; use IR (installs ÷ impressions).
  • Two dimensions can be perfectly (not just partially) confounded — cross-tab spend by both before a two-factor cut, or an empty off-diagonal will get misread as a real effect.
  • Summing DAU/DAV across an app’s format rows inflates by the format count — dedupe first.
  • Trust recomputed ARPDAU (revenue ÷ deduped DAU), not the provided per-format column.
  • Zero-impression/zero-revenue format rows mean “not running” — omit, don’t average in as zero.
  • Banner imp/DAU in the hundreds is refresh, not anomalous; never compare banner to full-screen formats on imp/DAU or eCPM — use revenue/DAU.
  • t1k-marketing-analytics-crawldata — live, scheduled, multi-network API crawl (Mintegral/Google Ads/Unity spend + Adjust/MAX/CloudX/AdMob revenue) merged per package into one Profit/ROAS report, for our own portfolio only. No cohort-maturity filtering (its data isn’t cohort-shaped) and no campaign-name decoding. Use it for a scheduled own-portfolio pipeline; use this skill for a manually-dropped CSV, own or competitor.
  • t1k-marketing-monetization-applovin-max — MAX SDK integration, ad-unit network config (Ad Unit Management API), and the Reporting API’s estimated_revenue semantics (a different data shape from the mediation CSV export in §5). Use it for SDK/mediation setup or live API pulls; use this skill for the dashboard CSV export.

Read/analysis-only. Any mutating ad-network call (pause, budget, bid, waterfall) is a separate, high-risk action gated by rules/ad-network-mcp-write-safety.md — never inferred or triggered from this skill.

Domain-knowledge playbook, no bundled scripts. If your project has a reference implementation of matured()/scaling-velocity/DAU-dedup (e.g. a regenerate.py / mediation.py pair), point to it from your project’s CLAUDE.md as the executable SSOT and keep it in sync with this skill via /t1k:sync-back whenever a pass surfaces a new correctness rule.