Skip to content
Launch offer: Mac Manager £9.99 first year

Shotlist guide

App Store Connect

Creating an App Store Connect API key, the publish plan and every row action, applying it, published.json and resuming, pruning, version selection, locale mapping, the iPhone Duo and creative assets.

App Store Connect

shotlist publish            # print the plan; changes nothing
shotlist publish --apply    # carry it out

Publishing always runs as a plan first. shotlist publish compares your composed screenshots and creative assets with what’s live on your app’s editable version, and prints one row per locale and size. Nothing changes until you run it again with --apply.

Shotlist never deletes a screenshot it didn’t upload unless you pass --prune, and it never submits anything for review.

The plan is free. Uploading (--apply) needs a subscription. See Subscription.

Before you publish

  1. The next version exists in App Store Connect and is editable: Prepare for Submission, or rejected. Shotlist only changes screenshots on an editable version.
  2. The version has a localisation for every locale you publish. Add any that are missing in App Store Connect. Shotlist doesn’t create localisations.
  3. shotlist verify passes. --apply runs it first and refuses on any problem, unreviewed captions included.
  4. You have an App Store Connect API key (below).

The API key

Shotlist uses an App Store Connect team API key: a .p8 private key, its Key ID and your team’s Issuer ID.

Create one

  1. In App Store Connect, open Users and Access › Integrations › App Store Connect API.
  2. Under Team Keys, generate a key. Give it a role that can edit your app’s App Store information, such as App Manager.
  3. Note the Key ID and the Issuer ID shown above the list.
  4. Download the .p8 file. Apple lets you download it only once.

Keep the key out of your repo. Apple’s own tools look for it in ~/.appstoreconnect/private_keys/, and so does Shotlist:

mkdir -p ~/.appstoreconnect/private_keys
mv ~/Downloads/AuthKey_ABC123DEFG.p8 ~/.appstoreconnect/private_keys/
chmod 600 ~/.appstoreconnect/private_keys/AuthKey_ABC123DEFG.p8

Shotlist refuses a key file that other users can read: it must be mode 600 or stricter.

Give it to the CLI

Either set three environment variables:

export ASC_KEY_ID=ABC123DEFG
export ASC_ISSUER_ID=00000000-0000-0000-0000-000000000000
export ASC_KEY_PATH=~/.appstoreconnect/private_keys/AuthKey_ABC123DEFG.p8

Or write the same three values to ~/.config/shotlist/asc.json:

{
  "keyID": "ABC123DEFG",
  "issuerID": "00000000-0000-0000-0000-000000000000",
  "keyPath": "~/.appstoreconnect/private_keys/AuthKey_ABC123DEFG.p8"
}

Environment variables win over the file. The key path is optional in both: it defaults to ~/.appstoreconnect/private_keys/AuthKey_<Key ID>.p8.

Give it to the Mac app

In the Publish screen’s inspector, under App Store Connect Key, enter the Key ID and Issuer ID, then choose Choose .p8 File…. The key goes into your login Keychain and nowhere else: not the project, not the logs. Remove Key… deletes it from the Keychain; the .p8 file itself isn’t touched.

The CLI doesn’t read the app’s Keychain item, and the app doesn’t read the CLI’s settings. Set the key up in each one you use.

How the key is used

Shotlist signs a short-lived token (15 minutes; Apple allows 20) with the key for each run, and renews it before it expires. The key and the token never appear in the log or the output.

Which app and version

  • The app is app.ascAppID in shotlist.json, if set. Otherwise Shotlist finds it by app.bundleID in the key’s team.
  • The version is the first editable iOS version: one in Prepare for Submission, Developer Rejected, Rejected, Metadata Rejected or Invalid Binary.

If no version is editable, the plan stops with the latest version’s state:

! no editable App Store version: 1.5 is WAITING_FOR_REVIEW. Create the next version in App Store Connect, then plan again.

To compare with a particular version instead, such as the one in review, name it:

shotlist publish --store-version 1.5

This plan is read-only unless that version is editable. --apply on a version that isn’t editable refuses: “version 1.5 is WAITING_FOR_REVIEW; App Store Connect only accepts screenshot changes on an editable version”.

The plan

App 1234567890 · version 2.1 (PREPARE_FOR_SUBMISSION) · plan only
  + add       en-GB  iphone-6.9        upload 4
  = unchanged en-GB  iphone-6.3        4 live
  ↻ replace   de-DE  iphone-6.3        upload 1, delete 1
  ⇅ reorder   de-DE  iphone-6.9        reorder 4
  · skip      en-GB  iphone-duo-outer  APP_IPHONE_DUO is undocumented; pass --experimental-duo to upload the outer display
  ↗ export    en-GB  iphone-duo-inner  the API has no display type for the Duo's inner display; upload these by hand in App Store Connect
  + add       en-GB  header art        upload and place
Nothing changed. Run again with --apply to carry this out.

Each row shows its action as a glyph and a word, the App Store Connect locale, the slot (or the kind of art) and what it would do.

Shotlist compares files by MD5 checksum, the checksum App Store Connect stores for each screenshot. It recognises its own uploads by Shotlist/published.json and by their file names, which start with shotlist-.

Screenshot rows

Action When
+ add There’s no screenshot set for this size and locale yet, or it’s empty. Uploads every composed file.
= unchanged The live set already shows exactly these images, in this order. Screenshots others uploaded may follow them.
↻ replace Uploads what’s missing and deletes Shotlist’s own screenshots that are no longer wanted. Screenshots Shotlist didn’t upload are kept, after Shotlist’s.
⇅ reorder The same images, in a different order.
− delete Only removals are needed.
↗ export The iPhone Duo’s inner display: the API has no display type for it. Upload the files by hand.
· skip Nothing composed for this size and locale, or the Duo’s outer display without --experimental-duo.
✗ blocked The version has no localisation for this locale, or the set would end up with more than 10 screenshots.

A set is blocked at more than 10 when your screenshots plus the ones others uploaded add up to more than 10: “5 new + 6 not uploaded by Shotlist would exceed 10; pass –prune to replace them”.

Creative rows

If creative is set in shotlist.json, there’s a row for the header and search art on each localisation:

Action When
= unchanged This exact image is already placed.
+ add Nothing is placed yet: “upload and place”, or “place the uploaded image” when the image is already in the app’s asset library.
↻ replace A placement Shotlist made shows an older image. Placements can’t be edited, so Shotlist deletes it and creates a new one.
✗ blocked A placement Shotlist didn’t make is live. Pass --prune to replace it.
· skip No art composed for it.

Only header.png and search.png are placed. The 16:9 alternatives aren’t uploaded.

Problems

Anything that stops the plan or a row is listed after it with !, and makes publish exit 1. If verify has problems, the plan still prints, with a reminder that --apply will refuse:

  ! `shotlist verify` reports 3 problem(s); --apply refuses until they're fixed

Applying

shotlist publish --apply
  1. Runs verify. On any problem it prints them and stops: “Not publishing: shotlist verify has 3 problem(s).”
  2. Works through the plan’s rows in order. For each screenshot set to change:
    • creates the screenshot set if there isn’t one;
    • reuses any identical image already live, rather than uploading it again;
    • reserves each new upload, sends its parts, and commits it with its checksum;
    • waits for App Store Connect to process it, up to 5 minutes;
    • deletes Shotlist’s superseded screenshots (and, with --prune, others’);
    • sets the order to match your sequence, with any screenshots others uploaded after Shotlist’s.
  3. For each creative row to change: uploads the image to the app’s asset library once (a shared header is uploaded once and placed on every localisation), then places it on the localisation.
  4. Saves Shotlist/published.json after every set and every placement.

Uploads are named shotlist-<slot>-<NN>-<checksum>.png in App Store Connect.

Each changed row’s detail ends “; applied”, or “; FAILED: ” if it failed. A failure doesn’t stop the other rows. publish exits 1 if any failed.

In the Mac app, Upload… asks first, naming the version and counting the changes:

Upload to version 2.1?
12 screenshots in 3 sets across 2 locales.
2 creative asset changes.
This changes the listing in App Store Connect. Nothing is submitted for review.

Running it again, and resuming

Every step is idempotent:

  • A second --apply straight after the first finds nothing to do: every row is unchanged.
  • If an apply stops part-way (a network failure, a processing failure, Control-C), run it again. Rows already applied are unchanged; the rest carry on. An upload of Shotlist’s that never finished processing is deleted and redone.
  • Shotlist recognises its own uploads by published.json or the shotlist- file-name prefix, so even an apply that stopped before saving published.json resumes safely.

published.json

Shotlist/published.json records, per App Store Connect locale and display type, the screenshots Shotlist put live (their IDs, checksums and source files), and for creative assets, the image and placement IDs. Commit it, so a teammate or CI publishing next knows which screenshots are Shotlist’s.

Pruning

shotlist publish --prune            # see what pruning would remove
shotlist publish --apply --prune

With --prune, Shotlist also deletes screenshots and creative placements it didn’t upload, so the set ends up exactly your composed files. Without it, anything someone uploaded by hand is kept, after Shotlist’s screenshots.

Run the plan with --prune first and read it. Pruning can’t be undone: deleted screenshots have to be uploaded again.

In the Mac app, the option is Delete screenshots Shotlist didn’t upload. Changing it discards the current plan, so you plan again before uploading.

Locales

Shotlist maps each locale to App Store Connect’s code: sv-SE → sv, nb-NO → no, and so on; most codes, such as en-GB and de-DE, stay as they are. Override any of them with app.ascLocales. See Configuration.

Each row in the plan shows the App Store Connect locale, so you can check the mapping before applying. A locale the version has no localisation for is blocked: “version 2.1 has no fr-FR localisation; add it in App Store Connect”.

Narrowing

shotlist publish --locales de-DE --slots iphone-6.3

--locales and --slots plan and apply only those rows. The verify gate applies to the same subset.

iPhone Duo

Apple’s documented API has no display type for either iPhone Duo display.

  • Outer display: --experimental-duo uploads it as APP_IPHONE_DUO. Developers report this works for the outer display, but it isn’t documented and could change. Without the flag, the row is skipped.
  • Inner display: always an export row. Upload Shotlist/Output/composed/<locale>/iphone-duo-inner-NN.png by hand in App Store Connect, in order.

In the Mac app, the option is Upload the Duo’s outer display (experimental).

Creative assets

Header and search art are placed on each version localisation. They’re reviewed with the version: placed before you submit, they go to App Review with it. Shotlist never submits anything for review itself. If the version is already live, placed art needs a review submission of its own, which you make in App Store Connect.

Options

Option Effect
--apply Carry out the plan.
--prune Also delete screenshots and placements Shotlist didn’t upload.
--experimental-duo Upload the Duo’s outer display as APP_IPHONE_DUO.
--allow-unreviewed Publish locales whose captions include unreviewed machine drafts.
--locales <list>, --slots <list> Only these.
--store-version <version> Compare with this App Store version instead of the editable one. Read-only unless it’s editable.
--json Print the plan as JSON. See CI and automation.
--project-dir <folder> The folder that holds Shotlist/.

Rate limits and errors

Shotlist waits and retries when App Store Connect rate-limits it (as long as the Retry-After header asks, up to four times), and retries a server error once. The log says when it does.

Message Fix
no App Store Connect key: set ASC_KEY_ID and ASC_ISSUER_ID (and ASC_KEY_PATH if the .p8 isn't in ~/.appstoreconnect/private_keys), or write ~/.config/shotlist/asc.json Set up the key as above.
no App Store Connect issuer: set ASC_ISSUER_ID or add issuerID to ~/.config/shotlist/asc.json Add the Issuer ID.
no App Store Connect key at <path> The .p8 isn’t there. Check ASC_KEY_PATH or keyPath.
<path> is readable by other users (mode 644); run `chmod 600 <path>` first Run that command.
the App Store Connect key <id> isn't a valid .p8 (PKCS #8 P-256) key Use the .p8 file as Apple gave it.
App Store Connect rejected the key <id> (401). Check the Mac's clock, and that the key hasn't been revoked in Users and Access › Integrations. Set the Mac’s clock automatically; check the key is still active.
no app with bundle ID <id> in this App Store Connect team Check app.bundleID, or set app.ascAppID, and that the key is for the right team.
no version <version> in App Store Connect Check the version string passed to --store-version.
App Store Connect couldn't process <file>: … Apple rejected the file. Run shotlist verify; recompose if needed.
<file> was still processing after 5 minutes Run --apply again later; it picks up where it stopped.
<METHOD> <path> failed with HTTP <code>: … The rest of the message is Apple’s explanation.
add an App Store Connect key first (Mac app) Add the key in the Publish inspector.