DEV Community

Devil Scrapes
Devil Scrapes

Posted on

44 rows of Apple's 'Icon + Asset Ad' format, and not one had an asset. Bug or null?

Quick answer

A deep QA run of the Apple Ad Repository Scraper came back 116 rows across five countries, two ad formats, and four placements — good breadth, and every field a buyer is billed for came back filled. But four fields were empty on every single row, and one pattern inside that gap stood out: adAssetVideoUrls and appIconVariations were empty across all 44 rows of the ad format literally named "Icon + Asset Ad." An ad format whose name promises assets, with a field named for assets, holding zero assets on every row, is not obviously "this field is legitimately null here" — it's the exact shape a misparse leaves behind. We didn't ship a fix for it, because we couldn't yet tell which one it was.

Why can't one run tell a misparse from a genuine null?

Because both produce the identical dataset. If Apple's API returns the video and icon-variation assets under a key our parser doesn't read, every row of that format comes back empty — that's a bug. If those particular "Icon + Asset Ad" placements genuinely only carry image assets server-side and the video/variation keys are null at the source, every row also comes back empty — that's correct behavior. One dataset sample cannot distinguish "the parser never looks here" from "the source never puts anything here," because the observable evidence — an empty array in the output — is bit-for-bit the same either way.

That distinction matters enough that this codebase has a name for the trap: sampling one run and calling every empty field a defect overstates the real problem, because plenty of empty fields are legitimately optional. But the inverse mistake — waving off every empty field as "probably legitimate" without checking — is how a real misparse ships quietly. The four always-empty fields here (subFormat, adAssetVideoUrls, adBannerPromotionalText, appIconVariations) don't block the release on their own, since nothing a buyer is charged for is missing. But subFormat empty across every row of a two-format sample, and asset fields empty across every row of the one format whose name implies assets, is a pattern worth a second look rather than a shrug.

So what actually settles it?

Not another Actor run — a raw payload diff. Running the Actor again would reproduce the identical empty fields either way, because if it's a source-side null, ten more runs look exactly like one. The real check is pulling one "Icon + Asset Ad" adId straight from adrepository.apple.com/api/v1 outside the Actor entirely, and diffing the raw JSON's actual keys against what parser.py reads. If the raw payload carries a video-asset key the parser skips, that's the bug, fixed in one place. If the raw payload genuinely has nothing under those keys for this format, the fields are correctly empty and the dataset was right all along.

An always-empty field sampled from a single run can mean two opposite things — "the source never fills this" or "the parser never reads this" — and they produce identical output. Settling which one you're looking at needs the raw source payload, not another pass through your own code.

What the Actor gives you

One row per ad per EU country Apple served it in — the API itself scopes each record to a single country, so no extra fan-out is needed. Every row carries the ad's placement and format, the app and developer identity (including the DSA-disclosed legal entity name), first/last impression dates, banner creative text, in-app-purchase and editorial-badge disclosure flags, creative asset URLs, and which audience-targeting dimensions (age, gender, location, customer type) the ad used. Give it an app or developer name — we resolve it to Apple's internal entity id ourselves — or skip straight to an ad-list query if you already know the id.

Honest limitations 🚧

This is Apple's own EU DSA disclosure data, so coverage is EU countries only and dataEndDate lags "today" by roughly a week — Apple's own documented freshness delay, not a gap in this Actor. subFormat, adAssetVideoUrls, adBannerPromotionalText, and appIconVariations came back empty on every row in our own deep-run sample; a raw-payload check against the live API is the open item to confirm whether that's the source or the parser, tracked openly rather than silently.

FAQ

Do I need an Apple Developer account or API key?
No — this queries Apple's public, keyless EU DSA Ad Repository API. No login, no key.

Why do some fields come back empty on every row?
Four fields were empty across our own deep-run sample. Three read as plausible genuine nulls; the fourth pattern — asset fields empty on every "Icon + Asset Ad" row — is flagged in our own notes as needing a raw-payload check rather than assumed either way.

Which countries does this cover?
EU countries under Apple's Article 39 disclosure scope — the default list is re-verified against Apple's live country endpoint rather than hardcoded from a stale capture.

How fresh is the data?
As fresh as Apple's own repository, which documents roughly a week's lag on dataEndDate — a source-side delay we surface in the output rather than hide.

Pricing

$0.20 per run plus $0.006 per ad row — $6.20 per 1,000 results. A run that matches nothing costs only the start fee.

Apple Ad Repository Scraper on Apify


Built by Devil Scrapes. We rotate fingerprints, retry with backoff, and — as this one shows — we log the fields we're not sure about instead of quietly shipping around them.

Top comments (0)