1. External requests / not fully self-contained

This is the single most common rejection category, and it hits AppLovin, Meta, Unity Ads, Vungle, and TikTok hardest — all five explicitly prohibit external requests. A Luna export that references assets by relative path, loads a font from a CDN, or has any script (even an innocuous analytics beacon) making a network call at runtime will get flagged, because these networks render the creative in a locked-down webview with no network access, or reject anything that isn't verifiably self-contained during review.

The fix is to embed every image, audio clip, font, and script directly into the HTML — base64 or base122 data URIs decoded at runtime, not fetched. Audit anything your build pipeline injects into the output (analytics, tracking pixels, third-party tags) for outbound calls; a single stray beacon script is enough to fail review even if the game logic itself never touches the network.

2. Oversized file

Every network here caps creative size at 5 MB, and Meta and Unity Ads specifically recommend staying nearer 2 MB. Luna exports tend to grow past these limits once every asset that was previously loaded on demand gets inlined as base64 — base64 encoding itself adds roughly 33% overhead on top of the original binary size, so a build that looked fine at the source-asset stage can land well over budget once packaged.

The fix is usually asset-level: compress textures and audio before export, trim unused Unity engine assets, and re-check total size after encoding, not before. Running your packaged output through PlayableKit's free size checker before you submit shows the actual post-encoding size per network target, not just the raw export size.

3. Wrong or missing click-through API

Each network expects a different call for its CTA, and none of them are interchangeable — shipping the wrong one is a near-automatic rejection because the network's review tooling looks for its specific API to confirm the click-through actually works:

  • Google AdsExitApi.exit(), via Google's own hosted exitapi.js.
  • AppLovin and Unity Ads — MRAID 2.0 mraid.open(url), and only after MRAID signals ready.
  • MetaFbPlayableAd.onCTAClick(); auto-redirects or window.open() are not accepted substitutes.
  • Mintegralwindow.install(), falling back to mraid.open(url), plus a working window.gameEnd() callback.
  • TikTokwindow.openAppStore() from TikTok's own Playable SDK; MRAID-style calls are explicitly rejected here.
  • Vunglewindow.parent.postMessage('download', '*') as the primary call, with MRAID open as fallback.

A build made for one network's click API dropped into another network's submission is a frequent cause of rejection precisely because it looks correct — the CTA button still renders and looks clickable, it just never fires the call that network's review actually checks for.

4. Wrong packaging shape

Some networks want a single HTML file; others want a ZIP with a specific internal structure, and the two are not interchangeable. AppLovin, Meta, Unity Ads, and Vungle all require exactly one self-contained HTML file — submitting a ZIP (even a small one) doesn't match the expected format. Google Ads and Mintegral want a ZIP, but with different internal rules: Google allows resources inside the archive as long as the entry file is index.html and the archive stays under 512 files; Mintegral additionally requires the archive name, the top-level folder name, and the HTML filename to all match each other — a mismatch here, often a side effect of renaming a Luna export folder without renaming everything inside it, is enough to fail intake before a reviewer even opens the creative.

TikTok also expects a ZIP, but wants the main HTML file at the first level named index.html, plus a first-level config.json — covered next — and explicitly prohibits dynamic external loading or JS redirects inside that package.

5. Missing structural requirements

A few networks check for specific markers before they even run the creative. Google Ads requires a valid DOCTYPE, proper HTML/body structure, and an orientation meta tag, which Luna's raw export doesn't always carry by default depending on template settings. TikTok requires a config.json in the first-level directory declaring Playable_orientation, alongside its required Playable SDK reference — a plain Luna export has neither, since both are TikTok-specific. AppLovin requires the creative to support both orientations and to wait for MRAID's ready signal before making any MRAID calls or layout decisions; a build that assumes one orientation, or calls MRAID immediately, can pass a casual test and still fail review.

These are easy to miss because a browser will happily render a playable that's missing all of them — the failure only shows up once the network's own review or SDK checks for the marker it expects.

Worth knowing: across all five categories above, the common thread is that a Luna/Playworks export is built to run in a browser, not to satisfy any one network's submission checklist — and it was never meant to satisfy all seven at once from a single output.

Catching these before you submit

Every category above — self-contained assets, size after encoding, the right click API, correct packaging shape, and the structural markers a network checks for — is verifiable before a creative reaches network review, instead of getting found out from a rejection notice days later. Running your packaged output through PlayableKit's free Playable Ad Validator checks for exactly these issues: missing entry files, broken inline scripts, external references that shouldn't be there, and structural gaps like a missing DOCTYPE or config.json — no signup, no cost. Pair it with the size checker to confirm you're under the real per-network limit once everything is embedded, not just at the source-asset stage.

If you're exporting a single self-contained HTML file for networks like AppLovin, Meta, Unity Ads, or Vungle and want to understand exactly what that format requires and why, see Luna single HTML export, explained. And if you're doing this per-network adaptation by hand for more than one or two networks, PlayableKit automates the click API, asset encoding, and package shape for every network from the same Luna/Playworks export.