A rejection feels like a verdict on the design. It usually is not. The screenshot that comes back is often the same image that looked right on your monitor — refused because of a channel in the header, a profile tag, a file that is 300 KB over a ceiling, or a number a reviewer read as a price. Fixing the design does nothing for any of those.

It helps to separate the three gates. The uploader reads bytes and fails deterministically; the same file fails the same way every time. The reviewer reads meaning, in every language you ship, and can disagree with you. The API reads state: which version is editable, which fields accept which characters. A file-level fix is a one-line command; a copy-level fix is a judgement call; a state-level fix is a different API call, not a different screenshot.

The commands below are macOS-first (with Linux notes where they differ). They read the file you are about to upload, which is the only file that matters.

Part 1 · Six causes

The file

These six are byte-level and repeatable. If the uploader refuses the file, one of them is why — and the error text usually names it.

01The file

The PNG has an alpha channel or a tRNS chunk

App Store Connect refuses the upload because the file carries an alpha channel or a transparency. Apple’s rule is one sentence: “Images can’t include alpha channels or transparencies.” The file usually looks completely opaque in Preview, which is why this catches teams at the end of a release.

Reproduce it

Read the colour type in the PNG header, not the pixels. Type 6 is RGBA and type 4 is grey+alpha; a palette file (type 3) can carry a separate transparency (tRNS) chunk even when it has no alpha channel. A viewer showing an opaque image proves nothing — the channel is structural.

python3 -c "d=open('shot.png','rb').read(); print({0:'grey',2:'RGB',3:'palette',4:'grey+alpha',6:'RGBA'}[d[25]])"

Fix

  • Flatten the final composite onto an opaque background. Hiding the background layer is not the same thing.
  • Export RGB at the last encode step, after every frame, caption and badge is merged.
  • Re-run the header check on the exported file: a new plugin or compressor can put the channel back.

RuleAlpha channel and tRNS: none. Check the header, not the preview.

Sources: Apple, r/AppStoreOptimization, W3C

02The fileReported, not Apple-published

The file is a palette (indexed-colour) PNG

The file is an 8-bit indexed-colour PNG — IHDR colour type 3 — which is what “Save for Web”, pngquant and several “PNG optimisers” produce. The published rules describe 24-bit RGB PNGs: Google Play states it explicitly and flags palette, 16-bit and alpha files, and store-facing validators apply the same test to App Store uploads. Gradients band, and a palette file can be refused before a reviewer ever sees it.

Reproduce it

Ask the file, not the export dialog. file prints “8-bit colormap” for a palette PNG, and pngcheck prints “color type: 3 (palette)”. Read the raw IHDR: sharp normalises a palette image to three channels and hides the colour type, which is why ScreenCraft’s file-level preflight checks the header rather than the decoded pixels.

file shot.png && pngcheck -v shot.png | head -3

Fix

  • Export store masters as 24-bit truecolour PNG. Never run pngquant or palette quantisation on the files you upload.
  • If a compressor is part of the pipeline, keep the palette step for previews and keep a truecolour master for the store.
  • Re-check the colour type after every tool change — this is the failure a “smaller file” optimisation introduces.

RuleTruecolour PNG (24-bit), not palette/indexed.

Sources: r/AppStoreOptimization, W3C

03The fileReported, not Apple-published

The PNG is tagged with a non-sRGB ICC profile

The file carries a Display P3 or Adobe RGB profile. Untagged RGB is treated as sRGB; a profile that is present and is not sRGB is a different file, and upload validators can refuse it. At best the colours shift on Apple’s surfaces; at worst the release stalls on a file property nobody looked at.

Reproduce it

A colour mode and a colour profile are different things: the pixels can be RGB and still be tagged P3. sips -g profile prints “Display P3” or “Adobe RGB (1998)”; an untagged file prints no profile.

sips -g profile -g space shot.png

Fix

  • Convert the final composite to sRGB and embed the sRGB profile — or strip the profile entirely, since untagged RGB is treated as sRGB.
  • Do the conversion at the end. Converting mid-pipeline and then compositing in P3 puts the wide gamut back.
  • Check every locale’s set, not just the English one: one P3 asset can fail the batch.

RulesRGB or untagged RGB — no P3/Adobe RGB profile.

Sources: r/iosdev, W3C

04The file

The file is over 8 MB

App Store Connect’s uploader refuses a screenshot over 8 MB. The cap is enforced on upload and by shipping validators, though it is not spelled out on the specifications page. A 1320 × 2868 PNG with a photographic background, noise or a full-canvas gradient reaches it easily; flat UI and type rarely do.

Reproduce it

Check the actual bytes, not the Finder’s rounded label. The ceiling is 8 × 1024 × 1024 = 8,388,608 bytes.

stat -f%z shot.png   # macOS; stat -c%s on Linux

Fix

  • Use JPEG for photographic or gradient-heavy panels — Apple accepts .jpg and .jpeg — and keep PNG for flat UI and type.
  • Cut noise and full-canvas gradients; they are what makes a PNG large.
  • Do not palette-quantise to save bytes (cause 2). Re-encode at a sane quality, then re-check dimensions and alpha.

Rule8 MB or less per file (8,388,608 bytes).

Sources: r/iosdev

05The file

The pixels do not match the slot

The uploader reports wrong dimensions. Apple accepts exact pixel sizes per slot, not aspect ratios: the 6.9-inch iPhone slot takes 1260 × 2736, 1290 × 2796 or 1320 × 2868 in portrait, or the same pairs swapped in landscape. One pixel off fails.

Reproduce it

Read the file, not the artboard: sips -g pixelWidth -g pixelHeight prints the real grid. A canvas labelled “6.9-inch” in the design tool can export at 1290 × 2796 while the set you are replacing expected 1320 × 2868. Both sizes are valid for the slot, but they are not interchangeable inside one set.

sips -g pixelWidth -g pixelHeight shot.png

Fix

  • Export at one of the slot’s exact sizes. Do not resize a finished PNG by a percentage in Preview.
  • Design at the largest master (1320 × 2868 for 6.9-inch) and let Apple scale down; upload per-size sets only when the layout really changes.
  • Re-check after any crop, device-frame or canvas change — those are the steps that move the grid by a few pixels.

RuleExact slot pixels: 6.9″ = 1260×2736, 1290×2796 or 1320×2868 (portrait).

Sources: Apple, r/AppStoreOptimization

06The fileReported, not Apple-published

Photoshop layer metadata is read as a dimension

The file measures correctly in Photoshop but the upload names a dimension you never exported. Photoshop writes XMP and tEXt chunks that describe the document — a second copy of the width and height, the layer/text description, the software version — and a validator that reads that block instead of the pixel header can compare the wrong number.

Reproduce it

Dump the metadata next to the real size. If the XMP dimensions disagree with sips, you have the condition. This cause is reported by designers rather than published by Apple, so treat the metadata as the suspect, not the pixel grid.

exiftool shot.png | grep -Ei 'Image Width|Image Height|Layer|XMP|Software'

Fix

  • Export with File → Export As → PNG and set Metadata to None, or use Save a Copy with “Remove metadata”.
  • Strip what is left: exiftool -all= -overwrite_original shot.png, keeping a master copy.
  • Make the metadata-free file the one you keep. A “fixed” file that still carries the old XMP can fail again.

RuleNo editor metadata (XMP/tEXt) in the uploaded PNG.

Sources: W3C, Apple

The 6.9-inch slot, exactly. Apple lists three accepted portrait sizes for iPhone with Dynamic Island (large display): 1260 × 2736, 1290 × 2796, 1320 × 2868 — or the same pairs swapped in landscape. ScreenCraft’s 6.9-inch export is 1320 × 2868, the largest of the three, so Apple can scale it down. The sizes are derived from src/lib/export-sizes.ts and checked by a test; this paragraph cannot drift from the ZIP you download.

Part 2 · Two causes

The copy

A reviewer reads the promise, not the pixel. This is where a translation turns ordinary marketing into a claim, and where a number stops being a number.

01The copy

The copy promises something the binary does not deliver

A reviewer rejects the build under 2.3.7 because a screenshot promises something the app does not do, or carries a price. Apple’s wording: metadata “should not include prices, terms, or descriptions that are not specific to the metadata type.” “Free”, “unlimited”, “no ads”, “#1”, “best” and a rendered price are the repeat offenders.

Reproduce it

Read every caption in every locale — including text baked into a screenshot of your own paywall. A price rendered in the image counts, not just a text layer. Search the copy for the terms, then check whether the shipped binary actually delivers each promise.

Fix

  • Cut the claim from the screenshot. Put the offer in the description, where it belongs.
  • If a claim stays, make it true in the shipped binary and keep the proof ready for App Review.
  • Re-read every translation: a harmless English phrase can become a literal promise in German or Swedish.

RuleNo “free”/“unlimited”/“no ads”/“#1”/“best” or price in screenshot copy.

Sources: Apple, r/appledevelopers

02The copyReported, not Apple-published

A bare number reads as a price — the Swedish 6

A bare 6 in Swedish copy is read as a price: 6 is how a 6 kr price starts. Spelled out it is worse — sex is the Swedish word for six, and a reviewer can read that as sexual content. Either way the number is doing something you did not intend.

Reproduce it

Open the Swedish frame and look for any numeral with no unit after it. If it is spelled out, read the sentence as an English speaker would see the string “sex”. This is a first-person report from a team whose reviewer flagged it, not a documented Apple rule.

Fix

  • Give the number a unit — “6 språk”, “6 skärmar” — or remove it.
  • Never leave a bare numeral in a position where a price would sit: next to a button, a plan name or a currency symbol.
  • Have a speaker read every localized screenshot, not just the translation file.

RuleNo bare numerals in Swedish copy; add the unit or cut the number.

Sources: r/iOSAppsMarketing

The translation trap is worth its own beat: a line that is merely enthusiastic in English can become a literal, unqualified promise in German or Swedish. If you localize screenshots, run the copy check in every selected locale — and read the German text-expansion checklist for the layout half of the same problem.

Part 3 · Two causes

The API

Here the screenshot is fine and the call is wrong. Retrying does not help; the state or the character set has to change.

01The APIReported, not Apple-published

The live version’s media is locked (409)

The API returns 409 Conflict when you try to change screenshots or previews on the version that is live. Media on a Ready for Sale version is locked: the file is fine, the target is not.

Reproduce it

Read the version state before you write. GET /v1/apps/{id}/appStoreVersions returns each version’s state; READY_FOR_SALE is the one that will refuse the screenshot-set update. In the console the media well is read-only on the same version.

curl -s -H "Authorization: Bearer $ASC_TOKEN" "https://api.appstoreconnect.apple.com/v1/apps/$APP_ID/appStoreVersions" | jq '.data[] | {versionString, state}'

Fix

  • Attach the media to the next version — create it, or use the editable version you are about to submit.
  • For a rejected submission, cancel the review submission to free the version before resubmitting (PATCH the review submission with canceled: true).
  • Do not retry the 409: it is a state error, not a transient one.

RuleMedia goes on an editable version, never the live one.

Sources: r/setupapp

02The APIReported, not Apple-published

Emoji in What’s New or the description

The API rejects What’s New or the description when the string contains emoji. The web console may accept the same text, so a release that passes one path fails the other — usually mid-submit.

Reproduce it

Send the exact string through the API: PATCH /v1/appStoreVersionLocalizations/{id} with whatsNew containing an emoji. A plain-text twin of the same string succeeds, which isolates the character. The console accepting the same text proves nothing about the API path.

Fix

  • Strip emoji from every string you send through the API — What’s New, description, promotional text.
  • Keep a plain-text variant per locale and validate the exact bytes you send, not the copy you pasted into the console.
  • If the console accepted it, do not assume the API will; the reverse is also true.

RuleNo emoji in What’s New or the description sent via the API.

Sources: r/setupapp

Both of these bite teams who moved from the console to scripts: the console and the API do not enforce the same things. If your release process posts metadata through the API, test the exact strings and the exact version state there, not in the web UI. The store listing audit is a separate check — it tells you which sizes and slots are missing, not whether the bytes are valid.

What we check automatically — and what we do not

A checklist you run by hand is a stopgap. ScreenCraft checks twice: the project in the Check step before you export, and the files themselves on every export.

In the Check step, before you export

Before export

Before you export, the editor reads your project — not your PNGs — and lists what would fail or read badly on the store.

  • Screenshot count against the slot limit (10 on the App Store, 8 on Play).
  • Empty screens, empty image slots and leftover placeholder copy.
  • Untranslated lines, and text that still overflows at the 70% auto-fit floor.
  • A missing caption or a headline too small to read in search results, on the first three screens.
  • 2.3.7 wording and price-like tokens in every selected locale — review-only findings you can dismiss; they never block an export.

File-level preflight, on every export

On export

The other half runs on the bytes of every exported screenshot, before the ZIP is written. A hard finding fails the export with EXPORT_PREFLIGHT_FAILED and no ZIP is produced; the Check step then names the screen, language, size and reason.

  • Exact dimensions for the slot the file is filed under.
  • Alpha channel and tRNS, read from the raw PNG header.
  • Palette/indexed colour, also read from the header.
  • A present ICC profile that is not sRGB (untagged RGB passes).
  • The 8 MB ceiling, with a warning from 6 MB.
  • Screen count per device slot: a screenshot that failed to render fails its slot, and a set past the store limit (10 on the App Store, 8 on Play) gets a warning.
  • Embedded editor metadata (XMP/tEXt) as a warning.
Neither list judges: whether a screenshot shows the app accurately, whether a claim is substantiated, whether a reviewer will read a number as a price, or whether the version you are editing is live. Those stay human checks — the last three causes above are exactly that.

If a translation only fails on width, the free text-fit checker answers that half in the browser. For the longer version of localized copy as a process, see why hand-localized screenshots stop scaling.