agentsclimarketplace

App store connect

Skill sesamehut/appstore-connect-skill/.claude/skills/app-store-connect

Operates Apple App Store Connect through a bundled CLI. Lists apps and App Store versions; reads and updates store metadata and localizations (description, keywords, what's new, promotional text, app name, subtitle, privacy policy); adds new locales; reads customer reviews and posts or replaces developer responses; downloads sales, finance, and analytics reports to disk; uploads, lists, reorders, and deletes App Store screenshots and preview videos; manages TestFlight beta groups, testers, test info, beta review detail and submission, and reads/downloads beta feedback; lists builds, resolves the latest processed build, edits build distribution and notes, and expires builds; runs an App Store submission-readiness preflight, sets review contact/demo detail, configures release timing and the export-compliance flag, reads submission status, and submits, cancels, or releases a version for App Review; verifies credentials against the live App Store Connect API. Use when the user asks about App Store Connect, ASC, app metadata, store listings, localization, customer reviews, review replies, sales or download numbers, finance reports, analytics, TestFlight, beta testers, beta groups, beta feedback, builds, App Store reports, screenshots, preview videos, submitting an app for review, App Review, release timing, export compliance, releasing a version, or checking that App Store Connect API credentials work.From its SKILL.md

Install
npx -y skills add sesamehut/appstore-connect-skill --skill app-store-connect

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

24.7 KB, ~5.8k tokens by cl100k_base, as published. Nobody here has run it

App Store Connect

All capabilities go through one CLI. Never call the ASC HTTP API directly; run the CLI and parse its output.

node "${CLAUDE_SKILL_DIR}/../../../dist/cli/index.js" <domain> <verb> [flags]

Every command and subcommand answers --help with its exact flags. When in doubt, ask the command itself.

Capability boundary

Works now: apps (list/get), versions (list), metadata (app-level and version-level localizations: list/get/update/add-locale), reviews (list/get/get-response/respond), reports (sales/finance downloads, the analytics report lifecycle and downloads), media (screenshots and preview videos: list-sets/list/upload/upload-set/delete/delete-set/reorder/status), testflight (groups list/get/create/update/delete/testers/add-testers/remove-testers/builds/public-link/criteria/criteria-build-check; testers list/get/create/bulk-add/delete/remove-from-app; test-info list/set/delete; review-detail get/set; feedback list-crashes/list-screenshots/get-crash/get-screenshot/download), builds (list/get/latest/expire; beta-detail get/set; notes list/set/delete; review status/submit; groups add/remove; testers list/add/remove; pre-release-versions list), submission (preflight; status list/get; review-detail get/set; release-config set; export-compliance set; submit/cancel/release — high side effect, --force), doctor, auth (check — one live read to confirm the credentials authenticate), capabilities.

Not implemented here yet (the CLI answers these with exit code 5 and the planned milestone): nothing in the current first-party scope — every domain above is implemented. Deferred-but-Apple-supported writes (phased-release control, age-rating questionnaire, export-compliance declaration documents) are not exposed yet; tell the user they are planned, not that Apple lacks them.

Not possible via Apple's API (route the user to the App Store Connect website): editing or deleting customer reviews or star ratings, App Review / Resolution Center messages, agreements/tax/banking, creating or downloading API keys, the legacy per-version submission model (Apple removed it — use submission submit), editing a review submission's items after submit (cancel and re-submit instead), and un-canceling a canceled submission.

Run capabilities for the authoritative machine-readable map — do not guess.

One-time setup

Credentials come from environment variables; never echo private key content, and never write them into a git-tracked file. The user supplies them — if they are missing, help the user set up (below) instead of giving up.

VariableMeaning
ASC_KEY_IDApp Store Connect API key ID (required)
ASC_ISSUER_IDIssuer ID — set for team keys, omit for individual keys
ASC_PRIVATE_KEYThe .p8 private key content, inline PEM
ASC_PRIVATE_KEY_PATHPath to the .p8 file (exactly one of the two key variables)
ASC_VENDOR_NUMBEROptional; needed for sales/finance report downloads (or pass --vendor)

Keys are created in App Store Connect → Users and Access → Integrations. The vendor number is shown in App Store Connect → Payments and Financial Reports; the API cannot read it.

Build once after install or after CLI changes (paths are explicit so the working directory never matters):

npm ci --prefix "${CLAUDE_SKILL_DIR}/../../.."
npm run build --prefix "${CLAUDE_SKILL_DIR}/../../.."
node "${CLAUDE_SKILL_DIR}/../../../dist/cli/index.js" doctor

doctor is offline and reports exactly what is missing and how to fix it (it also warns when a Key ID and Issuer ID look swapped, or a private key looks quoted or non-PEM). auth check is the online counterpart: one harmless read that proves the credentials actually authenticate and the key's role can read.

Helping a user set up

A credentials error (exit 2, or a failing doctor credentials check) usually means the user has not configured the key yet — walk them through it instead of stopping:

  1. Get the values from the App Store Connect locations noted above — Integrations for the keys and Issuer ID, Payments and Financial Reports for the optional vendor number; the .p8 downloads once.

  2. Pick where the credentials live, then set them there. Offer the choice and the trade-off:

    • User-level Claude Code settings env block (~/.claude/settings.json, or %USERPROFILE%\.claude\settings.json) — recommended: outside every git repo, so the key can't be committed by accident.
    • A project's gitignored .claude/settings.local.json env block — per project; confirm it is ignored before committing.
    • Shell export / $env: before launching — ephemeral, one session only.

    ASC_PRIVATE_KEY_PATH (a path to the saved .p8) is simplest; ASC_PRIVATE_KEY (the .p8 on one line) also works — set exactly one. A settings env change needs a Claude Code restart to load.

  3. Re-run doctor, then auth check to confirm. doctor proves the variables are present and well-formed offline; auth check proves they actually authenticate against Apple. A 401 there usually means a wrong or revoked key (or, for a non-coder, an inaccurate computer clock); a 403 means the key's role is too narrow — note that a non-Admin user can self-generate an individual key under Users and Access → Integrations without needing the account holder.

Default to hands-off: tell the user exactly what to paste where, and let them paste it. You MAY write the file for them, but only after saying out loud that the private key will sit as plaintext on disk and getting explicit consent — and even then write only to a non-tracked file (user-level settings, or a gitignored .claude/settings.local.json), never a git-tracked file such as a project's shared .claude/settings.json, and never print the key back into the conversation.

When the user picks a location, record that preference — the storage method only, never the key — to your memory, so a later setup skips the question. Do not record "setup done": whether credentials exist is always a live doctor check, not memory (a stale note misleads across machines and projects).

Reading output

  • stdout carries only the JSON result envelope: { ok, command, data, pagination?, rateLimit?, resolved? }. On failure stdout is empty — parse it only when the exit code is 0.
  • stderr carries diagnostics: error[<category>]: ... followed by a hint: line with the next action.
  • List reads default to a single page. pagination.truncated: true means more data exists — always relay truncated/total honestly to the user. Read more deliberately with --all or --max-items N.
  • resolved reports intermediate resources the CLI picked for you (e.g. which appInfo a metadata app command targeted).

Exit codes:

CodeMeaningNext action
0successparse stdout
1unexpected failureinspect stderr; report a bug
2credentials / configurationfix env vars; run doctor
3ASC request error (auth, permission, not-found, invalid input, upstream, network)read error[<category>] and the hint
4rate limit (real 429 or proactive safety floor)stop; wait or narrow the read
5not implemented in this project yettell the user the planned milestone; do not retry
6not supported by Apple's APIroute the user to the ASC website
64usage errorfix the command line per --help

Task routing

TaskCommand
Check the environment (offline)doctor
Confirm credentials work against Apple (live)auth check
Find an appapps list --bundle-id com.example.app
Read app detailsapps get <appId>
List versions / find the editable oneversions list --app <appId> --state PREPARE_FOR_SUBMISSION
Read version metadata for a localemetadata version get --version <versionId> --locale en-US
List a version's localesmetadata version list --version <versionId>
Update store description / keywords / what's newmetadata version update --version <versionId> --locale en-US --description "..."
Update promotional text (works on the live version)metadata version update --version <versionId> --locale en-US --promotional-text "..."
Add a language to a versionmetadata version add-locale --version <versionId> --locale fr-FR --description "..."
Read app name / subtitlemetadata app get --app <appId> --locale en-US
Change app name / subtitlemetadata app update --app <appId> --locale en-US --subtitle "..."
Add an app-level languagemetadata app add-locale --app <appId> --locale fr-FR --name "..."
List reviewsreviews list --app <appId> --sort -createdDate
Reviews still needing a replyreviews list --app <appId> --unanswered
Read one review (with the reply)reviews get <reviewId> --include-response
Read the existing replyreviews get-response --review <reviewId>
Reply to a review (creates or replaces)reviews respond --review <reviewId> --body-file reply.txt
Download a day's sales reportreports sales download --date 2026-06-10
Download a monthly finance reportreports finance download --region ZZ --date 2026-05
Set up analytics reports (one-time)reports analytics ensure-request --app <appId>
See which analytics reports existreports analytics list-reports --request <requestId>
Download an analytics reportreports analytics download --app <appId> --name "App Downloads Standard"
Upload one screenshotmedia screenshots upload --version <versionId> --locale en-US --display-type APP_IPHONE_67 --file shot.png
Upload a folder of screenshotsmedia screenshots upload-set --version <versionId> --locale en-US --display-type APP_IPHONE_67 --dir ./shots --reorder
List a version's screenshot setsmedia screenshots list-sets --version <versionId> --locale en-US
List a set's screenshotsmedia screenshots list --set <setId>
Reorder a setmedia screenshots reorder --set <setId> --order id1,id2,id3
Remove a screenshot / clear a stuck uploadmedia screenshots delete <screenshotId>
Check a screenshot's processingmedia screenshots status <screenshotId> --wait
Upload one preview videomedia previews upload --version <versionId> --locale en-US --preview-type IPHONE_67 --file clip.mov
List beta groupstestflight groups list --app <appId>
Read one beta grouptestflight groups get <groupId> --include app,builds,betaTesters
Create a beta grouptestflight groups create --app <appId> --name "Internal" --internal
Update a beta grouptestflight groups update <groupId> --name "..." --feedback
Delete a beta grouptestflight groups delete <groupId> --force
List a group's testerstestflight groups testers <groupId>
Add testers to a group (emails invites)testflight groups add-testers <groupId> --testers id1,id2 --force
Remove testers from a grouptestflight groups remove-testers <groupId> --testers id1,id2 --force
List a group's buildstestflight groups builds <groupId>
Enable/disable a public link (exposes the app)testflight groups public-link <groupId> --enable --force
Read/set/clear recruitment criteriatestflight groups criteria set <groupId> --filter IPHONE:15.0:17.0
List the recruitment-criteria matrixtestflight groups criteria options
Preflight a group's compatible buildtestflight groups criteria-build-check <groupId>
List beta testerstestflight testers list --app <appId>
Read one testertestflight testers get <testerId> --include apps,betaGroups
Create a tester (may email an invite)testflight testers create --email [email protected] --group <groupId> --force
Add many testers by email to a grouptestflight testers bulk-add --group <groupId> --emails-file emails.txt --force
Delete a tester (account level)testflight testers delete <testerId> --force
Remove a tester from appstestflight testers remove-from-app <testerId> --app id1,id2 --force
List app-level TestFlight metadatatestflight test-info list --app <appId>
Set TestFlight metadata for a localetestflight test-info set --app <appId> --locale en-US --description "..."
Delete a TestFlight metadata localetestflight test-info delete <localizationId> --force
Read beta review contact/demo infotestflight review-detail get --app <appId>
Set beta review contact/demo infotestflight review-detail set <detailId> --contact-email [email protected]
List crash feedbacktestflight feedback list-crashes --app <appId>
List screenshot feedbacktestflight feedback list-screenshots --app <appId>
Read one crash (with the log text)testflight feedback get-crash --id <submissionId> --with-log
Read one screenshot submissiontestflight feedback get-screenshot --id <submissionId>
Download feedback attachments to disktestflight feedback download --app <appId> --kind both --output ./feedback
List buildsbuilds list --app <appId> --processing-state VALID
Read one buildbuilds get <buildId> --include preReleaseVersion
Resolve the latest processed buildbuilds latest --app <appId>
Expire a build (irreversible)builds expire <buildId> --force
Read a build's beta statesbuilds beta-detail get <buildId>
Set a build's auto-notifybuilds beta-detail set <buildId> --auto-notify true
List/set a build's "what to test" notesbuilds notes set <buildId> --locale en-US --whats-new "..."
Delete a build note localebuilds notes delete <localizationId> --force
Read a build's beta review statusbuilds review status <buildId>
Submit a build for beta review (real review)builds review submit <buildId> --force
Distribute a build to groupsbuilds groups add <buildId> --group id1,id2 --force
Stop distributing a build to groupsbuilds groups remove <buildId> --group id1,id2 --force
List/add/remove a build's individual testersbuilds testers add <buildId> --tester id1,id2 --force
List pre-release (train) versionsbuilds pre-release-versions list --app <appId>
Check if a version is ready to submitsubmission preflight --version <versionId>
List a version's review submissionssubmission status list --app <appId>
Read one review submissionsubmission status get <submissionId> --include app,items,appStoreVersionForReview
Read a version's App Review contact/demo detailsubmission review-detail get --version <versionId>
Set a version's App Review contact/demo detailsubmission review-detail set --version <versionId> --contact-email [email protected]
Configure release timing (manual/scheduled)submission release-config set --version <versionId> --release-type MANUAL
Attach/swap a build on a versionsubmission release-config set --version <versionId> --build <buildId>
Set a build's export-compliance flagsubmission export-compliance set --build <buildId> --uses-non-exempt-encryption false
Submit a version for App Review (real review)submission submit --version <versionId> --force
Cancel/withdraw a review submission (forces re-review)submission cancel <submissionId> --force
Release an approved version to the public nowsubmission release --version <versionId> --force

media previews mirrors media screenshots, swapping --display-type for --preview-type (and upload adds optional --mime-type / --frame-time-code).

Conventions

  • Ids come from prior list commands; never invent them.
  • Locales are BCP-47 (en-US, de-DE).
  • For multi-line or quoted text, write it to a file and use --from-json file.json (metadata) or --body-file file.txt (review replies) instead of inline flags — this avoids shell quoting issues, especially on Windows.
  • In --from-json, a JSON null clears a field; an omitted key leaves it unchanged.
  • metadata app writes target the editable appInfo automatically; pass --live to read the live one, or --app-info <id> to target explicitly.
  • Most version metadata is writable only while the version is editable (e.g. PREPARE_FOR_SUBMISSION); promotional-text is writable any time. A STATE_ERROR on exit 3 means the target was not editable.
  • reviews respond replaces any existing response and publishes asynchronously (state starts as PENDING_PUBLISH).
  • Report downloads write files to disk; the envelope's data.file (or data.segments for analytics) carries the path, row count, and headers — always relay the on-disk path to the user. --format json additionally writes a JSON conversion next to the raw TSV/CSV.
  • Sales/finance --output is a single file path, not a folder; omit it to auto-name the file in the working directory, or pass a full path to place it. Analytics download instead takes --output-dir (a folder for its segment files). To drop a sales/finance report into a specific folder, join your own filename onto that folder for --output.
  • The sales --date format follows --frequency: DAILY/WEEKLY use YYYY-MM-DD (weekly = the week's closing date), MONTHLY uses YYYY-MM, YEARLY uses YYYY; omit the date for the latest report. The finance --date is Apple's FISCAL month (YYYY-MM), which shifts against the calendar.
  • Analytics reports need a one-time ensure-request per app; Apple generates the first data 1-2 days later (the catalog of report names appears immediately, dated instances follow). If list-requests shows stoppedDueToInactivity: true, run ensure-request again — it creates a fresh request and reports the stopped ones.
  • A sales/finance 404 usually means the report does not exist for that date/frequency (timing or no activity), not a wrong id — the error message carries the availability rules.
  • media upload addresses a localization by --version <id> --locale <code>; the CLI resolves it and finds-or-creates the set for the --display-type / --preview-type (resolved.setCreated reports which). --display-type and --preview-type are device classes (run --help for the list); the values are the same ones Apple's API uses.
  • Upload reserves, transfers the bytes to Apple's short-lived signed URL (never logged, never in the envelope), commits a checksum, then blocks until Apple finishes processing. --no-wait returns right after the commit; status <id> (optionally --wait) resumes the check. A poll timeout is a success with resolved.pollTimedOut: true, not a failure — the bytes are up.
  • Upload appends to a set (Apple allows several per device class); it does not replace. upload-set uploads a folder in filename order and, with --reorder, makes that batch lead the set. Remove extras or a stuck reservation with delete.
  • A file-processing exit 3 names the stage in stderr: transfer (the PUT failed — re-run for fresh upload URLs), commit (checksum rejected), or processing (Apple rejected the asset's dimensions/format — the state errors say why). delete-set needs --force when the set still holds assets.
  • TestFlight invitations are real emails. testflight groups add-testers, testflight testers create --group, testflight testers bulk-add, and builds testers add email real TestFlight invitations to real people. They all require --force. Confirm the recipients with the user first; an invite cannot be unsent.
  • Submitting a build for beta review triggers a real Apple review. builds review submit <buildId> --force starts an external TestFlight beta review; the submission cannot be patched or deleted (a rejection needs a fresh submit). Never run it speculatively.
  • builds expire is irreversible. Apple's API has no un-expire; an expired build leaves testing for good. Requires --force.
  • Submitting a version for App Review starts a real, public review. submission submit --version <versionId> --force opens a modern review submission and PATCHes it submitted — this triggers a REAL Apple App Review of the live store listing. Run submission preflight first and confirm with the user; never submit speculatively. The call is async-accept (the envelope reports accepted, not a final state).
  • Releasing a version goes public immediately and cannot be undone. submission release --version <versionId> --force releases an approved (MANUAL, pending-developer-release) version to the public right away. There is no un-release. Confirm with the user first.
  • Canceling a review submission forces a fresh review. submission cancel <submissionId> --force withdraws a submission; the version flips to Developer Rejected, accepted items must be re-submitted, and re-review starts from scratch. A canceled submission cannot be un-canceled.
  • The three submission high-side-effect verbs — submit, cancel, release — require --force (a missing --force is exit 64 before any request). They are never run by the smoke check; treat them like the other irreversible actions and confirm with the user.
  • The legacy per-version submission model (appStoreVersionSubmissions create/ read) is not supported by Apple's API (exit 6) — Apple removed it. Use submission submit/status/preflight instead. Editing a submission's items after submit is also unsupported (cancel and re-submit); un-canceling is too.
  • Enabling a public link exposes the app. testflight groups public-link --enable (and create --public-link) opens public external recruitment — a real exposure, no per-person email — and requires --force.
  • Destructive deletes/removes need --force, raised as a usage error (exit 64) before any request: groups delete, groups remove-testers, testers delete, testers remove-from-app, test-info delete, notes delete, groups criteria clear, builds groups remove, builds testers remove, builds expire.
  • Feedback download writes files to disk and the path is relayed. testflight feedback download --output <dir> writes crash logs and screenshots into the directory; the envelope's data.submissions[].savedFiles carries the on-disk paths — relay them to the user. Downloads continue on error (an expired signed URL records an item-level error and the batch proceeds).
  • The envelope never contains a signed attachment URL. Screenshot feedback carries short-lived signed URLs that are secrets; the CLI fetches the bytes for you (download) and only ever reports on-disk paths, byte counts, width/height, expirationDate, and a de-queried sanitizedUrl. Never try to fetch a signed URL yourself — use download.
  • TestFlight reads default to a single page like every list; relay pagination.truncated/total honestly. A not-found on testflight review-detail get means no beta review detail exists for that app yet, not a wrong id.
  • A group's --internal/--all-builds are fixed at creation; groups update rejects them (exit 64). Recruitment --filter is deviceFamily:minOs:maxOs (OS bounds optional), repeatable; run testflight groups criteria options for the legal device-family/OS matrix.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,286. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.