Technical Documentation: rorkai/App-Store-Connect-CLI
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (`README.md`)
App Store Connect CLI
A fast, lightweight, and scriptable CLI for the App Store Connect API.
Automate iOS, macOS, tvOS, and visionOS release workflows from your terminal, IDE, or CI/CD pipeline.
Table of Contents
- asc skills
- Sponsors
- Quick Start
- Troubleshooting
- Privacy and telemetry
- Support
- Wall of Apps
- Common Workflows
- Commands and Reference
- Documentation
- Contributing
- License
asc skills
Agent Skills for automating asc workflows including builds, TestFlight, metadata sync, submissions, and signing:
https://github.com/rorkai/app-store-connect-cli-skills
Install them globally so they are available across projects:
asc install-skillsasc install-skills checks out reviewed commitf52c4f04323bb2dfb21ca8be82e6494e9cd0b4d8 and copies its 25 skills directly
into the standard global agent-skills directory. It verifies the complete pack
and every installed file before succeeding, preserves unrelated skills and
unrelated lock entries, and pins the 25 ASC lock entries to the same reviewed
commit so external checks cannot update them from a mutable branch. It rolls
back the pack if any replacement fails. Onlygit is required; the command does not execute Node.js, npx, an npm package,
or repository scripts.
git --version
asc install-skillsQuick Start
If you want to confirm the binary works before configuring authentication:
asc version
asc --help1. Install
# Homebrew (recommended)
brew install asc
# Install script (macOS/Linux)
curl -fsSL https://asccli.sh/install | bash# Windows (WinGet, once the package is accepted)
winget install asc
# Exact fallback when scripting
winget install --id Rorkai.ASC --exactThe WinGet package is tracked in
GitHub Discussion #1552.
Until it appears in winget search asc, Windows users can download the signed
release binaries directly from the
GitHub releases page.
For source builds and contributor setup, see CONTRIBUTING.md.
Released binaries are self-contained and do not require a Go installation;
source builds use the toolchain version declared by go.mod.
Releases built with Go 1.27 require macOS 13 Ventura or later. On older Macs,
retain an older compatible release or upgrade macOS before updating asc.
2. Authenticate
asc auth login \
--name "MyApp" \
--key-id "ABC123" \
--issuer-id "DEF456" \
--private-key /path/to/AuthKey.p8 \
--networkIndividual API keys have no issuer ID. Omit --issuer-id and pass --key-type individual:
asc auth login \
--name "MyIndividualKey" \
--key-id "ABC123" \
--key-type individual \
--private-key /path/to/AuthKey.p8Generate API keys at:
https://appstoreconnect.apple.com/access/integrations/api
If you are running in CI, a headless shell, or a machine where keychain access is not available, use config-backed auth instead:
asc auth login \
--bypass-keychain \
--name "MyCIKey" \
--key-id "ABC123" \
--issuer-id "DEF456" \
--private-key /path/to/AuthKey.p83. Validate auth
asc auth status --validate
asc auth doctor4. First command
asc apps list --output table
asc apps list --output json --prettyOutput defaults (TTY-aware)
asc chooses a default --output based on where stdout is connected:
- Interactive terminal (TTY):
table - Non-interactive output (pipes/files/CI):
json
You can still set a global preference:
export ASC_DEFAULT_OUTPUT=markdownAnd explicit flags always win:
asc apps list --output jsonCompatibility
Commands are supported as documented. Deprecation warnings identify the
available migration path and the command's long-term replacement.
Troubleshooting
Homebrew
- Refresh Homebrew first:
brew update && brew upgrade asc - Check which binary you are running:
which asc - Confirm the installed version:
asc version - If Homebrew is behind the latest GitHub release, use the install script from
https://asccli.sh/install
WinGet
- Refresh WinGet sources first:
winget source update - Prefer the short install once available:
winget install asc - If the short name ever becomes ambiguous, use the package identifier:
winget install --id Rorkai.ASC --exact - Confirm the installed command resolves:
Get-Command ascandasc version
Authentication
- Validate the active profile:
asc auth status --validate - Run the auth health check:
asc auth doctor - If keychain access is blocked, retry with
ASC_BYPASS_KEYCHAIN=1or re-runasc auth login --bypass-keychain - Use
asc auth login --local --bypass-keychain ...when you want repo-local credentials in./.asc/config.json
Apple service health
- Check Apple's developer services without credentials:
asc system-status - Narrow unexpected API or upload failures:
asc system-status --service "App Store Connect" - Poll only when requested:
asc system-status --watch --poll-interval 30s - Use
--issues-onlyfor a concise incident view; summary counts still cover all matched services
Output
ascdefaults totablein an interactive terminal andjsonin pipes, files, and CI- Use an explicit format when scripting or sharing repro steps:
--output json,--output table, or--output markdown - Use
--prettywith JSON when you want readable output in terminals or bug reports - Set a personal default with
ASC_DEFAULT_OUTPUT, but remember--outputalways wins
Privacy and telemetry
asc sends pseudonymous command-level usage telemetry by default to help
maintainers understand which commands are used and where reliability work is
needed. Local events include a random installation ID, which lets events from
one installation be grouped over time; it is not derived from an Apple account
or machine identifier.
Telemetry includes the CLI version, operating system and architecture,
registered command path, duration, runtime context, invocation source, a
bounded outcome class, and the HTTP status when an API request fails. It may
include a sanitized public flag name. It does not include raw arguments,
stderr, error messages, flag values, response bodies, credentials, private
keys, Apple account, team, or issuer IDs, app or bundle IDs, usernames,
hostnames, repository names, or file paths.
Review or change telemetry at any time:
asc telemetry status
asc telemetry disable
asc telemetry reset-idASC_TELEMETRY_DISABLED=1 and DO_NOT_TRACK=1 also disable telemetry. See the
telemetry reference for the exact event payload,
runtime handling, collector endpoint, and all controls.
Support
- Use GitHub Discussions for install help, authentication setup, workflow advice, and "how do I...?" questions
- Use GitHub Issues for reproducible bugs and concrete feature requests
- See SUPPORT.md for the support policy and bug-report checklist
- Before filing an auth or API bug, retry with
ASC_BYPASS_KEYCHAIN=1; if it is safe to do so, include redacted output fromASC_DEBUG=api asc ...orasc --api-debug ...
Wall of Apps
Want to add yours?asc apps wall submit --app "1234567890" --confirm
The command uses your authenticated gh session to fork the repo and open a pull request that updates docs/wall-of-apps.json.
It resolves the public App Store name, URL, and icon from the app ID automatically. For manual entries that are not on the public App Store yet, use --link with --name.
Use asc apps wall submit --dry-run to preview the fork, branch, and PR plan before creating anything.
Common Workflows
TestFlight feedback and crashes
asc testflight feedback list --app "123456789" --paginate
asc testflight crashes list --app "123456789" --sort -createdDate --limit 10
asc testflight crashes log --submission-id "SUBMISSION_ID"Builds and distribution
asc builds upload --app "123456789" --ipa "/path/to/MyApp.ipa"
asc builds list --app "123456789" --output table
asc testflight groups list --app "123456789" --output tableFor macOS TestFlight distribution, upload the exported .pkg first, then add
the processed build to a beta group:
asc builds upload --app "123456789" --pkg "./build/MyMacApp.pkg" --version "1.2.3" --build-number "42" --wait --output json
asc builds add-groups --app "123456789" --build-number "42" --version "1.2.3" --platform MAC_OS --group "Internal Testers"--app is the App Store Connect app ID. If you use local Xcode build flags such
as --archive-path, also pass exactly one of --workspace or --project plus--scheme; otherwise use a pre-exported .ipa or .pkg upload. Add--submit --confirm to asc builds add-groups when distributing to an external
TestFlight group that needs beta app review submission.
Release (high-level App Store publish flow)
# Optional: preview the staging plan before submission
asc release stage --app "123456789" --version "1.2.3" --build-id "BUILD_ID" --copy-metadata-from "1.2.2" --dry-run
# Canonical upload + attach + submit command
asc publish appstore --app "123456789" --ipa "/path/to/MyApp.ipa" --version "1.2.3" --submit --confirm
# Wait for the App Review decision (exit 0 approved, 1 rejected, 7 still pending at --timeout)
asc status --app "123456789" --until review-done --timeout 2hLower-level submission lifecycle commands (for debugging or partial workflows):
# Canonical readiness check
asc validate --app "123456789" --version "1.2.3"
asc submit status --version-id "VERSION_ID"
asc submit cancel --version-id "VERSION_ID" --confirmReadiness validation also warns when localized app names, subtitles,
descriptions, keywords, promotional text, or What's New copy still contains an
unmistakable template marker such as Lorem ipsum, TODO, TBD, or FIXME.Lorem ipsum matches in any letter case. TODO, TBD, and FIXME match only
in uppercase because their lowercase spellings can be ordinary product wording.
The warning includes the locale, field, matched text, resource ID, and a
remediation step. It is advisory by default and becomes blocking only with--strict:
asc validate --app "123456789" --version "1.2.3" --output json
asc validate --app "123456789" --version "1.2.3" --strictAdd the --deep mode when a release needs checks that Apple
exposes only through its signed-in web app:
asc web auth login --apple-id "[email protected]"
asc validate --app "123456789" --version "1.2.3" --deepDeep validation reuses the file-backed cache without opening a login, Keychain,
password, or 2FA prompt. It checks App Privacy publication, relevant agreements,
and the first auto-renewable subscription attachment; availability and required
App Review fields still come from the public API. Paid-agreement relevance also
uses the app's current public price so an upfront-paid app isn't mistaken for a
free one. The private Apple endpoints can change without notice, so an
unavailable or changed response is unverified, not a false blocker. Add--strict if CI must fail when a requested deep check can't be verified.
This lint intentionally does not judge platform names, roadmap language, or
beta/demo wording because those phrases can be legitimate product copy and
cannot be classified reliably offline. It also leaves shorter Lorem Ipsum
product wording and ordinary localized TODO copy without marker punctuation
unflagged; only template-like residue is reported.
Review status and blockers
asc review status --app "123456789"
asc review doctor --app "123456789"Metadata and localization
asc localizations list --app "123456789" --type app-info
asc metadata init --dir "./metadata" --version "1.2.3" --locale "en-US"
# Fill in the values to apply and delete keys to leave unchanged; empty values are rejected.
asc metadata apply --app "123456789" --version "1.2.3" --dir "./metadata" --dry-run
asc metadata keywords audit --app "123456789" --version "1.2.3" --blocked-terms-file "./blocked-terms.txt"
asc apps info view --app "123456789" --output json --prettyUse asc metadata keywords audit before sync or apply when you want an ASO-focused
review of live keyword metadata across locales. It reports duplicate phrases, repeated
terms across locales, overlap with localized app name or subtitle, byte-budget usage,
and optional blocked terms from repeated --blocked-term flags or a text file.
Screenshots and media
asc screenshots plan --app "123456789" --version "1.2.3" --review-output-dir "./screenshots/review"
asc screenshots apply --app "123456789" --version "1.2.3" --review-output-dir "./screenshots/review" --confirm
asc screenshots list --version-localization "VERSION_LOCALIZATION_ID"
asc video-previews list --version-localization "VERSION_LOCALIZATION_ID"Uploading screenshots for a single locale:
asc apps list
asc versions list --app "APP_ID"
asc localizations list --version "VERSION_ID" --output json --locale "en-US" | jsonpp
asc screenshots upload --version-localization "VERSION_LOCALIZATION_ID" --path "./screenshots/en-US" --device-type "IPHONE_65" --replace --confirm --max-screenshots 10VERSION_LOCALIZATION_ID is the App Store version localization resource ID
from data[].id, not the locale code from attributes.locale.
For local capture coverage across multiple devices, locales, appearances, and
content fixtures, use a matrix plan. Targets must already be
booted simulators; the command writes raw artifacts and an offline review
report, without uploading to App Store Connect:
asc screenshots matrix --plan .asc/screenshots-matrix.json --max-concurrency 2 --output json --prettySee docs/design/screenshots-matrix.md for
the JSONC schema, isolated output layout, retry behavior, and review contract.
Signing and bundle IDs
asc bundle-ids capabilities list --bundle "BUNDLE_ID"
asc signing fetch --bundle-id com.example.app --profile-type IOS_APP_STORE --output .asc/signing
asc signing sync pull --repo [email protected]:team/signing.git --password-file ~/.config/asc/signing-sync-password --output-dir .asc/signing/pulledsigning fetch downloads public certificates and provisioning profiles. A
usable signing identity also needs the matching private key; signing sync
can verify that local identity and share it through an encrypted store (Git by
default). When the team has no active certificate, signing fetch --create-missing --create-missing-certificate creates the key, certificate,
profile, and .p12 in one pass. signing keychain installs and manages a
dedicated keychain, and xcode signing plan --profile maps downloaded profiles
onto Xcode targets; docs/CI_CD.md lists the fresh-runner
sequence. For multi-target release testing, signing reconcile plans exact
device/profile changes. signing run provides a temporary macOS keychain only
for single-target archives. Multi-target exports must import the identity into
a job-scoped keychain and install every reconciled profile for the job-exclusive
macOS user. See the
signing guide for setup, CI, rotation, security
boundaries, and troubleshooting.
Workflow automation
asc workflow validate --output json
asc workflow run --dry-run testflight_beta VERSION:1.2.3Raw API requests
asc api GET /v1/apps --query limit=5
asc api GET /v1/apps/APP_ID/relationships/builds --paginate
asc api PATCH /v1/apps/APP_ID --confirm --body-file update.jsonVerified local Xcode -> TestFlight workflow
See docs/WORKFLOWS.md for local compile validation withasc xcode build and a copyable .asc/deployment.json, .asc/workflow.json,
and ExportOptions.plist that use asc builds next-build-number,asc xcode inject, asc xcode archive, asc xcode export --timeout 10m, andasc publish testflight --group ... --wait.
Add --submit --confirm when
distributing to an external TestFlight group that needs beta app review submission.
The asc xcode test command provides local unit/UI test execution
with structured results and preserved .xcresult bundles; see the same workflow
guide for an invocation.
asc workflow validate --output json
asc xcode inject --manifest .asc/deployment.json --set version=1.2.3 --set build_number=42 --dry-run --output json
asc xcode build --project App.xcodeproj --scheme App --destination 'platform=iOS Simulator,name=iPhone 17 Pro Max,OS=27.0' --no-code-signing --output json
asc workflow run --dry-run testflight_beta VERSION:1.2.3
asc workflow run testflight_beta VERSION:1.2.3Replace the example Xcode destination with a simulator installed on the host.
Xcode Cloud workflows and build runs
# Trigger from a pull request
asc xcode-cloud run --workflow-id "WORKFLOW_ID" --pull-request-id "PR_ID"
# Rerun from an existing build run with a clean build
asc xcode-cloud run --source-run-id "BUILD_RUN_ID" --clean
# Fetch a single build run by ID
asc xcode-cloud build-runs view --id "BUILD_RUN_ID"Apple Ads campaign management
Apple Ads uses separate OAuth credentials from App Store Connect:
asc ads auth login --name "Marketing" --client-id "SEARCHADS_CLIENT_ID" --team-id "SEARCHADS_TEAM_ID" --key-id "KEY_ID" --private-key ./ads-key.pem --ad-account "987654"
asc ads auth discover --output json
asc ads campaigns find --ad-account "987654" --file query.json --output json
asc ads reports apps campaigns --ad-account "987654" --file report.json --output jsonSee guides/apple-ads-playbooks.mdx for
operator playbooks covering credential safety, org inspection, read-only smoke
tests, reporting, raw API usage, and guarded mutations.
StoreKit server APIs
The App Store Server API and Retention Messaging use a dedicated In-App
Purchase API key, separate from App Store Connect API credentials:
asc storekit auth login --name Production --key-id "KEY_ID" --issuer-id "ISSUER_ID" --private-key ./SubscriptionKey.p8 --bundle-id com.example.app
asc storekit auth doctor --environment sandbox --network
asc storekit transactions history --transaction-id "2000000000000001" --environment production --paginate
asc storekit subscriptions status --transaction-id "2000000000000001" --environment production --output table
asc storekit notifications history --start 2026-09-01 --end 2026-09-08 --only-failures --environment production
asc storekit retention-messaging messages list --environment sandbox --output jsonJSON output prints Apple's response unmodified. --decode adds decoded JWS
payloads without verifying Apple's signature, so don't grant entitlements from
decoded output alone.
See docs/architecture/storekit-retention-messaging.md
for the endpoint map, message and image requirements, environment variables,
and the full sandbox verification sequence.
Commands and Reference
Use built-in help as the source of truth:
asc --help
asc <command> --help
asc <command> <subcommand> --helpReference hierarchy:
asc --help: authoritative command and flag surfacedocs/COMMANDS.md: generated top-level taxonomy mapasc docs show workflows: curated workflow recipesasc docs show reference: repo-local quick reference template used byasc init
For full command families, flags, and discovery patterns, see:
Documentation
- docs/CI_CD.md - CI/CD integration guides (GitHub Actions, GitLab, Bitrise, CircleCI)
- commands/signing.mdx - signing identities, encrypted sync, CI, and release-testing profiles
- docs/COMMANDS.md - Command families and reference navigation
- docs/PARITY.md - Remaining parity areas and intentional non-goals
- docs/WORKFLOWS.md - Reusable workflow patterns, including local Xcode to TestFlight
- guides/apple-ads-playbooks.mdx - Apple Ads operator playbooks
- docs/architecture/storekit-retention-messaging.md - Retention Messaging setup and sandbox verification
- docs/API_NOTES.md - API quirks and behaviors
- docs/CONTRIBUTING.md - CLI development and testing notes
- docs/TESTING.md - Testing patterns and conventions
- docs/openapi/README.md - Offline OpenAPI snapshot + update flow
- CONTRIBUTING.md - Contribution guide
Acknowledgements
Local screenshot framing uses Koubou (pinned to 0.20.0) for deterministic device-frame rendering.
GitHub: https://github.com/bitomule/koubou
Simulator UI automation for screenshot capture and interactions uses AXe CLI.
GitHub: https://github.com/cameroncooke/AXe
The keyword difficulty methodology used by asc optimize keywords score is adapted from semihcihan's App Store Optimization CLI (MIT licensed).
GitHub: https://github.com/semihcihan/App-Store-Optimization-CLI
Contributing
For portable Linux iOS builds and local IPA packaging with xtool and rcodesign,
see the Linux iOS workflow. Native simulator execution still
requires a Mac host; existing Xcode archive/export helpers retain their contracts.
Contributions are welcome. See CONTRIBUTING.md for details.
License
MIT License - see LICENSE for details.
Overview (`docs/openapi/README.md`)
OpenAPI snapshot (offline)
This folder keeps an offline snapshot of the App Store Connect OpenAPI spec for
agents that cannot access the internet.
Files
latest.json: full OpenAPI spec snapshot (see source below)paths.txt: generated path+method index for quick existence checks
Source
Preferred sources for the OpenAPI spec:
- Official Apple download (zip):
https://developer.apple.com/sample-code/app-store-connect/app-store-connect-openapi-specification.zip - Community mirror that tracks Apple's published spec:
https://github.com/EvanBacon/App-Store-Connect-OpenAPI-Spec
Note: The published OpenAPI spec can lag reality and may omit some operations that
still work in the API (parity checks can surface these gaps).
Update process
- Replace
latest.jsonwith a newer spec file. - Run
make update-openapito regeneratepaths.txt. - Run
make update-schema-indexto regenerate the runtimeasc schemaindex. - Run
make check-docsto verify both generated indexes are current. - Update the "Last synced" date below and commit the snapshot and indexes together.
Last synced: 2026-10-07
Snapshot version: 4.5.1, from the official Apple archive listed above.
- Archive SHA-256:
7b826ae9ca3e45b0e7df2643cbb811c9930632640944db11a8af4586fab9db46 - Extracted
openapi.oas.jsonSHA-256:7518d3a94a8bd701ac25c1c601b95b8f53aad92091affb15a2a9d331713dac1a
The snapshot preserves the extracted JSON bytes. Both indexes are generated
from that snapshot using the update process above.
Overview (`internal/cli/devices/testdata/README.md`)
device-callback.p7 is a synthetic, encapsulated CMS SignedData callback containing a plist with UDID, PRODUCT, and VERSION, signed with a self-signed RSA test certificate using OpenSSL cms -sign -binary -nodetach -outform DER. It models the signed profile-service response described by Apple. It is not a captured device response and proves neither Apple certificate trust nor live iOS acceptance. The private key was discarded.
API CANARY LOG (`docs/API_CANARY_LOG.md`)
API Canary & Capture Log
Dated live-canary transcripts, authenticated browser/source captures, disposable test fixture records, and historical verification notes extracted from API_NOTES.md. For active operational contracts and endpoint rules, see API_NOTES.md.
Web App Privacy Data Usage Updates
- Live canary on 2026-09-04 using a disposable app and a redacted web session verified the private
PATCH /iris/v1/appDataUsages/{usageID}contract for a same-category, same-purposeDATA_LINKED_TO_YOUtoDATA_NOT_LINKED_TO_YOUidentity flip. The request uses the existingappDataUsagesJSON:API resource and itsdataProtectionrelationship; Apple returned200, and a fresh GET showed the same usage ID with the new protection. - A second live canary on 2026-09-05 using the same disposable app verified the reverse
DATA_NOT_LINKED_TO_YOUtoDATA_LINKED_TO_YOUtransition. The temporaryEMAIL_ADDRESS/APP_FUNCTIONALITYtuple returned200, and a fresh GET retained the same usage ID with the new protection. The exact pre-canaryDATA_NOT_COLLECTEDbaseline was restored and matched on a fresh GET. - The canary seeded the tuple with
POST(201), confirmed it by GET, then restored the semantic baseline by deleting it (204) and creating theDATA_NOT_COLLECTEDdeclaration (201). Direct restore attempts viaPOSTorPATCHtoDATA_NOT_COLLECTEDreturned409 STATE_ERROR. - The direct mutations advanced the remote
lastPublishedvalue whilepublishedremainedtrue, despite no publish endpoint being called.
Analytics & Sales Reports
- Although Apple's current Sales Reports endpoint table lists
SUBSCRIPTION1_3, live verification in PR #1842 proved1_4succeeds and is required by some accounts, so both are accepted and1_4remains the default.
Tax Categories and Transaction Tax Reports
- Verified against the App Store Connect OpenAPI snapshot in
docs/openapi/and the App Store Connect web-client source captured for issue #2299. - For App Information tax categories (
asc web apps tax-category), the request and response shapes are source-backed. A disposable-app canary verified explicit application tax configuration and readback. PATCH, condition changes, and account-specific errors remain unverified; selecting the legally correct classification remains the operator's responsibility. - For In-App Purchase tax categories (
asc web iap tax-category), a browser canary on 2026-09-06 against disposable app6759231657and IAP6760268101verified ADDON catalog discovery, create, update, explicit condition clearing, and delete/readback; the disposable IAP was restored and no resources were left behind. This is browser evidence only: final CLI live execution remains unverified.
Sandbox Testers
- Historical public v1 create required password confirmation, a secret question/answer, and a birth date; that removed v1 contract does not establish that those fields are accepted by the current private web flow.
- The current App Store Connect web bundle's New Tester form exposes first name, last name, email, password, confirm password, and country. Its two
POST /sandbox/v2/account/validateFieldsrequests project the first three fields, then addacAccountPassword;POST /sandbox/v2/account/createsends exactlyfirstName,lastName,acAccountName,acAccountPassword, andstoreFront.confirmPasswordis UI-only, and the bundle contains nosecretQuestion,secretAnswer, orbirthDatefields. This is a source-backed request shape; it does not prove a successful live create or acceptance of extra portal fields. See issue #2294. - The same bundle lists testers with
GET /sandbox/v2/provider/account/list?limit=50; the only live list response captured was the empty{"totalAccounts":0,"totalInactiveAccounts":0,"accounts":[]}response. No continuation or pagination contract was captured. Account rows includeidandisInFamily; the UI excludes family members from deletion. - Delete status/body, post-delete disappearance, and account-wide mutation acceptance for
POST /sandbox/v2/account/deleteremain unverified because no sandbox tester rows were available during capture; do not infer them from the legacy public v1 surface.
App Store Regulations & Permits declarations
- A sanitized live CLI canary on 2026-09-05 used disposable app
6759231657and a locally built candidate CLI. Theoverall-yes,regional-gbr-false, andoverall-noCLI steps all exited 0 with exact readbacks; the regionalGBRNo path was exercised while the overall declaration was Yes. One exact raw-form restore PUT returned 200 and its readback matched the saved form. The final baseline, session identity, temporary-file cleanup, and no-uncertain-retry checks all passed. This verifies the current CLI's negative regional path and restoration on that disposable app; it does not prove arbitrary affirmative legal/contact values. - The captured Business frontend identifies
personalServiceDeclaration,personalServiceApps, andnonPersonalServiceAppsin dynamic DAC7/MRDP/ITA/SERR compliance forms. The current account has no qualifying live requirement/form fixture to verify the applicable form identity, constraints, and write postcondition.
Web-session API keys
- The captured App Store Connect individual-keys frontend bundle confirms the actor-filtered v2 list (
GET /iris/v2/apiKeys?include=createdByActor,revokedByActor&filter[createdByActor]=USER:{actorID}), the emptyPOST /iris/v2/apiKeyscreate request, browser P-256 key generation with PKCS#8 private and SPKI public export, public-key registration by v2PATCH, and v2 inactive-state revocation. Before the detail/generation flow it checksGET /iris/v1/apiAccessesfor anAPPROVEDaccess resource and obtains the actor ID from initialized page state. This capture contains no/iris/v1/users/orAuthSession.UserEmailrequest. - No individual key was created or revoked during capture or validation, so provider acceptance of a newly created individual key and the individual revoke path remain unverified.
- A sanitized 2026-09-05 live canary created and revoked one newly created temporary team key through an authenticated web session. The pre-existing keys were unchanged, the new key was verified inactive, and temporary files were removed. This verifies the team request path for that account and session.
App price schedules (`POST /v1/appPriceSchedules`)
- Live runs against disposable app
6759231657on 2026-09-26 and 2026-09-27 (each one snapshot, attempts, restore, and verification in a single serialized session) returned HTTP 409 for past start dates, single future start dates, non-base-territory manual prices, and unknown price point IDs. The raw bodies are checked in underinternal/cli/cmdtest/testdata/pricing_schedule_create_409/, byte for byte including Apple's per-response errorid. - At 23:22 UTC on 2026-09-26,
startDate2026-09-26returned 201. At 00:03 UTC on 2026-09-27,startDate2026-09-27(the UTC date, and the CLI's default at the time) returned the future-start 409 (Entire timeline must be covered for USA. The first interval has a start date 2026-10-26T00:00 in the future), while2026-09-26(the US Pacific date) returned 201. OmittingstartDatealso returned 201 and restored the originalstartDate: null/endDate: nullinterval. - Apple's base64 price resource IDs encode interval boundaries as Unix seconds (
sd,ed); a captured in-app purchase schedule encodesedof one interval andsdof the next as the same value,1771228800(2026-02-16 00:00 PST,08:00 UTC). - No subscription-specific live capture exists for
subscriptionPricesstart-date timezone boundaries; the CLI applies the same US Pacific day as app and in-app purchase schedules.
Developer Portal iCloud containers
- fastlane Spaceship still uses the older form endpoint
POST /services-account/QH65B2/account/cloudContainer/addCloudContainer.actionwithname,identifier, andteamId(portal_client.rb#L483-L492) and sends the fulliCloud.-prefixed identifier unchanged (cloud_container.rb#L59-L62, commands_generator.rb#L263-L270). The captured list read shows the current portal reads iCloud containers from/services-account/v1/cloudContainers, not fastlane'slistCloudContainers.action. - Live capture (2026-09-29, CLI built from
414167918, one maintainer-approved permanent container): the sequence waslistTeams.action200, the visible and hidden preflight reads 200,POST https://developer.apple.com/services-account/v1/cloudContainers201, then the visible and hidden read-backs 200. The receipt wascontainerId7PU5FAD3YR, identifieriCloud.com.rorkai.asc.capture.t1790620906, nameasc capture container, prefixYQZQG7N4WG(the team prefix), andhidden:false. A separateasc web icloud-containers listshowed the same ID, identifier, and name. This container is permanent and cannot be removed. It stays in the team as the capture artifact; do not create another one to re-verify. - The create response body was not recorded.
internal/web/testdata/icloud_container_create_capture.jsonholds the captured request body and status, plus the read-back resource. ItscanEdit,canDelete, andresponseIdvalues are inferred. The replay test accepts both a JSON:API resource body and an empty 201 body, because the read-back is the authority either way.
Web-session Resolution Center
- No disposable live thread/draft fixture was available during implementation of
asc web review replyandasc web review drafts create|update|delete, so Apple provider acceptance of these writes remains unverified.
Web-session app create conflicts (`POST /iris/v1/apps`)
- Captured live on 2026-09-29 against disposable app
6759231657: re-runningasc web apps create --name "ASC Test 20260216074703" --bundle-id com.rudrank.asc.throwaway20260216074703 --sku asc-test-20260216074703 --auto-rename=falsereturned HTTP 409 with threeerrors[]entries whose codes are, in order,ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE,ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE, andENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE.SAME_ACCOUNT. The CLI exited 5 withweb apps create failed: web api error (status 409) ... codes=[...]. - Only the status and the codes were captured. The
detail,title, andsourceof each entry were not printed, so which attribute (name, SKU, or bundle ID) each entry refers to is unknown. The captured-shape test fixture carries codes only. A second fixture adds an app-namedetailandsourcepointers to exercise the--auto-renameinteraction; those fields are synthetic (inferred, not captured) and are labeled as such ininternal/cli/web/web_apps_create_if_exists_test.go.
Web-session app distribution method
- The current Apple frontend's pricing action supplies an inner
appsresource to its request action. The shared Iris transport serializer (module 60285, helperOe) converts that resource toJSON.stringify({data: resource})beforefetch(it), so the wire request is JSON:API. asc web apps distribution setwrites are based on the captured frontend constructor and local HTTP tests; no live Apple mutation or live eligibility/success proof was performed.
Web-session custom-distribution Apple Account recipients
- A redacted authenticated browser canary on 2026-09-06 exercised the workflow in CUSTOM mode, including
POST /iris/v1/customAppUserswith HTTP 201, an app-scoped list with HTTP 200, andDELETE /iris/v1/customAppUsers/{id}with HTTP 204. The disposable app was restored to its original public distribution and education settings; fresh app, user, and organization reads showed the baseline and zero remaining rows. This is browser contract evidence, not final CLI live verification, and no organization write was performed. - The live canary does not establish role behavior, duplicate rejection, organization recipient rows, or a second collection page.
App transfer status (web session)
- On 2026-09-06, an authenticated read of disposable app
6759231657(GET /iris/v1/apps/{appId}?include=appTransferRequest) returned HTTP 200, the expectedappsresource ID,relationships.appTransferRequest.data: null, and an emptyincludedarray. The related URL/iris/v1/apps/{appId}/appTransferRequestreturned HTTP 409 withENTITY_ERROR.RELATIONSHIP.INVALIDfor that fixture. No pending-transfer response was observed live.
Last-compatible version settings (`downloadable`)
- A web-session read (
asc web apps last-compatible-version view) briefly existed for this screen and was retired before it reached a release. It mirrored App Store Connect's own iris request (GET /iris/v1/apps/{id}?include=appStoreVersions&fields[appStoreVersions]=...,downloadable,...&limit[appStoreVersions]=2000), which required a web session and offered no write. The public API path supersedes it in both directions.
App Store version creation conflicts (`POST /v1/appStoreVersions`)
- Captured live on 2026-09-15: Apple answers HTTP 409 with
ENTITY_ERROR.RELATIONSHIP.INVALID("You cannot create a new version of the App in the current state.") both when the platform already has an unreleased version and, followed byENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE, when the version string is already used.
Web-session review submissions (iris)
- Verified live on 2026-09-03:
GET /iris/v1/reviewSubmissions/{id}/itemsrejectsinclude=appStoreVersionExperimentV2with HTTP 400PARAMETER_ERROR.INVALIDeven though the public OpenAPI snapshot lists that relationship.
TestFlight text validation (What to Test)
- A live probe on 2026-09-25 against
betaAppLocalizationsdescriptionon a disposable app (the app had no builds, sobetaBuildLocalizationswhatsNewcould not be written directly) rejected the write withAn attribute value has invalid text.: Text contains invalid characters/formats.for: any<(includinga,&, the literal text<, newlines, precomposed Vietnamese, and script-specific combining marks: Devanagari (हिंदी में परीक्षण करें, including a leading U+0902), Thai tone marks, Hebrew niqqud, Arabic harakat, and the Cyrillic titlo U+0483. - A live probe on 2026-09-26 against
betaBuildLocalizationswhatsNew(PATCH on an existing localization, disposable app) answered 409An attribute value has invalid text.: Text for whatsNew contains invalid characters:'[…]'and listed each offending character.- Rejected: every supplementary-plane character (U+1F600, ZWJ family, skin tone U+1F3FD, regional indicators U+1F1FA/U+1F1F8, U+1F004, U+1F130, U+1D400, U+1D11E, U+10330, U+20000); Variation Selectors U+FE00, U+FE0E, U+FE0F (even after a plain
a); enclosing marks U+20DD and keycap U+20E3; Control Pictures U+2400 and OCR U+2440; Box Drawing U+2500/U+257F and Block Elements U+2588/U+2591; Miscellaneous Symbols U+2600–U+26FF (☀☂☺♠♥⚠★☆, U+26FF); Dingbats U+2701–U+27BF (✓✔✖✗✂✨✅❤, U+2794, U+27BF); Miscellaneous Mathematical Symbols-A U+27C2 and -B U+2980; Braille U+2801; Supplemental Mathematical Operators U+2A00; Miscellaneous Symbols and Arrows U+2B00–U+2BFF (⬆⭐⭕⬛); Private Use U+E000 and U+F8FF; U+FFFD. - Accepted: Arrows (U+2192, U+2194, U+21FF), Supplemental Arrows-A U+27F6 and -B U+2934, Letterlike (
™,ℹ,№),©,®, Miscellaneous Technical (U+2300,⌘⌚⌛, U+23E9, U+23F0, U+23FF), Enclosed Alphanumerics (U+2460,Ⓜ, U+24FF), Geometric Shapes (U+25A0, U+25AA,▶, U+25CF, U+25FB, U+25FF), Mathematical Operators, General Punctuation (•…—‼⁉),€, U+3030, U+303D, U+3297, U+3299, newlines, CRLF, tabs, NBSP, soft hyphen, ZWSP, ZWJ/ZWNJ inside Devanagari and Persian, LRM/RLO, U+FEFF, and Latin, Greek, Cyrillic, IPA, CJK, Hangul, Thai, Hebrew, Arabic, Devanagari, and Tamil text. - Whole scripts and presentation forms rejected in the same sample (left to App Store Connect rather than refused locally because neighboring characters such as IPA and U+F900 were accepted): Ethiopic, Cherokee, Khmer, Georgian, Tibetan, Mongolian, Yi, Vai, Glagolitic, U+02C6 (Spacing Modifier Letters), Alphabetic Presentation Forms U+FB01, Arabic Presentation Forms U+FDFD, Vertical Forms U+FE10, Small Form Variants U+FE50, and CJK Compatibility U+3300.
- Rejected: every supplementary-plane character (U+1F600, ZWJ family, skin tone U+1F3FD, regional indicators U+1F1FA/U+1F1F8, U+1F004, U+1F130, U+1D400, U+1D11E, U+10330, U+20000); Variation Selectors U+FE00, U+FE0E, U+FE0F (even after a plain
- A live check on 2026-09-25 showed App Store Connect matches
filter[locale]onbetaAppLocalizationscase-insensitively (fr-frandFR-FRboth returnedfr-FR).
App Store review details (`appStoreReviewDetails`)
- Field length, contact requirement, and phone validation rules were derived from App Store Connect responses observed in agent runs between 2026-09-21 and 2026-09-30:
notes: Rejected over 4000 characters withAn attribute value is too long.: Review Notes cannot be longer than 4000 characters.(agents trimmed and retried up to 7 times, often measuring bytes withwc -c).- Contact fields on create:
POST /v1/appStoreReviewDetailssending onlycontactEmailfailed withYou must provide a value for the attribute 'contactFirstName' with this request; sending first name, last name, and email failed on'contactPhone'. An update sending onlycontactEmailto a detail without a first name also failed oncontactFirstName. contactPhone: Rejected 7 digits after+503, and 7- and 11-digit national numbers after+1, withThe phone number must be in a valid format. Preface the phone number with '+' followed by the country code (for example, +44 844 209 0611)(betaAppReviewDetailsansweredThe format of the phone number is invalid). Numbers without a plus sign (10- and 11-digit North American numbers) were accepted by the API.
Authentication & Rate Limiting
- The public storefront retry path is validated with deterministic
httptestcoverage for status boundaries, Retry-After parsing/capping, response-body draining, request replay, concurrency, and cancellation; it does not perform live mutations. - The bulk-mutating concurrency ceiling of 16 (
asc.WithBulkMutatingRequestLimit) is covered by request-gating tests with a fake transport; its behavior against App Store Connect rate limits has not been measured live yet (#2786).
Builds
- Read-only checks on 2026-09-30 found 62–74 iPad
deviceModelrows inGET /v1/buildBundles/{id}/buildBundleFileSizeson every one of five builds of a live iPhone-only app, matching universal apps. - fastlane encountered the same intermittent
GET /v1/apps/{id}/buildUploads404 flake (There is no resource of type 'apps' with id '…') after moving build-number lookups to that endpoint (https://github.com/fastlane/fastlane/issues/29908).
Xcode Cloud workflows
- On 2026-09-06, an authenticated browser session returned HTTP 200 for
GET /ci/api/teams/{teamID}/scm-providers-v2(six providers) andGET /ci/api/teams/{teamID}/scm-providers/{providerID}/connection-v2(one{"status":"success"}response) with zero mutations. This establishes the provider contract, not final CLI live execution. - Custom Xcode Cloud version aliases (
version-aliases-v3) request and response details are source-captured from the current frontend; no alias mutation was performed against Apple's service during implementation. - A live read on 2026-09-04 confirmed that private
workflows-v15payloads expose TestFlight post-actions underpost_actions[].deployment_config.testflight_deployment_ids. Two distinctbeta_group_idsobserved across two post-actions matched the same app's authenticated IRISbetaGroupsIDs and the public command output fromasc testflight groups list --app APP_ID --paginate --output json(three groups returned). The scan covered 14 products/workflows; all observedbeta_tester_idsarrays were empty, so tester-ID equivalence remains unverified.
Developer Portal session (web session)
- The public schema supports
fields[bundleIds]=bundleIdCapabilitiesandlimit[bundleIdCapabilities]=50; their acceptance by the private/services-account/v1proxy remains live-unverified.
Developer Portal Bundle ID reads (web session)
- Portal-only attributes observed in the captured
POST /services-account/v1/bundleIds(X-HTTP-Method-Override: GET) collection includeidentifier,dateModified,entitlementGroupName,bundleType,platform,wildcard,dateCreated,bundleIdCapabilitiesSettingOption,seedId,name,platformName,deploymentDataNotice, andresponseId.
Developer Portal Services IDs (web session)
- A 2026-09-26 browser capture accepted
PATCH /services-account/v1/bundleIds/{id}forasc web service-ids domains setwith an ID-lessbundleIdCapabilitieschild linked to capabilityAPPLE_ID_AUTHand its existingappConsentBundleId. The captured detail response reportedpaging.total=0withlimit=2147483647despite populated capability linkage. - Live CLI verification on 2026-09-27 confirmed a domain update, unchanged repeat, and two-domain/two-return-URL replacement with the existing primary App ID preserved. The disposable Services ID and parent App ID were deleted afterward.
- A 2026-09-28 live capture on the disposable team (
asc web service-ids create --identifier com.rorkai.asc.capture.services.t1790609684 --name "asc capture temp" --confirm, thenasc web service-ids view --service-id ID --output json, thenasc web service-ids delete --service-id ID --confirm; a follow-up view returned 404 and the list had no match) showed that a Services ID with zero capabilities returns{"meta":{"paging":{"total":0,"limit":2147483647}},"data":[],"links":{"self":...,"related":...}}with nonextlink. The sanitized relationship is checked in asinternal/web/testdata/service_id_empty_capabilities_relationship.json, with the captured resource ID in its links replaced byservice-1.
Developer Portal Website Push IDs (web session)
- The live
listWebsitePushIds.actionread returned HTTP 200 withresultCode=0,pageNumber=1,pageSize=1000, and an empty root-levelwebsitePushIdListin the captured account state. - The captured frontend (
captures/apple-developer-main.js) maps each legacy item toid=item.websitePushIdfor this resource (offset 849,700), while its generic identifier table readsnameandidentifier(offset 866,718); the topic picker also readswebsitePushId/idandidentifier(offsets 1,806,200–1,808,480). - A final built-CLI canary using binary SHA-256
fd940938b7477aa27b0f5c793618af0c5c1e30d050f85b6bacf1738378ce31f9exercised the complete happy path for one uniquely generated owned disposable identifier: baselinelist,create,view,delete, and finallistall exited successfully. The create and delete receipts and detail readback matched the canary identity, the capability relationship and included graph were empty, the baseline list remained unchanged, and final absence plus temporary-evidence cleanup were verified. The CLI view exposed no certificate fields;certificateAbsenceIndependentlyVerifiedis false because this command slice has no certificate endpoint, so the canary does not claim independent certificate absence.
Sparse Fieldsets Combined with Includes
- Observed on 2026-09-02 against a live App Store Connect team:
GET /v1/profileswithfields[profiles]=name&include=devicesreturned HTTP 200 and still put related devices inincluded, while omittingrelationshipson each profile unlessdeviceswas also infields[profiles].fields[devices]=name,udidsparse-filtered the included devices.GET /v1/certificateswithfields[certificates]=displayName&include=passTypeIdreturned HTTP 200. The team had noPASS_TYPE_IDcertificates, soincludedwas absent both with that sparse fieldset and withinclude=passTypeIdalone. Non-pass certificates exposedrelationships.passTypeId.data=nullonly when the relationship was in the fieldset (or when no certificate fieldset was sent).
Developer Portal App Groups (web session)
deleteApplicationGroup.actionwas inferred from siblinglistApplicationGroups.action/addApplicationGroup.actionactions and third-party portal client contracts; it was not live-verified when added because no Developer Portal web session was available.- Both single-resource
PATCH /services-account/v1/bundleIds/{id}capability updates and the pre-delete collection read (POST /services-account/v1/bundleIdswithX-HTTP-Method-Override: GET,fields[bundleIds]=bundleIdCapabilities, andlimit[bundleIdCapabilities]=50) remain unverified against a live private proxy.
Offline signature verification: Apple leaf certificate types
- Built on 2026-09-28 from public certificates on a macOS 27 host:
- Login keychain certificates (
security find-certificate -a -p),DeveloperCertificatesin local provisioning profiles (22 Apple Development, 11 Apple Distribution, 8 legacy iPhone Developer), leaves extracted withcodesign -d --extract-certificatesfrom signed apps and IPAs, and leaves read fromX509Certificateelements of flat-package tables of contents (xar --dump-toc), including Xcode's bundledCoreTypes,MobileDevice,MobileDeviceDevelopment, andXcodeSystemResourcespkgs. - Apple's device-management article "Allowing and denying apps and binaries" confirmed
6.1.9(App Store),6.1.13(Developer ID),6.1.4, and6.1.12(development). Apple's WWDR and Developer ID CPS pages were not machine-readable at the time.
- Login keychain certificates (
Offline signature verification: xar member payload checksums
- Checked on 2026-09-29 against
pkgutil --check-signatureon macOS 27:- Flipping one payload byte of Xcode's
MobileDeviceDevelopment.pkgmadepkgutilreport "package is invalid (checksum did not verify)", andpkg-inforeportedinvalidnamingPayload. - All 12 packages bundled in three installed Xcode releases and a notarized Developer ID Installer pkg verified as
valid. - An older Developer ID Installer pkg whose certificate expired in 2024 recomputed all 13 of its file checksums and still reported
expired. pkgutil --check-signaturestill accepted the older Developer ID Installer pkg after one of its extended-attribute (<ea>) heap bytes was flipped, confirming that<ea>checksums should not be enforced.
- Flipping one payload byte of Xcode's
API NOTES (`docs/API_NOTES.md`)
API Notes
Operational contracts, endpoint quirks, filter/include rules, and live-verification status for App Store Connect, Apple Ads, and Developer Portal surfaces.
Dated maintainer canary transcripts, authenticated browser/source captures, and disposable test fixture records live in the repository at API_CANARY_LOG.md and are intentionally excluded from the embedded asc docs show api-notes guide to keep CLI output concise.
Web App Privacy Data Usage Updates
PATCH /iris/v1/appDataUsages/{usageID}supports only a same-category, same-purpose identity flip betweenDATA_LINKED_TO_YOUandDATA_NOT_LINKED_TO_YOUusing theappDataUsagesJSON:API resource and itsdataProtectionrelationship (live-verified).- Tracking,
DATA_NOT_COLLECTED, and scope changes must useDELETE(204) +POST(201); directPOSTorPATCHtoDATA_NOT_COLLECTEDfails with409 STATE_ERROR. - Direct mutations advance
lastPublishedwhilepublishedremainstruewithout calling a publish endpoint. Treatasc web privacy applyas a published-state mutation.
Apple Ads Profile Context Isolation
- Named Apple Ads profiles use only the context stored on that profile and never inherit
ads.org_idorads.ad_account_idfrom root config or another profile. Profile-less access-token and environment authentication can still use matching root context.
Public App Store Ranking
asc apps public rankis an unauthenticated storefront read: iOS inspects up to 200 results from the public iTunes/searchendpoint, while Apple TV uses the undocumented MZStore search endpoint withX-Apple-Store-Front: <numeric-storefront-id>,33(available only for countries with a known numeric storefront ID).found: falsemeans only that the app was absent from the returned result window.
Analytics & Sales Reports
- Sales Reports require
YYYY-MMfor monthly reports andYYYYfor yearly reports on the wire; the CLI acceptsYYYY-MM-DDand reduces it to the live period identifier before sending. - Vendor number comes from Sales and Trends → Reports URL (
vendorNumber=...). - Sales Reports validates the report type/subtype/frequency/version tuple against Apple's endpoint table. Both
SUBSCRIPTION1_3and1_4are accepted (1_4is the default and live-verified). - Use
--paginatewithasc analytics view --processing-date(filter[processingDate]) to search every report page, or--next <links.next> --paginateto resume. Use--granularity "DAILY,WEEKLY,MONTHLY"to filter by granularity. RaiseASC_TIMEOUTfor long runs.
Finance Reports
Finance reports use Apple fiscal months (YYYY-MM), not calendar months.
API --report-type |
UI Option | --region Code(s) |
|---|---|---|
FINANCIAL |
All Countries or Regions (Single File) | ZZ (consolidated) |
FINANCIAL |
All Countries or Regions (Multiple Files) | US, EU, JP, etc. |
FINANCE_DETAIL |
All Countries or Regions (Detailed) | Z1 (required) |
| Not available | Transaction Tax (Single File) | N/A |
FINANCE_DETAILrequires region codeZ1.- Transaction Tax reports are not exposed in
/v1/financeReportsor/v1/salesReports; useasc web finance transaction-tax downloador App Store Connect. - Region codes:
asc finance regionsor https://developer.apple.com/help/app-store-connect/reference/financial-report-regions-and-currencies/.
Tax Categories and Transaction Tax Reports
- The public API has no tax-category resource or attribute on
apps,appInfos, orinAppPurchases.asc capabilities --area monetizationreports App Information and IAP tax categories as web-session coverage. - App Information (
asc web apps tax-category list|view|set):- Catalog read:
GET /iris/v1/taxCategories?filter[productType]=APPLICATION&include=subcategories,conditions&limit[subcategories]=100&limit[conditions]=100. - App read:
GET /iris/v1/appTaxCategories/{appId}?include=category,enabledConditions&limit[enabledConditions]=100. A missing resource means unconfigured (UI default: App Store Software). setrequires--confirm, validates category and condition IDs against the catalog, sends the complete condition set (enabledConditions.data=[]when--conditionis omitted), and verifies via read-back without retrying ambiguous writes. Explicit creation/readback is live-verified; PATCH, condition changes, and account-specific errors remain unverified.
- Catalog read:
- In-App Purchases (
asc web iap tax-category list|view|set|reset):- Catalog read uses
filter[productType]=ADDONand preserves the raw JSON:API envelope. viewreadsGET /iris/v2/inAppPurchases/{iapId}?include=inAppPurchaseTaxCategoryInfo. ExplicitinAppPurchaseTaxCategoryInfo.data: nullmeans inherited from the parent app (raw discovery envelope preserved). When configured, the CLI readsGET /iris/v1/inAppPurchaseTaxCategoryInfos/{taxInfoId}?include=category,enabledConditions,inAppPurchaseV2&limit[enabledConditions]=100and verifies the owner IAP ID.set(--confirm) validates against theADDONcatalog, creates when inherited or patches when configured, sends at most one write plus one verification read, and skips matching state as a no-op.reset(--confirm) deletes only theinAppPurchaseTaxCategoryInfosoverride and verifies an explicit-null relationship (no-op when already inherited). Browser contract is verified; final CLI live execution remains unverified.
- Catalog read uses
Sandbox Testers
- List, view, update, and clear-history use the public v2 API through
asc sandbox. asc web sandbox createrequires--first-name,--last-name,--email,--password(8+ chars with uppercase, lowercase, and digit), and--territory. Territory inputs accept alpha-2, alpha-3, or exact English country names and normalize to 3-letter ASC territory codes (limited to verified ASC alpha-3 surfaces).- Web create runs two
POST /sandbox/v2/account/validateFieldsrequests followed byPOST /sandbox/v2/account/createwithfirstName,lastName,acAccountName,acAccountPassword, andstoreFront(source-backed; live create acceptance is unverified). - Web list uses
GET /sandbox/v2/provider/account/list?limit=50(no pagination contract captured; rows includeidandisInFamily). asc web sandbox delete(--confirm) sendsPOST /sandbox/v2/account/deletewith{"ids":[...]}, refuses family members and incomplete list snapshots, sends one request, and verifies a fresh list. Delete status/body and post-delete disappearance remain unverified against live data.
App Store Regulations & Permits declarations
- The public OpenAPI snapshot has no medical-device, personal-service, trader, or DSA declaration endpoints (
TerritoryAvailability.contentStatusesexposes only read-only trader status consequence codes). - Declarations use the web-session
ppm/complianceform/v1service with UI headers (X-Csrf-Itc: itc,Origin, and/apps/{id}/distribution/infoReferer):asc web apps declarations list:GET /ppm/complianceform/v1/accounts/{accountId}/requirements?contentId={appId}. PrefersrequirementDatamatchingcontentId, falling back to emptycontentId.asc web apps medical-device view:GET /ppm/complianceform/v1/accounts/{accountId}/requirements/{requirementId}/forms?contentId={appId}, readingmedicalDeviceData.declaration(no,yes, or absent) alongside$[*]-rootedconstraints.asc web apps medical-device set: usesPOST .../contents/{appId}/requirements/{requirementId}/formswhenmedicalDeviceData.declarationis absent andPUTwhen present.--declared falsederives regions from form constraints, preserves existing regional rows, and skips writing when alreadyno+COLLECTED.--declared true [--countries-or-regions EEA,GBR,USA] --confirmlimits regions toEEA,GBR,USA(defaulting to all three), updatesregistrationInforows, and verifies readback.asc web apps medical-device region set --app APP_ID --region REGION --input PATH --confirm: sendsPUT .../formswith rootfs-anchoreddeclaration, localizedsupportInfo, andregistrationNumber(forUSA/EEA). Requires app-level declarationyesand region availability in$[*].countriesOrRegions. Performs contact preflight (legal entity, region coverage, non-empty phone/email/address, no missing constraint legal-entity candidates), sends one PUT, and verifies readback. Negative regional and restore paths are live-verified; arbitrary affirmative legal/contact values remain provider-dependent.
- Personal-service declarations (
personalServiceDeclaration) are not implemented (no live fixture). EU DSA trader status (GET /ppm/v1/accounts/{id}/sellerInfo,POST /ppm/v1/legalEntities/{id}/sellerInfo) requires interactiveid.apple.comidentity verification and is out of scope for unattended CLI writes.
Web-session API keys
asc web api-keys listcombines iris v1 team keys (GET /iris/v1/apiKeys?include=createdBy,revokedBy,provider&sort=-isActive,-revokingDate&limit=2000) and iris v2 individual keys (GET /iris/v2/apiKeys?include=visibleApps,createdByActor,revokedByActor&limit[visibleApps]=3&limit=2000), followinglinks.nextinternally (no--paginate). Useasc web auth capabilities --key-idto resolve actor-backed roles when an individual key'srolesarray is empty.asc web api-keys view --key-idreadsGET /iris/v1/apiKeys/{id}?include=provider(team keys only). Creation dates are absent from the payload and private key material is never printed.asc web api-keys create-individual --user-id USER_UUID --output-dir DIR --confirmchecksGET /iris/v1/apiAccesses(APPROVED) plus fail-closedGET /iris/v1/users/{userUUID}and session-email preflight, stages a local0600PKCS#8 P-256 private key under--output-dir, sendsPOST /iris/v2/apiKeys({data:{type:"apiKeys"}}), resolves the single new active key from the actor-filtered v2 list (filter[createdByActor]=USER:{actorID}), atomically renames the key toApiKey_<KEY_ID>.p8without replacing existing files, registers the SPKI public key viaPATCH /iris/v2/apiKeys/{KEY_ID}, and verifies active readback. Uncertain steps retain the local artifact and never retry automatically. Source-backed; live individual key creation remains unverified.asc web api-keys revoke --key-id KEY_ID --type team|individual --confirmsendsPATCH /iris/{v1|v2}/apiKeys/{id}with{"data":{"id":"","type":"apiKeys","attributes":{"isActive":false}}}after pre-listing the requested family, and verifies inactive post-state without retrying 5xx/transport failures. Team create/revoke is live-verified; individual revoke remains unverified.
Web app availability (iris)
GET /iris/v1/apps/{id}/appAvailabilityV2returnsavailableInNewTerritoriesand links-onlyrelationships.territoryAvailabilities;?include=availableTerritories&limit[availableTerritories]=200fails with 400PARAMETER_ERROR.INVALID. When unconfigured, it can returndata: null(treated as absent byasc web apps availability create).- Territory availability reads use
GET /iris/v2/appAvailabilities/{id}/territoryAvailabilities?include=territory&limit=200(followlinks.next;filter[available]=truereturns 400PARAMETER_ERROR.ILLEGAL, so filter client-side).asc web apps deleteuses this collection for its removed-from-sale preflight. - On the public API,
GET /v1/apps/{id}/appAvailabilityV2andGET /v2/inAppPurchases/{id}/inAppPurchaseAvailabilityreturn 404NOT_FOUNDnamingtype 'appAvailabilities'or'inAppPurchaseAvailabilities'when unconfigured (vs'apps'/'inAppPurchases'when the parent is missing);asc pricing availability viewtreats the unconfigured shape as an expected negative (exit 1,state_not_ready). - When an app has never had a price schedule,
GET /v1/apps/{id}/appPriceSchedulereturns a synthetic schedule resource, andGET /v1/appPriceSchedules/{id}/manualPricesreturns 404There is no resource of type 'null' with id '{appId}'.asc pricing currenttreats that (and a direct schedule 404 or missing current base price) as an expected negative (configured: falseorreason: no_current_base_price). asc web removed-apps restorepatches/iris/v1/apps/{id}withremoved:false, verifies restoration, and only then POSTs/iris/v1/userAppPermissions(GRANTorREVOKEforALL_SILOABLE_USERS).
App price schedules (`POST /v1/appPriceSchedules`)
POST /v1/appPriceSchedulesreplaces the whole schedule. Known HTTP 409 conflict shapes (fixtures ininternal/cli/cmdtest/testdata/pricing_schedule_create_409/):- Past
startDate:ENTITY_ERROR.INVALID_START_DATE(Interval can not have a start date in the past). - Single future
startDatewithout covering today:ENTITY_ERROR.INVALID_START_DATE(Entire timeline must be covered for USA...). - Manual price territory mismatching
baseTerritory:ENTITY_ERROR.BASE_TERRITORY_INTERVAL_REQUIRED. - Unknown or cross-app price point ID:
ENTITY_ERROR.NOT_FOUND.
- Past
- App Store Connect evaluates "today" and schedule interval boundaries at US Pacific midnight (
America/Los_Angeles) with exclusive end dates, and stores an accepted todaystartDateasnull.asc pricing,asc app-setup pricing,asc iap pricing,asc subscriptions pricing, andasc validateall evaluate current/resolved prices and default start dates using the US Pacific date.
Developer Portal iCloud containers
asc web icloud-containers listsendsPOST /services-account/v1/cloudContainers?filter[AND][hidden]=false(ortruewith--hidden) withX-HTTP-Method-Override: GET,teamId, andurlEncodedQueryParams=limit=1000&offset=0&sort=name(bounded first page, no--paginate; warns on stderr if continuation/total indicates truncation).asc web icloud-containers create --identifier iCloud.ID --name NAME --confirmrequires the exactiCloud.prefix, preflights both visible and hidden collections (failing closed on truncation or existing matches), sendsPOST /services-account/v1/cloudContainerswith{"data":{"type":"cloudContainers","attributes":{"identifier":...,"name":...,"teamId":...}}}(no rootteamId), and verifies the new container across both collections without ever retrying (iCloud containers cannot be deleted; live-verified).
Web-session Resolution Center
- Resolution Center has no public OpenAPI surface; all calls use
/iris/v1with Apple ID web-session auth and followlinks.nextinternally (no--paginate). - Submission scope (
GET /iris/v1/resolutionCenterThreads?filter[reviewSubmission]={id}&include=reviewSubmission, used byasc web review show) and app scope (GET /iris/v1/apps/{appId}/resolutionCenterThreads?include=appStoreVersions,app,appMessageThreadDetail,build,betaBackgroundAssetReviewSubmission&limit[appStoreVersions]=2000&filter[threadType]=REJECTION_BINARY,REJECTION_METADATA,REJECTION_REVIEW_SUBMISSION,APP_MESSAGE_ARC,APP_MESSAGE_ARB,APP_MESSAGE_COMM,APP_MESSAGE_INFORMATIONAL, used byasc web review threads --app) are distinct; send the exact known-good query shapes because narrowed/modified includes return 400. - Unsent draft replies live at
GET /iris/v1/resolutionCenterThreads/{threadId}/resolutionCenterDraftMessage?include=resolutionCenterMessageAttachments,fromActor&limit[resolutionCenterMessageAttachments]=1000. Explicitdata: nullor HTTP 404 means no draft; a response missingdatais malformed and fails closed.asc web review threads --draftsomits signed attachment download URLs. - Mutations (
POST /iris/v1/resolutionCenterDraftMessages,PATCH/DELETEon the draft resource, andPOST /iris/v1/resolutionCenterMessageswithcreateFromDraftMessageforasc web review replyandasc web review drafts create|update|delete) require--confirm, never upload attachments, never retry ambiguous writes, and remain unverified against a live fixture.
Web-session app create conflicts (`POST /iris/v1/apps`)
- Re-creating an existing app returns HTTP 409 with three
errors[]entries whose codes are, in order,ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE,ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE, andENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE.SAME_ACCOUNT(captured live on 2026-09-29;detail,title, andsourcewere not captured), soAPIError.AllCodes()must inspect every entry inerrors[]rather than only the first.asc web apps create --if-exists skipkeys on either code and resolves the existing app via the public API (GET /v1/apps?filter[bundleId]=, thenfilter[sku]=).
Web-session app distribution method
distributionType(APP_STOREorCUSTOM) andeducationDiscountType(DISCOUNTED,NOT_DISCOUNTED,NOT_APPLICABLE) exist only on/iris/v1/apps/{id}(no sparse fieldset). MissingdistributionTypeis rendered asunknownin tables and omitted in JSON.asc web apps distribution set --app APP_ID --method public|private --confirmpreflightsGET /iris/v1/apps/{id}, rejectsDIRECT_URLor unknown methods, sends onePATCH /iris/v1/apps/{id}witheducationDiscountTypeand conditionaldistributionType, and verifies via read-back without retries (uncertain outcomes returnstatus: "uncertain"). Source-backed; live mutation remains unverified. Unlisted App Store distribution has no captured endpoint.
Web-session custom-distribution Apple Account recipients
- Private JSON:API under
/iris/v1(customAppUsersonly;customAppOrganizationsis unsupported):asc web apps distribution users list --app APP_ID [--paginate]: readsGET /iris/v1/apps/{appId}/customAppUsers?limit=100and validates pagination invariants strictly.create --app APP_ID --recipient-apple-id APPLE_ACCOUNT --confirm(POST /iris/v1/customAppUsers->201) anddelete --app APP_ID --id RECIPIENT_ID --confirm(DELETE /iris/v1/customAppUsers/{recipientID}->204) require the app to be indistributionType: CUSTOM(never switching distribution implicitly), send at most one write without retry, and verify against a fresh complete collection read. Browser contract is live-verified; final CLI live execution remains unverified.
App transfer status (web session)
asc web apps transfer status --app APP_IDreadsGET /iris/v1/apps/{appId}?include=appTransferRequest(live-verified fordata: null). Do not call/iris/v1/apps/{appId}/appTransferRequestdirectly (returns HTTP 409ENTITY_ERROR.RELATIONSHIP.INVALIDwhen no transfer exists). Transfer initiation/acceptance actions have no captured API contract.
Last-compatible version settings (`downloadable`)
- Last-compatible version settings use the nullable boolean
downloadableattribute on publicappStoreVersions(asc versions list/view --output jsonandasc versions update --downloadable true|false;--downloadable falserequires--confirm). Absentdownloadableis never defaulted totrue. - Live-version lookups query
filter[appStoreState]andfilter[appVersionState]in separate requests (Apple combines them with AND) and preferappVersionState(READY_FOR_DISTRIBUTION) overappStoreState(READY_FOR_SALE), while checkingappStoreStateforPREORDER_READY_FOR_SALE,DEVELOPER_REMOVED_FROM_SALE, andREMOVED_FROM_SALE.
App Store version creation conflicts (`POST /v1/appStoreVersions`)
- Apple returns HTTP 409
ENTITY_ERROR.RELATIONSHIP.INVALID("You cannot create a new version of the App in the current state.") both when an unreleased version already exists for the platform and (withENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE) when the version string is already used. - On an unhandled 409,
asc versions createmakes a best-effort 5s paginated read ofGET /v1/apps/{id}/appStoreVersions?filter[platform]=...&limit=200(which has nosortparameter, so pages are sorted client-side bycreatedDate) to name existing/unreleased versions in the diagnostic.
Web-session app status history
asc web apps history --app APP_ID [--version-id VERSION_ID]lists versions viaGET /iris/v1/apps/{id}/appStoreVersionsand serially readsGET /iris/v1/appStoreVersions/{appStoreVersionId}/appStoreVersionStateChanges(appStoreState,date,initiator), followinglinks.nextinternally. Requires theAppStatusHistoryrole capability; use--version-idor raiseASC_TIMEOUTfor apps with long histories.
Web-session review submissions (iris)
GET /iris/v1/reviewSubmissions/{id}/itemsrejectsinclude=appStoreVersionExperimentV2with HTTP 400PARAMETER_ERROR.INVALID(live-verified);asc web review showomits it and keeps iris-accepted includes (inAppPurchaseVersion,subscriptionVersion,subscriptionGroupVersion).
TestFlight Distribution
externalBuildStatecannot be patched on build beta details (--external-testingwas removed in 5.0.0); manage external distribution viaasc builds add-groups ... --submit --confirmandasc builds remove-groups ... --confirm.POST /v1/builds/{id}/relationships/betaGroupscan briefly return a build-specific 404 right after upload;asc publish testflightconfirmsGET /v1/builds/{id}(stopping onFAILED/INVALID) and retries only that propagation 404 with bounded backoff.asc builds add-groupspreflights build state (and external build beta detail when assigning external groups), failing closed on blockers. On HTTP 422, it performs best-effort diagnostic reads of the build and beta detail.--dry-runperforms the read-only resolution/preflight without mutating or submitting (--submitconflicts with--dry-run).
TestFlight text validation (What to Test)
betaAppLocalizations.descriptionandbetaBuildLocalizations.whatsNewreject<, uncomposed generic combining marks, supplementary-plane characters (U+10000–U+10FFFF, including all emoji), Variation Selectors (U+FE00–U+FE0F), enclosing/keycap marks (U+20D0–U+20FF), and symbol/dingbat/box/braille/private-use ranges (U+2400–U+245F,U+2500–U+259F,U+2600–U+27EF,U+2800–U+28FF,U+2980–U+2BFF,U+E000–U+F8FF,U+FFFD) with 409 invalid-character errors (Text for whatsNew contains invalid characters/Text contains invalid characters/formats). Representative live probes across these blocks (includingU+1F600,U+10330,U+20000,U+FE00/U+FE0E/U+FE0F,U+20DD/U+20E3,U+2400/U+2440,U+2500/U+257F/U+2588/U+2591,U+2600–U+26FF,U+2701–U+27BF,U+27C2,U+2801,U+2980,U+2A00,U+2B00–U+2BFF,U+E000/U+F8FF, andU+FFFD) were rejected, while sampled code points in neighboring blocks (Arrows,Letterlike Symbols,Miscellaneous Technical,Enclosed Alphanumerics,Geometric Shapes) and script-specific marks (Devanagari, Thai, Hebrew, Arabic, Cyrillic) were accepted (full probe log in API_CANARY_LOG.md).asc builds test-notes create|update,asc builds upload --test-notes, andasc publish testflight --test-notesvalidate before any HTTP call: they NFC-normalize, strip<and uncomposed generic combining marks (U+0300–036F,U+1AB0–1AFF,U+1DC0–1DFF,U+20D0–20FF,U+FE20–FE2F) with a stderr notice, and exit 2 on rejected symbol/emoji code points without echoing the text.- Before writing What to Test, the CLI ensures
betaAppLocalizationsexists for the locale (filter[app]+ case-insensitivefilter[locale]), creating it if absent and re-checking onPOST /v1/betaAppLocalizationsHTTP 409 (which can mean either duplicate or invalid locale).
App Store review details (`appStoreReviewDetails`)
notes: rejected over 4000 characters;asc review details-createanddetails-updatecount Unicode characters of trimmed text with CRLF as 1 (validation.ReviewNotesLength) and exit 2 before sending when over 4000.- Contact fields:
details-createwith default--if-exists failrequires non-blank--contact-first-nameand--contact-phonebeforePOST /v1/appStoreReviewDetails(--if-exists skip|updateanddetails-updateare exempt). contactPhone:+1numbers written with digits and standard separators must have exactly 10 digits after+1; other country codes and numbers without+are left to App Store Connect.
Game Center
- Endpoints resolve Game Center detail via
/v1/apps/{id}/gameCenterDetail(404 when disabled;asc game-center details listhas no pagination flags). - Achievements, leaderboards, and leaderboard sets require creating a release resource to go live; image uploads use reserve → upload → commit.
challengesMinimumPlatformVersionsongameCenterDetailsis a replace-onlyPATCHrelationship (GET is rejected) that requires liveappStoreVersionslinkages (rejectsgameCenterAppVersionsand non-live versions withENTITY_ERROR.RELATIONSHIP.INVALID.MIN_CHALLENGES_VERSION_MUST_BE_LIVE).- Leaderboard-set member localizations have no direct GET;
view --idresolves the parent leaderboard and set, then scans the filtered collection. Group challenge relationships are read-only; useasc game-center challenges create --group-id.
Apple Ads Platform API v1
- Platform API v1 (
https://api.ads.apple.com/v1/) is the primaryasc adssurface (since 4.4.0). Legacy Campaign Management API v5 (retiring January 26, 2027) lives underasc ads v5with deprecation warnings (asc ads v5 api requeststays v5;asc ads api requestis v1). - Account-scoped v1 calls send
X-AP-Context: adAccountId=<AD_ACCOUNT_ID>;resolved from--ad-account,ASC_ADS_AD_ACCOUNT_ID, profilead_account_id, or rootads.ad_account_idwhen no profile is selected (--org/ASC_ADS_ORG_IDare never fallbacks). POST /v1/ad-accountsomitsX-AP-Context, whereasGET/PUT /v1/ad-accounts/{id}requireX-AP-Context: adAccountId=;matching{id}.asc ads auth login --network,asc ads auth status --validate, andasc ads auth discovercallGET /v1/me(andGET /v1/aclsfordiscover) withoutX-AP-Context(ASC_ADS_ACCESS_TOKENskips OAuth token exchange).- V1 report pagination belongs in the JSON request body (no
--paginateflag). Negative keywords use a single v1 resource without bulk-delete; product-page countries/devices and custom impression-share report list/view have no one-command v1 replacement. The deprecatedasc ads v5 reports presetwarning follows--level: campaigns, ad groups, ads, keywords, and search terms point to their matchingasc ads reports appscommand; the two ad-group-specific keyword levels point to v1's consolidatedkeywordsorsearch-termsreport.
Authentication & Rate Limiting
- App Store Connect JWTs are valid for 10 minutes.
--api-debug/ASC_DEBUG=apilogsX-Rate-Limitto stderr. - Retries (
ASC_MAX_RETRIES,ASC_BASE_DELAY,ASC_MAX_DELAY,ASC_RETRY_LOG;ASC_MAX_RETRIES=0disables):- GET/HEAD retry transient 408/429/5xx and transport errors.
- Ordinary POST/PATCH/PUT/DELETE replay only on 429 (ambiguous 408/5xx and transport errors are not replayed); explicitly idempotent mutations retry transient failures.
- Presigned
uploadOperationsPUT parts retry per part on 408/429/500/502/503/504 and transient transport errors withinASC_UPLOAD_TIMEOUT. - Unauthenticated public storefront GET commands retry 429/5xx and honor seconds or HTTP-date
Retry-Afterup toASC_MAX_DELAY.
- Concurrency: clients cap ordinary mutating requests at 8, with an opt-in bulk ceiling of 16 (
asc.WithBulkMutatingRequestLimit) used by per-territory availability PATCH fan-out (PATCH /v1/territoryAvailabilities/{id}; bulkPOST /v2/appAvailabilitieson already-configured apps is unverified).
Profiles and certificates list filters
GET /v1/profilesandGET /v1/certificatesrejectfilter[platform]; only Bundle ID list acceptsfilter[platform](includingUNIVERSAL).- Provisioning profile names generated by
signing fetch/signing syncand explicitprofiles create --namevalues are capped at 64 Unicode code points.
Builds
GET /v1/apps/{id}/buildshas no defined order and rejectssortwith 400PARAMETER_ERROR.ILLEGAL; always query latest builds viaGET /v1/builds?filter[app]={id}&sort=-uploadedDate&limit=1. Relationship endpoints and top-level collections frequently accept differentsort/filterparameters.- Build resources and
GET /v1/buildBundles/{id}/buildBundleFileSizesdo not identifyUIDeviceFamily(iPhone-only builds still list iPaddeviceModelrows);asc validatereadsUIDeviceFamilyfrom--ipawhen checking required iPad screenshots. - Build Upload API reads (
/v1/buildUploads*,/v1/buildUploadFiles*,/v1/apps/{id}/buildUploads,/v1/apps/{id}/relationships/buildUploads) intermittently return a false 404There is no resource of type 'apps' with id '…'. Top-level reads retry thattype 'apps'404 directly; app-scoped reads first verifyGET /v1/apps/{id}once and retry only when the app exists.
Xcode Cloud workflows
- Public
/v1/ciWorkflowsquirks:GET /v1/ciWorkflows/{id}omitsdatalinkages on relationships unless?include=product,repository,xcodeVersion,macOsVersionis requested (all four are required byPOST /v1/ciWorkflows), and emitsnullfor optional non-nullable create properties (destination,testConfiguration,filesAndFoldersRule) thatworkflows duplicatemust strip.- TestFlight post-actions (
post_actions[].deployment_config.testflight_deployment_ids, whosebeta_group_idsmatch publicbetaGroupsIDs;beta_tester_idsequivalence is unverified) and workflow-scoped env vars/secrets exist only on private/ci/api/payloads (asc web xcode-cloud env-vars).
- Private
/ci/apiweb-session surfaces:asc web xcode-cloud scm providers list(GET /ci/api/teams/{teamID}/scm-providers-v2) andconnection-status(GET .../scm-providers/{providerID}/connection-v2): read-only, no pagination; provider contract is browser-verified, CLI live execution unverified.asc web xcode-cloud settings next-build-number show|set:GETand bodylessPUT .../products/{productID}/next-build-number?next_build_number=N(--confirm; requiresN> current, verifies read-back, and redacts any returned TestFlight URL).asc web xcode-cloud settings version-aliases list|view|create|update|delete: uses/ci/api/teams/{publicProviderID}/products/{productID}/configuration-options/version-aliases-v3(?limit=100on list, no--paginate; detailGET/PUT/DELETEon.../{versionAliasID}). Saves{name,type,build,locked}with trimmedname(1–40 UTF-16 code units) andtypeinmacos_version|xcode_version. Mutations require--confirm, never retry 408/5xx, and reconcile via read-back (or 404 on delete). Source-captured; live alias mutations remain unverified.
Devices
- Devices have no DELETE endpoint; enable or disable via
PATCH. Registration requires a UDID (iOS) or Hardware UUID (macOS). Resetting the device list is portal-only once per membership year, and disabling does not free slots.
Subscription Offer Codes
POST /v1/subscriptionOfferCodesrequires thepricesrelationship for all modes:FREE_TRIALmust omitsubscriptionPricePoint(--prices "DEU,FRA", otherwise 409ENTITY_ERROR.RELATIONSHIP.INVALID), while paid modes require--prices "DEU:PRICE_POINT_ID".
Monthly Subscriptions with a 12-Month Commitment
- Exposed via
subscriptionPlanAvailabilities(/v1/subscriptions/{id}/planAvailabilities) withplanType=MONTHLY(upfront availability stays onsubscriptionAvailability). /v1/subscriptionPricePoints/{id}/adjustedEqualizationsaccepts onlyfilter[planType]=MONTHLY(rejectsUPFRONT).- Unavailable in
USAandSGP; the CLI strips both territories before writing monthly-commitment plan availability.
Subscription Plan Availability
GET /v1/subscriptions/{id}/planAvailabilities?include=availableTerritoriescapslimit[availableTerritories]at 50; complete territory lists requireGET /v1/subscriptionPlanAvailabilities/{id}/relationships/availableTerritories(limit=200, cursor-paginated).PATCH /v1/subscriptionPlanAvailabilities/{id}replacesavailableTerritorieswholesale and allows onlyavailableInNewTerritoriesas a mutable attribute (planTypeis create-only, andavailableInNewTerritoriesis unsupported forMONTHLY).setverifies territories via the paginated relationship endpoint andavailableInNewTerritoriesvia a fresh resource GET.- Removing an approved subscription from sale (emptying
availableTerritories) requires the Account Holder web session (asc web subscriptions availability remove-from-saleon/iris/v1).
Developer Portal session (web session)
- Bundle IDs, Services IDs, Website Push IDs, App Groups, iCloud containers, and agreements bootstrap CSRF and team context via
POST /services-account/QH65B2/account/listTeams.action. Same-origin HTTPS redirects are enforced, and cookies/CSRF tokens are never logged. --developer-team(ID or exact name) is accepted only on Developer Portal commands (web bundle-ids,web service-ids,web website-push-ids,web app-groups,web icloud-containers,web agreements). An explicit selector overrides the cached team; without one, the cached team is reused if available, otherwise the selected App Store Connect provider is matched to a team. A single remaining team is used; multiple unmatched teams require--developer-team. There is no team-selection environment-variable fallback.asc web bundle-ids capabilities disable --bundle-id ID --capability PRIVATE_CLOUD_COMPUTE --confirmrequires confirmation and currently supports onlyPRIVATE_CLOUD_COMPUTE. It resolves the Bundle ID throughPOST /services-account/v1/bundleIds(X-HTTP-Method-Override: GET), readsfields[bundleIds]=bundleIdCapabilities+include=bundleIdCapabilities,bundleIdCapabilities.capability(limit[bundleIdCapabilities]=50), fails closed on pagination/linkage ambiguity, sends a singlePATCH /services-account/v1/bundleIds/{id}withenabled:falseon the target capability, and verifies disabled/removed state via read-back without retry (private proxy acceptance of the field/limit query remains live-unverified).
Developer Portal Bundle ID reads (web session)
asc web bundle-ids listsendsPOST /services-account/v1/bundleIds(X-HTTP-Method-Override: GET) withteamIdandurlEncodedQueryParams(limit=1000,sort=name,filter[platform]=IOS,MACOS). Preserves the JSON:API envelope and warns on stderr if more pages are indicated (no--paginate).asc web bundle-ids view --bundle-id IDsendsPOST /services-account/v1/bundleIds/{id}(X-HTTP-Method-Override: GET, body{teamId}) withfields[bundleIds]=name,identifier,platform,seedId,wildcard,~permissions.delete,~permissions.editand the full capability/app-group/merchant/cloud-container/certificate/consent include graph.
Developer Portal Services IDs (web session)
- Public OpenAPI
BundleIdPlatformomitsSERVICES; Services IDs use the private/services-account/v1/bundleIdsproxy (X-HTTP-Method-OverrideforGET/DELETE, directPOST/PATCHfor writes) and verifyattributes.platform=SERVICES:list:POST /services-account/v1/bundleIds(X-HTTP-Method-Override: GET,limit=1000,sort=name,filter[platform]=SERVICES, body{urlEncodedQueryParams, teamId}).view --service-id ID:POST /services-account/v1/bundleIds/{id}(X-HTTP-Method-Override: GET, body{teamId}) withinclude=bundleIdCapabilities,bundleIdCapabilities.capability,bundleIdCapabilities.appConsentBundleId.create --identifier ID --name NAME --confirm:POST /services-account/v1/bundleIdswithdata.type=bundleIds,attributes.{identifier,name,platform:"SERVICES",seedId,teamId}, and emptybundleIdCapabilities.data(must omit root-levelteamId, which Apple rejects on non-overridePOST). Verifies via detail read-back.rename --service-id ID --name NAME --confirmanddomains set: preflight and post-write reads enforcebundleIdCapabilitiescompleteness (nonextlink, and either exactpaging.totalor the capturedpaging.total=0, limit=2147483647placeholder with resolved references; known navigation-only links likeappGroups/bundleIdwithoutdataare omitted fromPATCH).domains setupdatesAPPLE_ID_AUTH(APPLE_ID_AUTH_WEB_DOMAIN,APPLE_ID_AUTH_WEB_RETURN_URL) on an already enabled Services ID and is live-verified.delete --service-id ID --confirm: sendsPOST /services-account/v1/bundleIds/{id}withX-HTTP-Method-Override: DELETEand root-levelteamId(omitting it returns HTTP 403Please select a team.), expects 204, and verifies a follow-up detail 404. No Services ID mutation retries automatically.
Developer Portal Website Push IDs (web session)
asc web website-push-ids listcalls legacyPOST /services-account/QH65B2/account/ios/identifiers/listWebsitePushIds.action(onlyCountLists=true,pageSize=1000,pageNumber=1,sort=name=asc,teamId, nosidx; no--paginate) and preserves the rootwebsitePushIdListresponse.view,create, anddeleteuse modern/services-account/v1/websitepushIds(live-verified):view:POST /services-account/v1/websitepushIds/{id}?include=websitepushIdCapabilities(X-HTTP-Method-Override: GET, body{teamId}).create(--confirm): requires legacy list preflight to be non-full (pageNumber=1, rows <pageSize) with no matching identifier and an empty capability catalog, sendsPOST /services-account/v1/websitepushIds(data.type=websitepushIds,name,identifier,teamId, emptywebsitepushIdCapabilities), and settles via detail/legacy-list read-back without retry.delete(--confirm): preflights modern detail (canDelete=trueand proven empty capability relationship), sendsPOST /services-account/v1/websitepushIds/{id}(X-HTTP-Method-Override: DELETE, body{teamId}), expects 204, and verifies absence in both detail and legacy list without retry. Rename and non-empty capability configuration are unsupported.
Developer Portal Agreements (web session)
asc web agreements status|accept|downloadusesPOST /services-account/QH65B2/account/getAgreementHistoryandPOST /services-account/QH65B2/account/acceptAgreements(HTTP 200 withresultCode=0on success).acceptre-readsgetAgreementHistoryto verifydateAccepted >= dateEffective.downloadresolves root-relativeagreementDownloadUrlpaths against the Developer Portal origin (same-origin HTTPS only, non-empty/non-HTML) and redacts the URL from download receipts and errors.
Transaction Tax reports (web session)
asc web finance transaction-tax downloadreads/WebObjects/iTunesConnect.woa/ra/paymentConsolidation/providers/{providerId}/sapVendorNumbers/{sapVendorNumber}?year={YYYY}&month={M}(requiringhasVendorTaxReport=true), derivesregionCurrencyIdsfromreportSummaries[].proceedsByRegion[], triggers generation viaGET .../reports?...&isVendorTaxReportReq=true, polls/reports/{uuid}/statusforreadyForDownload, and streams the same-origin HTTPS ZIP under a bounded workflow context into a new0600file without retries or leaking sensitive identifiers/URLs.
Pass Type IDs
/v1/passTypeIds/{id}/certificatesrejectsinclude=passTypeIdandfields[passTypeIds]on the live API despite the OpenAPI spec;asc pass-type-ids certificates listomits both.
Sparse Fieldsets Combined with Includes
GET /v1/profileswithfields[profiles]=name&include=devicespopulatesincludedeven thoughdevicesis omitted from the primary fieldset;fields[devices]=...still sparse-filters included devices. Apple omits per-profilerelationshipsunlessdevicesis also infields[profiles](live-verified).GET /v1/certificatesacceptsfields[certificates]=displayName&include=passTypeId. The probed team had noPASS_TYPE_IDcertificates, soincludedwas absent with and without the sparse fieldset; population of related pass types remains unverified. Non-pass certificates exposerelationships.passTypeId.data=nullonly whenpassTypeIdis in the fieldset or no certificate fieldset is sent (live-verified; see the canary record).
App Store Connect API 4.4.1
- Apple added discrete versions for in-app purchases, subscriptions, and subscription groups. Their v2 localizations and images are version-scoped; pass a version ID rather than the legacy product, subscription, or group ID.
- Review submissions accept
inAppPurchaseVersions,subscriptionVersions, andsubscriptionGroupVersionsthroughreviewSubmissionItems. The CLI preserves both relationship data andincludedresources in JSON output. - API 4.4.1 has no item-detail GET operation (
asc review items viewandasc review items-getwere removed in 4.0.0); list a parent submission's items withasc review items list --submission "SUBMISSION_ID". - API 4.4.1 has no marketplace-webhook instance GET operation;
asc marketplace webhooks viewselects the exact ID across all pages of the collection GET. - Review-item updates accept only nullable
resolvedandremovedattributes (--statewas removed in 4.0.0; use--resolved,--removed, or their matching--clear-*flags, with--confirmforremoved=true). - Review-submission updates expose nullable
platform,submitted, andcanceledvalues plus matching--clear-*flags (--confirmrequired forsubmitted=trueorcanceled=true; false, null, and platform-only updates do not). - Canceling a review submission in a non-cancellable state returns HTTP 409
Resource state is invalid.: Resource is not in cancellable state. On that error path,asc review submissions-cancel,asc review submissions-update --canceled=true, andasc submit cancelreadGET /v1/reviewSubmissions/{id}once to explain its state and next step while preserving the conflict exit code. - The create schema names its second experiment relationship
appStoreVersionExperimentV2, while its linked resource type remainsappStoreVersionExperiments; the CLI selector isappStoreVersionExperimentsV2(the 3.x--item-type appStoreVersionExperimentV2alias was removed in 4.0.0, and experiment treatments are not valid review-item create relationships). Review items requireappCustomProductPageVersions(appCustomProductPagesis rejected). - The product-scoped v1 localization, image, submission, and
synccommand families (asc iap localizations|images|submit,asc subscriptions localizations|images|submit,asc subscriptions groups localizations|submit,asc subscriptions review submit|submit-group) were removed in 5.0.0.asc iap setupandasc subscriptions setupremain supported and warn only when localization flags request legacy v1 localization steps. - Migration mapping:
- IAP localizations/images → create or resolve an IAP version, then use
asc iap versions localizations .../asc iap versions images .... - Subscription localizations/images → create or resolve a subscription version, then use
asc subscriptions versions localizations .../asc subscriptions versions images .... - Subscription group localizations → create or resolve a group version, then use
asc subscriptions groups versions localizations .... - IAP / subscription / subscription-group submissions →
asc review items add --submission "SUBMISSION_ID" --item-type --item-id "<VERSION_ID>".
- IAP localizations/images → create or resolve an IAP version, then use
- There is no one-command version-scoped replacement for the removed legacy localization
syncleaves; reconcile entries through the matching version-localizationlist/create/update/deletecommands. - A legacy IAP image file re-upload has no one-to-one v2 update: create the replacement version image, then delete the old version image if needed. Subscription v2 image updates do not expose the legacy checksum flag; use the version image upload workflow for a new file.
- Exported
internal/asc.Clientmethods targeting these legacy resources remain callable during the transition window, withDeprecated:comments on legacy submission and IAP localization/image entrypoints naming their version-scoped or review-item replacements. - Nullable v2 localization updates distinguish omitted, value, and JSON
null; use the corresponding--clear-*flag for explicit clears.
Developer Portal App Groups (web session)
asc web app-groupscombines legacy form endpoints under/services-account/QH65B2/account/ios/identifiers/(listApplicationGroups.action,addApplicationGroup.action,deleteApplicationGroup.action;application/x-www-form-urlencodedwithteamIdand scopedcsrf/csrf_tsheaders, returningresultCode=0) with/services-account/v1/bundleIds:delete(deleteApplicationGroup.action, inferred and live-unverified) first scans all team Bundle IDs via proxiedPOST /services-account/v1/bundleIds(X-HTTP-Method-Override: GET,fields[bundleIds]=bundleIdCapabilities,limit=200,limit[bundleIdCapabilities]=50, followinglinks.next) and refuses if anyAPP_GROUPScapability (even disabled) references the group. After delete, it verifies absence across the strictly paginated App Group list.assign,unassign, andsetread the Bundle ID withfields[bundleIds]=bundleIdCapabilitiesandlimit[bundleIdCapabilities]=50, fail closed on incomplete/ambiguous capability or nestedappGroupspagination, rewrite onlyAPP_GROUPS, andPATCH /services-account/v1/bundleIds/{id}(live proxy acceptance remains unverified).assignrejects agroup.*identifier with exit 2 when the team listing proves an opaque resource ID is required. Ambiguous writes settle via read-back without retry and returnDeveloperAppGroupUnverifiedErrorwhen unverified.
Offline signature verification: Apple leaf certificate types
The offline verifier uses Go's certificate-chain policy, not macOS's platform trust policy. SHA-1-signed leaf or intermediate certificates report untrusted-chain, even when codesign --verify --strict accepts the binary. The receipt still reports successful CMS and code-page integrity checks when those checks pass. Recognizing an Apple leaf type does not override certificate-chain policy, expiry, or trust anchors.
asc ipa-info --verify-signature accepts only code-signing leaves and asc pkg-info --verify-signature accepts only installer leaves (internal/artifacts/verify_leaf.go). Leaves are matched most-specific-first by Apple OID markers (1.2.840.113635.100.…, treated as handled critical extensions) and required EKU; unrecognized or dual-use leaves report untrusted-chain:
| Type | Apple markers (1.2.840.113635.100.…) |
EKU | Artifact | Observed on |
|---|---|---|---|---|
| Apple Development | 6.1.2 + 6.1.12 |
codeSigning | IPA / Mach-O | keychain; profiles; Apple docs |
| Apple Distribution | 6.1.4 + 6.1.7 |
codeSigning | IPA / Mach-O | keychain; profiles; Apple docs |
| iPhone Developer (legacy) | 6.1.2 |
codeSigning | IPA / Mach-O | keychain; profiles |
| iPhone Distribution (legacy) | 6.1.4 |
codeSigning | IPA / Mach-O | keychain; profiles; App Store export IPA executable |
| Mac App Distribution | 6.1.7 |
codeSigning | IPA / Mach-O | Mac half of Apple Distribution |
| Mac Development | 6.1.12 |
codeSigning | IPA / Mach-O | Mac half of Apple Development; Apple docs |
| Developer ID Application | 6.1.13 |
codeSigning | IPA / Mach-O | keychain; third-party /Applications apps; Apple docs |
| Mac App Store application signing | 6.1.9 |
codeSigning | IPA / Mach-O | Mac App Store app in /Applications; Apple docs |
| Apple platform signing | 6.22 |
codeSigning | IPA / Mach-O | /bin/ls, Safari |
| Developer ID Installer | 6.1.14 |
1.2.840.113635.100.4.13 |
pkg | notarized third-party pkg |
| Mac Installer Distribution | 6.1.8 |
1.2.840.113635.100.4.9 |
pkg | Xcode-exported Mac App Store pkgs |
| Apple package signing (Software Update) | 6.1.29.2 |
1.2.840.113635.100.4.1 |
pkg | Xcode's bundled CoreTypes, MobileDevice, MobileDeviceDevelopment, and XcodeSystemResources pkgs |
- iOS App Store re-signing and TestFlight (
6.1.25.x) leaves lack local certificate fixtures and reportuntrusted-chain. CMS-onlyx-signatureMac App Store pkgs reportunsupported.
Offline signature verification: xar member payload checksums
asc pkg-info --verify-signature(internal/artifacts/verify_xar.go) validates all<file><data>heap ranges in the xar table of contents (including component packages) before hashing and verifies each distinct range'sarchived-checksum(sha1,sha256,sha384,sha512,md5; other algorithms reportunsupportedunless another member isinvalid).- Missing checksums on heap data, malformed hex, out-of-heap ranges, or partially overlapping ranges report
invalid. Extended-attribute (<ea>) checksums are intentionally not enforced becausepkgutil --check-signatureaccepts modified<ea>heap bytes.
CI CD (`docs/CI_CD.md`)
CI/CD Integrations
GitHub Actions
Install asc using the official setup action:
- uses: rudrankriyam/setup-asc@v1
with:
version: latest
- run: asc --helpFor end-to-end examples, see:
https://github.com/rudrankriyam/setup-asc
GitLab CI/CD Components
Use the official asc-ci-components repository:
include:
- component: gitlab.com/rudrankriyam/asc-ci-components/run@main
inputs:
stage: deploy
job_prefix: release
asc_version: latest
command: asc --helpFor install/run templates and self-managed examples:
https://github.com/rudrankriyam/asc-ci-components
Bitrise
Use the official setup-asc Bitrise step repository:
workflows:
primary:
steps:
- git::https://github.com/rudrankriyam/steps-setup-asc.git@main:
inputs:
- mode: run
- version: latest
- command: asc --helpCircleCI
Use the official CircleCI orb repository:
https://github.com/rudrankriyam/asc-orb
Code signing on a fresh runner
A fresh macOS runner can go from an App Store Connect API key to a signed
archive without hand-written security calls. Provide ASC_KEY_ID,ASC_ISSUER_ID, and ASC_PRIVATE_KEY_B64 from the CI secret store, then run
these commands in one shell step:
- First run only, when the team has no active certificate of the required
type:asc signing fetch --create-missing --create-missing-certificate
creates the private key, CSR, certificate, profile, and a
password-protected.p12. Push that identity to the encrypted store
(asc signing sync pushwith--identity) before building, because the
private key exists only on this runner. - Every later run:
asc signing sync pulldecrypts the stored identity and
profiles. asc signing keychain install --confirmimports the identity into a new
dedicated keychain, andasc profiles local installinstalls each profile.asc xcode signing planwith--profileinfers each target's signing
settings and anExportOptions.plistfrom the profiles, andasc xcode signing apply --confirmwrites those settings into the project.asc xcode archive, thenasc xcode exportwith--export-options,
produce the signed archive and IPA.asc signing keychain delete --confirmremoves the job keychain.
The first-run commands look like this. The full script, including the
password files, cleanup trap, and later-run variant, is in the
code signing guide and the
signing command reference.
asc signing fetch --bundle-id com.example.app --profile-type IOS_APP_STORE --create-missing --create-missing-certificate --identity-password-file "$p12_password_file" --output .asc/signing/bootstrap --format json > .asc/signing/fetch-result.json
asc signing sync push --bundle-id com.example.app --profile-type IOS_APP_STORE --repo [email protected]:team/signing.git --password-file "$sync_password_file" --identity "$p12_path" --identity-password-file "$p12_password_file" --output json
asc signing keychain install --identity "$p12_path" --identity-password-file "$p12_password_file" --keychain "$keychain_path" --keychain-password-file "$keychain_password_file" --add-to-search-list --confirm --output json
asc profiles local install --path "$profile_path" --output json
asc xcode signing plan --project Example.xcodeproj --profile "$profile_path" --configuration Release --export-options-out .asc/artifacts/ExportOptions.plist --output json
asc xcode signing apply --plan .asc/xcode/signing/plan.json --confirm --output json
asc xcode archive --project Example.xcodeproj --scheme Example --configuration Release --archive-path .asc/artifacts/Example.xcarchive
asc xcode export --archive-path .asc/artifacts/Example.xcarchive --export-options .asc/artifacts/ExportOptions.plist --ipa-path .asc/artifacts/Example.ipa
asc signing keychain delete --keychain "$keychain_path" --confirmp12_path and profile_path come from the p12Path and profileFile fields
of the fetch receipt. xcode signing plan exits 0 with ready: false when a
target has no matching profile; check ready before applying.
Expired or invalid profiles can already be removed withasc signing fetch --delete-stale-profiles, and certificates can be
deactivated or revoked with asc certificates. With Git storage,asc signing sync push --renew-expired replaces an expired synced profile,--force-for-new-devices (optionally with --include-mac-in-profiles)
recreates a development or ad hoc profile when its devices change, andasc signing sync nuke removes one profile type with its certificates and
encrypted files. These lifecycle options require Git storage.
Signing assets in object storage with workload identity
asc signing sync push, pull, and rotate-password can keep the encrypted
artifacts in an S3 bucket instead of a Git repository with --storage object
and --object-bucket, plus optional --object-prefix, --object-region, and--object-endpoint. Objects use the Git tree's layout, so a bucket prefix and
a signing repository can be mirrored byte for byte. No flag accepts a cloud
credential: credentials and, unless --object-region is set, the region come
from the standard AWS configuration chain (environment variables, shared
config or SSO profiles, web identity, or container and instance metadata).
In GitHub Actions, OpenID Connect lets the job assume an AWS role and receive
short-lived credentials, so no AWS access key is stored in the repository or
passed to asc:
permissions:
id-token: write
contents: read
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: rudrankriyam/setup-asc@v1
with:
version: latest
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/asc-signing-read
aws-region: us-east-1
- name: Pull signing assets
env:
ASC_SIGNING_SYNC_PASSWORD: ${{ secrets.ASC_SIGNING_SYNC_PASSWORD }}
run: |
asc signing sync pull --storage object --object-bucket team-certs --object-prefix asc/ --output-dir .asc/signing/pulled --output jsonconfigure-aws-credentials exchanges the job's OIDC token for temporary role
credentials and exports them, with AWS_REGION, to later steps. The role's
trust policy should accept only this repository (and, ideally, a protected
branch or environment), and a pull-only role needs just s3:ListBucket ands3:GetObject on the prefix. A job that runs sync push orsync rotate-password also needs s3:PutObject, and rotation needss3:DeleteObject to remove its staged objects. The repository encryption
password still comes from the CI secret store, never from a command argument:
push and pull read ASC_SIGNING_SYNC_PASSWORD or a protected--password-file, and rotate-password requires protected--password-file and --new-password-file files and ignores the environment
variable.
Push and rotation use conditional writes, so a push that races another writer
fails with exit code 1 instead of overwriting newer data; pull and retry. For an
S3-compatible service, add --object-endpoint with its HTTPS origin; requests
then use path-style addressing, and a service that ignores If-Match andIf-None-Match loses the conflict protection. Receipts includestorage.kind (object) and storage.location (s3:///).
COMMANDS (`docs/COMMANDS.md`)
Command Reference Guide
This file is generated from live CLI help output.
For authoritative command behavior, also use:
asc --help
asc <command> --help
asc <command> <subcommand> --helpTo regenerate:
make generate-command-docsUsage Pattern
asc <subcommand> [flags]Global Flags
--api-debug- Enable HTTP debug logging to stderr (redacts sensitive values)--debug- Enable debug logging to stderr--profile- Use named authentication profile (accepted before or after the command name)--read-only- Refuse every mutating request (POST/PATCH/PUT/DELETE) before it is sent; ASC_READ_ONLY=1 has the same effect (default: false)--report- Report format for CI output (e.g., junit)--report-file- Path to write CI report file--retry-log- Enable retry logging to stderr (overrides ASC_RETRY_LOG/config when set)--strict-auth- Fail when credentials are resolved from multiple sources (default: false)--version- Print version and exit (default: false)
Command Families
Getting Started
auth- Manage authentication for the App Store Connect API.doctor- Diagnose authentication configuration issues.install-skills- Install the asc skill pack globally for App Store Connect workflows.init- Initialize asc helper docs in the current repo.docs- Access embedded documentation guides and reference helpers.
Web Session Commands
web- Apple web-session workflows, including finance report downloads.
Analytics and Finance
analytics- Request and download analytics and sales reports.ads- Manage Apple Ads API resources.optimize- Build cross-API optimization plans.insights- Generate weekly and daily insights from App Store data sources.finance- Download payments and financial reports.performance- Access performance metrics and diagnostic logs.
App Management
apps- List and manage apps in App Store Connect.app-setup- Post-create app setup automation.app-tags- Inspect Apple-generated App Store discoverability tags.versions- Manage App Store versions.localizations- Manage App Store localization metadata.metadata- Manage app metadata with deterministic workflows and keyword tooling.screenshots- Upload and manage App Store screenshots, including local capture, framing, and matrices.video-previews- Manage App Store app preview videos.asset-library- Inspect Asset Library media and specifications, and upload images and videos.background-assets- Manage background assets.product-pages- Manage custom product pages and product page experiments.routing-coverage- Manage routing app coverage files.pricing- Manage app pricing and availability.pre-orders- Manage app pre-orders.categories- Manage App Store categories.age-rating- Manage App Store age rating declarations.accessibility- Manage accessibility declarations.encryption- Manage app encryption declarations and documents.eula- Manage End User License Agreements (EULA).agreements- Manage agreements in App Store Connect.app-clips- Manage App Clip experiences and invocations.android-ios-mapping- Manage Android-to-iOS app mapping details.marketplace- Manage marketplace resources.alternative-distribution- Manage alternative distribution resources.nominations- Manage featuring nominations.game-center- Manage Game Center resources in App Store Connect.
TestFlight and Builds
testflight- Manage TestFlight workflows.builds- Manage builds in App Store Connect.build-bundles- Manage build bundles and App Clip data.build-localizations- Manage build release notes localizations.xcode- Local Xcode build/archive/export and signing-settings helpers.distribute- Plan, execute, inspect, and publish iOS distribution artifacts.ipa-info- Inspect a local IPA without contacting App Store Connect.pkg-info- Inspect a local flat package or product archive without contacting Apple.sandbox- Manage sandbox testers in App Store Connect.
Review and Release
release- Run high-level App Store release workflows.status- Show a release pipeline dashboard for an app.release-notes- Generate and manage App Store release notes.review- Manage App Store review details, attachments, and submissions.reviews- List and manage App Store customer reviews.submit- Submission lifecycle tools; usepublish appstore --submitto ship.validate- Canonical App Store submission readiness report.publish- High-level publish workflows for TestFlight and App Store.
Monetization
iap- Manage in-app purchases in App Store Connect.storekit- Manage StoreKit server APIs with In-App Purchase API keys.app-events- Manage App Store in-app events.subscriptions- Manage subscription groups and subscriptions.
Signing
signing- Manage signing certificates and profiles.bundle-ids- Manage bundle IDs and capabilities.certificates- Manage signing certificates.profiles- Manage provisioning profiles.merchant-ids- Manage merchant IDs and certificates.pass-type-ids- Manage pass type IDs.notarization- Manage macOS notarization submissions.
Team and Access
account- Inspect account-level health and access signals.users- Manage users and invitations in App Store Connect.actors- Lookup actors (users, API keys) by ID.devices- Manage devices in App Store Connect.
Automation
workflow- Run multi-step automation workflows.webhooks- Manage webhooks in App Store Connect.xcode-cloud- Trigger and monitor Xcode Cloud workflows.notify- Send notifications to external services.migrate- Migrate metadata from/to fastlane format.
Utility
system-status- Check Apple Developer service health.diff- Generate deterministic non-mutating diff plans.capabilities- Show CLI, API, web-only, and public-API-limited capability coverage.search- Search asc commands and examples for agent-oriented command discovery.snitch- Report CLI friction as a GitHub issue.version- Print version information and exit.completion- Print shell completion scripts.schema- Inspect App Store Connect API endpoint schemas at runtime.api- Send an authenticated raw request to the App Store Connect API.telemetry- Manage CLI telemetry settings.
Scripting Tips
- Output defaults are TTY-aware: interactive terminals default to
table, while piped/non-interactive output defaults to minifiedjson. - Use
--output tableor--output markdownfor explicit human-readable output. - Use
--output jsonfor explicit machine-readable output. - Use
--paginateon list commands to fetch all pages automatically. - Use
--limitand--nextfor manual pagination control. - Any flag that takes a value accepts
@env:NAMEor@file:PATHto read it from the environment or a file; escape a literal leading@as@@.--outputand the root--profile,--report, and--report-fileare always read literally. - Prefer explicit flags and deterministic outputs in CI scripts.
High-Signal Examples
/* Detailed source-code truncated for AI context efficiency. */Related Documentation
- ../README.md - onboarding and common workflows
- API_NOTES.md - API-specific behavior and caveats
- TESTING.md - test strategy and patterns
- CONTRIBUTING.md - contribution and dev workflow
CONTRIBUTING (`docs/CONTRIBUTING.md`)
Contributing
For development setup, integration tests, and PR guidelines, see the root CONTRIBUTING.md.
This file covers patterns for AI agents working on the codebase.
Git Workflow
- Branch from
mainand keep one logical change per branch - Do not commit directly to
mainunless explicitly instructed; prefer PRs - Prefer
git worktree addfor parallel tasks; remove withgit worktree removewhen done - Compare the branch with current
mainbefore merging. Do not rebase or mergemaininto a clean branch merely becausemainadvanced; update only when GitHub reports an actual merge conflict or refuses an explicitly authorized merge attempt made after every readiness gate passes under strict up-to-date branch protection, and never bypass protection with an admin merge - Preserve additive PR commits. Do not force-push, rebase, squash, or otherwise rewrite shared history unless the user explicitly requests it. When merging, use a regular merge commit by default
- Commit small, coherent changes; no WIP commits on shared branches
- Use concise, present-tense commit messages that match repo style
- Never commit secrets or local config files (keys,
.env,.asc/config.json)
Before Committing
For substantive behavior changes, run:
make build # Build the CLI
make format # Format code
make lint # Check for issues
make check-docs # Verify repo docs, website docs, and command docs stay in sync
ASC_BYPASS_KEYCHAIN=1 make test # Run all tests without keychain prompts
git diff # Review changes before stagingFor a narrowly scoped documentation or skill change, run make check-docs; it includes the repository and skill validators. Use broader checks when the changed surface or repository policy requires them.
If docs/wall-of-apps.json is the only staged change, the local hook skips the full Go pipeline and only runs:
make check-wall-of-appsRepository Agent Skills
Keep always-on repository invariants in AGENTS.md and task-specific maintainer workflows in .agents/skills/<skill-name>/.
Each skill must contain a SKILL.md with only name and description frontmatter plus matching agents/openai.yaml UI metadata. Keep trigger descriptions specific, link detailed references through progressive disclosure, and run:
make check-agent-skillsmake check-docs includes this validation.
CLI Structure
- Command implementations live in
internal/cli/<domain>packages - Each domain exposes a top-level
XCommand() *ffcli.Command cmd/only contains the root entry point; do not add wrapper files- Register top-level commands in
internal/cli/registry/registry.go(order matters) - Shared CLI helpers go in
internal/cli/shared(useshared_wrappers.goas needed)
Adding a New Command
- Add or extend a domain package in
internal/cli/<domain> - Implement a command factory (e.g.,
XCommand() *ffcli.Command) - Register it in
internal/cli/registry/registry.go - Write tests in the domain package (or
internal/cli/cmdtestfor root-level tests) - Update README.md with usage examples
Adding a New API Endpoint
- Add method to
internal/asc/client.go - Add types for request/response structs
- Add helper functions for table/markdown output
- Create command in
internal/cli/<domain>to expose the endpoint - Write HTTP client tests with mocked responses
- If endpoint tests are repetitive, group the request-wiring cases, but keep at least one representative non-empty decode assertion and one representative output-structure assertion where formatting is user-facing
Releases
Tag releases with plain semver like 0.1.0 (no v prefix).
Pre-Release Checklist
Before tagging a release, verify:
# 1. Update release-facing docs
# - update any website pages affected by the release
# - release notes are auto-generated from merged PRs on tag push
# 2. Verify documentation
make check-docs
# 3. All tests pass
ASC_BYPASS_KEYCHAIN=1 make test
# 4. Audit help output for all parent commands
for cmd in auth analytics finance apps app-tags testflight builds versions \
feedback crashes localizations \
build-localizations sandbox submit xcode-cloud reviews; do
echo "=== $cmd ===" && ./asc $cmd --help 2>&1
done
# 5. Check for duplicate sections (should see SUBCOMMANDS only once per command)
# 6. Verify bold formatting renders correctlyCommon issues to check:
- No duplicate "Subcommands:" sections (don't list subcommands in LongHelp; DefaultUsageFunc handles it)
- All flags have descriptions
- Examples are up to date
Parity map
Remaining fastlane/ASC parity areas and the intentional non-goal list live in PARITY.md.
GO STANDARDS (`docs/GO_STANDARDS.md`)
Go Standards
Follow idiomatic Go so the code is predictable to anyone who reads Go.
Formatting
- Always run
gofmt(andgofumptviamake format) - No manual formatting
Naming
- Use mixedCaps (not snake_case)
- Keep common initialisms uppercase:
ID,URL,API,JSON
Error Handling
- Return errors, don't panic for expected failures
- Wrap with context using
%w:fmt.Errorf("operation failed: %w", err)
Context
- Pass
context.Contextinto network operations - Respect timeouts and cancellations
Types
- Model request/response types with JSON tags
- Use pointers for optional fields, values for required fields
- Prefer typed
constvalues (not raw strings) for API enums and resource types
CLI Behavior
- If a flag is accepted, it must be implemented or error
- Never silently ignore flags
- Data goes to stdout, errors to stderr
- Keep JSON minified by default
- If a command already prints structured output and must exit non-zero,
returnshared.NewReportedError(err); the root printserronce to stderr
as anError:line - If the command already wrote its own error line to stderr, return
shared.NewStderrReportedError(err)instead so nothing is printed twice
Dependencies
- Standard library first
- Avoid new deps unless necessary and justified
CLI Help Output
- Use
UsageFuncon ffcli commands for consistent help formatting - When returning
flag.ErrHelp, do not callfs.Usage()manually (avoids duplicate output) - Help output is written to stderr by default
Testing
Error Checking
- Do: Use
errors.Is()or typed extraction viaerrors.AsType()(Go 1.26+) - Do: Use
errors.As()when you need compatibility with older Go versions - Do: Simply check
err != nilwhen only verifying an error occurred - Don't: Match error strings with
strings.Contains(err.Error(), "...")- this is fragile
// Good: Check for specific error type (Go 1.26+)
if notFoundErr, ok := errors.AsType[*NotFoundError](err); ok {
// handle not found
_ = notFoundErr
}
// Also valid: classic errors.As pattern
var legacyErr *NotFoundError
if errors.As(err, &legacyErr) {
// handle not found
}
// Good: Just verify error occurred
if err == nil {
t.Fatal("expected error, got nil")
}
t.Logf("got expected error: %v", err)
// Bad: Fragile string matching
if !strings.Contains(err.Error(), "not found") { // DON'T DO THIS
t.Fatal("wrong error")
}Empty Results
- Prefer empty arrays/objects over
nullwhen no data is returned - CLI commands should exit
0for empty results and render empty output cleanly - Add tests for empty responses (including empty pagination)
Test Isolation
- Use
t.TempDir()for temporary files - Use
t.ArtifactDir()for test artifacts you want to inspect withgo test -artifacts - Use
t.Setenv()to set environment variables (auto-cleaned up) - Isolate from user config by setting
ASC_CONFIG_PATHto a temp path; a temporaryHOMEalone is not enough, because the upward.asc/config.jsonsearch from a checkout inside the developer's home reaches~/.asc/config.json make test,make test-short(used by the pre-commit hook),make test-parallel, andmake test-coverageunset inheritedASC_*variables (except the opt-in test switchesASC_UPDATE_GOLDEN,ASC_SIGNING_RUN_LIVE_TEST, andASC_SIGNING_KEYCHAIN_INSTALL_LIVE_TEST) andDO_NOT_TRACKand pointASC_CONFIG_PATHat a missing file in a fresh, read-only per-run directory; tests that need any of those inputs set them witht.Setenv
PARITY (`docs/PARITY.md`)
Parity map
Lookup table for remaining fastlane/ASC parity after the 2026-08-30 backlog
(#2227–#2236) and the 2026-09-02 web-session issues. Command claims were
checked against a built asc binary's --help. Live inventory:asc capabilities (use --status web-session for web-only rows). Do not
duplicate that catalog here.
Each area is exactly one of: covered by a command, tracked by issue(s), or an
intentional non-goal. Do not copy this file into AGENTS.md.
Remaining areas
| Area | Resolution |
|---|---|
| App creation plus Developer Portal service associations as one end-to-end flow | Intentional non-goal. No single end-to-end command. Compose asc web apps create with asc web bundle-ids. Remaining identifier/services/iCloud work is #2290. |
| Metadata, screenshots, preview videos, binary, and submission as one operation | Intentional non-goal. Separate commands by design. Closest composed paths: asc publish appstore (IPA or local build, optional metadata, optional submit) and asc release stage (metadata, attach, validate). Screenshots and preview videos stay asc screenshots and asc video-previews. Chain with asc workflow. |
| Simulator creation, booting, app installation, and screenshot capture beyond the bounded matrix in #2230 | Intentional non-goal for mutation. asc xcode test-destinations lists simulators and the local Mac with a read-only simctl list. asc screenshots run and default iOS asc screenshots capture require a booted simulator and an installed app. asc screenshots capture --provider macos captures a running macOS app. asc xcode install targets a connected physical device, not a simulator. Bounded matrix capture is #2230. Create/boot/clone/delete stay on host Simulator/simctl. |
Test configuration and reporting beyond the first asc xcode test slice (#2227) |
Intentional non-goal for extra test knobs. First slice shipped as asc xcode test (test, build-for-testing, test-without-building, destinations, test plans, filters, result bundle, JUnit via --report junit --report-file PATH). An existing .xcresult converts with asc xcode test junit. Further scan-style knobs stay --xcodebuild-flag unless demand appears. |
| Code signing on a fresh CI runner: certificate and private-key creation, profiles, shared encrypted identity, keychain lifecycle, and Xcode signing settings | Covered. asc signing fetch --create-missing --create-missing-certificate creates the key, CSR, certificate, profile, and .p12; --match-extensions adds extension bundle IDs; --delete-stale-profiles removes expired or invalid profiles (asc profiles list --stale-only lists them). asc signing sync push and pull share the encrypted identity (Git, S3-compatible object storage, or experimental GitLab Secure Files and AWS Secrets Manager). asc signing keychain install, list, unlock, lock, set-timeout, set-partition-list, and delete own the dedicated keychain. asc xcode signing plan (with --profile) and asc xcode signing apply set per-target signing settings and ExportOptions.plist; asc xcode archive and asc xcode export produce the signed artifact. Sequence: CI_CD.md. |
| Signing profile renewal, bulk cleanup, device refresh, and Mac profile inclusion | Covered. With Git storage, asc signing sync push --renew-expired replaces an expired synced profile with a same-name successor; --force-for-new-devices recreates a development or ad hoc profile when its devices change, and --include-mac-in-profiles adds enabled Apple silicon Macs to iOS development and ad hoc device sets; asc signing sync nuke deletes every profile of one type, revokes its certificates, and removes their encrypted files. Stale cleanup outside sync is asc signing fetch --delete-stale-profiles; certificates are deactivated or revoked with asc certificates. |
| Signing object storage | Covered. asc signing sync push, pull, and rotate-password with --storage object --object-bucket NAME (optional --object-prefix, --object-region, --object-endpoint for S3-compatible services) store the same ciphertext in the Git tree's layout, with conditional writes that never overwrite a concurrent push. Credentials come from the standard AWS chain, including workload identity; no flag accepts one. Profile renewal, device refresh, and sync nuke remain Git-only. CI example: CI_CD.md. |
| Web-session-only capabilities with no public Apple API | Tracked by open 2026-09-02 web-session issues: #2275, #2278, #2283, #2284, #2286, #2287, #2288, #2290, #2291, #2292, #2293, #2294, #2295, #2297, #2298, #2299, #2300, #2301. Shipped the same day (do not reopen): #2268, #2269, #2271, #2272, #2274, #2277, #2280, #2281, #2289, #2296. Shipped inventory: asc capabilities --status web-session. |
| Android tooling, plugin/lane runtime, chat connectors, documentation generators, obsolete actions | Intentional non-goal. See list below. |
Intentional non-goals
Unless user demand appears, do not build:
- Android tooling (Play/Gradle/supply).
asc android-ios-mappingis Apple's Android-to-iOS mapping API, not Android CI. - Plugin/lane runtime (Fastfile plugins).
asc workflowruns repo-local steps; it is not a plugin host. - Chat notification connectors beyond existing
asc notify slack(incoming webhook): Mailgun, Chatwork, IFTTT, Flock, desktop notifications. - Documentation generators (API/doc-site generators, fastlane docs plugins).
asc docs/asc initare CLI reference helpers. - Obsolete actions Apple or fastlane have already retired.
TESTING (`docs/TESTING.md`)
Testing Guidelines
The rules for which tests to write, and which not to, are in the Tests section of AGENTS.md. This file covers patterns.
General Principles
- Mock external API calls (
httptestor a stub transport); only the opt-inmake test-integrationsuite calls App Store Connect. - Write the failing test first, then implement.
- Prefer a few high-signal tests over broad repetitive matrices.
Client Endpoint Coverage
Shared request, validation, and error-decoding code is tested once. Do not add a success, validation, and API-error test to every endpoint; test only what an endpoint does differently, such as its path, query, body, or response shape.
When consolidating repetitive client tests:
- Keep grouped/table-driven coverage for repeated request wiring
- Preserve at least one representative non-empty response assertion per response family
- Do not replace all list tests with
{"data":[]}smoke checks; assert decoded fields for at least one realistic payload - For user-facing renderers, assert output structure or headers in addition to value presence
Test Patterns
Table-Driven Tests
Table-driven tests are preferred for repetitive endpoint coverage, but they should not weaken the regression signal. Group repeated limit/next-url/request-wiring cases together, then keep one focused representative assertion that proves the endpoint still decodes the correct response shape.
func TestSomething(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{"valid input", "foo", "bar", false},
{"empty input", "", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Something(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
}
if got != tt.want {
t.Errorf("got %v, want %v", got, tt.want)
}
})
}
}Helper Functions
- Use
t.Helper()in test helper functions - For JSON assertions, unmarshal and assert fields (not
strings.Contains) - For renderer/output assertions, avoid token-only checks when structure matters; include header or formatting assertions for representative cases
Assertions Outside the Test Goroutine
t.Fatal, t.Fatalf, and t.FailNow may only be called from the goroutine running the test. Inside an httptest handler they skip the response write and leave the client under test waiting; inside a worker goroutine or an injected dependency stub they terminate that goroutine before it can send its result, so the test deadlocks instead of failing and the real failure is buried in a timeout dump.
Use internal/handlertest instead. It records the failure with Errorf, which is safe from any goroutine, and returns a value the fixture hands back so the code under test unwinds normally:
fixture := handlertest.New(t)
// RoundTrippers, dependency stubs, and worker goroutines return the error.
transport := roundTripFunc(func(req *http.Request) (*http.Response, error) {
return nil, fixture.Errorf("unexpected request: %s %s", req.Method, req.URL)
})
// httptest handlers cannot return, so answer with a 500 and stop.
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
fixture.Respond(w, "unexpected request: %s %s", req.Method, req.URL.Path)
}))
// Fixture builders that must return a response answer with a 500 body.
func territoryResponse(fixture *handlertest.Asserter, payload any) *http.Response {
body, err := json.Marshal(payload)
if err != nil {
return fixture.Response("marshal territory response: %v", err)
}
return jsonHTTPResponse(http.StatusOK, string(body))
}Errorf renders with fmt.Errorf, so %w keeps wrapping the operand and still prints readably in the test log. Keep using t.Fatal for setup that runs on the test goroutine.
CLI Tests
- Use
internal/cli/cmdtestfor command output and parsing tests - Capture stderr for usage text (help output goes to stderr)
Running Tests
ASC_BYPASS_KEYCHAIN=1 make test # Run all tests
ASC_BYPASS_KEYCHAIN=1 go test -v ./... # Verbose output
ASC_BYPASS_KEYCHAIN=1 go test -run TestName ./pkg # Run specific testAlways set ASC_BYPASS_KEYCHAIN=1 for manual test commands so host keychain profiles cannot affect results. The make test target also sets it internally.
WINGET (`docs/WINGET.md`)
WinGet Packaging
This document captures the Windows Package Manager packaging flow from
GitHub Discussion #1552.
The goal is to make the common install command work:
winget install ascKeep the exact package identifier documented as the scripting fallback:
winget install --id Rorkai.ASC --exactPackage Identity
Use this identity for microsoft/winget-pkgs submissions:
PackageIdentifier: Rorkai.ASC
PackageName: asc
Moniker: asc
Commands:
- ascMoniker: asc is what lets winget install asc resolve to this package when
there is no ambiguity. PackageIdentifier: Rorkai.ASC remains the stable,
non-ambiguous form for automation.
First Submission
WinGet packages live in the publicmicrosoft/winget-pkgs repository,
not in this repository. For the initial submission, create a portable package
from the Windows release asset:
PackageIdentifier: Rorkai.ASC
PackageVersion: 1.5.1
DefaultLocale: en-US
ManifestType: version
ManifestVersion: 1.6.0PackageIdentifier: Rorkai.ASC
PackageVersion: 1.5.1
PackageLocale: en-US
Publisher: Rorkai
PackageName: asc
PackageUrl: https://github.com/rorkai/App-Store-Connect-CLI
License: MIT
LicenseUrl: https://github.com/rorkai/App-Store-Connect-CLI/blob/main/LICENSE
ShortDescription: Fast, scriptable CLI for the App Store Connect API.
Moniker: asc
Tags:
- app-store-connect
- app-store-connect-api
- cli
- ios
- testflight
ReleaseNotesUrl: https://github.com/rorkai/App-Store-Connect-CLI/releases/tag/1.5.1
ManifestType: defaultLocale
ManifestVersion: 1.6.0PackageIdentifier: Rorkai.ASC
PackageVersion: 1.5.1
InstallerType: portable
Commands:
- asc
Installers:
- Architecture: x64
InstallerUrl: https://github.com/rorkai/App-Store-Connect-CLI/releases/download/1.5.1/asc_1.5.1_windows_amd64.exe
InstallerSha256: REPLACE_WITH_RELEASE_SHA256
ManifestType: installer
ManifestVersion: 1.6.0The release workflow generates these files withscripts/generate_winget_manifests.py using the SHA256 fromasc_<version>_checksums.txt.
Release Automation
.github/workflows/release.yml generates the WinGet manifests after the GitHub
release is published, in parallel with the Homebrew tap update. It then opens or
updates a PR against microsoft/winget-pkgs.
Required release configuration:
secrets.WINGET_GITHUB_TOKEN: token that can create/use a fork ofmicrosoft/winget-pkgs, push branches to that fork, and open PRs againstmicrosoft/winget-pkgs.vars.WINGET_FORK_OWNER: optional organization/user that owns the fork. When
unset, the workflow uses the authenticated token owner.
The submission step logs the token's gh api rate_limit quota first, then runs
every gh and git network call through a bounded retry helper (five attempts,
exponential backoff from 10s capped at 120s, with jitter). Only GitHub rate
limits and transient transport failures are retried; authentication,
permission, validation, and scope-check failures fail immediately. Clone and PR
creation are idempotent under retry, so a lost response cannot open a duplicatemicrosoft/winget-pkgs PR.
If the winget job still fails after the retries, re-run that job (or dispatch
the release workflow for the same version). If the preflight showed a primary
quota (core, graphql, or search) at zero, wait for that bucket's logged
reset time first; for secondary throttles, 5xx responses, or transport
failures the reset times are unrelated, so re-run after a few minutes. The job
is safe to re-run: it reuses the published release artifact,
recognizes an existing rorkai-asc-<version> branch, and skips PR creation
when one is already open. Fall back to a manual submission only when a re-run
fails for a non-transient reason.
The generated branch name is:
rorkai-asc-<version>The manifest path in winget-pkgs is:
manifests/r/Rorkai/ASC/<version>/To generate manifests locally for a release asset directory:
python3 scripts/generate_winget_manifests.py \
--version 1.5.1 \
--release-dir release \
--output-dir /tmp/asc-wingetAcceptance Check
After the manifest PR is merged and WinGet sources update, run the manualWinGet smoke workflow or use a clean Windows machine:
winget source update
winget search asc --source winget
winget install asc --source winget --accept-source-agreements --accept-package-agreements
asc --help
asc version
winget uninstall --id Rorkai.ASC --exactIf winget install asc prompts for disambiguation, keep the README fallback as
the documented automation-safe command:
winget install --id Rorkai.ASC --exactWORKFLOWS (`docs/WORKFLOWS.md`)
Workflow Patterns
Use the high-level workflow surfaces deliberately:
asc publish appstore: canonical App Store shipping pathasc publish testflight: canonical high-level TestFlight publish pathasc workflow: user-defined orchestration for repo-specific pipelines
To include store assets in a canonical metadata round-trip, select the asset scopes explicitly:
asc metadata pull --app "APP_ID" --version "1.2.3" --dir ./metadata --include localizations,app-clip,previews
asc metadata push --app "APP_ID" --version "1.2.3" --dir ./metadata --include localizations,app-clip,previews --dry-run
asc metadata push --app "APP_ID" --version "1.2.3" --dir ./metadata --include localizations,app-clip,previews --confirmApp Clip files are stored at app_clip/action.txt and <locale>/app_clip/ beneath--dir; previews use app_previews/<locale>/<device_type>/. asc migrate uses the
same App Clip layout beneath its resolved metadata/ directory and previews
beneath the selected Fastlane directory. Existing JSON and Fastlane text metadata
formats are unchanged. Preview validation needs ffprobe on PATH. Exported media
is Apple's delivered rendition, which may differ from the original upload bytes;
previews are exported as .m3u8 playlists that only reference existing remote
previews; importing them where that preview does not exist requires the original videos.
asc workflow lets you compose existing asc commands and shell commands into
repeatable release pipelines once you know which top-level path you want.
Verified local Xcode -> TestFlight workflow
This pattern was validated against a real app using:
asc builds next-build-numberto choose the next build number for a versionasc xcode injectto materialize deployment metadata into generated Xcode
plist/config files and asset paths before archivingasc xcode buildto compile a scheme for an explicit simulator or device
destination before the archive/export pathasc xcode archiveto create a deterministic.xcarchiveasc xcode exportto create a deterministic.ipaasc publish testflight --group ... --waitto upload, wait for processing,
and add the build to a TestFlight group--submit --confirmonasc publish testflightwhen the target is an
external group that should trigger beta app review submission
For a simulator compile check without modifying project files, use the typed
destination and explicit no-signing flag. For that invocation,--no-code-signing overrides signing with CODE_SIGNING_ALLOWED=NO. Derived
data defaults to a stable asc cache path outside the checkout; use--derived-data-path when the workflow needs a specific artifact directory.
Use --result-bundle-path to produce a new .xcresult bundle at an explicit
path; the destination must not already exist and asc does not overwrite it.
Replace the example destination below with a simulator installed on the host.
asc xcode build \
--project App.xcodeproj \
--scheme App \
--configuration Debug \
--destination 'platform=iOS Simulator,name=iPhone 17 Pro Max,OS=27.0' \
--no-code-signing \
--output jsonFor local unit or UI test execution, use the typed test command.
It keeps Xcode diagnostics on stderr, writes a structured result to stdout, and
retains the .xcresult bundle for inspection:
asc xcode test \
--project App.xcodeproj \
--scheme App \
--destination 'platform=iOS Simulator,name=iPhone 17 Pro Max,OS=27.0' \
--result-bundle-path .asc/artifacts/App-tests.xcresult \
--output jsonUse --action build-for-testing to produce test products, then pass the safely
discovered .xctestrun path to --action test-without-building. Each action
requires an explicit --destination; Xcode may boot or launch the selected
simulator or device. The command does not change project files, upload
artifacts, or call App Store Connect.
Device builds retain Xcode's signing behavior unless --no-code-signing is
provided explicitly.
asc xcode export generates archive-specific App Store Connect export options
automatically when --export-options is omitted. It chooses a unique
archive-adjacent path and never overwrites an existing file. The command result
reports the exact path used. To persist a plist at a deterministic path for
inspection or reuse, generate it after the archive:
asc xcode export-options generate \
--archive-path .asc/artifacts/App.xcarchive \
--output-path .asc/export-options-app-store.plist \
--overwriteThe standalone command defaults to .asc/export-options-app-store.plist and
requires --overwrite if that file already exists. Automatic generation is
cross-platform and only reads archive metadata. Manual signing resolution is
Darwin-only because it inspects local Xcode signing identities and provisioning
profiles.
To export an IPA for registered devices, use the modern Xcode
method name release-testing (the older ad-hoc spelling is deprecated):
asc xcode export \
--archive-path .asc/artifacts/App.xcarchive \
--ipa-path .asc/artifacts/App.ipa \
--method release-testing \
--signing-style manual \
--team-id TEAM_IDThe default remains app-store-connect; release-testing always produces a
local export and cannot be combined with --wait.
For a PCC-capable app, or another multi-target app that needs manual signing,
pass the signing policy directly to export. ASC reads the archive and matches
installed profiles for the app and its embedded targets, so the command does
not need profile UUID flags or a checked-in plist:
asc xcode export \
--archive-path .asc/artifacts/App.xcarchive \
--ipa-path .asc/artifacts/App.ipa \
--signing-style manual \
--team-id TEAM_IDThe same flags work when the archive and export are owned by a local publish
flow:
asc publish testflight \
--app APP_ID \
--workspace App.xcworkspace \
--scheme App \
--version 1.2.3 \
--group GROUP_ID \
--signing-style manual \
--team-id TEAM_IDAn explicit --export-options plist cannot be combined with --method,--signing-style, or --team-id. When supplied without those flags, the
plist is authoritative.
Create .asc/deployment.json:
{
"values": {
"bundle_id": "com.example.app",
"app_name": "Example",
"version": "",
"build_number": ""
},
"outputs": [
{
"type": "plist",
"path": "../Generated/Info.generated.plist",
"values": {
"CFBundleIdentifier": "${bundle_id}",
"CFBundleDisplayName": "${app_name}",
"CFBundleShortVersionString": "${version}",
"CFBundleVersion": "${build_number}"
}
},
{
"type": "text",
"path": "../Generated/Deployment.xcconfig",
"contents": "PRODUCT_BUNDLE_IDENTIFIER = ${bundle_id}\nMARKETING_VERSION = ${version}\nCURRENT_PROJECT_VERSION = ${build_number}\n"
},
{
"type": "copy",
"source": "../Assets/AppIcon.appiconset/Contents.json",
"path": "../Generated/Assets.xcassets/AppIcon.appiconset/Contents.json"
}
]
}Point your Xcode project at generated files such as Generated/Info.generated.plist
or include Generated/Deployment.xcconfig from the build configuration. Then runasc xcode inject before archive time to fill in the release-specific values
that previously came from Fastlane scripts.
Create .asc/workflow.json:
/* Detailed source-code truncated for AI context efficiency. */Run it:
asc workflow validate --output json
asc workflow run --dry-run testflight_beta VERSION:1.2.3
asc workflow run testflight_beta VERSION:1.2.3Resumable upload and distribution steps
When upload and external distribution need separate retry boundaries, the
upload-only flag can make the upload its own output-producing
step. The successful upload step is persisted with BUILD_ID; if the later
processing wait or distribution step fails, --resume skips the upload and
reuses that exact build ID.
{
"env": {
"APP_ID": "1234567890",
"IPA_PATH": ".asc/artifacts/App.ipa",
"TESTFLIGHT_GROUP": "External Testers"
},
"workflows": {
"testflight_external": {
"steps": [
{
"name": "upload",
"run": "asc publish testflight --app \"$APP_ID\" --ipa \"$IPA_PATH\" --upload-only --output json",
"outputs": {
"BUILD_ID": "$.buildId",
"BUILD_VERSION": "$.buildVersion",
"BUILD_NUMBER": "$.buildNumber"
}
},
{
"name": "wait",
"run": "asc builds wait --build-id ${steps.upload.BUILD_ID} --fail-on-invalid --output json"
},
{
"name": "distribute",
"run": "asc builds add-groups --build-id ${steps.upload.BUILD_ID} --group \"$TESTFLIGHT_GROUP\" --submit --confirm --output json"
}
]
}
}
}After a failed wait or distribution step, use the run ID printed byasc workflow:
asc workflow run testflight_external --resume RUN_IDThe upload step is not executed again because its declared outputs were already
persisted in the run-state file.
Notes:
VERSIONmust be a valid next marketing version for your app. If the latest
App Store version is alreadyREADY_FOR_DISTRIBUTION, reusing that same
version can cause App Store Connect to reject the upload.TESTFLIGHT_GROUPaccepts either a beta group name or group ID.- Add
"ASC_BYPASS_KEYCHAIN": "1"to the top-levelenvblock if you want the
workflow to resolve credentials from environment variables or config instead
of the macOS keychain. - Output-producing step names only need to stay unique within workflows that
can execute together in the same run graph. Independent workflows can reuse
names likearchiveorpublish. - Declared outputs keep the exact JSON value the command printed. A numeric
$.nextBuildNumberof42is stored and interpolated as42, so it can be
passed straight toCURRENT_PROJECT_VERSIONor another build-number flag.
Bounded retry and timeout
Long-form run steps can opt into a fixed-delay retry policy and a per-attempt
timeout. This replaces shell retry loops for operations such as Apple's
eventually consistent build-to-beta-group relationship:
{
"name": "add_build_to_group",
"run": "asc builds add-groups --build-id $BUILD_ID --group $GROUP_ID",
"retry": {
"max_attempts": 6,
"delay": "10s"
},
"timeout": "2m"
}max_attempts includes the initial execution and must be between 2 and 100.delay and timeout use positive Go duration strings up to 24h; examples
include 250ms, 10s, and 2m. The delay is fixed and has no jitter. Timeout
applies separately to each attempt. With the example above, the total policy is
bounded by six two-minute attempts plus five ten-second delays.
Retry and timeout are supported only on run steps. Workflow-call steps and thebefore_all, after_all, and error string hooks remain single-execution.
Caller cancellation stops a running process tree or retry delay immediately.
The error hook runs only after the final attempt fails; after_all still runs
only after success.
Use retry only when you have explicitly decided the command is safe to repeat.
The runner does not infer whether a shell command is read-only, idempotent, or a
mutation. A successful command with invalid declared output is not retried,
because its side effect may already have happened. That failure is terminal:
the structured result sets terminal: true, the run state records the terminal
reason, and --resume rejects the run instead of executing the command again.
A timeout-only failure is terminal for the same replay-safety reason: local
termination cannot prove that a remote mutation was not accepted. A step that
configures both retry and timeout remains resumable because retry is the
explicit repeat-safety opt-in.
Failed-attempt diagnostics from timeout alone do not create a resume checkpoint.
Recovery requires a previously successful step or hook, or a retry-enabled
failed step. Omit retry or timeout to disable it; explicit null is invalid.
Attempt numbers and retry delays are written to stderr. stdout remains the one
machine-readable workflow result. Each attempt gets a fresh output buffer, and
declared outputs are extracted and persisted only from the successful attempt.
Persisted attempt diagnostics remain available across --resume; already
successful steps are never rerun.
Example:
{
"workflows": {
"testflight_beta": {
"steps": [
{
"name": "archive",
"run": "printf '{\"buildId\":\"beta\"}'",
"outputs": {
"BUILD_ID": "$.buildId"
}
}
]
},
"appstore_release": {
"steps": [
{
"name": "archive",
"run": "printf '{\"buildId\":\"release\"}'",
"outputs": {
"BUILD_ID": "$.buildId"
}
}
]
}
}
}This is valid because those workflows are independent. If a third workflow can
call both of them in the same run, the duplicate archive producers still need
to be renamed.
Game Center Blocked Players (`docs/game-center-blocked-players.md`)
Game Center blocked players
API 4.5 adds a blocked-player collection for a Game Center detail and an endpoint
for updating a player's blocked status.
asc game-center details blocked-players list --detail-id "DETAIL_ID" --paginate
asc game-center details blocked-players update --id "PLAYER_ID" --blocked true --confirm
asc game-center details blocked-players update --id "PLAYER_ID" --blocked false --confirmThe list contains only blocked players. It is not a directory of every player.
Resolve the Game Center detail ID with asc game-center details list --app "APP_ID". Player IDs for a new block can come from a submitted score's player
relationship. Apple does not expose a standalone player lookup endpoint in this
API version.
Lists support --fields nickname,blocked,bundleId, --limit (1–200), --next,
and --paginate. A continuation URL cannot be combined with a detail ID, fields,
or limit because it already specifies the complete query. Each page gets a fresh
request timeout.
Updates require an explicit --blocked true|false and --confirm. An optional--bundle-id sends Apple's bundleId update attribute; omitting it leaves that
attribute unchanged. An explicitly empty bundle ID is rejected. JSON retains
the resource envelope, including explicit false and empty nickname values.
Table and Markdown output show the ID, nickname, blocked status, and bundle ID.
The ID-only blocked-player relationship endpoint remains accessible withasc api.
Game Center Score Moderation (`docs/game-center-score-moderation.md`)
Game Center score moderation
API 4.5 adds score moderation for v2 leaderboards. List the submitted scores and
include their player relationships:
asc game-center leaderboards v2 score-moderations list --leaderboard-id "LEADERBOARD_ID" --include player --paginateThe list supports --limit (1–200), --next, --fields, and --player-fields.--player-fields requires --include player. Including a player automatically
retains the player relationship in an explicit score field selection.--exists-blocked true|false forwards Apple's exists[blocked] filter.
A --next URL supplies its complete query, so it cannot be combined with the
leaderboard ID, filters, includes, fields, or limit.
Block or unblock a score by its moderation ID:
asc game-center leaderboards v2 score-moderations update --id "SCORE_ID" --blocked true --confirm
asc game-center leaderboards v2 score-moderations update --id "SCORE_ID" --blocked false --confirmBoth updates require --confirm. This changes an individual score's blocked
status. It does not block the player. JSON retains Apple's resource envelope,
player relationships, included resources, and numeric score strings without
converting them to floating-point numbers. Table and Markdown output summarize
ID, rank, score, blocked status, and submission date.
Linux Ios (`docs/linux-ios.md`)
Build and package iOS apps on Linux
ASC's portable workflow uses an installed Swift/Apple SDK toolchain, xtool,
and rcodesign. The tested xtool backend is
joshuaswarren/xtool at f0a1f90;
it must replace its app output directory on a successful build so ASC can reject
stale output. It does not install these tools or provide an iOS simulator
runtime. The initial backend supports one thin arm64 iOS app plus resources;
nested executable code, extensions, embedded frameworks, watch apps, App Clips,
and app symlinks are rejected.
The installed xtool backend owns SDK maintenance. Before compiling, it can
automatically rebuild and replace an outdated normal SDK using its existing
Xcode copy. Pin a compatible xtool/SDK pair to avoid that update; a prepared
Swift package does not isolate or freeze the installed SDK.
User-supplied tooling and SDK licensing
ASC provides a local integration with tools installed by the user. It does not
bundle, download, or redistribute Xcode, Apple SDKs, or Swift toolchains, and it
does not operate a hosted build service. Users must obtain and configure their
own toolchain and signing materials and are responsible for reviewing and
complying with the agreements applicable to their account and build environment.
This follows the tooling boundary described by xtool's author,
Kabir Oberai:
xtool does not distribute Apple's SDKs or toolchains, asks users to supply Xcode,
and leaves license compliance to users. He also suggests using macOS CI for the
final App Store build when concerned about a Linux build environment. Operators
can use a macOS/Xcode workflow for their final distribution build.
Providing an integration does not establish permission to use an SDK in every
environment. Apple's standard
Developer Program agreement
§2.6 restricts Apple SDK use on non-Apple computers and enabling others to do so.
The Xcode/SDK agreement authorizes
execution on Apple hardware running macOS. No applicable exception or written
Apple permission has been verified. User-supplied tooling does not waive these
terms or establish Apple approval of this workflow. Technical verification does
not establish licensing compliance or App Store acceptance.
For an Xcode project, first prepare a Swift package using the converter from
omarchy-apple-dev:
python3 /opt/omarchy-apple-dev/tools/xcodeproj2xtool.py App.xcodeproj --target AppThe converter creates omarchy-xtool/. Inspect its generated Package.swift,
xtool.yml, Info.plist, dependencies, and resource handling. Xcode schemes,
workspaces, build settings, and arbitrary scripts are not interchangeable with
Swift package targets. Keep the Apple SDK/toolchain version pinned.
Build a release device app:
asc builds compile --package-path ./omarchy-xtool --product App \
--platform device --configuration release --app-path ./artifacts/App.appPackage it with an existing local identity and provisioning profile:
asc builds package --app-path ./artifacts/App.app --ipa-path ./artifacts/App.ipa \
--identity ./signing/App.p12 --identity-password-file ./secrets/password \
--provisioning-profile ./signing/App.mobileprovision --entitlements ./signing/entitlements.plistThe profile must be signed by Apple's trusted provisioning signer, unexpired,
and match the exact app bundle ID and supplied signing certificate. Requested
entitlements must be authorized by that profile. Optional capabilities are not
automatically copied from the profile or an existing code signature. ASC passes
the validated identity and password to rcodesign through pipes and never writes
them or the decrypted private key to disk; the password file must contain a
single line. Signing with an identity requires Linux or macOS. This
command packages fresh build output; use asc signing resign on macOS for its
existing complete IPA re-signing contract.
For a local pipeline test without an Apple identity:
asc builds package --app-path ./artifacts/App.app --ipa-path ./artifacts/Test.ipa --ad-hocAd hoc test output is not accepted by Apple and cannot be installed on an
ordinary physical iPhone. A simulator app is a separate build target:
asc builds compile --package-path ./omarchy-xtool --product App \
--platform simulator --configuration debug --app-path ./artifacts/Simulator.appSimulator output gets corrected platform metadata and a Linux-created ad hoc
signature. A Mac iOS simulator host is still needed to install and run it. A
simulator executable cannot be packaged as a device IPA.
Compiler and signer diagnostics go to stderr. JSON (or explicit table/Markdown)
receipts go to stdout. Outputs are create-only; source apps are copied into
private staging before signing. Inspect a reported incomplete app publication
before retrying. Existing Xcode archive/export/resign behavior is unchanged.
Only one asc builds compile may use a prepared package at a time, including
different products or configurations. A competing invocation fails before
starting xtool. The package's .asc-ios-build.lock file remains after completion;
its presence alone does not mean a build is running. Avoid running xtool directly
against the same package while ASC is compiling it.
On macOS and Linux, signing inputs must belong to the current user and have one
hard link. Identity and password files must not grant group or other access;
profiles and entitlements must not be group- or other-writable.
Device compilation reports signingType: unknown: xtool may apply an ad hoc
signature for entitlements in its product configuration, and ASC does not
determine the device output's signing type during compilation. Package with
explicit signing inputs before distribution. Simulator compilation reportsadHoc after its explicit rcodesign step.
signatureVerified: false is intentional: rcodesign completion is not Apple's
complete bundle signature verification. profileValidated: true reports the
specific profile/identity/entitlement authorization checks, not App Store
acceptance. Apple metadata requirements, assets, capabilities, SDK stamping,
debug symbols, and project compatibility still need validation before shipping.
Upload a genuinely signed, prepared IPA as a separate authorized step:
asc builds upload --app APP_ID --ipa ./artifacts/App.ipa --waitNeither portable command discovers Apple credentials, creates certificates or
profiles, invokes upstream ship.sh, or uploads anything. rcodesign configuration
is disabled and timestamp requests are disabled for local signing.
Child tools get an allowlisted environment. xtool receives process, locale,
proxy, and toolchain settings (PATH, HOME, TMPDIR, LANG, LC_*, XDG_*,XTL_*, XTOOL_*, SWIFT_*, SWIFTPM_*, SDKROOT, DEVELOPER_DIR,TOOLCHAINS, proxy and TLS certificate variables, SSH_AUTH_SOCK); rcodesign
receives only PATH, HOME, temporary-directory, and locale settings. Other
variables, including ASC_* credentials and cloud or GitHub tokens, are not
passed.
Release Notes App Info Kids Age Band (`docs/release-notes-app-info-kids-age-band.md`)
AppInfo kidsAgeBand deprecation
API 4.5 removed kidsAgeBand from the AppInfo response schema. AppInfo sparse
selectors now emit one deprecation warning on stderr per invocation. Existing
flags and HTTP query forwarding remain available for compatibility, and JSON
stdout is unchanged. Apple errors are still reported normally.
The warning applies to apps/apps list, apps view, apps info list,apps info view, localizations list --type app-info, andxcode-cloud products app when they select AppInfo kidsAgeBand. Supported
continuation URLs carrying that selector also warn.
Replace reads such as:
asc apps info view --info-id "APP_INFO_ID" --fields kidsAgeBandwith:
asc age-rating view --app-info-id "APP_INFO_ID"AgeRatingDeclaration.kidsAgeBand remains in API 4.5, and the age-rating--kids-age-band edit flag is unchanged. This release does not remove any
stable flag or announce a removal date.
Release Notes App Tag Territories (`docs/release-notes-app-tag-territories.md`)
App-tag territory deprecation
Apple's API 4.5 release notes
deprecate the app-tag territories relationship and the full-resource and
ID-only territory endpoints. They also retire territory-related list parameters,
although the published OpenAPI snapshot still exposes them.
The CLI retains existing flags, HTTP requests, and pagination for compatibility.
Territory selections on app-tags list/view, the territories andterritories-links commands, and territory parameters in continuation URLs now
emit one stderr warning per invocation. JSON stdout and API errors are unchanged.
No stable command is removed and no removal date is announced.
For core tag reads, omit territory selections:
asc app-tags list --app "APP_ID" --fields name,visibleInAppStore
asc app-tags view --app "APP_ID" --id "TAG_ID"Remove territories from --fields and omit --include territories,--territory-fields, and --territory-limit. Apple does not name an equivalent
replacement for territory lookups. app-tags links and tag visibility updates
remain warning-free.
Release Notes Apple Ads Platform V1 (`docs/release-notes-apple-ads-platform-v1.md`)
Apple Ads Platform API v1 and v5 deprecation
Release 4.4.0 makes Apple Ads Platform API v1 the direct asc ads resource
surface. Campaign Management API v5 remains runnable under asc ads v5, but
Apple retires that API on January 26, 2027 and every v5 leaf now prints a dated
deprecation warning on stderr.
Existing scripts that must keep their v5 endpoint, payload, IDs, response, and
organization context need to add v5 to the command path:
| Before 4.4.0 | 4.4.0 compatibility path |
|---|---|
asc ads campaigns list --org ORG_ID |
asc ads v5 campaigns list --org ORG_ID |
asc ads reports campaigns --org ORG_ID --file report.json |
asc ads v5 reports campaigns --org ORG_ID --file report.json |
asc ads reports preset --level ads --org ORG_ID |
asc ads v5 reports preset --level ads --org ORG_ID |
asc ads api request --path v5/me |
asc ads v5 api request --path v5/me |
New automation should use the direct v1 commands. V1 account-scoped requests
take --ad-account or ASC_ADS_AD_ACCOUNT_ID; they don't substitute the v5--org value. Query and report bodies also use v1 schemas, so the CLI doesn't
translate old selectors or reporting requests:
asc ads campaigns find \
--ad-account "AD_ACCOUNT_ID" \
--file query.json \
--output json
asc ads reports apps campaigns \
--ad-account "AD_ACCOUNT_ID" \
--file report.json \
--output jsonNamed Ads profiles keep their organization and ad-account contexts separate.
After upgrading, a selected profile won't inherit either value from the root
Ads config. If an older named profile depended on the root ads.org_id, store
its organization again with asc ads auth login --name NAME --org ORG_ID, or
pass --org on its v5 command. Profile-less access-token and environment auth
can still use the root context.
asc ads auth discover now calls Platform API v1 GET /v1/me andGET /v1/acls. Use asc ads me view and asc ads acls list when a script
needs one response at a time.
Seven v5 leaves have no one-command v1 replacement: product-page countries,
product-page devices, three keyword bulk-delete operations, and custom
impression-share report list and view. Their warnings explain the manual
migration path. Run the matching command with --help before removing the v5
namespace from an existing script.
Release Notes Auth Login Config Path (`docs/release-notes-auth-login-config-path.md`)
Keychain-bypassing login writes the ASC_CONFIG_PATH file
Changed in 5.9.1. When ASC_CONFIG_PATH is set, asc auth login with--bypass-keychain or a truthy ASC_BYPASS_KEYCHAIN now stores credentials in
that file. Earlier releases wrote ~/.asc/config.json instead, even though every
command reads only the ASC_CONFIG_PATH file when it is set. The login reported
success, but later commands in the same environment could not find the profile,
and the credentials landed in the global config the variable was meant to
isolate from.
asc auth init follows the same rule: without --local, it writes its template
to the ASC_CONFIG_PATH file when set.
Behavior without ASC_CONFIG_PATH is unchanged: a keychain-bypassing login andasc auth init write ~/.asc/config.json, and --local writes./.asc/config.json. Keychain logins are unchanged.
An explicit --local still writes ./.asc/config.json when ASC_CONFIG_PATH
is set. Because later commands read only the ASC_CONFIG_PATH file, the command
now prints a warning to stderr:
Warning: ASC_CONFIG_PATH is set, so other commands read /path/to/config.json instead of /repo/.asc/config.json; unset ASC_CONFIG_PATH or point it at /repo/.asc/config.json to use this file.Migration
If a script set ASC_CONFIG_PATH, logged in with the keychain bypassed, and then
read the credentials from ~/.asc/config.json with ASC_CONFIG_PATH unset, run
that login with ASC_CONFIG_PATH unset. Credentials an earlier release wrote to~/.asc/config.json stay there; check that file and remove any profile you did
not mean to keep.
Release Notes Auth Logout Config Scope (`docs/release-notes-auth-logout-config-scope.md`)
Auth logout respects ASC_CONFIG_PATH
Changed in 5.9.1. When ASC_CONFIG_PATH is set, asc auth logout (with--name, --all, or neither) removes credentials only from that file and the
keychain. Earlier releases also removed matching credentials from~/.asc/config.json, even though no command reads that file whileASC_CONFIG_PATH is set. As a result, asc auth logout --all --confirm in an
isolated CI job, sandbox, agent, or test run also deleted the developer's own
global credentials.
A keychain login that replaces a profile follows the same rule: whileASC_CONFIG_PATH is set, it removes the replaced profile's config entry only
from that file.
Keychain cleanup is unchanged, because the keychain is not scoped byASC_CONFIG_PATH. Behavior without ASC_CONFIG_PATH is unchanged: logout
removes matching credentials from the active config file and~/.asc/config.json.
When logout leaves matching credentials in ~/.asc/config.json, it says so on
stderr instead of removing them silently:
Warning: ASC_CONFIG_PATH is set, so auth logout did not change /Users/you/.asc/config.json, which still holds credentials named 'MyKey'; to remove them, run auth logout with ASC_CONFIG_PATH unset or pass --include-global.The command still exits 0 and prints the same stdout as before. If--name matches a profile only in ~/.asc/config.json, nothing in scope
matches, so logout prints the warning and then fails with the existing
not-found error (exit 1), where earlier releases removed the global profile.
The same not-found error now applies when the active config file does not
exist; earlier releases reported success there without removing anything.
Migration
To keep the earlier cross-file cleanup, pass the new --include-global flag:
asc auth logout --all --include-global --confirm
asc auth logout --name "MyKey" --include-global --confirmOr run logout with ASC_CONFIG_PATH unset, which cleans the active config file
and ~/.asc/config.json as before.
Releases before 5.9.1 could write a keychain-bypassing login to~/.asc/config.json even when ASC_CONFIG_PATH was set (seedocs/release-notes-auth-login-config-path.md). The earlier logout behavior
also removed those misplaced profiles. If you relied on that, check the warning
output and use --include-global once to remove them.
Release Notes Auth Logout Confirmation (`docs/release-notes-auth-logout-confirmation.md`)
Auth logout confirmation migration
Status: the compatibility window described below closed in 5.0.0.
asc auth logoutwithout--confirmis now a usage error before any credential is
removed. Seemigrate-to-5-0.mdx.
asc auth logout now accepts --confirm so scripts can migrate before
confirmation becomes mandatory in 5.0.0. New invocations should name the target
explicitly:
asc auth logout --name "PROFILE" --confirm
asc auth logout --all --confirmDuring the compatibility window, existing logout calls still remove credentials
but print this warning to stderr:
Warning: auth logout without --confirm is deprecated and will be rejected in 5.0.0; pass --confirm to acknowledge credential removal.Bare asc auth logout continues to target all stored App Store Connect
credentials. Stray positional arguments and an explicit --confirm=false are
now rejected before any credential store is changed.
Release Notes Auth Logout Keychain Bypass (`docs/release-notes-auth-logout-keychain-bypass.md`)
Auth logout respects ASC_BYPASS_KEYCHAIN
Changed in 5.10.1. When ASC_BYPASS_KEYCHAIN is set to 1/true/yes/on,asc auth logout --all (and --all --include-global, or logout with no--name) removes credentials only from config files. Earlier releases also
deleted every asc credential from the system keychain, even though every
other auth command skips the keychain while the variable is set. As a result,asc auth logout --all --confirm in a CI job, sandbox, agent, or test run that
set ASC_BYPASS_KEYCHAIN=1 deleted the developer's own keychain credentials.
asc auth logout --name already skipped the keychain under the bypass and is
unchanged. Behavior without ASC_BYPASS_KEYCHAIN is unchanged.
While the bypass is set, logout says so on stderr:
Note: ASC_BYPASS_KEYCHAIN is set, so auth logout changes config files only and leaves keychain entries untouched.The command still exits 0 and prints the same stdout as before. To remove
keychain credentials, run asc auth logout with ASC_BYPASS_KEYCHAIN unset.
asc ads auth logout and asc storekit auth logout keep their own variables,ASC_ADS_BYPASS_KEYCHAIN and ASC_STOREKIT_BYPASS_KEYCHAIN, which already
applied to every keychain operation in those stores.
Release Notes Beta Groups Server Side Filtering (`docs/release-notes-beta-groups-server-side-filtering.md`)
TestFlight beta groups server-side filtering release note
asc testflight groups list --app APP_ID --internal and --external are now
filtered by App Store Connect instead of in the CLI. GET /v1/apps/{id}/betaGroups
accepts only limit and fields[betaGroups], so the command previously fetched every page and
filtered the aggregate in Go. Those requests now go to GET /v1/betaGroups
with filter[app] and filter[isInternalGroup], while retaining the complete
multi-page result.
Two new flags cover query parameters that endpoint already
documents: --name filters on the exact group name (filter[name]) and--sort accepts name, -name, createdDate, -createdDate, publicLinkEnabled,-publicLinkEnabled, publicLinkLimit, and -publicLinkLimit. An unsupported--sort value is a usage error listing the valid values.
Pagination compatibility
App-scoped --internal and --external listings continue to collect every
matching page automatically when --name and --sort are absent, so existing
invocations retain complete output:
asc testflight groups list --app APP_ID --internalCombined app-scoped filters, including --internal --name NAME, follow the
standard one-page default; add --paginate to collect all matching pages.
Listings that use only the new --name or --sort flags behave the same way.
Global listings are unchanged and also require --paginate for aggregation.
For the stable app-scoped --internal and --external paths, --limit remains
a cap on the final aggregate after every page is fetched. Without explicit--paginate, the aggregate fetch uses the maximum page size of 200 regardless
of --limit, then applies the same final cap. Explicit --paginate uses the
same page size of 200. Other filtered and global listings retain their standard
page-size semantics for --limit.
--next now rejects --internal, --external, --name, and --sort. Alinks.next URL is followed verbatim and already carries the query it came
from, so those flags were previously accepted and discarded. Drop them from the
follow-up call; a bare --next, with or without --paginate, is unchanged.
Unfiltered app-scoped listing (--app APP_ID with no filter or sort) still usesGET /v1/apps/{id}/betaGroups and is unchanged.
Release Notes Build Id Flag (`docs/release-notes-build-id-flag.md`)
Canonical `--build-id` selector
Status: the hidden
--buildcompatibility alias described below was removed
in 5.0.0 on every command in the table (and onasc versions attach-build).--buildis now an unknown flag; use--build-id. Seemigrate-to-5-0.mdx.
Release 4.7.0 standardizes the build-ID selector across the remaining command
paths that previously used --build. New automation should use--build-id; it is now the documented flag and appears in command help.
The affected commands are:
| Command | Build-ID use |
|---|---|
asc review submit |
Build to attach to the review submission |
asc publish testflight |
Existing build to distribute |
asc validate testflight |
Build to validate |
asc release stage |
Build to attach during staging |
asc build-localizations list |
Build whose localizations are listed |
asc build-localizations create |
Build whose localization is created |
asc build-bundles list |
Build whose bundles are listed |
asc testflight crashes list |
Build-ID filter |
asc testflight feedback list |
Build-ID filter |
asc performance metrics view |
Build whose metrics are viewed |
asc performance diagnostics list |
Build-ID filter |
asc performance download |
Build whose diagnostics are downloaded |
asc encryption declarations list |
Build-ID filter |
asc encryption declarations assign-builds |
Build IDs to assign |
asc apps app-encryption-declarations list |
Build-ID filter |
Compatibility window
Existing scripts may continue to pass --build during the 4.x deprecation
window. The flag is hidden from help, retains its existing behavior, and emits
one migration warning on stderr pointing to --build-id; stdout and API
semantics remain unchanged. Supplying both spellings with different values is
a usage error before any API side effect, while identical values continue with
the deprecation warning.
Migrate automation to --build-id before the next major release, 5.0.0,
where deprecated compatibility flags may be removed under the CLI stability
ladder.
Release Notes Game Center Replacement Confirmation (`docs/release-notes-game-center-replacement-confirmation.md`)
Game Center relationship replacement confirmation
Status: the compatibility window described below closed in 5.0.0. The
relationship setters now reject invocations without--confirmbefore any
request. Seemigrate-to-5-0.mdx.
Game Center relationship setters now accept an --confirm flag.
Invocations that omit it continue to work in the 4.x compatibility window and
emit a deprecation warning. Starting in 5.0.0, --confirm will be required to
acknowledge that these setters replace the complete relationship collection and
may remove existing members.
Release Notes Iap Review Screenshots Update (`docs/release-notes-iap-review-screenshots-update.md`)
IAP review screenshot update
asc iap review-screenshots update supports upload finalization and resuming
an in-progress file upload.
Use --checksum and/or --uploaded to finalize an existing upload reservation
with Apple's supported PATCH attributes. These attributes do not replace a
completed asset.
Use --file to resume an existing screenshot reservation when the API returnsuploadOperations; the file size must match the reservation, and the command
uploads, commits, and verifies the same screenshot ID. App Store Connect
rejects creating a second IAP review screenshot while a completed one exists,
and its update schema has no asset-replacement field. Therefore update fails
before any POST, PATCH, or DELETE for a completed screenshot and prints the
manual delete-then-create workflow. The delete command requires --confirm,
so the existing screenshot is never removed implicitly.
Release Notes Keychain Signing Identifier (`docs/release-notes-keychain-signing-identifier.md`)
Stable code-signing identifier for keychain access
Release binaries for macOS are now signed with the fixed identifiercom.rorkai.asc. Earlier releases let codesign derive the identifier from
the versioned file name (for example asc_5.3.0_macOS_arm64), so every
release carried a different designated requirement. macOS keychain access
lists trust applications by that requirement, which is why "Always Allow" on
the stored ASC API Key item did not carry over to the next upgrade.
Resolving one profile also reads only that profile's keychain secret now.
Previously every stored profile was read to select one, so each stored
profile raised its own authorization prompt on every command.
The first run of a release with the new identifier prompts once more per
stored profile because the identifier changed. Choose "Always Allow" and
later upgrades reuse that decision.
Fixes #2512.
Release Notes Koubou 0 20 0 (`docs/release-notes-koubou-0-20-0.md`)
Koubou 0.20.0 for screenshot framing
asc screenshots frame now pins Koubou 0.20.0 instead of 0.18.1. Other
Koubou versions are rejected as unsupported; an older install gets an upgrade
hint. Upgrade and refresh the downloaded frames:
pip install -U koubou==0.20.0 # or: brew upgrade bitomule/tap/koubou
kou setup-framesFrames generated by asc look the same after the upgrade. The frame artwork is
unchanged, and asc now emits alignment: center on every generated image item
as well as on text, so Koubou 0.19.0's alignment change keeps them centered.
Custom --config YAML files may need updates:
- Koubou 0.19.4 removed
"from 18 frame names, for exampleiPad Air 13" - M2 - Space Gray - Portraitis nowiPad Air 13 - M2 - Space Gray - Portrait. - Koubou 0.19.0 made content
alignmentthe true horizontal anchor for text
and images. Items withalignment: leftorrightnow start or end at theirpositionx value.
Release Notes Last Compatible Version Public Api (`docs/release-notes-last-compatible-version-public-api.md`)
Last-compatible version settings move to the public API
App Store Connect's Last-Compatible Version Settings screen is the nullabledownloadable attribute on appStoreVersions. Both directions are now on the
public API path.
Read it with the versions JSON output, which preserves the attribute whenever
Apple returns it:
asc versions list --app "APP_ID" --paginate --output json
asc versions view --version-id "VERSION_ID" --output jsonWrite it with a new tri-state --downloadable flag on asc versions update:
asc versions update --version-id "VERSION_ID" --downloadable true
asc versions update --version-id "VERSION_ID" --downloadable false --confirmLeaving --downloadable unset sends no downloadable attribute, so an
unrelated asc versions update never changes download availability. Setting it
to false makes a previously released version unavailable for download on
older operating systems and devices and is not reversible from every state, so
that direction requires --confirm:
Error: --confirm is required with --downloadable false because making a released version unavailable for download is not reversible from every state. No request was sent.--confirm is rejected in any other combination rather than silently ignored,
including an explicit --confirm=false:
Error: --confirm applies only to --downloadable false; remove it or pass --downloadable false--downloadable false --confirm=false is not confirmation either, so the write
is still refused.
asc versions update --output json now also echoes downloadable in its
receipt when Apple returns the attribute, matching asc versions view.
Use --paginate on the list read. The versions this setting usually targets are
the oldest ones, which fall past the first page for apps with a long release
history.
The short-lived web-session read asc web apps last-compatible-version view is
removed. It shipped after 4.11.0 and was never part of a tagged release, so it
never reached the stable rung of the command lifecycle and is removed directly
rather than deprecated. It was read-only, required an interactive web session,
and returned the same downloadable attribute the public API now exposes.
Migration:
| Retired | Replacement |
|---|---|
asc web apps last-compatible-version view --app APP_ID |
asc versions list --app APP_ID --paginate --output json |
| (no write existed) | asc versions update --version-id VERSION_ID --downloadable true |
| (no write existed) | asc versions update --version-id VERSION_ID --downloadable false --confirm |
The capabilities inventory row for this feature moves from partial tocli-supported and no longer lists a web-session command.
Release Notes Official Search Optimization (`docs/release-notes-official-search-optimization.md`)
Official search optimization plan
Release 4.4.4 adds a read-only workflow that turns official
Apple Ads Platform API v1 evidence and App Store Connect metadata into one
reviewable search plan:
asc optimize search plan \
--app "APP_ID" \
--version "APP_VERSION" \
--ad-account "AD_ACCOUNT_ID" \
--country "US" \
--genre "PRODUCTIVITY_UTILITIES" \
--locale "en-US" \
--out-dir ".asc/optimization/APP_VERSION" \
--output markdownThe command keeps Apple's metrics semantically separate: Apple's 1–5 and
1–100 popularity scores are market search demand, impression share is
app-specific paid reach, search-term report rows are actual paid outcomes, and
metadata coverage comes from the selected App Store localization. Deterministic
actions identify converting broad terms,
costly zero-install terms, metadata candidates, low-share opportunities, and
already-saturated terms without inventing organic rank or proprietary
difficulty scores.
When --out-dir is set, the command writes a canonical report plus review-only
metadata, exact-keyword, and negative-keyword import files. It never applies
those files or mutates campaigns or App Store metadata. Partial Apple Ads
source failures remain visible in the report, while successful official data
is preserved. Actions that require proving a keyword is absent are suppressed
when their dependency is unavailable, and table or Markdown output shows the
source matrix and notices directly.
For apps with multiple App Info records, the command matches App Info to the
selected version state. If that match is ambiguous, pass the explicit record
with --app-info. The JSON report records both the resolved version ID and App
Info ID so the evidence can be reproduced.
The report also preserves Apple's app-and-country target-CPA suggestion for a
new Maximize Conversions campaign separately from recommendations for existing
campaigns.
App Store Connect and Apple Ads credentials are independent. Run asc auth
for App Store Connect and asc ads auth login for Apple Ads before using the
workflow. Credentialed live verification is still recommended before relying
on generated candidates in production.
Release Notes Publish Macos Pkg (`docs/release-notes-publish-macos-pkg.md`)
Prebuilt macOS PKG publishing
asc publish testflight and asc publish appstore now accept a prebuilt macOS
installer through --pkg. PKG uploads select MAC_OS automatically and
require explicit --version and --build-number values.
The existing --ipa --platform MAC_OS combination remains available during a
compatibility window, but now prints a deprecation warning. Migrate those
invocations to:
asc publish appstore \
--app "APP_ID" \
--pkg "MacApp.pkg" \
--version "1.2.3" \
--build-number "42"The deprecated macOS IPA publishing combination will be removed in a future
major release. IPA publishing for iOS, tvOS, and visionOS is unchanged.
Release Notes Signing Identity Sync (`docs/release-notes-signing-identity-sync.md`)
Signing identity sync release note
Status: the legacy password sources described below were removed in 5.0.0.
--passwordis no longer registered andASC_MATCH_PASSWORDis no longer
read; use--password-fileorASC_SIGNING_SYNC_PASSWORD. Seemigrate-to-5-0.mdx.
asc signing sync push can now pair a local PKCS#12 or RSA/EC private key with
the certificate embedded in the selected provisioning profile, then store one
canonical encrypted identity for other agents and CI workers. New identity
envelopes use scrypt plus authenticated metadata, while existing
certificate/profile repository behavior stays compatible.
Use --password-file or ASC_SIGNING_SYNC_PASSWORD for the signing repository
password. The older --password flag and ASC_MATCH_PASSWORD environment
variable remain available through 4.x with a deprecation warning and will be
rejected in 5.0.0.
Profile lifecycle
asc signing sync push --renew-expired replaces an expired profile with a
same-name successor. --force-for-new-devices recreates a development or ad
hoc profile when its devices differ from the enabled device list, and--include-mac-in-profiles adds enabled Apple silicon Macs to iOS development
and ad hoc device sets. asc signing sync nuke --profile-type TYPE --confirm
deletes every profile of one type, revokes its certificates, and removes their
encrypted artifacts; --dry-run previews the plan without changes. See
Renew, refresh, and remove synced profiles.
Release Notes Web Bundle Ids Sync App Clip Confirm (`docs/release-notes-web-bundle-ids-sync-app-clip-confirm.md`)
Web bundle-ids sync-app-clip confirmation and idempotency
asc web bundle-ids capabilities sync-app-clip now requires --confirm, in
line with every other asc web mutation. Syncing rewrites the App Clip Bundle
ID capability graph, and Apple invalidates existing provisioning profiles that
contain the changed App ID.
asc web bundle-ids capabilities sync-app-clip \
--bundle-id "CLIP_BUNDLE_ID" \
--parent-bundle-id "PARENT_BUNDLE_ID" \
--capability "PUSH_NOTIFICATIONS" \
--confirmThe previous invocation shape still parses, but it fails with usage exit code2 before a web session is resolved or any request is sent, and prints this
migration guidance to stderr:
Warning: web bundle-ids capabilities sync-app-clip now requires --confirm because syncing rewrites the App Clip Bundle ID capability graph and invalidates existing provisioning profiles; re-run with --confirm to acknowledge. No request was sent.
Error: --confirm is requiredBecause a no-op write still invalidates profiles, the command is now
idempotent. It reads the current capability graph first and, when the
capability is already enabled with the requested parentBundleId (and the
requested settings, when --settings-json is passed), it sends no PATCH and
reports changed: false with status already-synced. When it does write, it
preserves every unrelated capability and relationship (including to-many
relationships such as App Groups and iCloud containers, which previously failed
to parse), sends only the writable enabled and settings capability
attributes, warns on stderr that existing provisioning profiles are now
invalid, and caches any refreshed session cookies.
The JSON receipt gains two additive fields, changed and status
(synced or already-synced); the table and Markdown outputs gain matchingChanged and Status columns.
Release Notes Web Session Required (`docs/release-notes-web-session-required.md`)
Clearer error when no Apple web session is available
Changed in 5.9.0. Some asc web commands need a signed-in Apple Account
session that they cannot create. This happens when:
- nothing is cached and no account is selected, or
- the selected account has no usable cached session, and
ASC_WEB_PASSWORD,
a saved password, and an interactive terminal are all unavailable.
Every web command except asc web auth login now prints a short error and aHint: line with the next step in that case, instead of the command's full
usage page:
Error: no Apple web session is cached
Hint: asc web commands need a signed-in Apple Account session, and signing in needs an interactive terminal for the password and two-factor code. Run 'asc web auth login --apple-id EMAIL' in a terminal, or load a session exported elsewhere with 'asc web auth import --file FILE'. Unattended sign-in needs ASC_WEB_PASSWORD and ASC_WEB_2FA_CODE_COMMAND, plus --apple-id or ASC_WEB_APPLE_ID.When an account was selected, the error names it. Where the public App Store
Connect API can answer instead, the hint also names that command. For example,asc web review show points to asc review status --app APP_ID.
The exit code is unchanged: these failures still exit with usage code 2.asc web auth login keeps its existing usage errors and usage page.
Release Notes Xcode Cloud Status Id Alias (`docs/release-notes-xcode-cloud-status-id-alias.md`)
Xcode Cloud status ID alias
Status: the
--idalias described below was removed in 5.0.0.asc xcode-cloud status --idis now an unknown flag; use--run-id. Seemigrate-to-5-0.mdx.
asc xcode-cloud status now accepts --id as a deprecated compatibility alias
for --run-id. Existing alias calls continue after printing this warning to
stderr:
Warning: `--id` is deprecated. Use `--run-id`.New scripts should use asc xcode-cloud status --run-id "BUILD_RUN_ID".
Supplying both --id and --run-id is a usage error, even when the values
match. The alias may be removed in the next major release after the standard
deprecation window.
Sign In Keys Design (`docs/sign-in-keys-design.md`)
Sign in with Apple private keys
Add asc web sign-in-keys list, view --key-id ID, create --name NAME --bundle-id RESOURCE_ID --output-dir DIR --confirm, and download --key-id ID --output-dir DIR --confirm to the existing web-session command family. The installed CLI has no Developer Portal authentication-key lifecycle commands. Existing App Store Connect API-key commands remain unchanged.
Use the existing Developer Portal session and team selection, request timeout, error handling, root-anchored, identity-checked private file writes on macOS and Linux, and output conventions. Creating a remote key or consuming a one-time download requires --confirm; neither is automatically retried after an ambiguous response. Downloaded P8 material must never appear in output; reserve a private destination before downloading, validate a real EC PKCS8 key, and emit a receipt containing only the key ID and saved path. Listing preserves Apple's raw envelope. Do not expose revoke in this bounded addition.
The private Developer Portal endpoints are outside the public OpenAPI schema. Confirm endpoint and payload evidence from maintained portal clients before implementation and verify through the live account. Tests cover required flags before authentication, expected request payloads, one-time download output handling, path collision and invalid responses. Run adjacent package tests, command generation and repository gates plus the mandated isolated Codex review.
Endpoint evidence: Expo's @expo/apple-utils 2.2.1 and Fastlane's portal client use legacy /services-account/QH65B2/account/auth/key/{list,get,v2/create,download}. The create request uses application/json and serviceConfigurationsRequests: [{isNew: true, serviceId: "APPLE_ID_AUTH_KEY_CONFIGURATION", identifiers: {bundle: [resourceID]}}]. The bodyless download GET must omit Content-Type. Listing primes key-family CSRF. Detail configuration entries identify the primary bundle using type: "bundle" and the opaque id.
Live verification on September 15, 2026: list and view succeeded; a dedicated key was created with the requested bundle association and its P8 was downloaded, parsed as P-256 PKCS8, saved mode 0600, and secured in the operator's vault. Initial download rejected the JSON:API Content-Type with HTTP 415 without consuming the key; removing Content-Type matched Expo's transport and completed the download. Existing keys were preserved.
Subscription Notification Design (`docs/subscription-notification-design.md`)
App Store Server Notifications URLs
asc apps update currently supports bundle ID, primary locale, and content rights,
but cannot configure App Store Server Notifications endpoints. Add two optional
flags in the existing command rather than a separate command or web-only path:
asc apps update --id APP_ID --subscription-status-url https://example.com/notifications
asc apps update --id APP_ID --sandbox-subscription-status-url https://example.com/sandbox-notificationsThe offline OpenAPI snapshot supports both fields in PATCH /v1/apps/{id} viaAppUpdateRequest.data.attributes: subscriptionStatusUrl andsubscriptionStatusUrlForSandbox. Each URL is paired with its correspondingsubscriptionStatusUrlVersion or subscriptionStatusUrlVersionForSandbox set toV2. The schema permits V1 and V2; this command explicitly configures V2 signed
payloads, while omitted endpoints retain their current version and URL. URL removal
and configuring legacy V1 payloads are outside this additive change.
URLs must use absolute HTTPS without credentials or fragments. Explicit empty
values are usage errors, validated before authentication or network access. Existing
app metadata flags remain available and can be combined with either URL flag.
Mutations keep the Apple resource envelope and existing output formats. Add all four
fields to app response attributes so JSON reads and mutation responses retain them.
Verification covers exact PATCH payloads for each endpoint, combined metadata,
omitted-field preservation, returned URLs and versions, and invalid URLs without
network activity. Establish RED on the unsupported flags, reach GREEN, verify built
binary help and usage-error exit code 2, and run the repository validation and review
gates. Any authorized live update must target only the operator-selected app and
read its configuration back afterward.
Live verification found that Apple omits notification fields from default GET app
responses even after a successful update. Add apps view --fields for the app
attributes decoded by this client, passing the selection through fields[apps].
The exact GET endpoint schema permits all four notification fields. Default reads
stay unchanged; explicit empty or unknown selectors are usage errors. A CLI
regression asserts the sparse-field query and all four decoded response values.
Wall Of Apps.Json (`docs/wall-of-apps.json`)
[
{
"app": "6 Nations - Rugby Scores Live",
"link": "https://apps.apple.com/us/app/6-nations-rugby-scores-live/id414001342?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/34/09/f3/3409f3bc-b011-ef18-f9cf-1e885da8f97f/6nations-0-0-1x_U007epad-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "66-Day Streak: Habit Builder",
"link": "https://apps.apple.com/us/app/66-day-streak-habit-builder/id6759220059",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/5c/ce/67/5cce6782-729c-07af-1e0b-7322b8ffd914/SixtysixStreaksIcon-0-0-1x_U007emarketing-0-9-0-85-220.png/512x512bb.jpg"
},
{
"app": "Aanvi: Baby Memory Book",
"link": "https://apps.apple.com/us/app/aanvi-baby-memory-book/id6759002114?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/75/5c/d8/755cd8e5-854a-e583-2908-54e6dea0566f/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Accountable Ai - Habit Blocker",
"link": "https://apps.apple.com/us/app/accountable-ai-habit-blocker/id6747360923?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/83/4f/eb/834feba8-5b75-ac68-d2f3-4a3c8ce5510f/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "AcheNote - Symptom & Visit Log",
"link": "https://apps.apple.com/us/app/achenote-symptom-visit-log/id6760022585?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/c3/f6/39/c3f63991-8f6c-9a0f-f538-d5eb95fcb77c/AppIcon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "ACHIEVE: Business Growth Ideas",
"link": "https://apps.apple.com/us/app/achieve-business-growth-ideas/id6468916239?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/73/f2/2c/73f22ca2-4b29-225e-4a38-300cd2f752f3/AppIcon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "AdMate App",
"link": "https://apps.apple.com/us/app/admate-app/id6705137320?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/fb/86/59/fb865967-771a-6e3f-d5b6-325f5c60287f/AppIcon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "aether: Remote Deck ControlPad",
"link": "https://apps.apple.com/us/app/aether-remote-deck-controlpad/id6761128470?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/4e/44/bd/4e44bde8-ee38-ba9a-c1f1-ff8485e5e5ff/aetherIcon-0-0-1x_U007ephone-0-1-P3-85-220.png/512x512bb.jpg"
},
{
"app": "AI Chat Assistant - Chappie",
"link": "https://apps.apple.com/us/app/ai-chat-assistant-chappie/id6756681102?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/60/12/19/60121940-0202-b42d-6c0d-074edb4fff49/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "AI Journal & Diary: Unfold",
"link": "https://apps.apple.com/us/app/ai-journal-diary-unfold/id6747351461?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/21/b4/7b/21b47bb0-055b-2510-f225-c93afde2be61/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "AI Sound Effects: Audio Studio",
"link": "https://apps.apple.com/us/app/ai-sound-effects-audio-studio/id6759847647?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/4b/63/2f/4b632f89-4083-8c06-f708-94270fce6d14/AppIcon-0-0-1x_U007ephone-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Aida Simple Fast Daily Planner",
"link": "https://apps.apple.com/us/app/aida-simple-fast-daily-planner/id6757127406?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/cc/6b/36/cc6b3631-cfdf-8dbc-a66a-d5f6861a4514/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Airtist",
"link": "https://apps.apple.com/app/id6745053878",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/a1/85/81/a185815f-0e82-98b4-ac37-aed44c57d719/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Akari Glow: Light Up Puzzles",
"link": "https://apps.apple.com/us/app/akari-glow-light-up-puzzles/id6759948370?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/6a/d6/06/6ad6063e-a7c4-ef18-dd4e-afecc0c3ba14/AppIcon_Akari-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Alarm One: Shift Alarm Clock",
"link": "https://apps.apple.com/us/app/alarm-one-shift-alarm-clock/id6748434148?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/4a/2b/43/4a2b43de-4feb-4758-e0be-5d5fee24f68e/icon-0-0-1x_U007epad-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Aletheon",
"link": "https://apps.apple.com/us/app/aletheon/id6761150392?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/32/b3/7a/32b37a05-8f82-eb89-4388-c0169410b7c3/AppIcon-0-0-1x_U007epad-0-1-0-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Allergy Translate",
"link": "https://apps.apple.com/us/app/allergy-translate/id1366620257?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/6f/d5/6b/6fd56b58-41da-7aa7-4737-04bee755418d/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Alune: Planner & Assistant",
"link": "https://apps.apple.com/us/app/alune-planner-assistant/id6761420047?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/00/14/93/0014939d-ce61-2386-0bf3-b79c4dd678c4/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "AnimAtlas",
"link": "https://apps.apple.com/us/app/animatlas/id6792998018?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/50/cd/84/50cd848a-8b56-9d26-4b78-385bf9432f54/ExplorerDex-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "AnLi Kids",
"link": "https://apps.apple.com/us/app/anli-kids/id6759224873?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/32/55/ba/3255ba71-f714-8d4c-b0b2-2dadd0a9fe26/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "App Time Limit: Screen Leech",
"link": "https://apps.apple.com/us/app/app-time-limit-screen-leech/id6743212513?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/f5/a4/6e/f5a46e54-20c9-8a3f-69d0-df232214a7fc/AppIcon-0-0-1x_U007epad-0-1-0-sRGB-85-220-0.png/512x512bb.jpg"
},
{
"app": "Appboard: Find Apps · Rankings",
"link": "https://apps.apple.com/us/app/appboard-find-apps-rankings/id1667492380?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/b8/86/4e/b8864eea-7c79-4b57-dc58-4a208fc28863/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Aseta - Net Worth Tracker",
"link": "https://apps.apple.com/us/app/aseta-net-worth-tracker/id6759186841?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/32/51/2d/32512d40-1347-5bcc-5a37-845b35c537ea/AppIconAseta-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Audio Horoscope · Astrologica",
"link": "https://apps.apple.com/app/id6754387408",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/de/bd/7c/debd7c60-6be9-a231-d7c0-aa54a776dc3c/astrologica-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Audioer: Convert Audio Formats",
"link": "https://apps.apple.com/us/app/audioer-convert-audio-formats/id6743841447?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/75/4b/bd/754bbd84-d6f5-3393-1de4-b0b04c6be328/AppIcon-0-0-85-220-0-5-0-2x.png/512x512bb.png"
},
{
"app": "Authenticator app - 2FA, MFA",
"link": "https://apps.apple.com/us/app/authenticator-app-2fa-mfa/id1625466768?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/fe/d3/08/fed308ed-910f-052c-7046-9280241cc22d/AppIcon-0-0-1x_U007epad-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Auto Metric - Expense Tracker",
"link": "https://apps.apple.com/us/app/auto-metric-expense-tracker/id6757914584?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/51/07/86/51078666-e31e-5e8c-14f8-833eb7091863/AppIcon-1x_U007emarketing-0-11-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "Axo Tap: Idle Axolotl Pet",
"link": "https://apps.apple.com/us/app/axo-tap-idle-axolotl-pet/id6799872294?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/25/72/c2/2572c20e-6176-4340-43e3-e07a4c65cf5a/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Azivault",
"link": "https://apps.apple.com/us/app/azivault/id6774401513?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/cc/35/bd/cc35bd9b-409b-876b-e20c-036942bbee7f/AppIcon-0-0-85-220-0-5-0-2x.png/512x512bb.png"
},
{
"app": "Baby feeding & diaper tracker",
"link": "https://apps.apple.com/us/app/baby-feeding-diaper-tracker/id6451158784?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/03/45/5a/03455ad1-896f-c341-6d38-13bab0c40e8b/AppIcon-0-1x_U007epad-0-1-P3-85-220-0.png/512x512bb.jpg"
},
{
"app": "Baby Sleep Sounds - Dozy",
"link": "https://apps.apple.com/us/app/baby-sleep-sounds-dozy/id6747475875?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/51/a2/ba/51a2babe-ae87-6425-508c-fb303753debb/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "BananaSplit: Split Expenses",
"link": "https://apps.apple.com/us/app/bananasplit-split-expenses/id6752376267?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/ec/4a/d1/ec4ad10e-6a0e-166d-3cf5-f22826a5d6a7/app-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "BeatS [Afropop Edition]",
"link": "https://apps.apple.com/us/app/beats-afropop-edition/id418786868?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/92/08/f6/9208f69f-e17c-4457-a10a-a2293abb2a8a/AppIcon-Kwaito-0-1x_U007emarketing-0-8-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "BeatS [Christmas Edition]",
"link": "https://apps.apple.com/us/app/beats-christmas-edition/id410016967?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/98/f6/6a/98f66a6c-a1ef-1255-56e5-63642c4d70fd/AppIcon-Xmas-0-1x_U007emarketing-0-8-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "BeatS [Hip-Hop/Rap Edition]",
"link": "https://apps.apple.com/us/app/beats-hip-hop-rap-edition/id397611091?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/55/31/9e/55319e96-134d-c7dc-edd7-08552951642e/AppIcon-Hip-Hop-0-1x_U007emarketing-0-8-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "BeatS [R&B/Soul Edition]",
"link": "https://apps.apple.com/us/app/beats-r-b-soul-edition/id404929463?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/e0/2a/e9/e02ae90f-3742-5a44-bfa9-f5744764e605/AppIcon-R_U0026B-0-1x_U007emarketing-0-8-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "BeatS [Reggaeton Edition]",
"link": "https://apps.apple.com/us/app/beats-reggaeton-edition/id410001686?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/8f/a1/44/8fa14469-aa54-2b02-fbe9-748649e27cc1/AppIcon-Reggaeton-0-1x_U007emarketing-0-8-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "Beauty Fonts - Font Keyboard",
"link": "https://apps.apple.com/us/app/beauty-fonts-font-keyboard/id1438854446?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/ce/85/c5/ce85c50d-e009-34b8-96f1-924e6881a364/AppIcon-0-0-1x_U007epad-0-1-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "Biaura - Sleep & Focus Sounds",
"link": "https://apps.apple.com/us/app/biaura-sleep-focus-sounds/id6748336447?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/af/9c/df/af9cdf51-1465-d861-f435-b05afb49def6/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Bibliostats",
"link": "https://apps.apple.com/app/id6759411516",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/06/d8/48/06d84871-63f1-8c85-ceb5-e6a140b2831c/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "bijou.fm",
"link": "https://apps.apple.com/us/app/bijou-fm-for-last-fm/id6450460066",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/1f/a9/bc/1fa9bcd6-5795-038c-d4ec-80dec18706ac/AppIcon-0-0-1x_U007epad-0-0-0-1-0-0-85-220.png/512x512bb.jpg"
},
{
"app": "Bitbook",
"link": "https://apps.apple.com/us/app/bitbook/id1672879200?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/3c/b9/15/3cb915f7-d30c-9b35-098a-026c8cb7398b/Bitbook-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "BitLocal: BTC-Friendly Shops",
"link": "https://apps.apple.com/us/app/bitlocal-btc-friendly-shops/id6447485666?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/71/18/13/71181314-64e0-ab1a-4e44-9f6134f12f01/AppIcon-0-0-1x_U007epad-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Blimber – Student Planner",
"link": "https://apps.apple.com/us/app/blimber-student-planner/id6751923150?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/1b/d4/ca/1bd4ca07-3310-f9e9-b2a6-06e0a7c7a955/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Bloom",
"link": "https://apps.apple.com/app/id6739955926",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/8e/9b/52/8e9b5222-940f-0058-1528-aec270296b39/Blueberry-0-1x_U007ephone-0-1-0-sRGB-85-220-0.png/512x512bb.jpg"
},
{
"app": "Bloom - Coffee Shelf & Recipe",
"link": "https://apps.apple.com/us/app/bloom-coffee-shelf-recipe/id6759914524?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/a4/c8/2a/a4c82a25-fae8-4c04-6d02-91a15b72bb4f/BloomIconFinal-0-1x_U007ephone-0-1-0-sRGB-85-220-0.png/512x512bb.jpg"
},
{
"app": "Board buddy – score counter",
"link": "https://apps.apple.com/us/app/board-buddy-score-counter/id6743980638?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/6a/dc/58/6adc5814-da30-95a2-eb8d-2cca1aaf50b7/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Boba Tycoon - Idle Tea Shop",
"link": "https://apps.apple.com/us/app/boba-tycoon-idle-tea-shop/id6758029554?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/ad/d4/c3/add4c3bf-d7b2-9445-3245-40d4e97403f9/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Bobbin",
"link": "https://apps.apple.com/us/app/threads-growth-bobbin/id6756035789",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/fe/a3/9d/fea39d77-422f-d815-5af9-c0ee518f5b97/AppIcon-0-0-1x_U007epad-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Box Box Club: Formula Widgets",
"link": "https://apps.apple.com/us/app/box-box-club-formula-widgets/id1621241916?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f9/73/48/f9734812-afa3-83b4-4862-a9331863f84b/AppIcon-0-0-1x_U007epad-0-0-0-1-0-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "BrainDrain: 1 Minute Journal",
"link": "https://apps.apple.com/us/app/braindrain-1-minute-journal/id6741376368?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/4b/ce/14/4bce140a-0e8c-3c4b-cba1-80288b5878ae/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "BratVPN",
"link": "https://apps.apple.com/us/app/bratvpn/id6759875957?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/29/91/b8/2991b84b-72ec-130e-071e-18f6fd175a44/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "BTCtoMOON",
"link": "https://apps.apple.com/us/app/btctomoon/id6756674743?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/45/de/cb/45decbde-ac19-a049-67f4-2541a7e5ce48/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "BuddyTask: AI To Do List",
"link": "https://apps.apple.com/us/app/buddytask-ai-to-do-list/id6758902036?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/80/f5/84/80f584d0-6956-72b2-1294-8a5157c450b9/AppIcon-0-0-1x_U007emarketing-0-7-0-85-220.png/512x512bb.jpg"
},
{
"app": "CabinLink",
"link": "https://apps.apple.com/us/app/cabinlink/id6761247556?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f8/91/a8/f891a8c5-d49e-a5bf-83f4-cbe86e0eac04/CabinLink-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Cairn - Your Expense Tracker",
"link": "https://apps.apple.com/us/app/cairn-your-expense-tracker/id6762088614?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/3d/7e/ab/3d7eabc0-2f4d-52b4-b629-5b979d65de14/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Cal AI - Calorie Tracker",
"link": "https://apps.apple.com/us/app/cal-ai-calorie-tracker/id6480417616?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/5e/5d/e9/5e5de9bc-18ef-3c07-79f0-e0a9e3009629/AppIcon-0-1x_U007ephone-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Caliscool: Calisthenics",
"link": "https://apps.apple.com/us/app/caliscool-calisthenics/id6758521484?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/45/fd/12/45fd1294-2f62-d4d9-368e-da68e27adc22/AppIcon-0-0-1x_U007epad-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Calorimeter",
"link": "https://apps.apple.com/us/app/calorimeter/id6502800890?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/d3/da/14/d3da14c9-99e0-1a7c-21b7-7f380b078084/AppIcon-0-1x_U007ephone-0-1-P3-85-220-0.png/512x512bb.jpg"
},
{
"app": "Capture GO - AI & Online",
"link": "https://apps.apple.com/us/app/capture-go-ai-online/id6751532288?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/b3/d6/f9/b3d6f97c-0a17-9972-860c-03e401426033/AppIcon-0-0-1x_U007epad-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Cardstock: Sports Card Scanner",
"link": "https://apps.apple.com/us/app/cardstock-sports-card-scanner/id1508888618?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/3f/e4/a2/3fe4a277-9a47-455b-313b-5939c61f7258/AppIcon-0-0-1x_U007epad-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Carmory",
"link": "https://apps.apple.com/us/app/carmory/id6757974031?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/68/53/44/68534453-8146-fd14-cf29-0e987dbec900/AppIcon-0-0-1x_U007epad-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Cat on Chair",
"link": "https://apps.apple.com/ca/app/cat-on-chair-focus-timer/id6746562271",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/02/3d/6e/023d6ea0-1a98-d132-613c-d01b115d2e0d/AppIcon-0-0-1x_U007epad-0-1-0-85-220.jpeg/512x512bb.jpg"
},
{
"app": "CFB GameDay Board",
"link": "https://apps.apple.com/us/app/cfb-gameday-board/id6809035228?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/be/8b/53/be8b5384-addc-fd00-0aeb-7e605b9d8817/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Checkify: #1 AI Bill Splitter",
"link": "https://apps.apple.com/us/app/checkify-1-ai-bill-splitter/id6755905554?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/fe/da/29/feda29cd-84dc-ad22-66f4-a276004811fc/appicon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "ChessLock: Screen Time Control",
"link": "https://apps.apple.com/us/app/chesslock-screen-time-control/id6744930081?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/57/ee/71/57ee713f-9cec-7083-bd6f-220b5668f523/AppIcon-0-0-1x_U007epad-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Chronoid",
"link": "https://www.chronoid.app",
"icon": "https://www.chronoid.app/app-icon.png"
},
{
"app": "Cinefuse: AI Movie Maker",
"link": "https://apps.apple.com/us/app/cinefuse-ai-movie-maker/id6758482061?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/98/b8/9f/98b89fce-4ee9-3a15-45d5-4def0e3808e0/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Clean Links: URL Privacy",
"link": "https://apps.apple.com/us/app/clean-links-url-privacy/id6747395062?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/06/bd/0d/06bd0de6-4342-ed6d-357d-6178b1ebce6c/AppIcon-0-0-1x_U007epad-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Clip Jar",
"link": "https://apps.apple.com/us/app/clip-jar/id1628120600?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/51/13/d7/5113d707-1dce-2010-2488-db621e84ae42/AppIcon-0-0-1x_U007epad-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Cloak: App Locker",
"link": "https://apps.apple.com/us/app/cloak-app-locker/id6451156267?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/eb/b9/c4/ebb9c4be-fd15-228d-dd9a-05bfc5fa37ad/AppIcon-0-0-1x_U007epad-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "CodexMonitor",
"link": "https://github.com/Dimillian/CodexMonitor"
},
{
"app": "Color Match Game: Chroma",
"link": "https://apps.apple.com/us/app/color-match-game-chroma/id1500196580",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/e0/22/11/e02211b9-8e91-5250-17ed-a6105cd42922/Chroma-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "ColorCub: AI Coloring Pages",
"link": "https://apps.apple.com/us/app/colorcub-ai-coloring-pages/id6745206164?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/4a/7f/ae/4a7faef1-a45e-75d7-d90a-066eda390ea4/AppIcon-0-0-1x_U007emarketing-0-8-0-0-85-220.png/512x512bb.jpg"
},
{
"app": "Colour Within",
"link": "https://apps.apple.com/us/app/colour-within/id6763698447?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/c4/85/67/c4856759-f9ff-bbc0-08e8-6dea0241ee5f/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Comer en Asturias",
"link": "https://apps.apple.com/us/app/comer-en-asturias/id6802125809?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/7e/b1/18/7eb118c2-0cfb-c083-839e-cc896b31577c/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Concert Memories 2: Countdown",
"link": "https://apps.apple.com/us/app/concert-memories-2-countdown/id6744301154?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/6c/ce/a1/6ccea161-6da4-52c7-1260-f44955b0bc70/ConcertMemories2-0-0-1x_U007epad-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Connectarr",
"link": "https://apps.apple.com/us/app/connectarr/id6757109537?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/48/3c/75/483c7585-5910-ef5a-d037-9183b6beafed/Connectarr-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Constellation: Mind Atlas",
"link": "https://apps.apple.com/us/app/constellation-mind-atlas/id6756132557?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/5d/39/50/5d3950a0-3c8d-f420-ab62-877c5b05e48f/AppIcon.lsr/512x512bb.jpg"
},
{
"app": "Contact Sheet",
"link": "https://apps.apple.com/us/app/contact-sheet-generator/id6744368077",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/cd/14/5e/cd145e49-9277-f536-bcf8-0c323a1985b0/AppIconUpdate-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Contact Sheet: AI Photo Review",
"link": "https://apps.apple.com/us/app/contact-sheet-ai-photo-review/id6760267143?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/4d/84/49/4d84498d-802a-03b0-262f-bc80aa2c0f1a/AppIcon-0-0-1x_U007epad-0-1-0-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Convusic",
"link": "https://apps.apple.com/us/app/convusic/id1591366129?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/40/b9/d8/40b9d80c-b3e5-79e4-1fc6-44f04efc95c4/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Copiek",
"link": "https://apps.apple.com/us/app/copiek/id6813259113?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/ce/6e/3d/ce6e3dcf-67ac-f6a0-3f3e-92077eec6b84/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Correspondance: One Inbox",
"link": "https://apps.apple.com/us/app/correspondance-one-inbox/id6807945434?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/01/4c/d6/014cd6a7-bcf4-1926-4560-bc7261370fa4/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Countdown Visual Timer: Kids",
"link": "https://apps.apple.com/us/app/countdown-visual-timer-kids/id6474494320?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/35/3d/ab/353dab23-80da-b33d-4091-db8d3d230cbd/ATVC_Icon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Cruisea: Real-time Tracking",
"link": "https://apps.apple.com/us/app/cruisea-real-time-tracking/id6755068162?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/ad/6c/03/ad6c0376-c349-cdee-bfac-c7882f57a4d7/AppIcon-0-1x_U007ephone-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Crunch: Compress & Convert",
"link": "https://apps.apple.com/us/app/crunch-compress-convert/id6748700457?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/9e/88/85/9e888594-e92b-f739-c358-7fd083cae2f6/AppIcon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Cubby",
"link": "https://apps.apple.com/app/id6751732388",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/2b/cf/8b/2bcf8b94-8050-926d-bce1-b95266a061d0/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "cubby: stash & track",
"link": "https://apps.apple.com/us/app/cubby-stash-track/id6759426602?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/77/e6/29/77e6296a-282e-ba85-74d2-93f673233114/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Curvely",
"link": "https://apps.apple.com/us/app/curvely/id6794988370?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/b6/b4/2c/b6b42cc0-44fe-0545-0cac-275ebc6fe8d9/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "CUStats",
"link": "https://apps.apple.com/us/app/custats/id6756333957?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/2f/71/e8/2f71e8b6-40ff-cd21-cd48-0161abdff235/AppIcon-0-0-85-220-0-5-0-2x.png/512x512bb.png"
},
{
"app": "CUStats Go",
"link": "https://apps.apple.com/us/app/custats-go/id6757679404?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/5d/65/7f/5d657f01-3cf3-2d0b-c343-45112c16a295/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Custom AI Charades",
"link": "https://apps.apple.com/us/app/custom-ai-charades/id6475172244?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/0b/1f/fa/0b1ffadc-4c03-342a-02c2-92c8014af53d/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "CutWise: AI Calorie Counter",
"link": "https://apps.apple.com/us/app/cutwise-ai-calorie-counter/id6780189151?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/17/48/67/174867f1-44ab-aaad-58b4-50065f5e412d/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Daily Self Care - Anicca",
"link": "https://apps.apple.com/us/app/daily-self-care-anicca/id6755129214?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/11/df/b2/11dfb2d2-9666-f952-fb04-b52f22fc971c/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Daily – Hours & Time Tracker",
"link": "https://apps.apple.com/us/app/daily-hours-time-tracker/id686910553?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/f0/02/8d/f0028d3e-b002-ff2a-2c0a-0d60041bc9ff/AppIcon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Dandelion",
"link": "https://apps.apple.com/us/app/dandelion-write-and-let-go/id6757363901",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/e1/e4/e3/e1e4e3af-42bc-bbb0-cd4d-3876d720de3e/AppIcon-0-0-1x_U007epad-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Dash: Widgets for Workouts",
"link": "https://apps.apple.com/us/app/dash-widgets-for-workouts/id1556281712",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/cf/14/8f/cf148fbc-2902-8a67-35a7-350b1ec57971/AppIcon-0-0-1x_U007epad-0-1-P3-85-220.png/512x512bb.jpg"
},
{
"app": "Dateminder - Event Reminders",
"link": "https://apps.apple.com/us/app/dateminder-event-reminders/id6480020164?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/a2/23/3c/a2233c68-f1ef-e5a4-4574-9ec5fa2a1d4c/AppIcon-0-1x_U007ephone-0-1-sRGB-85-220-0.png/512x512bb.jpg"
},
{
"app": "DAWN for Mastodon by Nightfox",
"link": "https://apps.apple.com/us/app/dawn-for-mastodon-by-nightfox/id1668645019?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/da/40/73/da4073fc-6cdb-a01e-3666-3575f021ff74/AppIcon10-0-0-1x_U007epad-0-0-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "DayBar",
"link": "https://apps.apple.com/us/app/daybar/id6739052447?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/79/16/d5/7916d56d-bf7f-37a0-cb0c-bb50f04ed4f3/AppIcon-0-0-85-220-0-5-0-2x.png/512x512bb.png"
},
{
"app": "Daybard: Your Day as an RPG",
"link": "https://apps.apple.com/us/app/daybard-your-day-as-an-rpg/id6786213426?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/2e/7f/30/2e7f3052-0c2e-102c-c994-e44e126d3a67/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "DayCount: Countdown Widget",
"link": "https://apps.apple.com/us/app/daycount-countdown-widget/id6765979160?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/1a/98/4d/1a984db8-c625-4bcc-c3da-2caf3ded04ba/AppIcon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Deep Hour - Deep Focus Ritual",
"link": "https://apps.apple.com/app/id6758889630",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/55/90/80/55908099-0de3-f577-f029-7e76d29e3481/AppIcon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "DeskTally",
"link": "https://apps.apple.com/us/app/desktally/id6760261861?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/ca/bd/6c/cabd6c0c-3283-a46e-e1ed-51e2fc253af6/AppIcon-0-1x_U007ephone-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "DevHub",
"link": "https://apps.apple.com/us/app/devhub/id6476452351?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/c9/fa/8b/c9fa8b9f-1ec9-3612-2cb6-a0dbb610468d/AppIcon-0-0-85-220-0-0-5-0-2x-0-0-0.png/512x512bb.png"
},
{
"app": "Devotion: Daily Bible",
"link": "https://apps.apple.com/us/app/devotion-daily-bible/id6789295449?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/06/b0/6c/06b06ce2-ce56-0e00-040d-374ff0a7d0eb/Icon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "DevTutor: Learn Swift, SwiftUl",
"link": "https://apps.apple.com/us/app/devtutor-learn-swift-swiftul/id6471227008?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/a0/5e/d3/a05ed356-a8d1-47ec-b464-f1422a717a75/AppIcon-0-0-1x_U007epad-0-0-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Dither By Eyed™ - Photo Editor",
"link": "https://apps.apple.com/us/app/dither-by-eyed-photo-editor/id6751736178?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/75/0a/23/750a23f4-cd91-8ec7-1155-fa2127042e4b/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Do? - Sex Tracker & Map Log",
"link": "https://apps.apple.com/app/id6757778324",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/74/16/00/7416000d-28a9-7c8f-6537-9b6d8b92cc18/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Doc Translator: PDF, EPUB, OCR",
"link": "https://apps.apple.com/us/app/doc-translator-pdf-epub-ocr/id6810911331?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/0e/5f/8a/0e5f8ae7-a68b-1495-ec8c-fe0e8206dfd4/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Doco Translate: PDF Translator",
"link": "https://apps.apple.com/us/app/doco-translate-pdf-translator/id6758205667?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/cf/13/14/cf1314c4-8eb3-026f-47c4-52de70683231/AppIcon-0-0-85-220-0-0-1-0-2x-P3.png/512x512bb.png"
},
{
"app": "Don't Spiral: ROCD & Anxiety",
"link": "https://apps.apple.com/us/app/dont-spiral-rocd-anxiety/id6758227509?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/c6/e9/77/c6e977ac-bc3b-5f1c-fbdb-e7f46ae3e467/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Dot. Extend Your Digital World",
"link": "https://apps.apple.com/us/app/dot-extend-your-digital-world/id6739468133?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/3b/74/fe/3b74fe4f-e2d1-eb5c-2c3f-2255d003c618/icon-0-0-1x_U007epad-0-1-P3-85-220.png/512x512bb.jpg"
},
{
"app": "DoubleMemory",
"link": "https://apps.apple.com/app/id6737529034",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/65/58/32/6558325e-7e32-4385-90a8-454c6b80e373/DoubleMemory-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "DreamWhisper: Sleep Stories",
"link": "https://apps.apple.com/us/app/dreamwhisper-sleep-stories/id6746324271?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f5/89/fb/f589fb1e-656e-3c59-6f4d-4c397f0a63ad/AppIcon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Dripped",
"link": "https://apps.apple.com/app/id6749790183",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/45/67/fd/4567fd8b-feb1-c665-e8e0-4b05a3016150/DrippedIcon-0-1x_U007ephone-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Dtd Playlist-Daily Mood Music",
"link": "https://apps.apple.com/us/app/dtd-playlist-daily-mood-music/id1631654360?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/c3/12/31/c31231ef-8a43-d6dd-84e2-23728b669d59/AppIcon-0-0-1x_U007epad-0-0-0-1-0-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "DuoCam Multicam Video Camera",
"link": "https://apps.apple.com/us/app/duocam-multicam-video-camera/id1482277903?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f5/49/21/f5492122-83dc-594c-82ae-3010784cb984/AppIcon-0-0-1x_U007ephone-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Echo Sleep: AI Sleep Sounds",
"link": "https://apps.apple.com/us/app/echo-sleep-ai-sleep-sounds/id6746497347?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/98/d0/fe/98d0feb7-d137-eb25-a475-9c06caf643ed/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "EchoKeep: Smarter RSS Reader",
"link": "https://apps.apple.com/us/app/echokeep-smarter-rss-reader/id6752524967?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/23/4a/dc/234adc2b-ac03-64c7-b503-05112d5ad183/EchoKeep-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "EduSwipe: AI Flashcards & SRS",
"link": "https://apps.apple.com/us/app/eduswipe-ai-flashcards-srs/id6744727106?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/23/60/71/236071d7-7827-efd9-4c8f-03db0cee6aa9/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Elite Soccer Tactic Board",
"link": "https://apps.apple.com/us/app/elite-soccer-tactic-board/id6476522645?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/22/e0/85/22e085b9-1969-da7c-4d35-3bfe75009463/SoccerIcon-0-0-1x_U007epad-0-0-0-1-0-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Embers to Dyson",
"link": "https://apps.apple.com/us/app/embers-to-dyson/id6756216261?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/5c/b3/ab/5cb3ab34-60e5-81f8-c555-518185d61f87/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Emma's Library",
"link": "https://testflight.apple.com/join/69PadvA5"
},
{
"app": "Envelope Budget - Kualia",
"link": "https://apps.apple.com/us/app/envelope-budget-kualia/id6466455367?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/c9/42/c0/c942c0f5-bb5c-faf0-0306-0eaa72a08112/AppIcon-Kualia-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "EQMonitor",
"link": "https://apps.apple.com/jp/app/eqmonitor-%E5%9C%B0%E9%9C%87%E9%80%9F%E5%A0%B1/id6447546703?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/84/23/23/8423230f-6766-b3f1-3880-ad95174413a5/iOSAppIcon-0-0-1x_U007epad-0-85-220.png/512x512bb.jpg"
},
{
"app": "Expense Tracker: ExpenseKit",
"link": "https://apps.apple.com/us/app/expense-tracker-expensekit/id6756433597?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/a5/14/d8/a514d826-9475-8c8e-b4f5-6fe7b4fa660c/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Expenses: Spending Tracker",
"link": "https://apps.apple.com/us/app/expenses-spending-tracker/id1492055171?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/31/60/8f/31608f6a-8261-eac7-4bc2-2fda5af77a3d/AppIcon-0-0-1x_U007epad-0-0-0-1-0-0-85-220.png/512x512bb.jpg"
},
{
"app": "Fablum: EPUB & PDF Reader",
"link": "https://apps.apple.com/us/app/fablum-epub-pdf-reader/id6761669091?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/2e/d2/fe/2ed2fe2b-1ba6-4378-d8ab-f547fe123b95/AppIcon-0-1x_U007emarketing-0-11-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "FaceScreen - Desktop Camera",
"link": "https://apps.apple.com/us/app/facescreen-desktop-camera/id6702028512?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/79/71/89/797189ca-6bde-e2e0-9e24-99bd6593b928/AppIcon-0-0-85-220-0-5-0-2x-P3-0-0-0.png/512x512bb.png"
},
{
"app": "Family Flow: Family Organizer",
"link": "https://apps.apple.com/us/app/family-flow-family-organizer/id6760829141?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/99/a7/c7/99a7c78d-a8dc-3f60-d83f-c368efbcba71/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Fasting Works: IF & Meal Log",
"link": "https://apps.apple.com/us/app/fasting-works-if-meal-log/id6757606218?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/56/72/22/567222b9-40dd-cca2-1a58-01a46cfaa2d9/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Fax It: Send & Receive Fax",
"link": "https://apps.apple.com/us/app/fax-it-send-receive-fax/id1458261691?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/0a/06/0b/0a060b1f-ecdb-8d01-84f0-5928690fd028/Icon_4-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "FeedbackKit - User Insights",
"link": "https://apps.apple.com/us/app/feedbackkit-user-insights/id6757585445?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/5f/ac/3a/5fac3a58-6406-9fe6-fd75-89e9c4ab7cec/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "FeedFlow - RSS Feed Reader",
"link": "https://apps.apple.com/us/app/feedflow-rss-feed-reader/id6447210518?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/d3/68/28/d3682840-58a5-7c5d-31e0-70cc0fa62fad/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Fermento Fermentation Journal",
"link": "https://apps.apple.com/us/app/fermento-fermentation-journal/id6470132496?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/e4/e3/59/e4e3593b-0e71-922d-2313-b31c35cde061/AppIcon-0-0-1x_U007epad-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Finny: AI Money Tracker",
"link": "https://apps.apple.com/vn/app/finny-finance-tracker-app/id6736472735",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/04/e2/b6/04e2b6e5-d5b6-9e1b-dc3a-fbb65b2ddd84/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Fish Identifier: 96% Accurate",
"link": "https://apps.apple.com/us/app/fish-identifier-96-accurate/id6745837821?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/59/12/42/5912423b-8ce4-f251-3698-af61f3accff2/FishAppIcon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Fisherman SMS Filtering",
"link": "https://apps.apple.com/app/id6449192504",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/8d/e5/c9/8de5c9bd-5ad9-3371-378a-b30f074a671a/AppIcon-0-0-1x_U007epad-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "FishLogger: Fishing Journal",
"link": "https://apps.apple.com/us/app/fishlogger-fishing-journal/id6757573416?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/fe/d7/e8/fed7e84b-41b9-35f9-efbc-9ac785c708c2/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Fitnit: Health & Fitness",
"link": "https://apps.apple.com/us/app/fitnit-health-fitness/id6757887175?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/57/64/b6/5764b6e3-f3fb-9a70-925d-00e39f893263/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "FitPretty",
"link": "https://apps.apple.com/app/id6654895919",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/7f/a8/5a/7fa85a04-f258-17dd-f120-63c68061241b/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Fix My Treadmill",
"link": "https://apps.apple.com/us/app/fix-my-treadmill/id6758863304?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/20/33/96/203396ba-0542-cf69-356b-355b9948201b/AppIcon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Flash - appareil photo jetable",
"link": "https://apps.apple.com/fr/app/flash-appareil-photo-jetable/id6792328866?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/36/c4/0a/36c40ac9-f831-24c7-7ff1-cf8b02373b0c/expo-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "FlightCatcherPro – Plane Spot",
"link": "https://apps.apple.com/us/app/flightcatcherpro-plane-spot/id6749510087?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/79/4f/bc/794fbcf4-100d-d1ff-8458-5324666f4552/AppIcon-0-0-1x_U007emarketing-0-6-0-85-220.png/512x512bb.jpg"
},
{
"app": "Flip - Score tracker",
"link": "https://apps.apple.com/us/app/flip-score-tracker/id6760317673?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/a7/2d/95/a72d9540-e585-17c3-2e34-eed82c876bc0/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "FlipKit - Flipbook Maker",
"link": "https://apps.apple.com/us/app/flipkit-flipbook-maker/id1507694594?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/a1/e6/4f/a1e64f9f-de99-5fc1-f7e0-7ecbe7fafedb/AppIcon-0-1x_U007emarketing-0-11-0-sRGB-85-220-0.png/512x512bb.jpg"
},
{
"app": "Floating Head, show yourself",
"link": "https://apps.apple.com/us/app/floating-head-show-yourself/id1565946661?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/a8/1c/5f/a81c5f95-aeea-6b4a-08c2-f1e029af022c/AppIcon-0-0-85-220-0-5-0-2x.png/512x512bb.png"
},
{
"app": "FluidCast: Podcast Player",
"link": "https://apps.apple.com/us/app/fluidcast-podcast-player/id6754508580?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/c7/d2/d1/c7d2d17a-5764-0da5-d9e4-576b0390989a/AppIcon-0-0-1x_U007epad-0-1-P3-85-220.png/512x512bb.jpg"
},
{
"app": "FluxRate",
"link": "https://apps.apple.com/us/app/fluxrate/id6813696413?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/ef/1a/66/ef1a66f2-4abe-6e7a-6ad1-cd19acb8f227/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Focus & Streak: That One Thing",
"link": "https://apps.apple.com/us/app/focus-streak-that-one-thing/id6746286274?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/eb/f0/cc/ebf0ccfc-7693-db0a-0e4a-2fa5851cbca3/AppIcon_Cream-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Focus Rail: Pomodoro Timer",
"link": "https://apps.apple.com/us/app/focus-rail-pomodoro-timer/id6758016543?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/7d/f9/0e/7df90e3f-3a37-c70c-7bf9-cb3cc9c0f16d/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "FocusAir: Deep Focus Timer",
"link": "https://apps.apple.com/us/app/focusair-deep-focus-timer/id6756526902?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/09/e1/ad/09e1adab-b9fd-1707-57fc-387a7e19a7d0/AppIcon-0-1x_U007ephone-0-1-0-sRGB-85-220-0.png/512x512bb.jpg"
},
{
"app": "FocusCursor: Highlight &Markup",
"link": "https://apps.apple.com/us/app/focuscursor-highlight-markup/id6743495172?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/38/68/9e/38689e2a-598d-b3cd-58ac-ae551225d7b7/AppIcon-0-0-85-220-0-5-0-2x.png/512x512bb.png"
},
{
"app": "FocusScreen",
"link": "https://apps.apple.com/us/app/focusscreen/id6757796328?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/e9/5a/f2/e95af2c5-aaea-38d5-f33b-5c67a961d1e9/AppIcon-0-0-1x_U007epad-0-1-0-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Fogged: Travel Photo Map",
"link": "https://apps.apple.com/us/app/fogged-travel-photo-map/id6799502565?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/6c/76/2b/6c762b6b-2bb2-1c54-c708-b6211641e813/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Foliax",
"link": "https://apps.apple.com/us/app/foliax/id6760576005?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/df/ed/b0/dfedb05d-d90c-58bf-6048-c0b5ac57984c/AppIcon-0-0-1x_U007epad-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Font Wizard Pro: Font Manager",
"link": "https://apps.apple.com/us/app/font-wizard-pro-font-manager/id352966034?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/5e/23/bb/5e23bb9c-1a69-86c2-61bb-a3a1b5c6270b/AppIcon-0-1x_U007emarketing-0-8-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "Forerunner: Running & Wellness",
"link": "https://apps.apple.com/us/app/forerunner-running-wellness/id6745802851?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/06/bb/79/06bb796e-81a0-8812-5c4a-6148ee92e8bf/AppIcon-0-0-1x_U007epad-0-0-0-1-0-P3-85-220.png/512x512bb.jpg"
},
{
"app": "Foundation Lab",
"link": "https://testflight.apple.com/join/JWR9FpP3"
},
{
"app": "FPLai: FPL AI Assistant",
"link": "https://apps.apple.com/app/id6757015527",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/a3/a3/19/a3a31917-1ef0-2d81-3150-65391212a1bd/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "FreeDive Ultra",
"link": "https://apps.apple.com/us/app/freedive-ultra/id6760556868?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/1d/90/b8/1d90b818-be35-fe3b-6dd1-81bd6ac99858/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Freshwise",
"link": "https://apps.apple.com/us/app/freshwise/id6786175708?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/6a/35/81/6a3581eb-7a78-3a2e-5626-fe0994612676/AppIcon-0-0-1x_U007epad-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Fridgea: Fridge & Grocery List",
"link": "https://apps.apple.com/us/app/fridgea-fridge-grocery-list/id6756065960?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/a8/15/84/a81584b0-2187-9259-5b49-23cf2cb45650/AppIcon-0-0-1x_U007epad-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Fuel log tracker: Click2Track",
"link": "https://apps.apple.com/us/app/fuel-log-tracker-click2track/id6757174917?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/fe/27/ca/fe27cafb-6b57-b44d-525e-5b5d3c07b241/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Fuel: AI Nutrition",
"link": "https://apps.apple.com/app/id6748624107",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/52/0e/ed/520eed9c-d3f0-5d0e-3e68-5b6e698e5efc/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "FunVoice Reverse Audio Singing",
"link": "https://apps.apple.com/us/app/funvoice-reverse-audio-singing/id6753986607?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/c7/88/0f/c7880f37-6273-244c-8ef5-d4095471df2b/funvoice_2025-10-14-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Game Box - Games for Watch",
"link": "https://apps.apple.com/us/app/game-box-games-for-watch/id6741334696?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/fb/f2/5e/fbf25e58-55d1-77de-a0ad-a1e8488ab7d7/AppIcon-0-0-1x_U007epad-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Gazolina - Prix Essence Gasoil",
"link": "https://apps.apple.com/fr/app/gazolina-prix-essence-gasoil/id6757231174?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/3a/5d/48/3a5d4831-2f9a-7d7e-adfa-2e5eb7057c2f/AppIcon-0-0-1x_U007epad-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "GCC Talent",
"link": "https://apps.apple.com/us/app/gcc-talent/id6811953234?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/a1/1d/21/a11d216e-7b4e-c051-d9e9-019663439105/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Gecko Med",
"link": "https://apps.apple.com/us/app/gecko-med/id6737719743?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/63/4c/14/634c143e-7e6b-4afb-9748-0816af5d4544/apple-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Geeps: AI Chat BYOK",
"link": "https://apps.apple.com/us/app/geeps-ai-chat-byok/id6476961115?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/37/b6/e1/37b6e1c0-87da-9cb0-1d76-6d2a88b86b0a/Geeps-0-0-1x_U007epad-0-0-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Geni Kids",
"link": "https://apps.apple.com/us/app/geni-kids/id6761134405?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/72/a9/92/72a99255-2c75-ee9c-6bdf-215957eff996/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Glint: Morse Code Translator",
"link": "https://apps.apple.com/us/app/glint-morse-code-translator/id6758366218?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/69/e1/b3/69e1b3be-109a-084a-62a3-8df22c8076c2/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "GO Club: Step Counter",
"link": "https://apps.apple.com/us/app/go-club-step-counter/id6739782103?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/7a/23/fc/7a23fc3e-69d9-26b8-52e0-e2ce1587dab8/AppIcon-0-0-1x_U007ephone-0-0-0-1-0-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Goshuin Atlas",
"link": "https://apps.apple.com/app/goshuin-atlas-japan/id6746737093",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/11/49/d6/1149d644-0ac6-6277-9847-c9ac124510a8/AppIcon-0-1x_U007ephone-0-1-85-220-0.jpeg/512x512bb.jpg"
},
{
"app": "Gradient Match Game: Descent",
"link": "https://apps.apple.com/us/app/gradient-match-game-descent/id1479784361?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/e8/a4/81/e8a4813c-4d02-65b3-442a-3703431813d7/AppIcon-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Greenie: Plant Identifier AI",
"link": "https://apps.apple.com/us/app/greenie-plant-identifier-ai/id6746816013?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/91/ba/28/91ba28b2-dba6-24bc-c191-bb21508e55f0/GreenieIcon-0-0-1x_U007ephone-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Grove ◦ Fiber Tracker",
"link": "https://apps.apple.com/us/app/grove-fiber-tracker/id6760088502?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/99/ac/ff/99acff42-6f6f-4bbf-70e3-19000f97430d/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "GrowPal: Health & Fitness",
"link": "https://apps.apple.com/us/app/growpal-health-fitness/id1560604814?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/0c/be/8b/0cbe8bfa-a36d-7ebb-6580-959481f43294/Grow-0-0-1x_U007ephone-0-0-0-1-0-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Guitar Wiz - Tuner & Chords",
"link": "https://apps.apple.com/us/app/guitar-wiz-tuner-chords/id6740015002?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/fc/d0/65/fcd06547-95ac-9d05-23eb-c67ba81e52de/AppIcon-0-0-1x_U007epad-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Habit Tracker - HabitMax",
"link": "https://apps.apple.com/us/app/habit-tracker-habitmax/id6752316720?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/1c/5a/13/1c5a13a5-02e5-30c8-a359-e49972d11582/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Hands Time - Minimalist Widget",
"link": "https://apps.apple.com/us/app/hands-time-minimalist-widget/id6462440720?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/ea/cd/e9/eacde9d6-4e33-e631-55fc-05aa961523a5/AppIcon-0-0-1x_U007ephone-0-0-0-1-0-0-85-220.png/512x512bb.jpg"
},
{
"app": "Harry's: Fitness Challenges",
"link": "https://apps.apple.com/us/app/harrys-fitness-challenges/id6502330879?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/e6/e8/45/e6e845ca-71af-b03e-83fc-e6aa7c246c0c/Harry_U0027s-Challenges-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Harvest - One-Tap Summary",
"link": "https://apps.apple.com/us/app/harvest-one-tap-summary/id6738302076?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/0c/16/4e/0c164e6e-0b93-c0b3-314e-4b6b9742f282/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Haven: Setup Codes",
"link": "https://apps.apple.com/us/app/haven-setup-codes/id6760440926?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/72/e6/1f/72e61fe0-7f0e-bb59-732d-c89ed669ed59/Haven-0-0-1x_U007epad-0-0-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Hedwig - Block Text Messages",
"link": "https://apps.apple.com/us/app/hedwig-block-text-messages/id6736428346?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/56/a6/e5/56a6e530-ded3-69de-d871-0d0252bd0610/AppIcon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Home Cameras",
"link": "https://apps.apple.com/us/app/home-cameras/id6762038582?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/38/05/66/38056606-4649-101f-cdc3-a4ed626181dc/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "hora Calendar",
"link": "https://apps.apple.com/us/app/hora-calendar/id6761409895?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/e5/c2/e3/e5c2e3ee-f9f0-c491-4f22-5f6282853823/Hora-0-0-85-220-0-6-0-2x-sRGB.png/512x512bb.png"
},
{
"app": "HoverCalc",
"link": "https://apps.apple.com/us/app/hovercalc/id6759318126?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/4d/d5/0f/4dd50f26-4b76-e382-9774-5894156f11df/AppIcon-0-0-85-220-0-5-0-2x.png/512x512bb.png"
},
{
"app": "HowHigh · Barometer & Altimeter",
"link": "https://apps.apple.com/us/app/barometer-altimeter-howhigh/id921339656?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/79/66/ab/7966ab76-f237-ff79-e866-a51e96a530f3/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "HRM Battery",
"link": "https://apps.apple.com/app/id6758920011",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/7b/43/73/7b4373fc-26e2-030b-7162-2f0ab0a7681d/AppIcon-0-0-1x_U007ewatch-0-1-P3-85-220.png/512x512bb.jpg"
},
{
"app": "Hungry Wabbit",
"link": "https://apps.apple.com/us/app/hungry-wabbit/id992325663?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/5e/9a/32/5e9a3224-865e-ec24-1b56-87a2823a70ba/HungryWabbit-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Hush for visionOS",
"link": "https://apps.apple.com/us/app/hush-for-visionos/id6770631510?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/f2/00/bf/f200bf3b-4f19-5cf4-d08b-f65e39137346/AppIcon.lsr/512x512bb.jpg"
},
{
"app": "I Need That Widget: Calendar",
"link": "https://apps.apple.com/us/app/i-need-that-widget-calendar/id6753172187?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/c1/21/7d/c1217dec-82c4-f9e7-7c54-9c04da639595/INTW-0-0-1x_U007ephone-0-0-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "iCardSort",
"link": "https://apps.apple.com/us/app/icardsort/id384552728?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/91/5d/55/915d552c-3b3d-a636-622c-525e4d4ca4e2/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Iconize Folder",
"link": "https://apps.apple.com/us/app/iconize-folder/id6478772538?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/57/e3/94/57e39489-2c7c-c1cc-56bc-13f85096d9cc/AppIcon-0-0-85-220-0-5-0-2x.png/512x512bb.png"
},
{
"app": "Idle Coffee Empire Clicker",
"link": "https://apps.apple.com/us/app/idle-coffee-empire-clicker/id6761031587?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/4f/c9/94/4fc994dc-39e7-a593-8dd9-b5feff9e579a/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "iGIF",
"link": "https://apps.apple.com/us/app/igif/id6759003636?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/79/62/f3/7962f38f-f28f-90dd-88a0-34786e747692/AppIcon-0-0-1x_U007epad-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Imposter Game: Fakeout Party",
"link": "https://apps.apple.com/us/app/imposter-game-fakeout-party/id6761190861?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f1/8c/08/f18c08d9-6a65-3444-a15e-bb4d0540e180/AppIcon-Default-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Impulses: Stop Impulse Buying",
"link": "https://apps.apple.com/us/app/impulses-stop-impulse-buying/id6754372500?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/8a/05/57/8a055708-4850-8120-8070-91424269b2f7/AppIcon-0-1x_U007ephone-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Inferno by Eyed™ - Local LLM",
"link": "https://apps.apple.com/us/app/inferno-by-eyed-local-llm/id6760921200?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/d3/7d/1c/d37d1c47-8457-7bff-a52f-7c7c90feaa1b/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Inkpress",
"link": "https://apps.apple.com/us/app/inkpress/id6787759999?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/d6/32/4e/d6324e8a-b7d9-3ef9-93e9-6ad948ed6081/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Inkput",
"link": "https://apps.apple.com/app/id6758570182",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/83/e2/68/83e26872-d3d5-ab92-7164-673371673443/AppIcon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Inline - The inline AI",
"link": "https://apps.apple.com/us/app/inline-the-inline-ai/id6748410177?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/a2/73/bf/a273bfba-ccb0-61ed-4c7e-0c8c49c3347a/Inline-0-0-85-220-0-0-6-0-2x.png/512x512bb.png"
},
{
"app": "Inquizitive: Smart AI Quiz",
"link": "https://apps.apple.com/us/app/inquizitive-smart-ai-quiz/id6817424653?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/a5/5b/30/a55b305a-576b-e07a-386a-a111f736ae94/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Intake: Calorie Counter & Log",
"link": "https://apps.apple.com/us/app/intake-calorie-counter-log/id6757768955?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/9c/df/d1/9cdfd107-e11a-acc5-4859-45921df8cb0e/intake-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "iWave",
"link": "https://apps.apple.com/us/app/iwave/id6759223120?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/bf/e8/7b/bfe87b02-5721-33b6-cfe0-a62abc95d879/AppIcon-0-0-1x_U007ewatch-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "iWeatherShare",
"link": "https://apps.apple.com/us/app/iweathershare/id6757788252?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/67/80/bd/6780bd31-e96f-be72-2d30-6597c3d766df/AppIcon-0-0-1x_U007epad-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Jinshu Reader",
"link": "https://apps.apple.com/us/app/jinshu-reader/id6759705710?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/6c/ce/46/6cce46eb-8613-c97d-8913-6888c3d4f1d2/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Jiyu: Days into Seasons",
"link": "https://apps.apple.com/us/app/jiyu-days-into-seasons/id6789923573?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/6e/a3/fd/6ea3fd01-a8ea-fd12-8184-c93ed603e64a/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Joinly",
"link": "https://apps.apple.com/us/app/joinly/id6759517723?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/29/50/0a/29500a80-7e71-0e44-3039-d117a37a1587/AppIcon-0-0-85-220-0-5-0-2x.png/512x512bb.png"
},
{
"app": "journeybot: trip packing list",
"link": "https://apps.apple.com/us/app/journeybot-trip-packing-list/id6756543673?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/8a/c2/bc/8ac2bcd8-9ddc-da52-cf6a-14990c6e107f/AppIcon-0-1x_U007epad-0-1-0-sRGB-85-220-0.png/512x512bb.jpg"
},
{
"app": "Jurni: Trip Planner",
"link": "https://apps.apple.com/us/app/jurni-trip-planner/id6759559463?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/67/19/a1/6719a179-73ef-1266-5682-bf1c944f98b9/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Karacho: Digital Speedometer",
"link": "https://apps.apple.com/us/app/karacho-digital-speedometer/id6819278673?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/bb/b8/ee/bbb8ee0b-b055-b104-0d09-a0cc594d7977/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "kardfolio: Card Benefits",
"link": "https://apps.apple.com/us/app/kardfolio-card-benefits/id6759400652?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/89/51/81/8951816e-6e84-d3e2-dcce-e1c12a0fdd08/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Kavale - Sécurité Équestre",
"link": "https://apps.apple.com/fr/app/kavale-s%C3%A9curit%C3%A9-%C3%A9questre/id1458105068?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/fd/c6/76/fdc67602-ffd1-66a4-16c5-2ce0a52f278c/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "KeepCount: Tally Counter",
"link": "https://apps.apple.com/us/app/keepcount-tally-counter/id6758891370",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/ee/f4/e6/eef4e6d4-c2af-3856-6cdf-80af085b1cb9/AppIcon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "KeyClicker",
"link": "https://apps.apple.com/us/app/keyclicker/id6740425504?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/b3/48/0f/b3480f03-46d0-aa8e-d172-0c0e054890e3/AppIcon-0-0-85-220-0-5-0-2x-0-0-0.png/512x512bb.png"
},
{
"app": "KeyScreen - Display Keystrokes",
"link": "https://apps.apple.com/us/app/keyscreen-display-keystrokes/id6753302381?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/c8/38/fd/c838fddf-a2db-5007-12be-8cb9906926e1/AppIcon-0-0-85-220-0-5-0-2x-P3-0-0-0.png/512x512bb.png"
},
{
"app": "Kilowatt – Electric Car Timer",
"link": "https://apps.apple.com/us/app/kilowatt-electric-car-timer/id1502312657?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/cd/ac/b6/cdacb60b-c1c2-79f1-a7cf-7b3e6674b963/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Klick #1 AI Camera Assistant",
"link": "https://apps.apple.com/us/app/klick-1-ai-camera-assistant/id6749798728?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/47/23/d9/4723d958-44ab-af95-67a7-5431d6ff981a/AppIcon-0-0-1x_U007ephone-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Klick: keyboard sounds",
"link": "https://apps.apple.com/us/app/klick-keyboard-sounds/id6758880736?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/d1/c0/46/d1c046e6-f966-f063-e294-0a1969396b60/AppIcon-0-0-85-220-0-5-0-2x.png/512x512bb.png"
},
{
"app": "Knight's Graph",
"link": "https://apps.apple.com/us/app/knights-graph/id6737812039?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/78/83/4a/78834a4b-e815-1bfc-9ab0-bc0bdbea4e20/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "kora: Music Reviews & Ratings",
"link": "https://apps.apple.com/app/id6502549140",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/48/70/77/487077dc-caef-0687-6ccf-48daffa9cf5b/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Kosaku - Beautify the content",
"link": "https://apps.apple.com/us/app/kosaku-beautify-the-content/id1611559010?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/f4/63/32/f46332d0-c51c-6a40-2022-a2e92f745e2e/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Layered: AI Outfit Maker",
"link": "https://apps.apple.com/us/app/layered-ai-outfit-maker/id6746713932?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/91/a9/6e/91a96e06-a3de-fcc3-0719-109159431685/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Leafty - Travel Planner",
"link": "https://apps.apple.com/us/app/leafty-travel-planner/id6502776303?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/93/8c/82/938c8234-a602-a54a-83ea-6f3d85017add/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Lexie: Language Tamagotchi",
"link": "https://apps.apple.com/us/app/lexie-language-tamagotchi/id6748887471?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/6d/fa/a9/6dfaa937-68e3-31e5-a090-9341aa41d70f/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Lifterly",
"link": "https://apps.apple.com/us/app/lifterly/id6753913630?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/78/df/1e/78df1e26-9a13-20e6-3b37-df45306a684e/app-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Lights Out - Mafia Table Game",
"link": "https://apps.apple.com/us/app/lights-out-mafia-table-game/id6785313453?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/f4/77/3a/f4773ae1-5766-3c78-829c-df59b60f2c7f/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "LilyFM",
"link": "https://apps.apple.com/app/id6745472149",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/a6/aa/4e/a6aa4ed4-1750-12c4-c206-8b1031c9c95c/AppIcon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Litigate",
"link": "https://apps.apple.com/us/app/litigate/id6787857503?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/0f/47/13/0f471335-1649-ae5d-6da1-8406c48f5a74/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "LivaLog",
"link": "https://apps.apple.com/us/app/livalog/id6759440574",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/bf/cf/5d/bfcf5d5a-2cd1-b41a-f65d-a751085736c5/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Long Distance: Couple & BFF",
"link": "https://apps.apple.com/us/app/long-distance-couple-bff/id6757593531?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/d0/e1/3e/d0e13e2d-b2e9-70dd-538f-c8aa66ae7f54/AppIcon-0-1x_U007ephone-0-1-sRGB-85-220-0.png/512x512bb.jpg"
},
{
"app": "LookForward: Movies & Games",
"link": "https://apps.apple.com/us/app/lookforward-movies-games/id1492748952?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/dc/1a/c6/dc1ac682-8609-9d45-d50e-35b11aa45aad/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Loverzz: Couples App & Widgets",
"link": "https://apps.apple.com/us/app/loverzz-couples-app-widgets/id6744887601?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/34/14/b8/3414b8a0-0647-c6b2-550a-e2ca1eb28e77/AppIcon-0-1x_U007ephone-0-1-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "Lumical: Scan to Calendar",
"link": "https://apps.apple.com/us/app/lumical-scan-to-calendar/id6753274309",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/05/6a/92/056a928c-8748-1457-e513-43e96ce8a97e/Lumical-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Mafy - Dog Care Tracker",
"link": "https://apps.apple.com/us/app/mafy-dog-care-tracker/id6736774671?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/00/95/ed/0095ed43-61ca-da3c-f01d-9d7fb43137dc/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Magic Sticker Printer",
"link": "https://apps.apple.com/app/id6759186064",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/70/6a/e1/706ae16e-f081-eef4-6970-9d6896c4dbf4/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Mahjong AI - Mahjong Assistant",
"link": "https://apps.apple.com/app/id6758575182",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/12/8e/fb/128efb33-a5ee-d321-2720-924431d772dd/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Majlis",
"link": "https://www.joinmajlis.app"
},
{
"app": "Margin: AI Article Reader",
"link": "https://apps.apple.com/us/app/margin-ai-article-reader/id6748705626?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/1a/dd/73/1add730b-0e85-d8fd-09eb-722cb492877d/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Masseter: Jaw Clench Tracker",
"link": "https://apps.apple.com/us/app/masseter-jaw-clench-tracker/id6758920098?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/9d/34/53/9d345362-2b78-96c5-5294-33d2e084bb90/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Master Indicator",
"link": "https://apps.apple.com/us/app/master-indicator/id6758697856?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/25/2c/78/252c787f-d9e8-738e-51fb-61c64ee95c32/AppIcon-1x_U007emarketing-0-10-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "Matchday - Soccer Planner",
"link": "https://apps.apple.com/us/app/matchday-soccer-planner/id6776015801?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/d5/d8/af/d5d8af6c-325a-56f9-6c88-36b787e6bdc5/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "MD Clock - Flip Clock",
"link": "https://apps.apple.com/us/app/md-clock-flip-clock/id1536358464?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/4c/07/b7/4c07b7c5-0612-7f65-0b65-91594843e0fb/AppIcon-0-0-1x_U007epad-0-1-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "MD Vinyl for Music App",
"link": "https://apps.apple.com/us/app/md-vinyl-for-music-app/id1606306441?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/38/c4/f3/38c4f3b5-4bd2-86ad-e100-5e220c16e08c/AppIcon-0-0-1x_U007epad-0-0-0-1-0-0-85-220.png/512x512bb.jpg"
},
{
"app": "MD Widgets - Countdown widget",
"link": "https://apps.apple.com/us/app/md-widgets-countdown-widget/id1551277937?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/2f/ca/21/2fca210d-91ad-8736-575b-d729d3002f17/AppIcon-0-0-1x_U007epad-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "MealMate: Calorie Counter",
"link": "https://apps.apple.com/us/app/mealmate-calorie-counter/id6740268220?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/0f/f1/68/0ff1684f-f3fd-35c5-b6b3-5ef10b84d907/Icon-0-1x_U007ephone-0-0-0-1-0-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "Menuist: Right-Click & MenuBar",
"link": "https://apps.apple.com/us/app/menuist-right-click-menubar/id6737160756?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/7e/f5/ee/7ef5eea0-ae74-a4a1-475a-4df5b8bab935/AppIcon-0-0-85-220-0-5-0-2x-0-0-0.png/512x512bb.png"
},
{
"app": "Meshing",
"link": "https://apps.apple.com/app/id6567933550",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/77/7b/8f/777b8f6c-b8e6-0fef-50fb-9e6a59166d06/Meshing_Icon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Metrociego",
"link": "https://apps.apple.com/us/app/metrociego/id1231945684?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/50/8e/ed/508eedf3-5d31-7f42-c7dc-4c5551ce5d4a/AppIcon-0-0-1x_U007epad-0-0-0-1-0-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Midnight: Couples App & Games",
"link": "https://apps.apple.com/us/app/midnight-couples-app-games/id6758623374?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/e5/81/e1/e581e1f6-fed0-f6b0-0e70-cb2ad5a0c764/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "MileIO",
"link": "https://apps.apple.com/app/id6758225631",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/09/0a/21/090a21e8-5b19-ec94-c5d0-6c2d0f5175bd/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "MiniHue: Color Mini Sudoku 6x6",
"link": "https://apps.apple.com/us/app/minihue-color-mini-sudoku-6x6/id6761858520?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/28/3c/03/283c0325-ab33-bf68-b7f8-d51ce7e9ec48/AppIcon_MiniHue-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Minimal Diary - Life timeline",
"link": "https://apps.apple.com/us/app/minimal-diary-life-timeline/id1568936702?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/3d/26/b2/3d26b23e-3e10-76d2-ce22-6ac9d2094499/md-glass-icon-0-0-1x_U007epad-0-0-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Minimal Minesweeper",
"link": "https://apps.apple.com/us/app/minimal-minesweeper/id1029488767?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/dc/a1/50/dca15026-6e92-d75f-11c1-afa3d76b6e4b/AppIcon-1x_U007epad-0-1-P3-85-220-0.png/512x512bb.jpg"
},
{
"app": "minimo (video): Compressor",
"link": "https://apps.apple.com/us/app/minimo-video-compressor/id6787456951?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/cb/d0/91/cbd09181-c957-2dce-c8a4-2e258f73dc6d/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "minute cryptic • wordplay",
"link": "https://apps.apple.com/app/id6748607228",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/95/51/28/955128eb-6041-160a-464c-41795f7d0f12/wordplay-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Mixtape",
"link": "https://apps.apple.com/us/app/mixtape-personal-music-gift/id6756442910",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/71/af/7f/71af7f55-1c60-1638-6161-8491e23edd0e/AppIcon-0-0-1x_U007ephone-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Moda: Driving Permit Hours Log",
"link": "https://apps.apple.com/us/app/moda-driving-permit-hours-log/id6760154154?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/86/48/0d/86480d5e-7118-7a35-6a74-7cba92a9099d/AppIcon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Model Usage",
"link": "https://apps.apple.com/us/app/model-usage/id6761831599?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/6e/78/19/6e7819e8-76dc-2674-3df7-fdc7d8558633/AppIcon-0-0-1x_U007epad-0-1-P3-85-220.png/512x512bb.jpg"
},
{
"app": "mojo - GLP-1 Weight Tracker",
"link": "https://apps.apple.com/app/id6751704399",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/ee/00/84/ee008451-9632-a851-0da8-6a69caab37a5/AppIcon-0-0-1x_U007ephone-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Morning Pages",
"link": "https://apps.apple.com/us/app/morning-pages/id6738604034",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/c5/ec/37/c5ec371e-e920-33ec-1541-74d05fc067c2/app-icon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "MorseKit: Morse Code Trainer",
"link": "https://apps.apple.com/us/app/morsekit-morse-code-trainer/id6761839923?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/5f/b0/81/5fb081cd-fcd6-998e-cbb2-3c84816d6e6b/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "MultiPods Audio",
"link": "https://apps.apple.com/us/app/multipods-audio/id6766111375?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/85/12/63/8512639f-60c5-e662-6fb4-0f1214d6a3a1/AppIcon-0-0-85-220-0-5-0-2x-P3.png/512x512bb.png"
},
{
"app": "murmur: ai interview roleplay",
"link": "https://apps.apple.com/us/app/murmur-ai-interview-roleplay/id6755495030?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/a6/6f/1d/a66f1dd5-40fb-a7e8-f089-53f1790e67d7/AppIcon-0-0-1x_U007epad-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Music Plus - Player Extensions",
"link": "https://apps.apple.com/us/app/music-plus-player-extensions/id1626398921?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/19/ce/4c/19ce4c8e-a1b5-2c3e-e44f-66011b347558/AppIcon-0-1x_U007epad-0-1-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "Musicer: Simple music player",
"link": "https://apps.apple.com/us/app/musicer-simple-music-player/id6745227444?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/a0/aa/b2/a0aab285-9678-bfb9-a1dc-eff837ccef12/AppIcon-0-0-85-220-0-5-0-2x-0-0-0.png/512x512bb.png"
},
{
"app": "Musicr",
"link": "https://apps.apple.com/us/app/musicr/id6767491157?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/a9/4a/77/a94a7753-b1a7-4c9d-3f56-26769b923ccb/AppIcon-0-0-85-220-0-5-0-2x.png/512x512bb.png"
},
{
"app": "MyFooderPlan: AI Meal Planner",
"link": "https://apps.apple.com/us/app/myfooderplan-ai-meal-planner/id1342100580?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/86/e6/e4/86e6e45e-f2dd-abe2-e7df-87fa8450c769/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Mynu",
"link": "https://apps.apple.com/us/app/mynu/id6767801514?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/c0/0d/f5/c00df540-1a01-07b5-3676-cad11d54ea66/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Natus: Self-Discovery Journal",
"link": "https://apps.apple.com/us/app/natus-self-discovery-journal/id6787200064?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/05/12/da/0512da31-1283-74a1-5c00-3cfba5034c21/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Neko Health",
"link": "https://apps.apple.com/us/app/neko-health/id6751233363?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/4d/d7/e9/4dd7e931-3834-d3b3-ffe7-1716ffbdf381/app-0-0-1x_U007ephone-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Nemlys: Couple & Relationship",
"link": "https://apps.apple.com/us/app/nemlys-couple-relationship/id1662262055?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/76/c8/96/76c8965a-cf8c-fc3b-2a26-0be58f991537/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Neurona: Binaural Beats",
"link": "https://apps.apple.com/us/app/neurona-binaural-beats/id6757725304?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/1c/77/66/1c776632-25a0-9af7-38ac-2d6c31d42826/icon_composed-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Newgrain: Film Photo Community",
"link": "https://apps.apple.com/us/app/newgrain-film-photo-community/id6444198677?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/de/fa/c2/defac22e-5f4d-1204-4fb7-7bdeb1dfb970/AppIcon-1x_U007ephone-0-1-0-85-220-0.jpeg/512x512bb.jpg"
},
{
"app": "NextCatch",
"link": "https://apps.apple.com/us/app/nextcatch/id6758589575?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/5c/6c/21/5c6c21ac-7b19-3556-a639-55a63601163a/AppIcon-0-0-1x_U007emarketing-0-6-0-85-220.png/512x512bb.jpg"
},
{
"app": "Ninou: baby tracker",
"link": "https://apps.apple.com/us/app/ninou-baby-tracker/id6781282254?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/46/3a/34/463a345a-7f7b-7a93-979a-aa72c3e3e1be/Ninou-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Nitra",
"link": "https://apps.apple.com/us/app/nitra/id6749408802?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/18/b2/20/18b2200d-64cb-13a9-d567-60fcb8304625/nitra_app_icon-0-0-1x_U007ephone-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Notch: Target Scoring Tracker",
"link": "https://apps.apple.com/us/app/notch-target-scoring-tracker/id6747980153?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/d7/83/48/d783480d-74fa-4033-44ce-f4154c3ab7de/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Noteit - AI Journal",
"link": "https://apps.apple.com/app/id1659178001",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/59/57/b4/5957b419-040d-763a-3c16-cf799bdaaa76/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "NullVault: Secret Photo Vault",
"link": "https://apps.apple.com/us/app/nullvault-secret-photo-vault/id6793223535?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/3b/ff/0b/3bff0b2c-52c3-e5e3-5710-e8cff0da6800/NullVault-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Numby Pro",
"link": "https://apps.apple.com/us/app/numby-pro/id6755249901?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/82/a7/2e/82a72e8a-3dbd-7536-f153-282682b3bdc2/AppIcon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "OffScreen: Screen Time Control",
"link": "https://apps.apple.com/us/app/offscreen-screen-time-control/id1474340105?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/29/00/51/2900519e-a076-de61-c91c-7e0313faf41e/AppIcon-0-0-1x_U007epad-0-0-0-1-0-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "oh? daily home screen widget",
"link": "https://apps.apple.com/us/app/oh-daily-home-screen-widget/id6789181341?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/01/c5/6b/01c56bf4-5653-758b-8eb0-e4253687f800/AppIcon-0-0-1x_U007epad-0-1-P3-85-220.png/512x512bb.jpg"
},
{
"app": "Ordsnok - Snoop for words",
"link": "https://apps.apple.com/us/app/ordsnok-snoop-for-words/id6797599523?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/94/bb/c5/94bbc530-63d7-d7a6-ee19-32109782919c/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Otis — A friend to your stocks",
"link": "https://apps.apple.com/us/app/otis-a-friend-to-your-stocks/id6766045265?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/24/e3/57/24e3572d-cb12-9163-09b1-099d3e997dfd/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Otuli: Shared Baby Tracker",
"link": "https://apps.apple.com/us/app/otuli-shared-baby-tracker/id6801689523?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/d9/5a/24/d95a24c8-829e-1c0e-1112-9bd5954099af/Otuli-0-0-1x_U007ephone-0-0-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "P2P View",
"link": "https://apps.apple.com/us/app/p2p-view/id1643919225?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/f3/cc/c0/f3ccc01d-a2ab-5a17-13d7-5095b55763cc/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Padlok - Keypad manager",
"link": "https://apps.apple.com/us/app/padlok-keypad-manager/id1546719801?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/98/6e/04/986e049d-e097-e10b-a17d-480bf93eed1b/AppIcon-0-1x_U007ephone-0-0-0-1-0-0-P3-85-220-0.png/512x512bb.jpg"
},
{
"app": "Pal Cal - AI Calorie Counting",
"link": "https://apps.apple.com/us/app/pal-cal-ai-calorie-counting/id6758598728?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/02/b1/bf/02b1bf63-175e-cf12-6954-9bd80ecbe56c/ProteinIcon-0-0-1x_U007epad-0-1-P3-85-220.png/512x512bb.jpg"
},
{
"app": "Pal Chat - AI Chat Client",
"link": "https://apps.apple.com/us/app/pal-chat-ai-chat-client/id6447545085?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/0f/43/12/0f431290-6bfc-8f62-5c1c-b9955f947bbc/GlassIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Palabros - Diccionario",
"link": "https://apps.apple.com/us/app/palabros-diccionario/id6758098070?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/a9/25/1a/a9251ac1-2d3d-c390-ffa7-29c0dc645d45/AppIcon-0-1x_U007ephone-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "PaperlessHero – AI Mail Task",
"link": "https://apps.apple.com/us/app/paperlesshero-ai-mail-task/id6758307043?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/48/94/45/48944583-bcb5-a4e8-a1d1-9442a0b79676/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Parrot - AI Personas",
"link": "https://apps.apple.com/us/app/parrot-ai-personas/id6538716280?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/6f/f4/7e/6ff47e89-1f65-92c3-9b9a-2314845acd52/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Parsely",
"link": "https://testflight.apple.com/join/HjUZtzVm"
},
{
"app": "Peakme: Hike, Track, Explore",
"link": "https://apps.apple.com/app/id6733216670",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/22/a4/41/22a441e6-20c5-a464-2b0f-3db405633856/AppIcon-0-1x_U007ephone-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Pebbles: Tax & Visa Tracker",
"link": "https://apps.apple.com/us/app/pebbles-tax-visa-tracker/id6472096747?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/94/7b/d0/947bd072-9006-bf4f-8aa5-9af8f7ec9b37/AppIcon-0-1x_U007epad-0-1-0-sRGB-85-220-0.png/512x512bb.jpg"
},
{
"app": "Pegma - Peg Solitaire",
"link": "https://apps.apple.com/us/app/pegma-peg-solitaire/id6754343848?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/37/0f/a2/370fa230-9fc0-5651-ebc2-ed60736d7a23/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Peptide Protocol: Dose Tracker",
"link": "https://apps.apple.com/us/app/peptide-protocol-dose-tracker/id6762055081?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/ff/c5/73/ffc57381-77f1-ded8-db61-f9272cf32cc5/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Perchoir: Coding Agents",
"link": "https://apps.apple.com/us/app/perchoir-coding-agents/id6800054093?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/79/37/23/793723a9-c5d2-c6d4-1b08-151d0e5c468e/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Petal - AI Voice Recorder",
"link": "https://apps.apple.com/us/app/petal-ai-voice-recorder/id6759932376?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/ec/77/3a/ec773a74-274b-bcd4-4587-7f28cbb0cee2/AppIcon-0-1x_U007ephone-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Pets Check-in - PeekaPaw",
"link": "https://apps.apple.com/us/app/pets-check-in-peekapaw/id6759892136?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/c9/57/6e/c9576e6d-948b-2e9f-fded-18261f597999/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Photo Cleanup: Swiper",
"link": "https://apps.apple.com/us/app/photo-cleanup-swiper/id6752829792?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/9f/00/7a/9f007a81-ffab-5968-ae29-35add00577b1/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Photo Journal & Mood: Loggoo",
"link": "https://apps.apple.com/us/app/photo-journal-mood-loggoo/id6799161077?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/fb/cb/04/fbcb0465-34d5-c49f-a341-6df00efd1fc2/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Photo Memo+: Memory Game",
"link": "https://apps.apple.com/us/app/photo-memo-memory-game/id1607756335?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/d5/b0/dd/d5b0dd15-8ea1-d153-da8c-ac8d3533fff9/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Photo2Calendar Scanner Planner",
"link": "https://apps.apple.com/us/app/photo2calendar-scanner-planner/id6736373394?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/6c/e0/2f/6ce02fb3-6c3a-902e-3e15-ca189da30cfe/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "PhotoCal: AI Calorie Tracker",
"link": "https://apps.apple.com/us/app/photocal-ai-calorie-tracker/id6747694364?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/b1/6f/c8/b16fc8e3-34ea-a466-39c4-810c992ce82f/photocaloriecam_icon-2025-09-15-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Pickle Rite",
"link": "https://apps.apple.com/us/app/pickle-rite/id6754095362",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/cb/8f/0b/cb8f0b7e-ba14-a1cf-49ef-09c64d9ce87e/AppIcon-0-0-1x_U007ephone-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Pico-8 Emulator: Picpic",
"link": "https://apps.apple.com/us/app/pico-8-emulator-picpic/id6759208792?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/e7/cb/5c/e7cb5c08-a96f-f0f3-3642-416e1bba991e/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "PicSeek: AI Photo Manager",
"link": "https://apps.apple.com/us/app/picseek-ai-photo-manager/id6755383937?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/f3/90/bc/f390bc5a-185e-095a-7ea2-c4a105bd89f7/AppIcon-1x_U007emarketing-0-8-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "PicVore: Photo Cleaner",
"link": "https://apps.apple.com/us/app/picvore-photo-cleaner/id6759577942?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/58/a3/4b/58a34be8-0aed-098e-505b-a497386e5db8/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Pievra: Jules AI Coding Agent",
"link": "https://apps.apple.com/us/app/pievra-jules-ai-coding-agent/id6758630855?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/32/40/cf/3240cf5f-61e5-d99e-b395-a24fae6bb6bd/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Pill identifier: MedScan",
"link": "https://apps.apple.com/us/app/pill-identifier-medscan/id6747271665?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/cd/ad/ed/cdaded5e-e415-ec10-8fd1-b09a032506d0/AppIcon-0-0-1x_U007ephone-0-0-0-1-0-P3-85-220.png/512x512bb.jpg"
},
{
"app": "Pillcase - Medication Reminder",
"link": "https://apps.apple.com/us/app/pillcase-medication-reminder/id6762976257?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/87/1d/91/871d9134-d427-2469-ce00-dd1f5929519b/PillcaseIcon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "PixShrink - HEIF Conv.",
"link": "https://apps.apple.com/us/app/pixshrink-heif-conv/id6744530166?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/a9/b8/c7/a9b8c739-32a1-a66d-9b5c-208089c2a66b/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Placify - Map & Trip Planner",
"link": "https://apps.apple.com/us/app/placify-map-trip-planner/id6739698007?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/80/48/f5/8048f5c8-8ba9-c684-e574-8f8dfa91544f/AppIcon-0-0-1x_U007ephone-0-0-0-1-0-0-sRGB-85-220-0.png/512x512bb.jpg"
},
{
"app": "Planit: Gamified Daily Planner",
"link": "https://apps.apple.com/us/app/planit-gamified-daily-planner/id6759006335",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/94/b1/94/94b194db-586e-18bf-bc6f-b7a7acaed8bb/testiconPlanit-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "PlayTales: Audiobooks for Kids",
"link": "https://apps.apple.com/us/app/playtales-audiobooks-for-kids/id6444850972?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/36/4d/38/364d38b5-d054-48f5-0528-3e61a266be29/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Plinky",
"link": "https://apps.apple.com/us/app/plinky-easily-save-links/id1597187737",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/22/d3/2a/22d32adc-7513-2a04-438a-570d6ad8602e/App_Icon__U0028Primary_U0029-0-1x_U007epad-0-1-4-85-220-0.png/512x512bb.jpg"
},
{
"app": "Plinth.IT",
"link": "https://apps.apple.com/us/app/plinth-it/id6746931219",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/2f/57/02/2f570236-cd2e-4d0b-a797-1130d5b68c19/AppIcon-0-1x_U007emarketing-0-11-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "Plushify",
"link": "https://apps.apple.com/us/app/plushify/id6758206706?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/5d/49/25/5d4925ae-e8d1-6d72-5f7c-28f78c126f0f/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "PlusProMax",
"link": "https://apps.apple.com/us/app/pluspromax/id6808135519?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/b8/9b/94/b89b942b-2f3e-360e-1f8d-e4d8abdf0bcd/AppIcon-0-0-1x_U007epad-0-1-0-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Pocket Mole Watch",
"link": "https://apps.apple.com/us/app/pocket-mole-watch/id6766255492?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/89/bd/10/89bd10b6-2a9a-4292-2eaf-16948b018a7a/AppIcon-0-0-1x_U007emarketing-0-11-85-220.png/512x512bb.jpg"
},
{
"app": "Podcast App & Player - Skimr",
"link": "https://apps.apple.com/us/app/podcast-app-player-skimr/id6744159946?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/90/e4/3d/90e43d21-87d6-fca6-4e77-749af812e0fb/AppIcon-0-0-1x_U007ephone-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "PodSized: AI Podcast Summaries",
"link": "https://apps.apple.com/us/app/podsized-ai-podcast-summaries/id6737673525?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/ec/c6/43/ecc6438f-9c3e-2b6e-34d7-c27e82104512/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Pollenomics",
"link": "https://apps.apple.com/us/app/pollenomics/id6747726612?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/0a/d3/06/0ad306d3-f064-8598-c9fe-d2637498d5dd/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Pomodoro Timer: Tempo Focus",
"link": "https://apps.apple.com/us/app/pomodoro-timer-tempo-focus/id6758786811?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/3a/3f/ff/3a3fff6a-e780-4867-ed3f-7445137a4be6/TempoDefault-0-0-1x_U007ephone-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Pool, the screenshot app",
"link": "https://apps.apple.com/us/app/pool-the-screenshot-app/id6752956163?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/e4/af/3d/e4af3d29-eedd-8746-8c2d-f81d233c7fff/AppIcon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Pop - Find by Color",
"link": "https://apps.apple.com/us/app/pop-find-by-color/id6758629086?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/ee/8a/b3/ee8ab36e-f44e-a3d8-baf5-bf9036af5532/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Popcorn Stack: Track Watchlist",
"link": "https://apps.apple.com/app/id6759187614",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/1b/9c/62/1b9c62c3-b960-84cd-77a1-f1f4a734a572/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Positive",
"link": "https://apps.apple.com/us/app/positive/id653287635",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/ff/41/f6/ff41f60e-7df4-563f-3872-9cb6490e786f/AppIcon-0-0-1x_U007epad-0-0-0-1-0-0-P3-85-220.png/512x512bb.jpg"
},
{
"app": "PPL Exam Prep - PilotTestZone",
"link": "https://apps.apple.com/us/app/ppl-exam-prep-pilottestzone/id6741020820?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/3e/f5/07/3ef507a5-3bdc-0b13-99d5-ad9d1b2d2bf8/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Presentify - Screen Annotation",
"link": "https://apps.apple.com/us/app/presentify-screen-annotation/id1507246666?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/4b/38/19/4b3819d1-89fa-48bc-1ef6-835d0930d82e/AppIcon-0-0-85-220-0-0-5-0-2x-sRGB-0-0-0.png/512x512bb.png"
},
{
"app": "Prickly Pear: Brain & Hormones",
"link": "https://apps.apple.com/app/id6654889896",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/7a/04/14/7a04148e-e548-f6bf-8c0e-c26b9157a7cb/AppIcon-0-1x_U007ephone-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Printer Tools App",
"link": "https://apps.apple.com/us/app/printer-tools-app/id6745528803?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/0c/4b/03/0c4b03c9-821c-d641-4c9a-27eb39885edc/AppIcon-0-1x_U007epad-0-1-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "PrivaNota: Private AI Notes",
"link": "https://apps.apple.com/us/app/privanota-private-ai-notes/id6752555660",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/7f/e6/22/7fe622bc-1260-bcfd-a8e2-c179ba33e727/PrivaNota-0.6-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Progressio: Habit Tracker",
"link": "https://apps.apple.com/app/progressio-habit-tracker/id6740454461",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/0a/a6/3a/0aa63a87-186d-e560-42fe-377337abdd7a/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Prolific",
"link": "https://apps.apple.com/app/id1666347084",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/72/f7/f5/72f7f5de-2856-e0c4-e453-112ad3ad4e30/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "PronoGP",
"link": "https://apps.apple.com/us/app/pronogp/id6759366137?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/37/8e/c0/378ec069-9b26-137c-a243-24aeba899783/app-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Protocol Zero: Sleep Sounds",
"link": "https://apps.apple.com/us/app/protocol-zero-sleep-sounds/id6755014746?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/38/1c/5b/381c5b87-8ab7-7351-138b-0cef5c4d5084/AppIcon-1x_U007ephone-0-1-85-220-0.jpeg/512x512bb.jpg"
},
{
"app": "Public Toilet Finder - Phoo",
"link": "https://apps.apple.com/us/app/public-toilet-finder-phoo/id6804638882?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/66/93/26/66932699-6d97-3ea6-1638-157ad93bcc85/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "QELM",
"link": "https://apps.apple.com/us/app/qelm/id6762642772?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/17/10/d6/1710d67e-6275-de37-b0b4-6ff940a4a858/AppIcon-0-0-1x_U007ephone-0-1-P3-85-220.png/512x512bb.jpg"
},
{
"app": "QontactR",
"link": "https://apps.apple.com/us/app/qontactr/id1169525139?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/dd/e8/78/dde87816-0e87-d38d-9b0a-a0fc93dfd60e/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Quick RSS - RSS Reader",
"link": "https://apps.apple.com/us/app/quick-rss-rss-reader/id6670696072?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/d6/55/7f/d6557fd2-7a41-2057-7ca4-0b67c1c0638c/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "QuickPhrase - Quick Text Input",
"link": "https://apps.apple.com/us/app/quickphrase-quick-text-input/id6756532440?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/e5/28/3e/e5283eca-f679-a08e-a84f-8284e09f044d/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Quietude",
"link": "https://apps.apple.com/app/id6758955377",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/57/73/ae/5773ae23-b9f6-362c-893f-c2d85e59e916/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Quit Caffeine: Stop Coffee",
"link": "https://apps.apple.com/us/app/quit-caffeine-stop-coffee/id6756549120?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f0/b8/ba/f0b8bad8-af2d-10f9-e8a6-1c2e421736a1/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "QuitNicPouches Pouch Tracker",
"link": "https://apps.apple.com/us/app/quitnicpouches-pouch-tracker/id6753104415?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/99/f4/3b/99f43be3-8cd4-448c-26db-bbe190492794/AppIcon-0-0-1x_U007ephone-0-1-P3-85-220.png/512x512bb.jpg"
},
{
"app": "QuitShark: Quit Nicotine",
"link": "https://apps.apple.com/us/app/quitshark-quit-nicotine/id6759577475?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/97/6c/c4/976cc498-7d3d-2d16-008b-f271ca8edcc2/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Quotify: Quotation Maker",
"link": "https://apps.apple.com/us/app/quotify-quotation-maker/id6764370347?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/f4/37/a4/f437a409-1d7a-a3b1-259f-dce2835c47bc/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "RateTier",
"link": "https://apps.apple.com/us/app/ratetier/id6759999815?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/eb/49/d8/eb49d8c5-20bb-c2f7-068f-d24a970abb2f/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "rawcast — Terminal Weather",
"link": "https://apps.apple.com/us/app/rawcast-terminal-weather/id6760224045?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/7b/a4/47/7ba447d5-d934-754b-3f42-2b03670f82b7/AppIcon-0-0-1x_U007epad-0-5-85-220.png/512x512bb.jpg"
},
{
"app": "ReaderFlow | Read Later",
"link": "https://apps.apple.com/us/app/readerflow-read-later/id6739498537?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/67/1d/d7/671dd77c-6284-7b5e-b3d2-ede360d76269/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Receipts Manager: Receipteka",
"link": "https://apps.apple.com/us/app/receipts-manager-receipteka/id6474066806?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/3c/f7/5a/3cf75a8b-78f9-010e-906d-011849ab7ee2/AppIcon-0-0-1x_U007epad-0-1-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "RecipeBee: Recipes & Meal Plan",
"link": "https://apps.apple.com/us/app/recipebee-recipes-meal-plan/id6757370845?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/86/0e/52/860e52e1-9c23-7868-1f43-b43739700fae/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Recurred: Subscription Manager",
"link": "https://apps.apple.com/us/app/recurred-subscription-manager/id6783705092?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/0c/2b/c3/0c2bc310-a846-bc32-781b-1593b5086b60/recurred-icon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Reed: Breathwork & Pranayama",
"link": "https://apps.apple.com/us/app/reed-breathwork-pranayama/id6793167045?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/b4/0c/ca/b40ccafb-a182-1baf-fbe9-762d450974d2/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "RegexMate",
"link": "https://apps.apple.com/us/app/regexmate/id6479819388?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/6f/b7/29/6fb72938-5335-c724-e9f9-eaea5533a531/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Reloop - AI Transcribe & Speak",
"link": "https://apps.apple.com/us/app/reloop-ai-transcribe-speak/id6752853818",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/2e/57/88/2e5788c4-8164-e931-a976-fba05975baac/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Reminders at a Glance",
"link": "https://apps.apple.com/us/app/reminders-at-a-glance/id6760935916?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/03/19/51/031951c7-0539-a87b-5569-b5b60a6581be/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Repetti",
"link": "https://apps.apple.com/us/app/repetti-the-chores-list-app/id6758055413",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/e8/77/5c/e8775cab-2c64-3b11-e132-c636f3993a44/Icon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Reps: AI Fitness & Recovery",
"link": "https://apps.apple.com/app/id6746460451",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/d4/6b/88/d46b88b6-e459-a316-6e0d-321ccb5c501a/Reps_AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "ReShoot · Video Reframe & Crop",
"link": "https://apps.apple.com/us/app/reshoot-video-reframe-crop/id6753581365?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/52/db/cc/52dbcc8f-c4aa-2cf0-36f9-bdeacf943fd7/AppIcon-0-1x_U007epad-0-11-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "Rice: Dinner Planner",
"link": "https://apps.apple.com/us/app/rice-dinner-planner/id6794866173?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/53/8a/61/538a61f8-c666-76f0-1256-fd8af7479ab0/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Ring Widget",
"link": "https://apps.apple.com/us/app/ring-widget/id1663323988?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f9/a4/20/f9a420ea-1b6b-83f4-25b5-488aef5a11db/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Ripple News - AI Personal News",
"link": "https://apps.apple.com/us/app/ripple-news-ai-personal-news/id6758356024?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/19/33/17/193317c5-5112-6151-9fd9-5b98ac41b52d/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "RIRLift: Hypertrophy Log",
"link": "https://apps.apple.com/us/app/rirlift-hypertrophy-log/id6768680416?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/c1/f2/40/c1f24082-408f-d80c-85ea-de71d3238b4d/AppIcon-0-0-1x_U007emarketing-0-6-0-85-220.png/512x512bb.jpg"
},
{
"app": "Riverside Podcast Video Studio",
"link": "https://apps.apple.com/us/app/riverside-podcast-video-studio/id1554443872?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/50/64/bd/5064bd5c-0e6c-4b46-dd11-c3c7848c845a/AppIcon-0-0-1x_U007epad-0-1-P3-85-220.png/512x512bb.jpg"
},
{
"app": "rivra",
"link": "https://apps.apple.com/us/app/rivra/id6762450774?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/ba/68/2d/ba682da9-d0ba-aee5-de20-60eef09ecf15/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Rooted Mind: Self Growth Coach",
"link": "https://apps.apple.com/us/app/rooted-mind-self-growth-coach/id6787827688?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/a1/49/a2/a149a2bc-b775-66bb-307b-91f7b7ad3800/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Rote.ink",
"link": "https://apps.apple.com/us/app/rote-ink/id6755513897?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/20/6e/3f/206e3fdc-5999-43aa-d7b3-123de15f0c9a/Rote-0-0-1x_U007epad-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "RTSP & RTMP Cam - OctoStream",
"link": "https://apps.apple.com/us/app/rtsp-rtmp-cam-octostream/id6474928937?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/60/87/cd/6087cd16-7c94-f635-1634-f17dc68bc00d/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Ryform Golf",
"link": "https://apps.apple.com/us/app/id6746525972",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/63/a9/01/63a90152-943b-ba07-d666-402df1f9694e/AppIcon-0-0-1x_U007epad-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Safe Period Tracker - Soso",
"link": "https://apps.apple.com/us/app/safe-period-tracker-soso/id6757295825?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/e1/e5/26/e1e526f7-2399-d1b2-a7c0-9603fd42acbf/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "SajdaTV",
"link": "https://apps.apple.com/us/app/sajdatv/id6759179587",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/a2/86/a6/a286a647-d984-7f1d-6f2b-7d4fda64c3e2/App_Icon-marketing.lsr/512x512bb.jpg"
},
{
"app": "Sam Webpage Summarizer",
"link": "https://apps.apple.com/us/app/sam-webpage-summarizer/id6769648029?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/09/1e/b6/091eb6fc-de14-9b7c-e7f6-b1e59b8be9ac/AppIcon-0-0-1x_U007emarketing-0-8-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "SAO: Luxury Hotel Journal",
"link": "https://apps.apple.com/us/app/sao-luxury-hotel-journal/id6757209978?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/c5/60/ff/c560ffff-1d5c-15c9-ae25-ce1f62a7396d/AppIcon-0-0-1x_U007epad-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Scap: Screenshot & Markup Edit",
"link": "https://apps.apple.com/us/app/scap-screenshot-markup-edit/id6758053530?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/95/ed/7c/95ed7cad-90a7-5d3b-8742-444542d3dc55/AppIcon-0-0-85-220-0-0-5-0-2x-0-0-0.png/512x512bb.png"
},
{
"app": "Scenio: Real Scenes to Speak",
"link": "https://apps.apple.com/us/app/scenio-real-scenes-to-speak/id6782251685?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/72/e6/99/72e69987-205d-397d-1644-136b36033fe2/AppIcon-0-0-1x_U007emarketing-0-8-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Scooter Tools",
"link": "https://apps.apple.com/us/app/scooter-tools/id6452079114?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/cf/8e/88/cf8e88b7-50bc-e82f-f37b-734549d82fcd/AppIcon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Secure Storage: Private Vault",
"link": "https://apps.apple.com/us/app/secure-storage-private-vault/id6794763277?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/02/4e/89/024e8960-34e7-b77b-3da3-d293b7da39b5/SecureStorage-0-0-1x_U007ephone-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Seekly AI - Ask and Chat",
"link": "https://apps.apple.com/us/app/seekly-ai-ask-and-chat/id6743059382?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/12/5f/81/125f8168-16ef-c611-bac6-141a414a668b/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Set - Strength Log",
"link": "https://apps.apple.com/us/app/set-strength-log/id6759079224?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/4d/80/3d/4d803d98-3f09-a0f4-d5d7-65c6f0f08066/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "SF Catalog",
"link": "https://apps.apple.com/us/app/sf-catalog/id6759371914?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/69/3d/6b/693d6bb5-f68d-1ce3-0ec3-2382a45ffc3d/AppIcon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "SharePal - Quick info sharing!",
"link": "https://apps.apple.com/us/app/sharepal-quick-info-sharing/id6457261534?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/f6/53/0e/f6530e8e-faca-4c14-53c8-23b1f08db727/AppIcon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Sharing Me: Shared Journal",
"link": "https://apps.apple.com/us/app/sharing-me-shared-journal/id6756325680?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/53/33/66/53336647-1330-96ad-575f-1bdb47d653d1/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Shluffy - A Baby Sleep Coach",
"link": "https://apps.apple.com/us/app/shluffy-a-baby-sleep-coach/id6758364198?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/27/e1/3a/27e13a84-a7d7-b9f5-1ee3-51a56210385e/AppIcon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Shua - arXiv Paper Reader",
"link": "https://apps.apple.com/us/app/shua-arxiv-paper-reader/id6759848629?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/66/fc/74/66fc74ce-6a9d-7344-618d-5b0e5af2110b/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Sift - AI Food Scanner",
"link": "https://apps.apple.com/us/app/sift-ai-food-scanner/id6760613139?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/f4/71/8a/f4718a2c-0eab-61f7-19c4-4c3039dfaaa5/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Sight words: First Words Match",
"link": "https://apps.apple.com/us/app/sight-words-first-words-match/id6499176211?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/25/29/8c/25298c71-4e09-d790-53d1-19abc35b4e17/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Silk: Relationship Tracker",
"link": "https://apps.apple.com/us/app/silk-relationship-tracker/id6748890004?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f4/31/09/f4310949-7d27-ee90-c6df-c235c860d590/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "SilkScreen Mesh Gradient",
"link": "https://apps.apple.com/us/app/silkscreen-mesh-gradient/id6736693342?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/c4/40/c4/c440c47a-4a3c-f6f1-d8f1-d17dc120e59d/AppIcon-0-0-1x_U007epad-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Siraj — Your Ramadan Light",
"link": "https://apps.apple.com/app/id6759178047",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/9c/1d/75/9c1d7502-e0d7-0db1-bb9e-e9f9edd2c6da/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "SiteCalc: Trades Calculator",
"link": "https://apps.apple.com/us/app/sitecalc-trades-calculator/id6761667513?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/98/db/d6/98dbd62d-1923-b384-c9fe-7233fcf3e8e7/AppIcon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Skyjo - Score Keeper",
"link": "https://apps.apple.com/us/app/skyjo-score-keeper/id6754793003?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/a4/48/f1/a448f16b-10fc-c35c-61df-fd186411a6e8/AppIcon-0-0-1x_U007ephone-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "SlashPhotos: Storage Cleaner",
"link": "https://apps.apple.com/us/app/slashphotos-storage-cleaner/id6811618133?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/f2/cc/29/f2cc2958-3f45-eeec-4849-a23538ba2e28/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Sleep Analysis - Lunomia",
"link": "https://apps.apple.com/us/app/sleep-analysis-lunomia/id6741118991?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/64/36/fa/6436fa8f-de92-01a9-62a3-52a8c8bd8580/AppIcon-0-0-1x_U007epad-0-0-0-1-0-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Slop Or Not - AI Fake Detector",
"link": "https://apps.apple.com/us/app/slop-or-not-ai-fake-detector/id6744578596?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/fb/f9/05/fbf9052f-756b-1660-e575-62e7a85d73b7/AppIcon-0-0-1x_U007ephone-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Smart Music Stations: Wavyy",
"link": "https://apps.apple.com/us/app/smart-music-stations-wavyy/id6458877273?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/5a/c6/8d/5ac68df8-3d3f-4bf4-984c-06554678468d/AppIcon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "SmoothCapture",
"link": "https://www.smoothcapture.app",
"icon": "https://www.smoothcapture.app/presskit/[email protected]"
},
{
"app": "SnaPOP: Screenshot Editor",
"link": "https://apps.apple.com/app/snapop/id6756635978",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/83/d3/34/83d334a4-f19b-047a-8d62-75f1866d1c37/snapop-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "SnapSong - AI Music from Photo",
"link": "https://apps.apple.com/us/app/snapsong-ai-music-from-photo/id6758351507?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/58/de/7b/58de7b85-a047-2d07-b570-acd38f6e873b/SnapSong-Icon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "SnapVinyl",
"link": "https://apps.apple.com/app/id6741057688",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/c3/87/7c/c3877c8c-bac8-537d-453e-98ba2e171fe1/AppIcon-0-1x_U007epad-0-1-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "Somilo",
"link": "https://apps.apple.com/us/app/somilo/id6795198829?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/42/3f/f9/423ff919-5b8c-0617-55c4-6a2f35597704/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Sona · AI Journal & Companion",
"link": "https://apps.apple.com/us/app/sona-ai-journal-companion/id6759681212?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/3c/c2/d8/3cc2d877-adb5-24ea-440b-061d98acedd7/AppIcon-0-1x_U007ephone-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Soundbooth",
"link": "https://apps.apple.com/app/id1341485079",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/87/cf/44/87cf44ee-79ee-1a91-3edc-d195ad86582b/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Sources: RSS Reader",
"link": "https://apps.apple.com/us/app/sources-rss-reader/id6792150549?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/9e/41/03/9e410326-37a9-7216-3eeb-7860cba43e33/S-RSS-0-0-1x_U007epad-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Space - Life Planner",
"link": "https://apps.apple.com/us/app/space-life-planner/id6502294348?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/68/88/12/688812e9-4f3d-ce80-5822-5c55e715b809/default-0-0-1x_U007ephone-0-0-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Sparkler Room",
"link": "https://apps.apple.com/us/app/sparkler-room/id6807456248?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/22/6a/06/226a06a5-0fe4-2264-8750-66d6d7d4be44/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Speaker Cleaner: Sound Eject",
"link": "https://apps.apple.com/us/app/speaker-cleaner-sound-eject/id6808605832?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/1b/e2/a8/1be2a854-011a-cefe-f937-3298e10c0f2b/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Speed Reading - WPM Arena",
"link": "https://apps.apple.com/us/app/speed-reading-wpm-arena/id6757508919?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/07/4c/e6/074ce60c-fca7-5361-ddd9-eff545681cd3/icon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "SpeedReader",
"link": "https://apps.apple.com/app/id6757832936",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/90/bc/20/90bc2003-d998-ba7a-ce45-66d846d7685f/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Spending tracker - Budgy",
"link": "https://apps.apple.com/us/app/spending-tracker-budgy/id6762646444?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/3b/38/f3/3b38f3f6-ac6e-4229-3244-704227340f9c/AppIcon-0-0-1x_U007ephone-0-1-sRGB-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Splitie – Split & Settle Bills",
"link": "https://apps.apple.com/us/app/splitie-split-settle-bills/id6758516466?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/06/18/ba/0618ba46-ef94-b8d0-9f30-0617c08f284f/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Spottable: Marketplace Alerts",
"link": "https://apps.apple.com/us/app/spottable-marketplace-alerts/id6758812746?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/32/56/af/3256afd1-4a25-9c3d-5873-bd4492c77200/AppIcon-0-0-1x_U007ephone-0-0-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Spotter - The Travel Companion",
"link": "https://apps.apple.com/us/app/spotter-the-travel-companion/id6761238251?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/43/63/bd/4363bd1c-3f2d-017b-f15c-d06d89736a88/AppIcon_1-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Spritz",
"link": "https://apps.apple.com/us/app/spritz-cocktail-recipes/id6748970457?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/ff/db/6a/ffdb6a73-be0d-ce05-743b-4a6a6f05101d/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Squeaker - Dog Training Sounds",
"link": "https://apps.apple.com/us/app/squeaker-dog-training-sounds/id6751776211?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/87/d5/10/87d51098-4022-e6e4-1c8d-7d8a8a545538/Squeaker-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "SquishPic: Image Compressor",
"link": "https://apps.apple.com/us/app/squishpic-image-compressor/id6757522104?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/65/d1/ae/65d1ae1f-5f97-acb6-5458-79560afb84ab/AppIcon-0-0-85-220-0-0-5-0-2x.png/512x512bb.png"
},
{
"app": "Stars of AI",
"link": "https://apps.apple.com/us/app/stars-of-ai/id6760822530?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/43/07/fe/4307fe6d-8f13-39f8-a508-377ff76b9496/AppIcon-0-0-1x_U007epad-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "StartUp Business: Tycoon Sim",
"link": "https://apps.apple.com/us/app/startup-business-tycoon-sim/id1629065796?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/ca/42/94/ca4294fb-858c-1384-49a3-529c31c8971a/AppIcon-0-0-1x_U007epad-0-1-0-sRGB-85-220.jpeg/512x512bb.jpg"
},
{
"app": "Step Counter: Steps App",
"link": "https://apps.apple.com/us/app/step-counter-steps-app/id6753876664?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/6e/08/ae/6e08ae21-ae96-34fb-44fc-62d0b99a802c/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Steplee – Love. Walk. Win.",
"link": "https://apps.apple.com/us/app/steplee-love-walk-win/id6759522498?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/55/06/28/550628b7-72c9-cec6-86d2-21a45577928b/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Steps: Workout & Pedometer",
"link": "https://apps.apple.com/us/app/steps-workout-pedometer/id6746096378",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/db/43/04/db430406-c791-e82a-1b4a-4283cca28b78/StepsIcon-0-1x_U007ephone-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Sticky Widgets",
"link": "https://apps.apple.com/us/app/sticky-widgets/id1533254320",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/1e/b8/78/1eb878a7-0955-eb1a-f55a-09b630362b71/sw-0-1x_U007epad-0-0-0-1-0-0-sRGB-85-220-0.png/512x512bb.jpg"
},
{
"app": "Stitch It · Long Screenshots",
"link": "https://apps.apple.com/us/app/stitch-it-long-screenshots/id554594252?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/73/7a/3e/737a3e1e-ccbe-8c01-1249-ac12fcec4a17/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Storyie - AI Journal & Diary",
"link": "https://apps.apple.com/us/app/storyie-ai-journal-diary/id6742196309?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/b3/a8/e6/b3a8e6e7-02a4-5193-3dfe-d5eae455a3b2/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "StrictBlock: App Blocker",
"link": "https://apps.apple.com/us/app/strictblock-app-blocker/id6749779711?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/d8/18/fc/d818fc87-7a0e-8941-ed31-31321d7bd717/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Subbie - Money Calendar",
"link": "https://apps.apple.com/us/app/subbie-money-calendar/id6759132543?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/c5/07/a8/c507a8be-d488-516e-c971-8005a7751fd1/AppIcon-0-0-1x_U007epad-0-1-0-P3-85-220.png/512x512bb.jpg"
},
{
"app": "SubList: Subscription List",
"link": "https://apps.apple.com/us/app/sublist-subscription-list/id6757860829?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/45/7e/dd/457edd6b-d9f7-89a7-99b2-72a72cab212f/AppIcon-0-0-1x_U007epad-0-1-P3-85-220.png/512x512bb.jpg"
},
{
"app": "Subscription Tracker: Submindo",
"link": "https://apps.apple.com/us/app/subscription-tracker-submindo/id6777319489?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/99/30/b5/9930b5c9-1fb5-45f2-9589-e2c9ec83ef2d/AppIcon-0-0-1x_U007emarketing-0-7-0-85-220.png/512x512bb.jpg"
},
{
"app": "Summit",
"link": "https://apps.apple.com/app/id6756911679",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/32/ed/56/32ed5661-a8ff-7f13-47bb-08a87a15fd00/Icon-0-1x_U007epad-0-1-sRGB-85-220-0.png/512x512bb.jpg"
},
{
"app": "Sunbreak: Nightly App Blocker",
"link": "https://apps.apple.com/us/app/sunbreak-nightly-app-blocker/id6752121964",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/6d/87/66/6d87668f-1acf-38dc-9242-4e571ea81a0e/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Sunrise Sunset Time SolarWatch",
"link": "https://apps.apple.com/us/app/sunrise-sunset-time-solarwatch/id1191365122?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/07/93/2e/07932e05-1ccc-04ea-7dc9-65ed5dffcf1c/AppIcon-0-0-1x_U007epad-0-0-0-1-0-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Super Shot: EXIF Photo Frame",
"link": "https://apps.apple.com/us/app/super-shot-exif-photo-frame/id6760384143?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/49/01/f9/4901f9f9-8d90-7283-bbec-6e506aec4391/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Swayni: Custom Workout Timer",
"link": "https://apps.apple.com/us/app/swayni-custom-workout-timer/id6795447541?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/73/a6/c7/73a6c72a-0087-866f-ce05-8cf8e64c5e36/AppIcon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "SweatCount - Workout Calendar",
"link": "https://apps.apple.com/us/app/sweatcount-workout-calendar/id6755407742?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/9c/4a/88/9c4a88f0-7ce8-6c72-8eb4-6462b67625e5/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "SwiftBiu",
"link": "https://apps.apple.com/app/swiftbiu/id6754772331",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/90/3b/2e/903b2ef1-7097-f00c-3557-db67e054e174/AppIcon-0-0-85-220-0-5-0-2x.png/512x512bb.png"
},
{
"app": "SwiftyMenu",
"link": "https://apps.apple.com/us/app/swiftymenu/id1567748223?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/e6/0a/2c/e60a2c36-1e5c-f018-3a56-edc1fd1bfa40/AppIcon.png/512x512bb.png"
},
{
"app": "Swooni: Relationship Tracker",
"link": "https://apps.apple.com/us/app/swooni-relationship-tracker/id6557063166?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/ce/6a/7e/ce6a7e31-0c22-1d6d-20e6-5fb7e5b16171/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "takt - car glance",
"link": "https://apps.apple.com/us/app/takt-car-glance/id6799290528?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/8f/6a/ae/8f6aaef7-9f39-5dbe-3573-439b29d12f1a/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Talli",
"link": "https://apps.apple.com/us/app/talli/id6782366555?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/63/8e/dd/638edd28-f1d3-5002-d912-7511ff80de7f/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Tamloot",
"link": "https://apps.apple.com/il/app/tamloot/id6758220887",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/26/a3/f7/26a3f7f9-3cb6-dc8e-fcbd-0fc93630dd4c/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "TapTape",
"link": "https://apps.apple.com/us/app/taptape/id6748304805?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/b8/47/72/b8477252-9820-e366-84fc-4736974702a6/AppIcon-0-0-1x_U007epad-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "TeethPath",
"link": "https://apps.apple.com/us/app/teethpath/id6740765612?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/9d/5a/d8/9d5ad8ac-b8bc-af9e-33f0-32a4d98cba6e/AppIcon-0-0-1x_U007ephone-0-0-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Telescopo: Markdown & Diagrams",
"link": "https://apps.apple.com/us/app/telescopo-markdown-diagrams/id6747908871?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/eb/ba/d4/ebbad4d9-6a72-5d73-9a18-cf2719e6b8f3/telescopo-icon-0-0-85-220-0-6-0-2x-0-0-0.png/512x512bb.png"
},
{
"app": "Terraforge: Alchemy Craft Game",
"link": "https://apps.apple.com/us/app/terraforge-alchemy-craft-game/id6759879809?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f4/f1/65/f4f165e3-9826-8ced-d9be-d33b8a8e154c/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Tessera - Car Black Box",
"link": "https://apps.apple.com/us/app/tessera-car-black-box/id6778605690?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/6a/8b/a1/6a8ba19d-72e9-bd02-a39a-188c9d936f9f/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "text to speech · speakeasy",
"link": "https://apps.apple.com/app/id6758816495",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/0a/49/48/0a49486e-92b3-37b5-8bb8-c038aee9a0a4/speakeasy-icon-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "ThaiQuest",
"link": "https://apps.apple.com/us/app/thaiquest/id6759592901?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/49/51/6b/49516bdb-9a33-055e-3296-38f930c5d311/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "The Pot - Stake your claim",
"link": "https://apps.apple.com/us/app/the-pot-stake-your-claim/id6760322444?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/ef/1e/04/ef1e04d3-6b41-92d8-417f-de50d9f07005/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Thought Ease: End Overthinking",
"link": "https://apps.apple.com/app/id6759291269",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/38/89/82/3889821a-7a9e-d068-89ca-937b482923ed/AppIcon-0-0-1x_U007emarketing-0-7-0-85-220.png/512x512bb.jpg"
},
{
"app": "TickerPad",
"link": "https://apps.apple.com/app/id1578860989",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/d0/1b/19/d01b1977-6842-0528-9447-6e24a8cc1bf1/AppIcon-0-0-85-220-0-0-5-0-2x-0-0-0.png/512x512bb.png"
},
{
"app": "Tilt: Shop Live Fashion Deals",
"link": "https://apps.apple.com/us/app/tilt-shop-live-fashion-deals/id1588409720?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/3a/1f/f2/3a1ff2df-b4f5-d67d-ba51-2f30d281b871/AppIcon-1x_U007ephone-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Time Blocking Planner: Align",
"link": "https://apps.apple.com/us/app/time-blocking-planner-align/id6740183716?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/cf/17/ad/cf17adb8-589d-0239-b33f-85adee7c3e26/Align_icon_26-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Timefall",
"link": "https://apps.apple.com/app/id6757712804",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/d5/00/51/d500514a-c728-8598-f562-105dcc8005b9/AppIcon-0-0-1x_U007epad-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "Tintz: Color Picker",
"link": "https://apps.apple.com/us/app/tintz-color-picker/id6748126014?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/53/33/80/5333806a-5c46-8d24-6f30-fc455847922e/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Tiny Decisions",
"link": "https://apps.apple.com/us/app/tiny-decisions/id1338769645?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/57/19/ea/5719eae3-87a1-f412-34a1-4223cc8315c7/AppIcon-0-0-1x_U007epad-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Tirkit - Tabla Tanpura Riyaz",
"link": "https://apps.apple.com/us/app/tirkit-tabla-tanpura-riyaz/id6794581805?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/c0/0a/a9/c00aa9ac-b310-0b34-32fb-84ebc108b2ca/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "TMS - Bible Memory",
"link": "https://apps.apple.com/us/app/tms-bible-memory/id6463799590?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/9a/7a/a3/9a7aa376-7dd9-8939-d6ba-90012b0cf417/tmsAppIcon-0-0-1x_U007ephone-0-1-0-85-220.png/512x512bb.jpg"
},
{
"app": "ToMe: Save to Self",
"link": "https://apps.apple.com/app/id6755589008",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/ea/39/63/ea3963e1-524c-7402-fe36-f930ddedcc36/ToMe_Liquid-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Torqs",
"link": "https://apps.apple.com/us/app/torqs/id6754500631",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/1d/b0/ea/1db0eaa8-4504-cbe3-b472-5eab73d634c9/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Trailbot: MTB Trail Conditions",
"link": "https://apps.apple.com/us/app/trailbot-mtb-trail-conditions/id1559573510?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/12/a4/56/12a456ea-73f0-3972-9677-904b25e8c2e3/TrailbotGlass-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Train RunBuddy",
"link": "https://apps.apple.com/au/app/train-runbuddy/id6738111722?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/a3/7b/04/a37b0451-b55b-4b78-b5b1-93dc9e10380a/AppIcon-0-0-1x_U007epad-0-0-0-1-0-0-85-220.png/512x512bb.jpg"
},
{
"app": "Trip Atar",
"link": "https://apps.apple.com/us/app/trip-atar/id6758254251?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/ac/1a/a1/ac1aa167-3a2b-8d1e-d0aa-4e2c5bfb1c9b/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Truessay: Academic AI Writer",
"link": "https://apps.apple.com/us/app/truessay-academic-ai-writer/id6757461906?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/ea/b1/01/eab10103-3a5d-9de7-5094-0e6233c9c034/AppIcon-1x_U007ephone-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Tunable – Tuner & Metronome",
"link": "https://apps.apple.com/us/app/tunable-tuner-metronome/id608540795?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple115/v4/93/bd/99/93bd992d-825e-df5d-eedb-550eb17895c5/AppIcon-1x_U007emarketing-0-10-0-85-220.png/512x512bb.jpg"
},
{
"app": "TV Graphs: Season Ratings",
"link": "https://apps.apple.com/us/app/tv-graphs-season-ratings/id6804961357?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/29/b7/b8/29b7b8fa-4bc1-1a38-0ac7-a6c113e03b6f/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "TV Show Tracker",
"link": "https://apps.apple.com/us/app/tv-show-tracker-tv-club/id6497563903",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/43/b8/67/43b86702-f67b-c314-f65b-cd60980205b0/Icon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "TypeSafe: Verify & Protect",
"link": "https://apps.apple.com/us/app/typesafe-verify-protect/id6754827519?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/5e/d6/b3/5ed6b308-6268-474d-1009-5ba153d05266/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "ulives: Gamify Tasks & Habits",
"link": "https://apps.apple.com/us/app/ulives-gamify-tasks-habits/id6743084261?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/ed/78/34/ed783481-8296-ea6e-eb95-644a7f058353/AppIconDefault-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Unlimited Clipboard History",
"link": "https://apps.apple.com/us/app/unlimited-clipboard-history/id6705136056",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/6a/1e/8f/6a1e8f8e-1f53-fcde-f26b-f814416eeb69/glass-0-0-85-220-0-6-0-2x-sRGB.png/512x512bb.png"
},
{
"app": "Unseen Accessibility",
"link": "https://apps.apple.com/us/app/unseen-accessibility/id6770270032?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/59/52/01/595201c5-02a2-75ac-0e10-a00f7a4bd04c/AppIcon-0-0-1x_U007epad-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "UrbanAir: Unlock all escooters",
"link": "https://apps.apple.com/us/app/urbanair-unlock-all-escooters/id1491025864?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple122/v4/7f/75/08/7f750858-b379-719d-f98c-06e4fbc9a984/AppIcon-0-0-1x_U007emarketing-0-0-0-10-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/512x512bb.jpg"
},
{
"app": "Vakta: Prayer Times & Quran",
"link": "https://apps.apple.com/us/app/vakta-prayer-times-quran/id6759549958?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/c0/a4/8e/c0a48e40-32c3-8e31-6779-9b75593d9e38/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Vaultaire: Photo Vault",
"link": "https://apps.apple.com/us/app/vaultaire-photo-vault/id6758529311?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/3f/90/6f/3f906ff7-be78-e596-b72c-c5dee3bb0b7c/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Velko: IPTV Player & TV Guide",
"link": "https://apps.apple.com/us/app/velko-iptv-player-tv-guide/id6775969853?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/53/c3/b7/53c3b765-6630-5db0-a5f8-abf88f38d181/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Verbi: Speak a New Language",
"link": "https://apps.apple.com/us/app/verbi-speak-a-new-language/id6760734657?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/9a/75/c8/9a75c847-6917-09ba-3f07-9ddc003b25e4/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Victus: Discipline & Habits",
"link": "https://apps.apple.com/us/app/victus-discipline-habits/id6754204999?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/cc/a6/0a/cca60a66-1e16-573f-043e-97f4c092a07d/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Video Annotation",
"link": "https://apps.apple.com/us/app/video-annotation/id6758316355",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/43/c3/a9/43c3a9e9-e32b-081c-8a8b-a4f401846771/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Videoer",
"link": "https://apps.apple.com/us/app/videoer/id6742680573?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f6/51/f5/f651f5b7-8052-5407-77ec-a4203bbf456c/AppIcon-0-0-85-220-0-5-0-2x.png/512x512bb.png"
},
{
"app": "Vigil: Situation Room",
"link": "https://apps.apple.com/us/app/vigil-situation-room/id6773060534?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/5e/39/70/5e397004-de16-52f5-051c-03a5bf09db23/AppIcon.lsr/512x512bb.jpg"
},
{
"app": "Vintsnap: AI Listing Photos",
"link": "https://apps.apple.com/us/app/vintsnap-ai-listing-photos/id6761347142?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/43/ab/e6/43abe6b7-c579-18a2-c562-be9663d47a82/vintsnap-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Visa Day Tracker: Residency",
"link": "https://apps.apple.com/us/app/visa-day-tracker-residency/id6749048224?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/73/d0/88/73d088d4-104c-b8ea-4b0d-863da7932f6d/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "VivaDicta - Speech to Text",
"link": "https://apps.apple.com/us/app/vivadicta-speech-to-text/id6758147238?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/e4/3d/81/e43d810c-3bf3-ea24-9aa6-1ced116d0f1d/AppIcon3-0-0-1x_U007epad-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "VivyShot: Screen Studio",
"link": "https://apps.apple.com/us/app/vivyshot-screen-studio/id6760658121?mt=12&uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/cb/3a/3d/cb3a3d36-ba8b-6e36-a332-5a51209a2a86/AppIcon-85-220-0-5-0-0-2x-0-0.png/512x512bb.png"
},
{
"app": "Voice Do",
"link": "https://apps.apple.com/us/app/voice-do-task-manager/id6746357453",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/27/ed/a2/27eda257-5dd2-5150-7b0e-977f35636552/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Voice Reminder Planner: Echo",
"link": "https://apps.apple.com/us/app/voice-reminder-planner-echo/id6752544263?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/05/25/f3/0525f38c-b2cd-3b22-c1d7-420535cc43f4/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "VoiceBee: AI Voice Keyboard",
"link": "https://apps.apple.com/us/app/voicebee-ai-voice-keyboard/id6760564766?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/dd/b5/26/ddb52642-a03e-5ff7-ad0c-d973c80726c6/AppIcon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Voxel Max: 3D Modeling",
"link": "https://apps.apple.com/us/app/voxel-max-3d-modeling/id1442352186?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/f3/59/69/f35969ec-2a16-d98a-830a-55d92ce8ff81/VoxelMax-0-0-1x_U007epad-0-0-0-1-0-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "VVTerm: Native SSH Client",
"link": "https://apps.apple.com/us/app/vvterm-native-ssh-client/id6757482822?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/df/80/ae/df80aeb3-38cc-afe1-e15c-e0b27e957f66/AppIcon-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Wander: Markdown Editor",
"link": "https://apps.apple.com/us/app/wander-markdown-editor/id6791206855?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/86/fc/6f/86fc6f47-00d7-4465-b567-fe4246d47945/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "Wat Kham",
"link": "https://apps.apple.com/us/app/wat-kham/id6760705576?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/86/4a/45/864a4587-87e9-4a25-e53e-2e9e025800e0/AppIcon-1x_U007emarketing-0-8-0-85-220-0.png/512x512bb.jpg"
},
{
"app": "Watchletic - Run tracker",
"link": "https://apps.apple.com/us/app/watchletic-run-tracker/id1606429298?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/e0/4b/bd/e04bbd15-6b77-184e-381f-3d5e5654d97b/app-0-0-1x_U007epad-0-0-0-1-0-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "WatchRadar: Watches Identifier",
"link": "https://apps.apple.com/us/app/watchradar-watches-identifier/id6760263209?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/c9/7e/e0/c97ee08b-0e99-5233-ebcc-acba477cafd3/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "Weather mini - Trip Forecasts",
"link": "https://apps.apple.com/us/app/weather-mini-trip-forecasts/id892589817",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/3c/c6/c1/3cc6c19c-acec-fb4d-ee88-63a1bfc422c4/AppIcon-0-0-1x_U007epad-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Woofdoku: Sudoku with Dogs",
"link": "https://apps.apple.com/us/app/woofdoku-sudoku-with-dogs/id6799775594?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/84/4b/eb/844beb9b-2b97-1180-fb47-81f76f90a701/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "WordFlux: Word Puzzles",
"link": "https://apps.apple.com/us/app/wordflux-word-puzzles/id6757514622?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/eb/e9/b7/ebe9b79a-9dd1-d0a7-3bd9-ecb2a4517c83/AppIcon-0-0-1x_U007ephone-0-1-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Wordroot",
"link": "https://apps.apple.com/us/app/wordroot/id6794988021?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/4b/9d/f9/4b9df903-d342-3152-ec35-c823dcd86bc6/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "World Clock Time Zones - Timo",
"link": "https://apps.apple.com/us/app/world-clock-time-zones-timo/id6762064286?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/c9/df/36/c9df3620-c419-146f-154a-746d9f7a9f45/Icon-0-0-1x_U007ephone-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Wozi Vocabulary Builder",
"link": "https://apps.apple.com/app/id1536585848",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/cd/33/e3/cd33e3a5-2c7d-34e8-5c22-07bca8f44d7d/WoziAppIcon-0-0-1x_U007epad-0-1-P3-85-220.png/512x512bb.jpg"
},
{
"app": "Writone - Reply Assistant",
"link": "https://apps.apple.com/app/id6758207392",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/d8/48/f4/d848f4b9-7de8-3d14-57d0-bf534e142069/AppIcon-1x_U007ephone-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "wrkout - gym plan, log & track",
"link": "https://apps.apple.com/us/app/wrkout-gym-plan-log-track/id6759204353?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/01/0e/ce/010ece8d-5bcf-70fc-ef4a-7ed6c8aaa6ef/Logo-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "XO: Have I Ever",
"link": "https://apps.apple.com/us/app/xo-have-i-ever/id6757594745",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/d7/4d/4c/d74d4c07-c147-fa7e-86fe-cff01727629b/XO-0-0-1x_U007ephone-0-1-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "Yabaleli",
"link": "https://apps.apple.com/us/app/yabaleli/id6748582441?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/a4/78/92/a4789210-875e-8984-6ef0-5f2c034b53fd/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
},
{
"app": "YouInMeme: Face Swap Memes",
"link": "https://apps.apple.com/us/app/youinmeme-face-swap-memes/id6760035781?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/0e/38/cb/0e38cb41-820e-8d78-2547-eb2f945a66b2/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Your Next Tours: Audio Guide",
"link": "https://apps.apple.com/us/app/your-next-tours-audio-guide/id6783743090?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/a2/9a/cb/a29acbaa-94ac-05f9-3c76-84b654782781/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "Zen Flip Clock - Minimal Timer",
"link": "https://apps.apple.com/us/app/zen-flip-clock-minimal-timer/id1265404088?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/4b/f4/13/4bf4138d-b804-9563-f684-92a9a2845b16/zfc-glass-icon-0-0-1x_U007epad-0-0-0-1-0-0-sRGB-85-220.png/512x512bb.jpg"
},
{
"app": "ZenJournal:Stress-free Journal",
"link": "https://apps.apple.com/us/app/zenjournal-stress-free-journal/id1399816360?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/33/eb/e1/33ebe137-a850-63be-ef48-7a7a804ec151/ZenJournalIcon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "ZenShelf",
"link": "https://apps.apple.com/app/id6670147622",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/61/5a/dc/615adc54-4abd-f5f0-34a8-0ee239868470/AppIcon-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "Zenther: AI Calorie Tracker",
"link": "https://apps.apple.com/us/app/zenther-ai-calorie-tracker/id6748252780?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/c6/49/14/c6491404-390d-82e2-3c3c-1b6053594311/Zenther-0-1x_U007epad-0-1-85-220-0.png/512x512bb.jpg"
},
{
"app": "你的话 - 懂你的人在这",
"link": "https://apps.apple.com/us/app/%E4%BD%A0%E7%9A%84%E8%AF%9D-%E6%87%82%E4%BD%A0%E7%9A%84%E4%BA%BA%E5%9C%A8%E8%BF%99/id6760377155?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/ef/91/e6/ef91e682-10d1-f57b-a5d3-20de2e4c0fe7/AppIcon-0-0-1x_U007emarketing-0-11-0-85-220.png/512x512bb.jpg"
},
{
"app": "吃了吗您",
"link": "https://apps.apple.com/us/app/%E5%90%83%E4%BA%86%E5%90%97%E6%82%A8/id6757486351?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/7d/b2/56/7db25643-249f-507e-6bd5-2085d64e5b13/AppIcon-0-0-1x_U007epad-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "极品ID",
"link": "https://apps.apple.com/us/app/%E6%9E%81%E5%93%81id/id6744751474?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/8f/8c/05/8f8c0557-70e8-9a04-eb6c-4eb79d366392/AppIcon-0-0-1x_U007epad-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "知了,RSS智能聚合阅读",
"link": "https://apps.apple.com/app/id1525056652",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/11/3e/43/113e432e-0c18-db1b-fff8-0dec1e3d1fcb/AppIcon-0-0-1x_U007emarketing-0-6-0-85-220.png/512x512bb.jpg"
},
{
"app": "织诗 - AI写诗与古诗词创作",
"link": "https://apps.apple.com/us/app/%E7%BB%87%E8%AF%97-ai%E5%86%99%E8%AF%97%E4%B8%8E%E5%8F%A4%E8%AF%97%E8%AF%8D%E5%88%9B%E4%BD%9C/id6777856347?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/4b/05/11/4b051114-60b4-13e8-f567-23d7f4170600/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "补光HDR相机",
"link": "https://apps.apple.com/us/app/%E8%A1%A5%E5%85%89hdr%E7%9B%B8%E6%9C%BA/id6757137922?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/89/3d/5b/893d5b0e-f1c0-bf21-8292-4fcd4847bf67/AppIcon-0-0-1x_U007epad-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "表魂",
"link": "https://apps.apple.com/us/app/%E8%A1%A8%E9%AD%82/id6756651527?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/41/58/86/41588611-db95-a624-2fd6-f2ee3a069f71/AppIcon-0-0-1x_U007ewatch-0-1-85-220.jpeg/512x512bb.jpg"
},
{
"app": "词忆MemQuirk",
"link": "https://apps.apple.com/us/app/%E8%AF%8D%E5%BF%86memquirk/id6756350749?uo=4",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/12/8c/44/128c4424-fd03-64c3-1e8e-8cf913513550/AppIcon-0-0-1x_U007epad-0-1-85-220.png/512x512bb.jpg"
},
{
"app": "시선 - 사진 한 장, 시 한 줄",
"link": "https://apps.apple.com/app/id6748271659",
"icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/4e/a3/3b/4ea33b80-8dcb-11ec-4699-6ccff7efcfa9/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/512x512bb.jpg"
}
]
Guides Agent Ios Release (`guides/agent-ios-release.mdx`)
Agent iOS Release Checklist
Use this checklist as an agent release gate. See
App Store Submission for the full workflow and
troubleshooting guide.
This page adds the agent-specific contract: resolve exact resources, use scoped
validation, preview each mutation, and prove the final App Store Connect
state before reporting success.
Operating rules
- Run
--helpfor every command before using it. - Resolve the app ID and version target before changing anything. Resolve the
build ID before staging or submitting. - Use the most specific
asccommand for the workflow, includingasc webwhen the operation lives there. - Preview supported workflows with
--dry-run. Use--confirmonly after
checking every identifier available at that stage. - Never print credentials or personal identity values in logs or handoffs.
- A successful mutation response is not proof that the version entered review.
1. Validate authentication and resolve the target
Select the intended credential source before making a network request. When
using a named profile:
export ASC_PROFILE="release"
asc auth status --output tableIf ASC_PROFILE is unset, confirm that the status shows the intended default
profile or environment credential. Do not use asc auth status --validate as
the release gate because it validates every stored credential. The filteredasc apps list call below is the scoped network validation for the selected
credential and must succeed.
Resolve the app and release version:
asc apps list --bundle-id "com.example.app" --output table
export ASC_APP_ID="1234567890"
export VERSION="1.2.3"
asc versions list \
--app "$ASC_APP_ID" \
--version "$VERSION" \
--platform IOS \
--output jsonFor a build that is already in App Store Connect, resolve a valid processed
build and record its ID:
export BUILD_NUMBER="42"
asc builds info \
--app "$ASC_APP_ID" \
--build-number "$BUILD_NUMBER" \
--version "$VERSION" \
--platform IOS \
--output json
export ASC_BUILD_ID="BUILD_ID"Continue only when the response is for the expected build number and its
processing state is VALID.
Skip the build lookup when uploading a local IPA; resolve the build ID after
upload and processing. Do not create a duplicate version. The staging and
publishing commands below can create a missing version for their workflows.
2. Prepare the version
Choose the path that matches the current release state.
Stage an existing processed build
Preview the staging plan:
asc release stage \
--app "$ASC_APP_ID" \
--version "$VERSION" \
--build-id "$ASC_BUILD_ID" \
--platform IOS \
--copy-metadata-from "1.2.2" \
--dry-run \
--output tableA dry-run cannot attach the build. If its only readiness blocker is the missing
relationship for the exact resolved build, the confirmed pipeline fixes that
condition before its readiness step. Resolve every other blocker first.
After checking the resolved resources and plan, run the confirmed preparation
pipeline. It applies metadata and attaches the build. The final release gate runs
after any digital goods are submitted:
asc release stage \
--app "$ASC_APP_ID" \
--version "$VERSION" \
--build-id "$ASC_BUILD_ID" \
--platform IOS \
--copy-metadata-from "1.2.2" \
--confirm \
--output tableUpload and attach a local IPA
Preview the upload and preparation plan:
asc publish appstore \
--app "$ASC_APP_ID" \
--ipa "./build/ios/ipa/MyApp.ipa" \
--version "$VERSION" \
--platform IOS \
--wait \
--dry-run \
--output tableAfter checking the plan, run the preparation command and request structured
output:
asc publish appstore \
--app "$ASC_APP_ID" \
--ipa "./build/ios/ipa/MyApp.ipa" \
--version "$VERSION" \
--platform IOS \
--wait \
--output jsonDo not add --submit; release validation must run against the build after it is
attached. Set ASC_BUILD_ID to the exact buildId returned by this command and
retain its buildNumber:
export ASC_BUILD_ID="BUILD_ID_FROM_PUBLISH_RESULT"
export BUILD_NUMBER="BUILD_NUMBER_FROM_PUBLISH_RESULT"Never re-resolve an uploaded build with --latest. If the result must be
re-read, query the known build number and verify that the response has the same
build ID and a VALID processing state.
If the version is already prepared and has the intended build attached, skip
preparation and continue to the readiness gate.
3. Gate readiness and submit
After the build is attached, apps with digital goods must inspect and submit
their products before using the final readiness gate:
asc validate iap --app "$ASC_APP_ID" --output json
asc validate subscriptions --app "$ASC_APP_ID" --output jsonResolve the exact product resourceId and its current version ID for every
product included in this release. Use asc iap versions list andasc subscriptions versions list to find versions inPREPARE_FOR_SUBMISSION. READY_TO_SUBMIT is an action item, not a passing
state.
Create one review submission, retain its returned ID, and add every affected
product version. A submission supports up to 200 items:
asc review submissions-create \
--app "$ASC_APP_ID" \
--platform IOS \
--output json
asc review items add \
--submission "SUBMISSION_ID" \
--item-type inAppPurchaseVersions \
--item-id "IAP_VERSION_ID" \
--output json
asc review items add \
--submission "SUBMISSION_ID" \
--item-type subscriptionVersions \
--item-id "SUBSCRIPTION_VERSION_ID" \
--output json
asc review submissions-submit \
--id "SUBMISSION_ID" \
--confirm \
--output jsonThese review-submission commands do not support --dry-run; use the validation
report and exact product and version IDs as the pre-mutation check. Do not
submit the review submission until every intended item has been added.
After product submission, rerun the applicable validators:
asc validate iap --app "$ASC_APP_ID" --output json
asc validate subscriptions --app "$ASC_APP_ID" --output jsonFor every selected product ID, require the review-readiness warning to disappear
and verify a state such as WAITING_FOR_REVIEW, IN_REVIEW,PENDING_BINARY_APPROVAL, or APPROVED. These validators cover the full product
catalog and do not accept a product filter. Do not add --strict as an app
release gate when unrelated draft or legacy products exist.
Then run the unified app-version validation:
asc validate \
--app "$ASC_APP_ID" \
--version "$VERSION" \
--platform IOS \
--output jsonRequire summary.blocking to be 0, and resolve every warning that applies to
the target version, build, or selected products. Non-strict output keeps
unrelated product warnings advisory; it does not make them safe to ignore when
their resource IDs are part of this release.
Do not submit while a required metadata, build, product, privacy, or review
check is failing. When all applicable gates pass, preview the submission:
asc review submit \
--app "$ASC_APP_ID" \
--version "$VERSION" \
--build-id "$ASC_BUILD_ID" \
--platform IOS \
--dry-run \
--output tableAfter checking the preview, repeat that command with --confirm instead of--dry-run.
4. Prove the final state
Re-read App Store Connect and include the attached build and submission:
asc status \
--app "$ASC_APP_ID" \
--platform IOS \
--include builds,appstore,submission,review \
--output json
asc versions view \
--version-id "VERSION_ID" \
--include-build \
--include-submission \
--output json
asc submit status --version-id "VERSION_ID" --output jsonThe handoff must name the app ID or bundle ID, version string, version ID, build
number or build ID, submission ID when available, and the observed state. Report
a draft as a draft. Only claim submission after the exact version and build show
a review state such as WAITING_FOR_REVIEW, IN_REVIEW, orPENDING_DEVELOPER_RELEASE.
Web fallback
Use an explicit asc web command when the operation lives in the web-session
command family. asc web auth login supports interactive two-factor authentication,
but account-holder agreements,
payment issues, CAPTCHA, and unsupported surfaces may still require the browser.
Guides Analytics Reports (`guides/analytics-reports.mdx`)
Analytics and Reports
Download sales, analytics, and financial reports from App Store Connect
Access and download comprehensive analytics, sales, and financial reports for your apps.
Overview
The CLI provides access to:
- Sales Reports: Daily, weekly, and monthly sales data
- Analytics Reports: App usage, crashes, and performance metrics
- Financial Reports: Revenue and payment information
- Custom Analytics: Request specific metrics and segments
Sales Reports
Sales reports provide transaction-level data for your apps.
Download Daily Sales Report
asc analytics sales \
--vendor "YOUR_VENDOR_NUMBER" \
--type SALES \
--subtype SUMMARY \
--frequency DAILY \
--date "2024-01-20"Download Weekly Sales Report
asc analytics sales \
--vendor "YOUR_VENDOR_NUMBER" \
--type SALES \
--subtype SUMMARY \
--frequency WEEKLY \
--date "2024-01-21"Download Monthly Sales Report
asc analytics sales \
--vendor "YOUR_VENDOR_NUMBER" \
--type SALES \
--subtype SUMMARY \
--frequency MONTHLY \
--date "2024-01-01"Sales Report Types
- SALES: Standard sales transactions
- PRE_ORDER: Pre-order transactions
- NEWSSTAND: Newsstand sales (if applicable)
- SUBSCRIPTION: Subscription events
- SUBSCRIPTION_EVENT: Detailed subscription lifecycle events
Sales Report Subtypes
- SUMMARY: Aggregated sales summary
- DETAILED: Transaction-level details
- OPT_IN: Reports for apps with opted-in features
Analytics Reports
Request custom analytics reports for detailed app metrics.
Create Analytics Request
```bash theme={null}
asc analytics request \
--app "YOUR_APP_ID" \
--access-type ONGOING \
--reuse-existing
```
Or create a request directly:
```bash theme={null}
asc analytics request \
--app "YOUR_APP_ID" \
--access-type ONGOING
```
For one-time reports:
```bash theme={null}
asc analytics request \
--app "YOUR_APP_ID" \
--access-type ONE_TIME_SNAPSHOT
``````bash theme={null}
asc analytics requests --app "YOUR_APP_ID"
``````bash theme={null}
asc analytics view --request-id "REQUEST_ID"
``````bash theme={null}
asc analytics view --request-id "REQUEST_ID"
```Download Analytics Data
Download the actual report data:
asc analytics download \
--request-id "REQUEST_ID" \
--instance-id "INSTANCE_ID" \
--output "./analytics-report.csv"Analytics Report Segments
Segment analytics by various dimensions:
asc analytics segments view \
--segment-id "SEGMENT_ID" \
--output jsonCommon segments include:
- App version
- Device type
- Platform version
- Territory
- Source type
Financial Reports
Access payment and financial information:
Download Financial Report
asc finance reports \
--vendor "YOUR_VENDOR_NUMBER" \
--report-type FINANCIAL \
--region "ZZ" \
--date "2024-01"Report Regions
Financial reports are organized by region:
asc finance reports \
--vendor "YOUR_VENDOR_NUMBER" \
--report-type FINANCIAL \
--region "US" \
--date "2024-01"Common region codes:
US- United StatesEU- European UnionJP- JapanAU- AustraliaCN- China
Complete Reporting Workflow
Here's a complete workflow for downloading reports:
/* Detailed source-code truncated for AI context efficiency. */Automated Daily Reports (CI/CD)
GitHub Actions Example
name: Daily Reports
on:
schedule:
- cron: '0 9 * * *' # 9 AM UTC daily
workflow_dispatch:
jobs:
download_reports:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install asc CLI
run: |
curl -fsSL https://raw.githubusercontent.com/rorkai/App-Store-Connect-CLI/main/install.sh | bash
- name: Configure credentials
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_PRIVATE_KEY: ${{ secrets.ASC_PRIVATE_KEY }}
ASC_VENDOR_NUMBER: ${{ secrets.ASC_VENDOR_NUMBER }}
run: |
echo "Credentials configured"
- name: Download daily sales report
run: |
YESTERDAY=$(date -d "yesterday" +%Y-%m-%d)
asc analytics sales \
--vendor "$ASC_VENDOR_NUMBER" \
--type SALES \
--subtype SUMMARY \
--frequency DAILY \
--date "$YESTERDAY" \
--output "./sales-$YESTERDAY.txt"
- name: Upload reports as artifact
uses: actions/upload-artifact@v3
with:
name: sales-reports
path: ./sales-*.txt
retention-days: 30
- name: Send to analytics platform (optional)
run: |
# Parse and send to your analytics/BI platform
# Example: curl -X POST https://your-analytics.com/api/ingest -d @sales-*.txtReport Data Processing
Parse Sales Report
Sales reports are typically tab-delimited:
# Extract download counts
grep "^[0-9]" sales-report.txt | cut -f7 | awk '{sum+=$1} END {print sum}'
# Extract revenue by app
grep "^[0-9]" sales-report.txt | awk -F'\t' '{revenue[$3]+=$14} END {for(app in revenue) print app, revenue[app]}'
# Extract top countries by downloads
grep "^[0-9]" sales-report.txt | awk -F'\t' '{country[$13]+=$7} END {for(c in country) print country[c], c}' | sort -rn | head -10Convert to JSON
#!/usr/bin/env python3
import csv
import json
import sys
with open(sys.argv[1], 'r') as tsv_file:
reader = csv.DictReader(tsv_file, delimiter='\t')
rows = list(reader)
with open('sales-report.json', 'w') as json_file:
json.dump(rows, json_file, indent=2)
print(f"Converted {len(rows)} rows to JSON")Run with:
python convert_sales.py sales-report.txtTroubleshooting
"Vendor number not found"
Problem: Invalid or missing vendor number.
Solution: Find your vendor number in App Store Connect:
- Go to Sales and Trends
- Click Reports
- Your vendor number is displayed at the top
Set it as an environment variable:
export ASC_VENDOR_NUMBER="YOUR_VENDOR_NUMBER""Report not available for date"
Problem: Report for the requested date doesn't exist yet.
Solution:
- Daily reports are available ~24 hours after the date
- Weekly reports are available on Sundays for the previous week
- Monthly reports are available ~14 days after month end
"Analytics request pending"
Problem: Analytics request is still processing.
Solution: Check request status:
asc analytics view --request-id "REQUEST_ID"Retry asc analytics view --request-id "REQUEST_ID" --paginate --output json until data[].instances is non-empty, then download.
"Failed to download report"
Problem: Network issues or API errors during download.
Solution: Retry with increased timeout:
export ASC_TIMEOUT=180s
asc analytics sales --vendor "..." --date "..." --output report.txtBest Practices
Automate daily downloads: Set up scheduled jobs to download reports automatically
Store reports securely: Reports contain sensitive financial data—encrypt and restrict access
Parse and analyze: Don't just download—parse reports and feed into analytics platforms
Set up monitoring: Alert on anomalies like sudden drop in downloads or revenue
Use environment variables: Store vendor number and other config in environment variables
Handle missing reports gracefully: Not all reports are available for all dates
Archive historical data: Keep long-term archives for trend analysis
Validate data: Cross-check totals with App Store Connect web interface
Related Commands
- TestFlight Analytics - Beta tester metrics
- Build Analytics - Build processing and distribution metrics
Guides App Store Submission (`guides/app-store-submission.mdx`)
App Store Submission
Publish with
publish appstore --submit, then usevalidateorsubmitfor follow-up checks
Automate the complete App Store submission process with the canonical
high-level publish command, then use the lower-level submission lifecycle
commands to inspect or cancel review state when needed.
Overview
Use these surfaces deliberately:
asc publish appstore --submit- canonical App Store publish pathasc release stage- prepare a version without submitting itasc validate- canonical readiness check before submissionasc submit status- inspect submission or version review stateasc submit cancel- cancel an active submission
In 1.0, the deprecated asc submit preflight and deprecated asc submit create
paths were removed.
Prerequisites
Before submitting, ensure you have:
- A processed build uploaded to App Store Connect
- App Store version created with required metadata
- All localizations configured with required fields
- Screenshots uploaded for at least one device type
- Privacy information and app review details completed
Canonical Publish Workflow
```bash theme={null}
asc versions create \
--app "YOUR_APP_ID" \
--version "1.0.0" \
--platform IOS
```
Or list existing versions:
```bash theme={null}
asc versions list --app "YOUR_APP_ID" --platform IOS
```* Description
* Keywords
* What's New text
* Marketing URL (if applicable)
Update missing localizations:
```bash theme={null}
asc app-setup info set \
--app "YOUR_APP_ID" \
--locale "en-US" \
--name "My App" \
--subtitle "Amazing app subtitle" \
--privacy-policy-url "https://example.com/privacy"
```
Update version-specific metadata:
```bash theme={null}
asc localizations update \
--version "VERSION_ID" \
--locale "en-US" \
--description "Full app description" \
--keywords "productivity,tools,utility" \
--whats-new "Bug fixes and improvements"
``````bash theme={null}
asc builds list \
--app "YOUR_APP_ID" \
--version "1.0.0" \
--build-number "42" \
--output json
```
Or get the latest build:
```bash theme={null}
asc builds list \
--app "YOUR_APP_ID" \
--sort "-uploadedDate" \
--limit 1
``````bash theme={null}
asc publish appstore \
--app "YOUR_APP_ID" \
--ipa "./build/MyApp.ipa" \
--version "1.0.0" \
--submit \
--confirm
```
Use `asc release stage` instead when you want the same preparation flow
without creating the review submission yet:
```bash theme={null}
asc release stage \
--app "YOUR_APP_ID" \
--version "1.0.0" \
--build-id "BUILD_ID" \
--copy-metadata-from "0.9.0" \
--confirm
```Lower-level submission lifecycle
Use the submit command family when you need to inspect or manage the review
submission itself instead of running the full publish pipeline.
Validate readiness
asc validate --app "YOUR_APP_ID" --version "1.0.0"Monitoring Submission Status
```bash theme={null}
asc submit status --id "SUBMISSION_ID"
```
Using version ID:
```bash theme={null}
asc submit status --version-id "VERSION_ID"
``````bash theme={null}
asc versions view --version-id "VERSION_ID" --output json
```
`asc versions view` is the canonical command here. If older scripts still
use the deprecated `asc versions get`, update them during your 1.0 migration.
The version state indicates the current stage:
* `PREPARE_FOR_SUBMISSION`: Metadata incomplete
* `WAITING_FOR_REVIEW`: Submitted, awaiting review
* `IN_REVIEW`: Currently being reviewed
* `PENDING_DEVELOPER_RELEASE`: Approved, awaiting your release
* `READY_FOR_SALE`: Live on the App Store
* `REJECTED`: Rejected by review teamManaging Submissions
Cancel a Submission
If you need to cancel a submission before review:
asc submit cancel --id "SUBMISSION_ID" --confirmOr cancel using version ID:
asc submit cancel --version-id "VERSION_ID" --confirmResubmit After Changes
If you need to make changes after submitting:
Complete Example: iOS App Submission
Here's a complete workflow for publishing an iOS app:
/* Detailed source-code truncated for AI context efficiency. */Preflight Validation
The CLI performs automatic preflight checks before submission:
Localization Validation
Checks that all required fields are present for each localization:
- Description
- Keywords
- What's New text (for updates)
If any fields are missing, the submission will fail with a detailed error:
Submit preflight failed: submission-blocking localization fields are missing:
- en-US: description, keywords
- es-ES: whats-new
Fix these with `asc apps info edit` (optionally using `--copy-from-locale`) before retrying `asc validate` or `asc publish appstore --submit`.Fixing Validation Errors
Copy complete localization from one locale to another:
asc apps info edit \
--app "YOUR_APP_ID" \
--locale "es-ES" \
--copy-from-locale "en-US"Then update locale-specific fields:
asc localizations update \
--version "VERSION_ID" \
--locale "es-ES" \
--description "Descripción de la aplicación" \
--keywords "productividad,herramientas"Troubleshooting
"No app store version localizations found"
Problem: The version has no localizations configured.
Solution: Add at least one localization:
asc localizations update \
--version "VERSION_ID" \
--locale "en-US" \
--description "App description" \
--keywords "keyword1,keyword2" \
--whats-new "What's new in this version""Failed to attach build"
Problem: The build may not be compatible with the version or is still processing.
Solution: Verify build status:
asc builds info --build-id "BUILD_ID"Ensure the build:
- Has finished processing
- Matches the app ID
- Supports the correct platform
- Has passed automatic checks
"Stale review submission found"
Problem: A previous submission attempt left an orphaned submission.
Solution: The CLI automatically cancels stale submissions. If you see this warning, it's informational and the new submission will proceed.
Submission Rejected
If your submission is rejected:
- Check rejection details in App Store Connect web interface
- Address the issues mentioned in the rejection
- Upload a new build if code changes are needed, or just update metadata
- Resubmit with the canonical publish command:
asc publish appstore \
--app "YOUR_APP_ID" \
--ipa "./build/MyApp.ipa" \
--version "1.0.0" \
--submit \
--confirmPlatform-Specific Considerations
macOS Apps
For macOS apps, specify the MAC_OS platform:
asc publish appstore \
--app "YOUR_APP_ID" \
--ipa "./build/MyMacApp.ipa" \
--version "1.0.0" \
--platform MAC_OS \
--submit \
--confirmtvOS Apps
For tvOS apps, use the TV_OS platform:
asc publish appstore \
--app "YOUR_APP_ID" \
--ipa "./build/MyTVApp.ipa" \
--version "1.0.0" \
--platform TV_OS \
--submit \
--confirmvisionOS Apps
For visionOS apps, use the VISION_OS platform:
asc publish appstore \
--app "YOUR_APP_ID" \
--ipa "./build/MyVisionApp.ipa" \
--version "1.0.0" \
--platform VISION_OS \
--submit \
--confirmBest Practices
Always use
--confirmflag: Prevents accidental submissionsTest with TestFlight first: Distribute to internal testers before submitting to the App Store
Validate metadata beforehand: Use
asc localizations listto verify all required fields are presentMonitor submission status: Check status regularly during the review process
Keep builds ready: Have builds processed and ready before starting the submission workflow
Use version IDs for lifecycle automation: Version IDs are reliable for
submit statusandsubmit cancelHandle stale submissions: The CLI automatically cleans up orphaned submissions, but verify in case of errors
Related Commands
- TestFlight Distribution - Test builds before App Store submission
- Metadata Management - Manage app metadata and localizations
- Screenshots - Upload App Store screenshots
- Build Management - Manage and monitor builds
Guides Apple Ads Playbooks (`guides/apple-ads-playbooks.mdx`)
Apple Ads Operator Playbooks
Practical Apple Ads workflows for safe setup, read-only checks, reporting, and guarded mutations
Use these playbooks when you are operating Apple Ads from asc in a terminal,
CI job, or agent run. The goal is to start read-only, confirm the ad-account context,
and only mutate resources after the target IDs and payload files are reviewed.
The primary examples use the direct Platform API v1 resource commands underasc ads with --ad-account. The deprecated Campaign Management API v5 tree
is isolated in the migration and legacy sections below.
Setup and Credential Safety
Apple Ads credentials are separate from App Store Connect API credentials.asc auth login does not configure asc ads.
Create a named Apple Ads profile and store the default ad account when you know it:
asc ads auth login \
--name "Marketing" \
--client-id "SEARCHADS_CLIENT_ID" \
--team-id "SEARCHADS_TEAM_ID" \
--key-id "KEY_ID" \
--private-key "$HOME/.asc/apple-ads-private-key.pem" \
--ad-account "987654" \
--networkFor CI, prefer secrets and environment variables over checked-in config:
export ASC_ADS_CLIENT_ID="SEARCHADS_CLIENT_ID"
export ASC_ADS_TEAM_ID="SEARCHADS_TEAM_ID"
export ASC_ADS_KEY_ID="KEY_ID"
export ASC_ADS_PRIVATE_KEY_PATH="$HOME/.asc/apple-ads-private-key.pem"
export ASC_ADS_AD_ACCOUNT_ID="987654"
export ASC_ADS_BYPASS_KEYCHAIN=1
asc ads apps search --ad-account "$ASC_ADS_AD_ACCOUNT_ID" --query "Example" --output jsonUse a read-only API command for CI validation so the run resolves the exportedASC_ADS_* credentials and org context instead of only listing stored profiles.
Use ASC_ADS_PRIVATE_KEY_B64 when your CI secret store handles single-line
values more reliably than PEM blocks. Keep ASC_ADS_ACCESS_TOKEN for cases
where another trusted system already minted a short-lived token.
Platform API v1 migration
For new automation, start with the Apple Ads Platform API v1 command tree:
asc ads me view --output json
asc ads acls list --output json
asc ads apps search --ad-account "987654" --query "Example" --output jsonThe legacy Campaign Management API v5 commands under asc ads v5 still run in
4.4.0 and print a deprecation warning on stderr. Apple retires v5 on January
26, 2027. A v5 command keeps its existing --org context; a v1 command uses
the separate --ad-account context. Do not pass an organization ID where a v1
ad account ID is required, or assume that the two IDs identify the same thing.
V1 query, report, and mutation bodies use the Platform API schemas. Put the
appropriate v1 object in --file; the CLI does not convert a v5 selector,
reporting request, numeric ID, or response envelope. V1 report pagination stays
in the request JSON, so do not add the legacy --paginate flag to a v1 report
command:
asc ads reports apps campaigns \
--ad-account "987654" \
--file report.json \
--output jsonPlatform v1 exposes one negative-keywords resource for both campaign and ad
group scope. Move the scope into the v1 query or mutation body rather than
looking for separate campaign-negative-keywords and ad-group-negative-keywords
commands. The v5 asc ads v5 reports preset helper remains a warning-producing
compatibility path; replace it with an explicit v1 report leaf and reviewed
request file.
Seven v5 leaves have no one-command v1 replacement in 4.4.0: product-page
countries, product-page devices, targeting-keyword bulk delete, campaign
negative-keyword bulk delete, ad-group negative-keyword bulk delete, and
impression-share report list and view. Related v1 commands are not drop-in
replacements for those contracts. In particular, geo search is not the v5
country/device mapping, insights impression-share is not the custom report
list/view API, and v1 has no bulk-delete operation.
For raw calls, keep asc ads v5 api request for v5 paths and its --org context.
It remains runnable with a warning and is not rewritten. Useasc ads api request --path v1/... --ad-account ... for a raw v1 call.
Account Access Inspection
Start outside an ad-account context. These commands do not require--ad-account:
asc ads acls list --output jsonUse the ACL response to confirm the ad account ID, then pin it explicitly for
the rest of the session:
export ASC_ADS_AD_ACCOUNT_ID="987654"
asc ads apps search --ad-account "$ASC_ADS_AD_ACCOUNT_ID" --query "Example" --limit 1 --output jsonWhen operating multiple Apple Ads accounts, avoid relying on the ambient
default. Pass both profile and ad account:
asc ads apps search \
--ads-profile "Marketing" \
--ad-account "987654" \
--query "Example" \
--output jsonSafe Read-Only Smoke Test
Use this sequence before an automation run or after rotating credentials:
ASC_BYPASS_KEYCHAIN=1 asc ads acls list --output json
ASC_BYPASS_KEYCHAIN=1 asc ads apps search --ad-account "987654" --query "Example" --limit 1 --output jsonExpected result: every command exits successfully, and the app search returns
either a result envelope or an empty result. Treat auth failures, missing account
errors, or unexpected account names as stop conditions.
When using stored Ads profiles, add profile validation before the API smoke
test:
asc ads auth status --validate --output jsonCampaign and Ad Group Inventory
Query campaigns first, then inspect ad groups under one confirmed campaign ID:
asc ads campaigns find \
--ad-account "987654" \
--file campaign-query.json \
--output json
asc ads ad-groups find \
--ad-account "987654" \
--file ad-group-query.json \
--output jsonFor a full v1 query, keep pagination in the request body:
asc ads campaigns find --ad-account "987654" --file campaign-query.json --output json
asc ads ad-groups find --ad-account "987654" --file ad-group-query.json --output jsonDo not use report payloads as inventory replacements. Reports answer
performance questions; list and find endpoints answer "what exists right now?"
questions.
Report Workflow
Use Platform API v1 for new report automation. Its body shape differs from v5:
{
"pagination": {"offset": 0, "pageSize": 20},
"filters": [
{"field": "campaignId", "operator": "EQUALS", "value": ["444555666"]}
],
"groupBy": ["countryOrRegion"],
"timeRange": {
"start": "2025-01-01",
"end": "2025-01-31",
"timeZone": "ORTZ",
"granularity": "DAILY"
}
}asc ads reports apps campaigns \
--ad-account "987654" \
--file platform-report.json \
--output jsonThe v1 report response keeps Apple's raw result and pagination fields.
Change offset and pageSize in the file to fetch another page. Runasc ads reports apps campaigns --help for body and option guidance; report
entities do not all accept the same groupBy values.
Legacy v5 report compatibility
The request below is the deprecated v5 shape. Keep it only while existing
automation migrates:
{
"startTime": "2026-05-01",
"endTime": "2026-05-31",
"returnRowTotals": true,
"returnGrandTotals": true,
"selector": {
"pagination": {
"offset": 0,
"limit": 100
},
"orderBy": [
{
"field": "impressions",
"sortOrder": "DESCENDING"
}
]
}
}Run the campaign report with an explicit org and output format:
asc ads v5 reports campaigns \
--org "123456" \
--file reporting-request.json \
--output jsonFor ad-group, keyword, search-term, or ad-level reports, first verify the parent
campaign ID with asc ads v5 campaigns list. Report pagination lives inside the
Apple Ads request body, so update the file's selector pagination rather than
adding --paginate.
Raw API Safety
Use asc ads api request for Platform API v1 debugging, newly added Apple
fields, or support captures. Prefer first-class commands for routine work.
Read-only raw request:
asc ads api request \
--method POST \
--path v1/campaigns/query \
--ad-account "987654" \
--file campaign-query.json \
--output jsonRaw v1 requests only accept Platform API v1 paths orhttps://api.ads.apple.com/v1/... URLs. DELETE requests and risky mutation
payloads require --confirm; multipart asset upload must useasc ads assets upload.
For legacy troubleshooting only, keep asc ads v5 api request with its v5 path
and --org context:
asc ads v5 api request \
--method POST \
--path v5/campaigns/find \
--org "123456" \
--file selector.json \
--output jsonRaw requests only accept Apple Ads v5 paths or Apple Ads API URLs. DELETE
requests require --confirm; do not pass it until the command line includes
the exact target path you intend to delete.
Mutation Safety Checklist
Before running create, update, delete, or bulk commands:
- Run the matching
--helpcommand and confirm required flags. - Resolve account access with
asc ads acls list --output json. - Run a read-only resource check:
asc ads apps search --ad-account "987654" --query "Example" --limit 1 --output json. - Pass
--ads-profileand--ad-accountexplicitly when more than one account is possible. - Store request bodies in reviewed JSON files; avoid shell-escaped inline JSON.
- Create paused or clearly named test resources when validating workflow shape.
- Print or log the target IDs before using
--confirm. - Prefer deleting temporary parent campaigns only after confirming Apple allows the cleanup.
Example guarded delete:
asc ads campaigns view --ad-account "987654" --campaign "campaign-id" --output json
asc ads campaigns delete --ad-account "987654" --campaign "campaign-id" --confirmTroubleshooting Auth and Source Precedence
Use the auth doctor first:
asc ads auth doctor --output json
asc ads auth status --verbose --output jsonCommon fixes:
- Missing ad account: pass
--ad-account, exportASC_ADS_AD_ACCOUNT_ID, or store--ad-accountduring login. - Wrong profile: pass
--ads-profile "Marketing"or exportASC_ADS_PROFILE. - Keychain prompts in CI: export
ASC_ADS_BYPASS_KEYCHAIN=1. - Mixed sources: set
ASC_ADS_STRICT_AUTH=1to fail when both profile and env token/key sources are present. - Token-only runs:
ASC_ADS_ACCESS_TOKENstill needs--ad-account,ASC_ADS_AD_ACCOUNT_ID, or a stored profile account for scoped v1 commands.
When debugging API responses, use an explicit output format and redact tokens,
client IDs, team IDs, key IDs, org IDs, and account names before sharing logs.
Guides Asset Library (`guides/asset-library.mdx`)
Asset Library
Upload, inspect, and reuse App Store images and videos
Asset Library stores media independently of an app version. Uploading creates a library asset; a placement assigns that asset to a localization. App Review and storefront publication have their own requirements.
Find a library and inspect its assets
asc asset-library view --app APP_ID
asc asset-library images list --library-id LIBRARY_ID --paginate
asc asset-library videos list --library-id LIBRARY_ID --paginate
asc asset-library specsUse images view --id IMAGE_ID or videos view --id VIDEO_ID to inspect one asset. Use images placements --id IMAGE_ID or videos placements --id VIDEO_ID to inspect its usage. JSON reads preserve Apple's resource envelope, including unknown fields. Collection commands support --limit, --next, and --paginate; --output table or --output markdown provides human-readable output.
Filter and sort collections
asc asset-library images list --library-id LIBRARY_ID \
--category CREATIVE_ASSETS --state APPROVED --sort referenceName
asc asset-library videos list --library-id LIBRARY_ID \
--state FAILED --sort=-lastModifiedDate --paginate
asc asset-library images list --library-id LIBRARY_ID --spec-id SPEC_ID
asc asset-library images list --library-id LIBRARY_ID \
--reference-name "Summer header" --sort=-createdDate
asc asset-library videos list --library-id LIBRARY_ID --id VIDEO_ID --sort createdDateFilter by --category, --state, --spec-id, --reference-name, or --id. Filters and sort fields accept comma-separated values. Supported sort fields are referenceName, createdDate, and lastModifiedDate; prefix a field with - for descending order. Reference names are internal App Store Connect names. Apple validates state values and filter support. The CLI does not expose an --archived filter.
These filters belong to image/video list commands. Do not combine them or a library selector with --next: use Apple's next URL unchanged. --paginate follows those URLs automatically.
Upload images and videos
The default upload category is CREATIVE_ASSETS, for headers, search creative media, and events. Use APP_SCREENSHOTS_AND_PREVIEWS for media showing your app in use.
asc asset-library images upload --library-id LIBRARY_ID --file ./header.png
asc asset-library videos upload --library-id LIBRARY_ID --file ./header.mp4
asc asset-library images upload --library-id LIBRARY_ID \
--file ./duo-screenshot.png --category APP_SCREENSHOTS_AND_PREVIEWS
asc asset-library videos upload --library-id LIBRARY_ID \
--file ./duo-preview.mp4 --category APP_SCREENSHOTS_AND_PREVIEWS
asc asset-library images upload --library-id LIBRARY_ID \
--file ./cms-8f3a21.png --reference-name "Hero en-US 3.14"Apple derives each asset's file name and default reference name from the uploaded file, and the file name cannot change after upload. Pass --reference-name to set the searchable reference name at upload instead of running asc asset-library images rename (or videos rename) afterwards.
Image files must meet the selected category's media specifications. Screenshots and creative images cannot contain an alpha channel or transparency, even when all pixels appear opaque.
Video upload requires ffprobe from FFmpeg and a regular, nonempty .mp4, .m4v, or .mov file within the upload size limit. Before reservation, the CLI checks the staged file against Apple's current reference specifications: category, dimensions, aspect ratio, duration, frame rate, required audio, extension, container MIME type, and file size. Apple's processing remains authoritative for codec acceptance.
Creative asset specifications distinguish header/search videos, typically 5–30 seconds at 30 or 60 fps, from app previews. App preview specifications require 15–30 seconds and have their own delivery and encoding requirements. Check asc asset-library specs for the current reference data rather than treating these examples as a complete specification.
ASC_UPLOAD_TIMEOUT bounds upload and processing. Video uploads default to 30m, because Apple commonly takes 10 to 15 minutes to process a video; images use the general 5m default. Processing may continue after the command times out; increase the timeout when needed. A failure after reservation prints a partial receipt containing the asset ID. Inspect that ID with asc asset-library videos view --id VIDEO_ID (or images view) before retrying or deleting it; the CLI does not delete the reservation automatically.
Video transfer or a completed poster thumbnail does not prove the video is playable. Readiness requires the expected preparation state, a matched specification, and a usable video asset URL. Processing failures return an error with the reserved ID.
iPhone Duo sizes
Duo screenshot sizes are 1398×2034 for the outer display and 2007×2853 for the inner display, with landscape counterparts. Apple's documented Duo app preview delivery sizes are 886×1920 or 1920×886, which differ from the physical display sizes. Use the accepted preview delivery size when preparing a video.
See screenshot specifications and app preview specifications. Native asset placements use the IPHONE_DUO_PROFILE group.
Manage asset names, lifecycle, and poster frames
asc asset-library images rename --id IMAGE_ID --name "Summer header"
asc asset-library images archive --id IMAGE_ID --confirm
asc asset-library images unarchive --id IMAGE_ID
asc asset-library images delete --id IMAGE_ID --confirm
asc asset-library videos set-poster-frame --id VIDEO_ID --time-code 00:00:01:00Videos also support rename, archive, unarchive, and delete with --id. The reference name is internal to App Store Connect.
Only approved assets can be archived. Archived assets cannot be used in new placements; restoring an asset makes it available for new uses, subject to Apple's checks. Apple validates whether an asset can be deleted based on its current state and usage. To remove an assignment while retaining the media, delete its placement instead.
Poster changes affect every placement using that library video. Poster frames cannot be edited after the asset is approved. --time-code accepts HH:MM:SS:FF or HH:MM:SS.mmm; the CLI does not interpret frame values as a particular playback frame rate.
Apple validates lifecycle eligibility. These commands do not submit assets for review.
Reuse media in localized placements
Placement reads, creation, swaps, and deletion are available under four command groups. Reordering is available only for version, CPP, and PPO localizations:
| Context | Command group |
|---|---|
| App version localization | asc localizations placements |
| Custom product page localization | asc product-pages custom-pages localizations placements |
| Product page optimization treatment localization | asc product-pages experiments treatments localizations placements |
| In-App Event localization | asc app-events localizations placements |
Use the localization ID for the selected context, not its parent page, treatment, event, or version ID.
asc localizations placements list --localization-id LOCALIZATION_ID \
--include image,video --sort placementGroupPosition --paginate
asc localizations placements create --localization-id LOCALIZATION_ID \
--image-id IMAGE_ID --placement-type PRODUCT_PAGE_HEADER_ASSET
asc localizations placements create --localization-id LOCALIZATION_ID \
--video-id VIDEO_ID --placement-type APP_PREVIEW --placement-group IPHONE_DUO_PROFILE
asc app-events localizations placements create --localization-id EVENT_LOCALIZATION_ID \
--image-id IMAGE_ID --placement-type EVENT_CARD_ASSETSupply exactly one --image-id or --video-id. Screenshots require images; app previews require videos and an explicit device --placement-group. Header, search, and event creative media use DEFAULT_PROFILE. Header/search types are PRODUCT_PAGE_HEADER_ASSET and APP_STORE_SEARCH_RESULTS_ASSET. Events accept only EVENT_CARD_ASSET and EVENT_DETAILS_PAGE_ASSET.
Version localizations also support IMESSAGE_APP_SCREENSHOT. CPP and PPO creation do not support iMessage screenshot assignments. Apple validates media compatibility and available slots. Placement lists accept comma-separated type/group filters, related image,video inclusion, sorting, and pagination.
Replace an existing placement
asc product-pages custom-pages localizations placements swap \
--localization-id CPP_LOCALIZATION_ID --placement-id OLD_PLACEMENT_ID \
--image-id IMAGE_ID --placement-type PRODUCT_PAGE_HEADER_ASSET --confirmswap uses a live-supported extension outside the OpenAPI 4.5.1 create schema to link the existing placement to its replacement in one request. It accepts an image or video of the same media type as the existing placement and requires --confirm. Apple requires the replacement to keep the existing placement's group, so the command first reads that group from the existing placement and sends it with the swap; there is no group flag. Apple validates the existing context, media, and editability. A failed swap does not trigger a separate deletion or fallback removal.
For an already-live version, Apple's website guides users through Next → Publish when selecting an approved replacement. The CLI's swap receipt does not promise storefront publication; no separate public publication command is available. New-version assets still follow the version's review and release workflow.
Reorder and remove placements
asc localizations placements reorder --localization-id LOCALIZATION_ID \
--placement-group IPHONE_DUO_PROFILE --placement-ids FIRST_ID,SECOND_ID
asc localizations placements delete --id PLACEMENT_ID --confirm
asc localizations placements delete --placement-ids FIRST_ID,SECOND_ID --confirmReordering sends unique placement IDs in the requested order for version, CPP, or PPO localizations. Apple validates membership in the localization and group. Omitted placements are not deleted by the CLI. Event placement reordering is not supported.
Multiple removal uses sequential public DELETE requests. It is not atomic: processing stops at the first failure, prints a partial receipt with deletedPlacementIds and failedPlacementId, and returns a nonzero error. Earlier successful removals are not rolled back. Inspect current state before retrying an uncertain failure.
Placement deletion keeps the library media. IDs control removal; the command does not check that an ID belongs to the command group's localization or placement type.
Review and publication
Upload, placement, reorder, and lifecycle commands do not submit App Review. The CLI supports asc review items add with --item-type appAssetLibraryImages or appAssetLibraryVideos and --item-id using Apple's documented review relationships. Live image/video review-item acceptance has not been independently verified. A community image submission report reached WAITING_FOR_REVIEW; video acceptance and final asset approval remain unverified.
Separate asset review requires an approved app version. For the first version, include the assets with that version's submission. Assets assigned to a version, CPP, PPO test, or event are reviewed with that item; active submissions can restrict standalone submission. Apple's standalone submission guide explains platform selection and eligibility.
This guide's examples stop before submitting review or publishing. A processed upload, an ACTIVE placement, or a successful swap is not evidence of App Review approval or live storefront content.
Guides Automation (`guides/automation.mdx`)
Workflow Automation
Automate complex App Store Connect workflows with multi-step sequences
Define and execute multi-step automation workflows that combine multiple CLI commands and shell scripts.
Overview
Workflows allow you to:
- Chain multiple
asccommands together - Run shell scripts and commands
- Pass data between steps
- Handle errors with hooks
- Execute conditional steps
- Compose reusable sub-workflows
Quick Start
Create a Workflow File
Create .asc/workflow.json in your project:
{
"env": {
"APP_ID": "123456789",
"VERSION": "1.0.0"
},
"workflows": {
"beta": {
"description": "Distribute latest build to beta testers",
"steps": [
{
"name": "get_latest_build",
"run": "asc builds list --app $APP_ID --sort -uploadedDate --limit 1 --output json"
},
{
"name": "add_to_group",
"run": "asc builds add-groups --build-id $BUILD_ID --group $GROUP_ID"
}
]
}
}
}Run a Workflow
asc workflow run beta BUILD_ID:abc123 GROUP_ID:def456List Available Workflows
asc workflow listValidate Workflow File
asc workflow validateWorkflow Structure
Basic Workflow
{
"env": {
"GLOBAL_VAR": "value"
},
"before_all": "echo Starting workflows",
"after_all": "echo All workflows complete",
"error": "echo Error occurred",
"workflows": {
"workflow_name": {
"description": "What this workflow does",
"env": {
"WORKFLOW_VAR": "value"
},
"steps": [
{
"name": "step_1",
"run": "command to execute"
}
]
}
}
}Environment Variables
Global Environment (env at root):
- Available to all workflows
- Merged with system environment
Workflow Environment (env in workflow):
- Scoped to specific workflow
- Overrides global variables
Runtime Parameters:
- Passed via command line:
KEY:VALUE - Override workflow environment
Precedence for steps in the workflow you invoked (highest to lowest):
- Runtime parameters
- Workflow
env - Global
env - System environment
Precedence for steps inside a called sub-workflow (highest to lowest):
- The calling step's
with - Runtime parameters
- Calling workflow's
env - Global
env - Called workflow's own
env - System environment
A sub-workflow's own env is the lowest-priority map in its own scope, so it
provides defaults the caller overrides. The calling step's with is the only
source that outranks runtime parameters: a key hardcoded there cannot be
overridden with KEY:VALUE. with values are literal — only${steps.NAME.OUTPUT} references are interpolated.
Hooks
before_all: Runs before any workflow
"before_all": "asc auth status"after_all: Runs after successful workflow
"after_all": "echo Success! && notify-send 'Workflow Complete'"error: Runs when workflow fails
"error": "echo Failed! && notify-send 'Workflow Failed'"Step Types
Command Steps
Execute shell commands:
{
"name": "list_builds",
"run": "asc builds list --app $APP_ID --limit 5"
}Conditional Steps
if names a variable and runs the step only when that variable holds a truthy
value: 1, true, yes, y, or on (case-insensitive, surrounding
whitespace ignored):
{
"name": "optional_step",
"if": "INSPECT_BUILD",
"run": "asc builds info --build-id $BUILD_ID"
}Run it with asc workflow run beta INSPECT_BUILD:true. The variable is resolved
from the workflow environment first (runtime parameters, with, workflow env,
global env) and then from the process environment.
Every other value is falsy, including an unset variable, an empty string, and
any other non-empty value. "if": "BUILD_ID" with BUILD_ID:abc123, or"if": "IPA_PATH" with a path, always skips the step. Use if as a boolean
feature flag, never as a "was this ID supplied?" check.
Sub-Workflow Steps
Call another workflow:
{
"workflow": "preflight",
"with": {
"EXTRA_VAR": "value"
}
}The called workflow receives EXTRA_VAR in addition to its own environment.
Passing Data Between Steps
Each step runs in its own shell, so a variable assigned inside one step is gone
by the next one. BUILD_ID=$(...) in step 2 is not visible to step 3.
To move a value forward, declare outputs on a step and reference them as${steps.<step_name>.<OUTPUT_NAME>}. The step must print JSON on stdout, so
pass --output json to asc commands. Each output is a JSON path rooted at$. and may only walk object keys (no array indexes):
{
"steps": [
{
"name": "resolve_build",
"run": "asc builds info --app $APP_ID --latest --platform IOS --output json",
"outputs": {
"BUILD_ID": "$.data.id"
}
},
{
"name": "distribute",
"run": "asc builds add-groups --build-id ${steps.resolve_build.BUILD_ID} --group \"$GROUP_NAME\""
}
]
}Interpolated values are shell-quoted, and numbers keep the exact form the
command emitted (a build number of 42 stays 42). Declared outputs are also
persisted in the run-state file, so never map a secret into outputs.
Complete Workflow Examples
Beta Distribution Workflow
{
"env": {
"APP_ID": "123456789"
},
"workflows": {
"beta": {
"description": "Distribute latest build to TestFlight beta group",
"env": {
"GROUP_NAME": "External Beta",
"DISTRIBUTE": "false"
},
"steps": [
{
"name": "validate_auth",
"run": "asc auth status"
},
{
"name": "resolve_build",
"run": "asc builds info --app $APP_ID --latest --platform IOS --output json",
"outputs": {
"BUILD_ID": "$.data.id"
}
},
{
"name": "add_build_to_group",
"if": "DISTRIBUTE",
"run": "asc builds add-groups --build-id ${steps.resolve_build.BUILD_ID} --group \"$GROUP_NAME\"",
"retry": {
"max_attempts": 6,
"delay": "10s"
},
"timeout": "2m"
},
{
"name": "notify_success",
"if": "DISTRIBUTE",
"run": "echo Build ${steps.resolve_build.BUILD_ID} distributed to $GROUP_NAME"
}
]
}
}
}Run with:
asc workflow run beta DISTRIBUTE:trueLeave DISTRIBUTE off to resolve the latest build without touching the group:
asc workflow run betaApp Store Submission Workflow
{
"env": {
"APP_ID": "123456789",
"IPA_PATH": "./build/MyApp.ipa",
"PLATFORM": "IOS",
"SUBMIT": "false"
},
"before_all": "echo Starting submission workflow",
"after_all": "echo Submission complete!",
"error": "echo Submission failed - check logs",
"workflows": {
"release": {
"description": "Publish latest build to the App Store",
"steps": [
{
"name": "validate_metadata",
"run": "asc localizations list --version $VERSION_ID --output json | jq -e '.data | length > 0'"
},
{
"name": "ensure_ipa",
"run": "test -f \"$IPA_PATH\""
},
{
"name": "publish_release",
"if": "SUBMIT",
"run": "asc publish appstore --app $APP_ID --ipa $IPA_PATH --version $VERSION --submit --confirm"
},
{
"name": "check_status",
"run": "asc submit status --version-id $VERSION_ID"
}
]
}
}
}Run with:
asc workflow run release SUBMIT:true VERSION:1.0.0 VERSION_ID:abc123SUBMIT:true is what makes publish_release execute. Gating it on IPA_PATH
instead would skip the submission on every run, because ./build/MyApp.ipa is
not a truthy value. Presence of the file belongs in a real check such as theensure_ipa step above. Drop SUBMIT:true for a metadata-only dry pass:
asc workflow run release VERSION:1.0.0 VERSION_ID:abc123Complete Release Workflow
/* Detailed source-code truncated for AI context efficiency. */Run with:
asc workflow run release SUBMIT:true VERSION:2.0.0create_version declares VERSION_ID once, and every later sub-workflow reads
it as ${steps.create_version.VERSION_ID}. Step names that declare outputs
must stay unique across workflows that can run together, which is why the
producer is named once and referenced everywhere else.
CI/CD Integration
GitHub Actions
name: Release Workflow
on:
workflow_dispatch:
inputs:
version:
description: 'Version number (e.g., 1.0.0)'
required: true
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install asc CLI
run: |
curl -fsSL https://raw.githubusercontent.com/rorkai/App-Store-Connect-CLI/main/install.sh | bash
- name: Configure credentials
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_PRIVATE_KEY: ${{ secrets.ASC_PRIVATE_KEY }}
ASC_APP_ID: ${{ secrets.ASC_APP_ID }}
run: |
echo "Credentials configured"
- name: Run release workflow
env:
VERSION: ${{ github.event.inputs.version }}
run: |
asc workflow run release "VERSION:$VERSION"
- name: Upload workflow output
uses: actions/upload-artifact@v3
with:
name: workflow-output
path: /tmp/workflow-*.logGitLab CI
stages:
- release
release:production:
stage: release
tags:
- linux
script:
- curl -fsSL https://raw.githubusercontent.com/rorkai/App-Store-Connect-CLI/main/install.sh | bash
- asc workflow run release VERSION:$VERSION
variables:
VERSION: "1.0.0"
only:
- tags
when: manualAdvanced Features
Dry Run Mode
Preview workflow execution without running commands:
asc workflow run beta --dry-runShows:
- Steps that would be executed
- Environment variables
- Command interpolation
- Workflow order
Private Workflows
Mark workflows as private (only callable from other workflows):
{
"workflows": {
"helper": {
"private": true,
"description": "Helper workflow - not directly callable",
"steps": [
{"name": "help", "run": "echo Helping..."}
]
},
"main": {
"description": "Main workflow that calls helper",
"steps": [
{"workflow": "helper"}
]
}
}
}# Works
asc workflow run main
# Fails - private workflow
asc workflow run helperList including private workflows:
asc workflow list --allCustom Workflow File
Use a different workflow file:
asc workflow run beta --file ./custom-workflows.jsonPretty JSON Output
asc workflow run beta --prettyBest Practices
Use descriptive names: Name workflows and steps clearly
Add descriptions: Document what each workflow does
Validate before running: Always run
asc workflow validateafter editingUse environment variables: Parameterize workflows for reusability
Handle errors: Define
errorhooks to clean up on failureTest with dry-run: Use
--dry-runto preview executionKeep workflows focused: Break complex workflows into composable sub-workflows
Version control workflows: Store
.asc/workflow.jsonin gitDocument required parameters: Note required
KEY:VALUEparameters in descriptionsUse private workflows: Extract common logic into private sub-workflows
Security Considerations
Read-only runs
Export ASC_READ_ONLY=1 (or pass the global --read-only flag) when a workflow, CI job, or agent session is only allowed to inspect App Store Connect. Every command still runs, but any POST, PATCH, PUT, or DELETE is refused before it leaves the process with exit code 6. This is enforced in the HTTP clients, not per command, so it holds for every command in the workflow, and workflow steps inherit the mode as ASC_READ_ONLY=1 so a nested asc call is refused too. Steps that call other tools are outside the guarantee. See Read-only mode.
ASC_READ_ONLY=1 asc workflow run auditSafe Practices
// Good - uses environment variable
"run": "asc builds list --app $APP_ID"
// Bad - hardcoded sensitive data
"run": "asc builds list --app 123456789"
// Good - credentials from environment
"env": {
"ASC_KEY_ID": "$CI_ASC_KEY_ID"
}
// Bad - credentials in workflow file
"env": {
"ASC_KEY_ID": "ABC123XYZ"
}Troubleshooting
Validation Errors
Problem: asc workflow validate reports errors.
Solution: Check for:
- Circular workflow dependencies
- Missing workflow definitions
- Invalid JSON syntax
- Unknown step types
"Workflow not found"
Problem: Workflow name doesn't exist in file.
Solution: List available workflows:
asc workflow listVariable Not Interpolated
Problem: $VARIABLE appears literally in output.
Solution: Ensure the variable is:
- Defined in
env(global or workflow) - Passed as runtime parameter:
VARIABLE:value - Available in system environment
Step Fails Silently
Problem: Step executes but produces no output.
Solution:
- Check step output is written to stderr (workflow stdout is JSON-only)
- Enable verbose mode (if available)
- Add explicit
echostatements
Workflow Hangs
Problem: Workflow stops and doesn't complete.
Solution:
- Check for interactive commands (not supported)
- Verify commands don't wait for user input
- Add timeout to long-running commands
Related Topics
- TestFlight Distribution - Automate beta distribution
- App Store Submission - Automate submissions
- Metadata Management - Automate metadata updates
- Screenshots - Automate screenshot workflows
Guides Code Signing (`guides/code-signing.mdx`)
Code signing
Prepare bundle IDs, certificates, private keys, and provisioning profiles for local or CI builds.
A downloaded certificate is not a signing identity. The certificate contains a public key; Xcode also needs the matching private key. App Store Connect can return certificates and provisioning profiles, but it cannot return a private key after the certificate request has been created.
Use the signing command reference for every flag, profile-type table, encrypted repository format, encryption guarantees, multi-target manifest, CI example, rotation and revocation sequence, and troubleshooting case. This guide explains which workflow to choose.
Existing identity on one Mac
If the matching private key already exists in the selected macOS keychain, fetch the public certificate and provisioning profile:
asc signing fetch \
--app APP_ID \
--bundle-id com.example.app \
--profile-type IOS_APP_STORE \
--output .asc/signing/app-store \
--format json \
--prettyInstall the profile by file rather than copying it to a guessed Xcode directory:
asc profiles local install \
--path .asc/signing/app-store/PROFILE.mobileprovision \
--output jsonThe .cer file is useful for inspection and verification. Importing it without its private key does not make the machine able to sign.
New certificate
Generate the private key and certificate request locally, then send the request to App Store Connect:
mkdir -p .asc/signing .asc/secrets
chmod 700 .asc/signing .asc/secrets
certificate_response=.asc/signing/distribution-certificate.json
asc certificates create \
--certificate-type IOS_DISTRIBUTION \
--generate-csr \
--key-out .asc/secrets/distribution.key \
--csr-out .asc/signing/distribution.csr \
--output json \
--pretty > "$certificate_response"The private key remains in .asc/secrets/distribution.key. Do not commit it, print it, or upload it as a CI artifact. Back it up through encrypted signing sync before removing the bootstrap copy.
The key and CSR are written before the API request. After a timeout or ambiguous failure, first list certificates and check whether the request created one whose public key matches the preserved private key. Do not rerun --generate-csr or use --force:
asc certificates list \
--certificate-type IOS_DISTRIBUTION \
--paginate \
--output json \
--pretty > .asc/signing/distribution-certificates-after-ambiguous-create.jsonWhen a matching certificate exists, retrieve that resource into a fresh response file:
certificate_response=.asc/signing/distribution-certificate-recovered.json
asc certificates view \
--id MATCHING_CERTIFICATE_ID \
--output json \
--pretty > "$certificate_response"If no matching certificate exists, retry with the preserved CSR and a fresh response file:
certificate_response=.asc/signing/distribution-certificate-retry.json
asc certificates create \
--certificate-type IOS_DISTRIBUTION \
--csr .asc/signing/distribution.csr \
--output json \
--pretty > "$certificate_response"Shared App Store identity
Read the certificate resource ID and public certificate from the same create response. signing sync push uses the SHA-256 fingerprint to bind the generated private key, while profile reconciliation uses the resource ID to select that same certificate:
CERTIFICATE_ID="$(
jq -r '.data.id' "$certificate_response"
)"
CERTIFICATE_SHA256="$(
jq -r '.data.attributes.certificateContent' \
"$certificate_response" \
| openssl base64 -d -A \
| openssl x509 -inform DER -outform DER \
| shasum -a 256 \
| awk '{print toupper($1)}'
)"signing sync pull does not transport the App Store Connect certificate resource ID. Save CERTIFICATE_ID as a protected, non-secret CI variable named ASC_DISTRIBUTION_CERTIFICATE_ID, scoped to the environment that uses this signing repository. Keep that variable paired with the synced identity and update both during rotation.
Push that verified private key, certificate, and profile into a private encrypted Git repository:
chmod 600 \
~/.config/asc/signing-sync-password \
.asc/secrets/distribution.key
asc signing sync push \
--bundle-id com.example.app \
--profile-type IOS_APP_STORE \
--repo [email protected]:team/signing.git \
--password-file ~/.config/asc/signing-sync-password \
--private-key .asc/secrets/distribution.key \
--identity-sha256 "$CERTIFICATE_SHA256" \
--create-missing \
--output json \
--prettyFor an app with embedded targets, use one strict target manifest so the repository update covers all bundle identifiers in one commit:
{
"schemaVersion": 1,
"targets": [
{"bundleId": "com.example.app"},
{"bundleId": "com.example.app.widget"}
]
}asc signing sync push \
--targets-file ./signing-targets.json \
--profile-type IOS_APP_STORE \
--repo [email protected]:team/signing.git \
--password-file ~/.config/asc/signing-sync-password \
--private-key .asc/secrets/distribution.key \
--identity-sha256 "$CERTIFICATE_SHA256" \
--create-missing \
--output jsonThe same profile type, certificate filter, identity, and device set apply to every target. A batch can create profiles before the final repository push, so a later failure may leave those immutable App Store Connect profiles in place.
What AES-256-GCM, PBKDF2, scrypt, and authenticated metadata do and do not guarantee is in What the encrypted store protects.
Pull in CI
Write the repository password from the CI secret manager to a runner-private file, then pull the signing assets. signing sync already forces noninteractive Git (GIT_TERMINAL_PROMPT=0 and, unless you overrode SSH, GIT_SSH_COMMAND=ssh -o BatchMode=yes); keep any custom SSH wrapper equally noninteractive so a prompt cannot hang the runner. The full CI example lives in the signing command reference.
set +x
umask 077
signing_tmp="$(mktemp -d "$RUNNER_TEMP/asc-signing.XXXXXX")"
password_file="$signing_tmp/repository-password"
cleanup_signing_pull() {
rm -f "$password_file"
rmdir "$signing_tmp" >/dev/null 2>&1 || true
}
trap cleanup_signing_pull EXIT
printf '%s' "$SIGNING_SYNC_PASSWORD" > "$password_file"
mkdir -p .asc/signing
chmod 700 .asc/signing
asc signing sync pull \
--repo [email protected]:team/signing.git \
--password-file "$password_file" \
--output-dir .asc/signing/pulled \
--output json > .asc/signing/pull-result.jsonThe result lists private identities under sensitiveFiles. Install the pulled
PKCS#12 into a new dedicated keychain when separate archive and export commands
need the identity to persist:
identity_path="$(
jq -er '
[.sensitiveFiles[]? | select(endswith(".p12"))]
| if length == 1 then .[0]
else error("expected exactly one signing identity")
end
' .asc/signing/pull-result.json
)"
keychain_path="$signing_tmp/release.keychain-db"
keychain_password_file="$signing_tmp/keychain-password"
cleanup_signing() {
asc signing keychain delete \
--keychain "$keychain_path" \
--confirm >/dev/null 2>&1 || true
rm -f "$password_file" "$keychain_password_file"
rmdir "$signing_tmp" >/dev/null 2>&1 || true
}
trap cleanup_signing EXIT
openssl rand -base64 32 > "$keychain_password_file"
asc signing keychain install \
--identity ".asc/signing/pulled/$identity_path" \
--identity-password-file "$password_file" \
--keychain "$keychain_path" \
--keychain-password-file "$keychain_password_file" \
--add-to-search-list \
--confirm \
--output json
jq -r '.files[] | select(test("\\.(mobileprovision|provisionprofile)$"))' \
.asc/signing/pull-result.json \
| while IFS= read -r profile_path; do
asc profiles local install \
--path ".asc/signing/pulled/$profile_path" \
--output json
doneThe receipt supplies the identity's complete relative path. This example
requires exactly one pulled PKCS#12 identity and stops if that invariant is not
met. The identity password is the encrypted-repository password because sync
normalizes the pulled PKCS#12 under that secret. The keychain password is a
separate generated secret. The install command is macOS-only, requires a
cgo-enabled build, refuses an existing destination, and rolls back the new
keychain if import, codesigning verification, or search-list activation fails.
It does not change the default keychain. The run-owned temporary directory
ensures the cleanup trap cannot delete a pre-existing keychain if installation
fails.
The keychain remains available after the command succeeds. Keep archive and
export inside the lifetime of the cleanup trap on disposable CI workers; for a
long-lived machine, omit the trap only when the dedicated keychain is intended
to remain. Do not upload the decrypted directory, password files, or keychain.
Bootstrap a fresh CI runner
A fresh macOS runner starts with an App Store Connect API key and nothing else.
Before xcodebuild can sign, it needs a private key with its certificate, a
provisioning profile for every target, a keychain that codesign can use, and
project build settings that name those profiles. asc covers each step:
| Step | Command |
|---|---|
| Authenticate | ASC_KEY_ID, ASC_ISSUER_ID, and ASC_PRIVATE_KEY_B64 from the CI secret store |
| Create or obtain the identity and profiles | First run: asc signing fetch --create-missing --create-missing-certificate. Later runs: asc signing sync pull |
| Keep the identity for later runs | asc signing sync push --identity ... |
| Install the identity and profiles | asc signing keychain install --confirm and asc profiles local install |
| Point the project at the profiles | asc xcode signing plan --profile ..., then asc xcode signing apply --confirm |
| Archive and export | asc xcode archive, then asc xcode export --export-options ... |
| Clean up | asc signing keychain delete --confirm |
Provide the API key through environment variables
from the CI secret store. The key's role must allow creating certificates and
profiles for the first run.
First run: one pass from API key to signed archive
When the team has no active certificate of the required type, onesigning fetch creates the private key, certificate signing request,
certificate, profile, and password-protected .p12. The private key exists only on this
runner, so the script pushes the new identity to the encrypted store before it
builds anything. A disposable runner that skips that push loses the key when
the job ends.
/* Detailed source-code truncated for AI context efficiency. */Read these boundaries before relying on the script:
- A certificate is created only when no active certificate of the inferred type
exists. Otherwise the receipt reportscertificateCreationState: "reused"and
has nop12Path, so thejq -elookup stops the script underset -e. The
runner cannot sign with a certificate whose private key lives elsewhere; push that identity
once from the machine that holds it, as in Shared App Store identity. --create-missing-certificatefetches one bundle ID and cannot be combined
with--match-extensions. For an app with extensions, run a secondasc signing fetch --match-extensions --create-missinginto another output
directory after the certificate exists. Pass everyprofileFilefrom itsresultstoprofiles local installand toxcode signing planas repeated--profileflags. Push the identity for all of those bundle IDs withsigning sync push --bundle-id com.example.app --match-extensions, or with a--targets-filemanifest that lists each bundle ID as in
Shared App Store identity; the manifest takes
bundle IDs, not profile paths..asc/signing/bootstrapholds the private key, CSR, and.p12. Do not upload
it as an artifact. The.p12password in this script is a generated secret
that exists only for this job; the pushed copy is re-protected with the
store password.xcode signing planexits 0 withready: falsewhen a target has no matching
profile, so the script checksready.xcode signing applyedits the
checked-out project file and refuses a blocked or stale plan.sync pushcan also create the identity itself:asc signing sync push --create-missing --create-missing-certificate --identity-password-file PATH.
Use that form in a one-time bootstrap job when you do not want the first run
to build.
Every later run
Once the store holds the identity, a fresh runner pulls it instead of creating
another certificate. The CI example runssigning sync pull, signing keychain install, profiles local install,xcode signing plan --profile, xcode signing apply, xcode archive, andxcode export in one step, and removes the keychain afterward. A pulled .p12
is protected by the store password, not by the bootstrap .p12 password.
Registered-device release testing
For an iOS archive with extensions or other embedded targets, reconcile the archive against an exact protected device file:
: "${ASC_DISTRIBUTION_CERTIFICATE_ID:?configure the resource ID paired with the synced identity}"
chmod 600 .asc/distribution/devices.json
asc signing reconcile plan \
--archive-path .asc/artifacts/Example.xcarchive \
--devices-file .asc/distribution/devices.json \
--certificate "$ASC_DISTRIBUTION_CERTIFICATE_ID" \
--minimum-validity-days 14 \
--output json \
--pretty
asc signing reconcile apply \
--plan .asc/distribution/signing/plan.json \
--confirm \
--output json > .asc/distribution/signing/apply-result.jsonPlanning is read-only and can return a successful ready: false result with blockers. Apply performs only the additive actions recorded in a ready plan; it does not delete profiles, create certificates, or change capabilities.
Planning creates plan.json without replacing an existing file. If a plan reports blockers, review it and fix them first. Then rerun the same plan command with --overwrite, or choose a new --state-dir when the blocked plan must remain on disk.
signing run accepts exactly one profile. Use the temporary-keychain export below only for a single-target archive whose main app uses that profile. When this runs in a later CI step than the pull, recreate the runner-private password file because the pull step's cleanup trap removed its copy:
set +x
umask 077
password_file="$RUNNER_TEMP/asc-signing-password"
trap 'rm -f "$password_file"' EXIT
printf '%s' "$SIGNING_SYNC_PASSWORD" > "$password_file"
signing_command=(
asc xcode export
--archive-path .asc/artifacts/Example.xcarchive
--ipa-path .asc/artifacts/Example.ipa
--method release-testing
--signing-style manual
--team-id TEAM_ID
)
asc signing run \
--identity .asc/signing/pulled/identities/distribution/IDENTITY.p12 \
--identity-password-file "$password_file" \
--profile .asc/distribution/signing/profiles/PROFILE.mobileprovision \
--receipt .asc/distribution/signing-run.json \
-- "${signing_command[@]}"signing run accepts iOS ad hoc profiles with registered devices. It rejects App Store, development, enterprise, macOS, and tvOS profiles.
For an archive with extensions, App Clips, Watch apps, or other embedded targets, don't use this single-profile wrapper for export. On a disposable macOS worker, use asc signing keychain install for the job-scoped identity, install each completed profile action's outputPath from .asc/distribution/signing/apply-result.json, export, and discard the worker state when the job ends.
What the encrypted store does not do
The default store is Git. --storage object keeps the same ciphertext in an S3 or S3-compatible bucket, with credentials from the standard AWS configuration chain, including workload identity; --storage gitlab-secure-files and --storage aws-secrets-manager are experimental alternatives. Password rotation supports Git and object storage. The store does not renew certificates or assign different profile types or device sets per target. Profile renewal, device refresh, and one-type cleanup are explicit sync push options and sync nuke, all Git-only; see Renew, refresh, and remove synced profiles. Per-target Xcode build settings come from xcode signing plan --profile, and the dedicated keychain lifecycle is under Manage a dedicated keychain. Treat certificate rotation as a separate reviewed operation. Renew first, push the successor identity, verify a clean pull, then revoke or deactivate; the ordering is under Rotation and expiry.
Read the signing command reference before the first live push; it includes file-permission rules, exact output fields, failure boundaries, profile maintenance, and recovery behavior.
Guides Metadata Management (`guides/metadata-management.mdx`)
Metadata Management
Manage app metadata and localizations across multiple languages
Manage your app's metadata, descriptions, keywords, and localizations efficiently using the CLI.
Overview
App metadata includes:
- App Info: Name, subtitle, privacy policy, category (persistent across versions)
- Version Localizations: Description, keywords, what's new, release notes (version-specific)
- Screenshots and Previews: Visual assets for the App Store
- Search Keywords: Terms to improve discoverability
The CLI separates version-specific metadata from app-level metadata, allowing you to manage each independently.
App-Level Metadata (App Info)
App Info contains metadata that persists across all versions:
View App Info Localizations
asc localizations list --app "YOUR_APP_ID" --type app-infoFilter by locale:
asc localizations list --app "YOUR_APP_ID" --type app-info --locale "en-US,es-ES"Update App Info Localization
asc app-setup info set \
--app "YOUR_APP_ID" \
--locale "en-US" \
--name "My Awesome App" \
--subtitle "The best app for productivity" \
--privacy-policy-url "https://example.com/privacy"Create Another Language Localization
Seed the target locale explicitly with app-level metadata:
asc app-setup info set \
--app "YOUR_APP_ID" \
--locale "fr-FR" \
--name "Mon Application Géniale" \
--subtitle "L'app de productivité idéale"Then update locale-specific fields:
asc app-setup info set \
--app "YOUR_APP_ID" \
--locale "fr-FR" \
--privacy-policy-url "https://example.com/fr/privacy"Version-Specific Metadata
Version localizations contain metadata specific to each app version:
View Version Localizations
asc localizations list --version "VERSION_ID"Filter by specific locales:
asc localizations list --version "VERSION_ID" --locale "en-US,ja"Update Version Localization
asc localizations update \
--version "VERSION_ID" \
--locale "en-US" \
--description "Comprehensive app description with key features and benefits" \
--keywords "productivity,tools,utility,workflow" \
--whats-new "New features in this version:\n- Feature 1\n- Feature 2\n- Bug fixes"Update Marketing URL and Support URL
asc localizations update \
--version "VERSION_ID" \
--locale "en-US" \
--marketing-url "https://example.com/myapp" \
--support-url "https://support.example.com"Update Promotional Text
asc localizations update \
--version "VERSION_ID" \
--locale "en-US" \
--promotional-text "Limited time offer: Get 50% off premium features!"Working with Localization Files
For bulk operations, use .strings file format:
Download Localizations
```bash theme={null}
asc localizations download \
--version "VERSION_ID" \
--path "./localizations" \
--paginate
```
This creates files like:
```
localizations/
├── en-US.strings
├── es-ES.strings
├── fr-FR.strings
└── ja.strings
``````bash theme={null}
asc localizations download \
--version "VERSION_ID" \
--locale "en-US,es-ES" \
--path "./localizations"
``````bash theme={null}
asc localizations download \
--app "YOUR_APP_ID" \
--type app-info \
--path "./app-info-localizations" \
--paginate
```Edit .strings Files
The .strings files use standard format:
/* App Store Description */
"description" = "Your comprehensive app description here...";
/* Search Keywords (comma-separated) */
"keywords" = "productivity,tools,utility";
/* What's New */
"whatsNew" = "New in this version:\n- Feature 1\n- Feature 2";
/* Marketing URL */
"marketingUrl" = "https://example.com/myapp";
/* Support URL */
"supportUrl" = "https://support.example.com";
/* Promotional Text */
"promotionalText" = "Limited time promotion!";Upload Localizations
```bash theme={null}
asc localizations upload \
--version "VERSION_ID" \
--path "./localizations" \
--dry-run
``````bash theme={null}
asc localizations upload \
--version "VERSION_ID" \
--path "./localizations"
``````bash theme={null}
asc localizations upload \
--version "VERSION_ID" \
--locale "en-US,es-ES" \
--path "./localizations"
``````bash theme={null}
asc localizations upload \
--app "YOUR_APP_ID" \
--type app-info \
--path "./app-info-localizations"
```Search Keywords Management
Optimize App Store search with keywords:
List Current Keywords
asc localizations search-keywords list --localization-id "LOCALIZATION_ID"Update Keywords
asc localizations update \
--version "VERSION_ID" \
--locale "en-US" \
--keywords "productivity,calendar,tasks,todo,organizer,planner,schedule"Preview and Screenshot Sets
List Preview Sets
asc localizations preview-sets list --localization-id "LOCALIZATION_ID"Get Preview Set Details
asc localizations preview-sets view --id "PREVIEW_SET_ID"List Screenshot Sets
asc localizations screenshot-sets list --localization-id "LOCALIZATION_ID"Get Screenshot Set Details
asc localizations screenshot-sets view --id "SCREENSHOT_SET_ID"For uploading screenshots, see the Screenshots Guide.
Complete Localization Workflow
Here's a complete workflow for managing localizations:
#!/bin/bash
set -e
# Configuration
export ASC_APP_ID="YOUR_APP_ID"
VERSION="1.0.0"
# 1. Get version ID
VERSION_ID=$(asc versions list \
--app "$ASC_APP_ID" \
--output json | \
jq -r '.data[] | select(.attributes.versionString == "'$VERSION'") | .id' | head -1)
echo "Version ID: $VERSION_ID"
# 2. Download existing localizations
asc localizations download \
--version "$VERSION_ID" \
--path "./localizations" \
--paginate
# 3. Edit files locally
echo "Edit the .strings files in ./localizations/"
read -p "Press enter when ready to upload..."
# 4. Test with dry-run
asc localizations upload \
--version "$VERSION_ID" \
--path "./localizations" \
--dry-run
# 5. Upload changes
echo "Uploading localizations..."
asc localizations upload \
--version "$VERSION_ID" \
--path "./localizations"
echo "Localizations updated successfully!"
# 6. Verify changes
asc localizations list --version "$VERSION_ID" --output tableMulti-Language Support
Supported Locales
Common App Store locales:
Use asc localizations supported-locales --version "VERSION_ID" to inspect the
shared CLI locale catalog and see which version locales are already configured.
en-US- English (US)en-GB- English (UK)es-ES- Spanish (Spain)es-MX- Spanish (Mexico)fr-FR- Frenchde-DE- Germanit- Italianja- Japaneseko- Koreanpt-BR- Portuguese (Brazil)pt-PT- Portuguese (Portugal)ru- Russianzh-Hans- Chinese (Simplified)zh-Hant- Chinese (Traditional)ar-SA- Arabicnl-NL- Dutchth- Thaivi- Vietnamese
Add a New Language
Bulk Language Setup
Set up multiple languages at once:
#!/bin/bash
LOCALES=("es-ES" "fr-FR" "de-DE" "ja" "zh-Hans")
VERSION_ID="YOUR_VERSION_ID"
APP_ID="YOUR_APP_ID"
for locale in "${LOCALES[@]}"; do
echo "Setting up $locale..."
# Seed app info localization for this locale
asc app-setup info set \
--app "$APP_ID" \
--locale "$locale" \
--name "TODO localized app name" \
--subtitle "TODO localized app subtitle"
# Note: You'll still need to translate the actual text content
# This just creates the locale structure
done
echo "All locales created. Update translations in App Store Connect or via CLI."Best Practices
Download before editing: Always download current localizations before making changes
Use dry-run: Test uploads with
--dry-runto catch errors before applying changesSeparate app info from version metadata: Update persistent metadata (name, subtitle) via
app-setup info set; create new version locales withlocalizations create, then edit existing version metadata withlocalizations updateOptimize keywords: Research popular search terms and use all 100 characters effectively
Keep what's new concise: Focus on 3-5 key updates that matter to users
Maintain consistency: Keep terminology and tone consistent across all locales
Use professional translation: For important markets, use professional translation services
Test in target regions: If possible, have native speakers review localizations
Troubleshooting
"Localization not found"
Problem: The locale doesn't exist for the version.
Solution: Create the localization first:
asc localizations create \
--version "VERSION_ID" \
--locale "ja" \
--description "Initial description""Invalid keywords format"
Problem: Keywords exceed 100 characters or contain invalid characters.
Solution: Trim keywords to fit the limit:
# Good (under 100 chars)
--keywords "productivity,task,calendar,todo,planner"
# Bad (over 100 chars)
--keywords "productivity,task,calendar,todo,planner,organizer,scheduler,reminder,notes,documents,collaboration""Failed to upload .strings file"
Problem: File format is incorrect or contains parsing errors.
Solution: Validate the file format:
/* Correct format */
"description" = "Value here";
/* Incorrect - missing semicolon */
"description" = "Value here"
/* Incorrect - missing quotes */
description = "Value here";"Submission preflight failed: missing fields"
Problem: Required localization fields are empty when submitting.
Solution: Ensure all required fields are populated:
asc localizations update \
--version "VERSION_ID" \
--locale "en-US" \
--description "Required description" \
--keywords "required,keywords" \
--whats-new "Required what's new text"Related Commands
- App Store Submission - Submit apps with validated metadata
- Screenshots - Manage visual assets
- App Info API - Detailed API reference
Guides Screenshots (`guides/screenshots.mdx`)
Screenshots and Previews
Manage App Store screenshots and preview videos
Manage your App Store screenshots from capture to upload, including local automation workflows.
Overview
The CLI provides two screenshot workflows:
- App Store Workflow: Upload and manage screenshots in App Store Connect
- Local Automation: Capture, frame, review, and prepare screenshots locally
App Store Screenshot Management
The commands below upload screenshots directly to an app version. To store images
and videos independently and reuse them across version, custom product page,
optimization treatment, and event localizations, see the Asset Library guide.
Understanding Screenshot Requirements
View required screenshot sizes for App Store submission:
asc screenshots sizesFor the full matrix including all device types:
asc screenshots sizes --all- One iPhone set:
IPHONE_65(6.5" display) - One iPad set:
IPAD_PRO_3GEN_129(12.9" iPad Pro 3rd gen)
The App Store will automatically scale these to other sizes.
Upload Screenshots
Organize screenshots by device type:
```
screenshots/
├── iphone/
│ ├── 01-home.png
│ ├── 02-features.png
│ └── 03-settings.png
└── ipad/
├── 01-home.png
├── 02-features.png
└── 03-settings.png
``````bash theme={null}
asc screenshots upload \
--app "YOUR_APP_ID" \
--version "1.2.3" \
--locale "en-US" \
--path "./screenshots/iphone" \
--device-type "IPHONE_65" \
--concurrency 4
```
With `--locale`, `--path` points directly at that locale's screenshots.
The version must already have an `en-US` localization; otherwise the
command fails and lists the locales the version has.
Uploads within one screenshot set are bounded to four concurrent requests
by default. Use `--concurrency 1` for serial uploads or any value through
`8` when the API and network can sustain more fan-out; output ordering stays
deterministic.If you already have a version localization ID, --version-localization "LOCALIZATION_ID" uploads to it directly. asc localizations list --app "YOUR_APP_ID" --version "1.2.3" shows each localization's ID.
Device Types
Common device types for screenshots:
iPhone:
IPHONE_65- 6.5" display (iPhone 14 Plus, etc.)IPHONE_55- 5.5" display (iPhone 8 Plus)IPHONE_40- 4" display (iPhone SE 1st gen)
iPad:
IPAD_PRO_3GEN_129- 12.9" iPad Pro (3rd gen)IPAD_PRO_129- 12.9" iPad Pro (1st/2nd gen)IPAD_105- 10.5" iPad ProIPAD_97- 9.7" iPad
Apple Watch:
APPLE_WATCH_SERIES_7- 45mm Series 7APPLE_WATCH_SERIES_4- 44mm Series 4APPLE_WATCH_SERIES_3- 42mm Series 3
Mac:
DESKTOP
Apple TV:
APPLE_TV
List Existing Screenshots
asc screenshots list --version-localization "LOCALIZATION_ID"Filter client-side by device type:
asc screenshots list \
--version-localization "LOCALIZATION_ID" \
--output json | jq '.data[] | select(.attributes.displayType == "IPHONE_65")'Download Screenshots
Download all screenshots for a localization:
asc screenshots download \
--version-localization "LOCALIZATION_ID" \
--output-dir "./downloaded-screenshots"Delete Screenshots
Delete a specific screenshot:
asc screenshots delete --id "SCREENSHOT_ID" --confirmLocal Screenshot Automation
Complete Local Workflow
```bash theme={null}
asc screenshots capture \
--bundle-id "com.example.myapp" \
--name "home-screen" \
--output-dir "./screenshots/raw"
```
This captures from the currently running simulator.```bash theme={null}
asc screenshots frame \
--input "./screenshots/raw/home-screen.png" \
--device "iphone-air" \
--output-dir "./screenshots/framed"
```
See available device frames:
```bash theme={null}
asc screenshots list-frame-devices
``````bash theme={null}
asc screenshots review-generate \
--framed-dir "./screenshots/framed" \
--output-dir "./screenshots/review"
```This opens a browser with an interactive gallery for review.```bash theme={null}
asc screenshots review-approve \
--all-ready \
--output-dir "./screenshots/review"
```
Or approve selectively in the browser UI.```bash theme={null}
asc screenshots plan \
--app "123456789" \
--version "1.2.3"
``````bash theme={null}
asc screenshots apply \
--app "123456789" \
--version "1.2.3" \
--confirm
```Screenshot Plan Automation
Define a complete screenshot plan in .asc/screenshots.json:
{
"bundle_id": "com.example.myapp",
"device_types": ["iphone-air", "ipad-pro-129"],
"locales": ["en-US", "es-ES", "ja"],
"screenshots": [
{
"name": "home",
"description": "Home screen showing main features",
"device_type": "iphone-air"
},
{
"name": "feature-detail",
"description": "Detailed feature view",
"device_type": "iphone-air"
},
{
"name": "settings",
"description": "Settings and customization",
"device_type": "iphone-air"
}
],
"output_dirs": {
"raw": "./screenshots/raw",
"framed": "./screenshots/framed",
"review": "./screenshots/review"
}
}Run the entire workflow:
asc screenshots run --plan .asc/screenshots.jsonAvailable Frame Devices
List supported devices for framing:
asc screenshots list-frame-devices --output jsonSupported devices:
- iPhone:
iphone-air(default),iphone-17-pro,iphone-17-pro-max,iphone-16e,iphone-17 - iPad:
ipad-pro-13,ipad-pro-11,ipad-air-13,ipad-air-11,ipad-mini - Apple Watch:
watch-series-11,watch-ultra-3 - Apple TV:
apple-tv - Mac:
mac(canvas without a bezel)
Frame each device from a capture of the same screen shape, such as a 422x514
Apple Watch Ultra 3 capture for watch-ultra-3. When an input's aspect ratio
differs from the device screen by more than 2%, asc screenshots frame prints a
warning on stderr, because the image is letterboxed inside the screen.
Native simulator captures from xcrun simctl io screenshot fill the
frame without that warning:
| Frame device | Simulator capture | Size |
|---|---|---|
watch-series-11 |
Apple Watch Series 11 or 12 (46mm) | 416x496 |
watch-ultra-3 |
Apple Watch Ultra 3 or 4 (49mm) | 422x514 |
apple-tv |
Apple TV 4K | 3840x2160 |
xcrun simctl io "$WATCH_UDID" screenshot ./screenshots/raw/watch-home.png
asc screenshots frame --input ./screenshots/raw/watch-home.png --device watch-ultra-3 \
--title "Roll the die" --text-position bottomEach device entry lists its frameColors; pass one with --frame-color. See Frame Screenshots for the full device and color matrix.
To frame a whole directory and retry only failed inputs on the next run:
asc screenshots frame --input-dir ./screenshots/raw --output-dir ./screenshots/framed \
--device ipad-air-13 --frame-color blue --title "Plan every trip" --resumeTo keep captions legible on a busy or light background and use a brand font file that is not installed on the machine (for example, a CI runner), add a text box and pass the font file to --font:
asc screenshots frame --input-dir ./screenshots/raw --output-dir ./screenshots/framed \
--device iphone-air --title "Plan every trip" --bg-color "#f2c14e" \
--font ./brand/Brand-Bold.ttf --text-box --text-box-color "#1b3a5bcc"Per-file box settings go in --overlay-config entries as textBox, textBoxColor, textBoxPadding, and textBoxRadius. See Frame Screenshots for the defaults and validation rules.
Large batches are CPU-bound. Add --parallel-workers 4 (up to your CPU count) to render four inputs at once; the receipt order and --resume behavior stay the same.
Complete Screenshot Workflow Example
Here's a complete workflow for managing screenshots:
#!/bin/bash
set -e
# Configuration
export ASC_APP_ID="YOUR_APP_ID"
VERSION="1.2.3"
BUNDLE_ID="com.example.myapp"
# 1. Check the version's localizations
asc localizations list --app "$ASC_APP_ID" --version "$VERSION" --output table
# 2. Create directories
mkdir -p screenshots/{raw,framed,iphone,ipad}
# 3. Capture screenshots (requires simulator running)
echo "Launch your app in iOS Simulator and press enter..."
read
for screen in home features settings details; do
echo "Navigate to $screen screen and press enter..."
read
asc screenshots capture \
--bundle-id "$BUNDLE_ID" \
--name "$screen" \
--output-dir "./screenshots/raw"
done
# 4. Frame screenshots
echo "Framing screenshots..."
for raw_file in screenshots/raw/*.png; do
filename=$(basename "$raw_file")
asc screenshots frame \
--input "$raw_file" \
--device "iphone-air" \
--output-path "./screenshots/framed/$filename"
done
# 5. Copy to upload directories
cp screenshots/framed/*.png screenshots/iphone/
# 6. Upload to App Store Connect
echo "Uploading iPhone screenshots..."
asc screenshots upload \
--app "$ASC_APP_ID" \
--version "$VERSION" \
--locale "en-US" \
--path "./screenshots/iphone" \
--device-type "IPHONE_65"
echo "Screenshots uploaded successfully!"
# 7. Verify upload
asc screenshots list --app "$ASC_APP_ID" --version "$VERSION" --locale "en-US"If an upload stops partway through, the command prints a failure artifact path like.asc/reports/screenshots-upload/failures-123.json. Resume the remaining files with:
asc screenshots upload --resume ".asc/reports/screenshots-upload/failures-123.json"Preview Videos
Upload app preview videos:
Upload Preview Video
asc video-previews upload \
--version-localization "LOCALIZATION_ID" \
--path "./previews/app-preview.mp4" \
--device-type "IPHONE_65"Preview Video Requirements
- Format: H.264 or HEVC codec, .mov or .mp4
- Duration: 15-30 seconds
- Resolution: Matches device screenshot size
- File size: Up to 500 MB
- Aspect ratio: Matches device (typically 16:9 or device aspect)
Multi-Locale Screenshot Workflow
For apps with multiple localizations, you can now fan out one upload across
all matching locale directories:
asc screenshots upload \
--app "YOUR_APP_ID" \
--version "1.2.3" \
--path "./screenshots" \
--device-type "IPHONE_65"Recommended directory layout:
screenshots/
en-US/
iphone/
01-home.png
02-settings.png
fr-FR/
iphone/
01-home.png
ja/
iphone/
01-home.pngThe command resolves the app store version, fetches its version localizations,
and uploads each locale subtree to the matching remote localization. It only
includes files whose dimensions match the selected --device-type, so mixed
iPhone/iPad locale trees are safe as long as each locale contains the slot
you are uploading. A device-rooted layout such as screenshots/iphone/en-US
also works when --path points at ./screenshots/iphone.
For CI or large locale batches, save the printed failure artifact as a workflow
artifact so a rerun can continue withasc screenshots upload --resume <artifact>.
Troubleshooting
"Invalid image dimensions"
Problem: Screenshot dimensions don't match the device type.
Solution: Verify required dimensions:
asc screenshots sizesEnsure your images match exactly:
- iPhone 6.5": 1242 x 2688 pixels
- iPad Pro 12.9": 2048 x 2732 pixels
"Capture failed - no simulator running"
Problem: asc screenshots capture requires a running simulator.
Solution:
- Launch iOS Simulator
- Open your app in the simulator
- Run the capture command
"Frame device not found"
Problem: Invalid device name for framing.
Solution: List available devices:
asc screenshots list-frame-devicesUse exact device names from the output.
"Upload failed - file too large"
Problem: Image file size exceeds limits.
Solution: Optimize images:
# Using ImageMagick
mogrify -quality 85 -resize '1242x2688>' screenshots/*.png
# Using pngquant for compression
pngquant --quality=80-95 screenshots/*.pngBest Practices
Use consistent naming: Name screenshots clearly (
01-home.png,02-features.png)Optimize file sizes: Compress screenshots to reduce upload time while maintaining quality
Follow Apple guidelines:
- No UI chrome (status bars are optional)
- Show actual app content
- Use device-appropriate assets
Localize screenshots: Create locale-specific screenshots showing localized UI
Test on real devices: Capture from actual devices when possible for best quality
Version control: Keep screenshot sources in git for easy updates
Automate where possible: Use the local workflow tools to streamline repetitive tasks
Review before upload: Always review screenshots in context before uploading to App Store Connect
Related Commands
- Metadata Management - Manage app descriptions and metadata
- App Store Submission - Submit with screenshots
- Localizations API - Manage version localizations
Guides Testflight (`guides/testflight.mdx`)
TestFlight Distribution
Distribute builds to beta testers using TestFlight
Manage your TestFlight beta testing program end-to-end, from organizing beta groups to collecting feedback from testers.
Overview
TestFlight allows you to distribute pre-release builds to internal and external testers. The CLI provides complete automation for:
- Managing beta groups and testers
- Distributing builds to specific groups
- Collecting and reviewing crash reports and feedback
- Tracking beta tester metrics
Beta Groups Workflow
```bash theme={null}
asc testflight groups list --app "YOUR_APP_ID"
```
Filter to internal or external groups:
```bash theme={null}
asc testflight groups list --app "YOUR_APP_ID" --internal
asc testflight groups list --app "YOUR_APP_ID" --external
``````bash theme={null}
asc testflight groups create --app "YOUR_APP_ID" --name "External Beta Testers"
```
Create an internal beta group:
```bash theme={null}
asc testflight groups create --app "YOUR_APP_ID" --name "QA Team" --internal
``````bash theme={null}
asc testflight groups edit --id "GROUP_ID" \
--public-link-enabled \
--public-link-limit-enabled \
--public-link-limit 100
```
Enable feedback collection:
```bash theme={null}
asc testflight groups edit --id "GROUP_ID" --feedback-enabled
```Managing Beta Testers
```bash theme={null}
asc testflight testers add \
--app "YOUR_APP_ID" \
--email "[email protected]" \
--group "Beta Testers"
```
Optionally include name details:
```bash theme={null}
asc testflight testers add \
--app "YOUR_APP_ID" \
--email "[email protected]" \
--first-name "Jane" \
--last-name "Doe" \
--group "Beta Testers"
``````bash theme={null}
asc testflight testers export \
--app "YOUR_APP_ID" \
--output "./testers.csv"
```
<Note>
Export prefixes a cell with a single quote when its first character would start a spreadsheet formula (`=`, `+`, `-`, `@`), so opening the file in Excel, Numbers, or Google Sheets cannot execute tester or group values. When a prefix is needed, export adds an `_asc_formula_escaping` provenance column. `asc testflight testers import` only removes prefixes from rows carrying that marker, so leading apostrophes in third-party or user-authored CSV files remain unchanged.
</Note>
Import testers from CSV (with dry-run first):
```bash theme={null}
asc testflight testers import \
--app "YOUR_APP_ID" \
--input "./testers.csv" \
--dry-run
# After verifying, apply the plan
asc testflight testers import \
--app "YOUR_APP_ID" \
--input "./testers.csv" \
--confirm
```
`--confirm` is required unless `--dry-run` is set.```bash theme={null}
asc testflight testers list --app "YOUR_APP_ID"
```
Filter by beta group:
```bash theme={null}
asc testflight testers list --app "YOUR_APP_ID" --group "Beta Testers"
```
Filter by build:
```bash theme={null}
asc testflight testers list --app "YOUR_APP_ID" --build-id "BUILD_ID"
``````bash theme={null}
asc testflight testers invite \
--app "YOUR_APP_ID" \
--email "[email protected]"
```
Invite and auto-create if tester doesn't exist:
```bash theme={null}
asc testflight testers invite \
--app "YOUR_APP_ID" \
--email "[email protected]" \
--group "Beta Testers"
```Distributing Builds to Groups
```bash theme={null}
asc builds list --app "YOUR_APP_ID" --sort "-uploadedDate" --limit 5
```
Get a specific build:
```bash theme={null}
asc builds info --app "YOUR_APP_ID" --version "1.0" --build-number "42" --platform IOS
``````bash theme={null}
asc builds add-groups --build-id "BUILD_ID" --group "GROUP_ID"
```
Add to multiple groups:
```bash theme={null}
asc builds add-groups --build-id "BUILD_ID" --group "GROUP_ID1,GROUP_ID2"
``````bash theme={null}
asc testflight distribution view --build-id "BUILD_ID"
```Feedback and Crash Reports
```bash theme={null}
asc testflight crashes view --submission-id "SUBMISSION_ID"
```
Download the crash log:
```bash theme={null}
asc testflight crashes log --submission-id "SUBMISSION_ID"
``````bash theme={null}
asc testflight feedback view --submission-id "SUBMISSION_ID"
``````bash theme={null}
asc testflight crashes log --crash-log-id "CRASH_LOG_ID"
``````bash theme={null}
asc testflight crashes delete --submission-id "SUBMISSION_ID" --confirm
```
Delete screenshot submissions:
```bash theme={null}
asc testflight feedback delete --submission-id "SUBMISSION_ID" --confirm
```Beta Tester Metrics
Track how testers engage with your builds:
asc testflight metrics app-testers --app "YOUR_APP_ID"This provides insights into:
- Number of sessions per tester
- App version usage
- Install and session counts
Complete Example Workflow
Here's a complete workflow for distributing a new build:
# 1. Set your app ID
export ASC_APP_ID="YOUR_APP_ID"
# 2. Find the latest build
asc builds list --app "$ASC_APP_ID" --sort "-uploadedDate" --limit 1 --output json
BUILD_ID="..." # Extract from output
# 3. Create or find your beta group
asc testflight groups list --app "$ASC_APP_ID"
GROUP_ID="..." # Use existing or create new
# 4. Add build to the beta group
asc builds add-groups --build-id "$BUILD_ID" --group "$GROUP_ID"
# 5. List testers in the group
asc testflight testers list --app "$ASC_APP_ID" --group "$GROUP_ID"
# 6. Add new testers if needed
asc testflight testers add \
--app "$ASC_APP_ID" \
--email "[email protected]" \
--group "Beta Testers"
# 7. Send invitations
asc testflight testers invite --app "$ASC_APP_ID" --email "[email protected]"
# 8. Monitor beta tester activity
asc testflight metrics app-testers --app "$ASC_APP_ID"Troubleshooting
Tester Not Receiving Invitation
Verify the tester is added to the group:
bash theme={null}asc testflight testers list --app "YOUR_APP_ID" --group "GROUP_ID"Check if invitation was sent:
bash theme={null}asc testflight testers view --id "TESTER_ID"Resend invitation:
bash theme={null}asc testflight testers invite --app "YOUR_APP_ID" --email "[email protected]"
Build Not Appearing for Testers
Verify build is assigned to the group:
bash theme={null}asc testflight distribution view --build-id "BUILD_ID"Check build processing status:
bash theme={null}asc builds info --build-id "BUILD_ID"Ensure build passed TestFlight review:
bash theme={null}asc testflight review submissions list --build-id "BUILD_ID"
Managing Group Limits
Internal groups have a limit of 100 testers. External groups can have up to 10,000 testers.
To check current tester counts:
asc testflight groups view --id "GROUP_ID"If you hit limits, create additional groups:
asc testflight groups create --app "YOUR_APP_ID" --name "Beta Testers Group 2"Best Practices
Organize by testing focus: Create separate groups for different testing purposes (QA, feature testing, general beta)
Use CSV import for bulk operations: Export your current tester list, make changes, and re-import
Enable feedback collection: Turn on feedback for external groups to collect valuable user input
Monitor crash reports: Regularly check crash submissions and address issues before App Store release
Set public link limits: For public beta links, set reasonable limits to control your beta population size
Track metrics: Use beta tester usage metrics to identify your most active testers
Related Commands
- App Store Submission - Submit builds for App Store review
- Build Management - Manage builds in App Store Connect
- Analytics and Reports - Download usage and crash analytics
Architecture Apple Ads Api Support (`docs/architecture/apple-ads-api-support.md`)
Apple Ads Campaign Management API v5 Support (Historical Scope)
Status: Implemented; retained as the v5 compatibility reference
Research date: May 31, 2026
Original target API: Apple Ads Campaign Management API 5.5
Historical command root: asc ads
Release target: 4.4.0
Current API addition: Apple Ads Platform API v1
Preferred API in CLI 4.4.0: Apple Ads Platform API v1 under direct asc ads
Historical goal
This note records the existing Campaign Management API v5 surface and the
4.4.0 addition of the Apple Ads Platform API v1 surface. Direct asc ads
resource commands use v1. The incompatible, retiring v5 surface moves underasc ads v5; the intermediate nested prototype is removed before
merge. Users should not need a raw HTTP client for supported workflows.
The v1 and v5 request contracts remain separate because they use different
hosts, contexts, payloads, and response envelopes. Existing v5 commands move
under the explicit v5 group instead of being silently retargeted.
The original implementation preserved the local CLI style:
ffclicommands withshared.DefaultUsageFunc- explicit long flags
- TTY-aware output defaults through
shared.BindOutputFlags - non-interactive behavior only
--confirmfor deletes and bulk deletes--paginatefor multi-page list/search commands- JSON output that preserves Apple response envelopes for agents
- no new third-party dependencies
Historical v5 sources
Apple sources used for the May 2026 v5 implementation:
- Apple Ads root: https://developer.apple.com/documentation/apple_ads
- Platform API v1: https://developer.apple.com/documentation/apple-ads-platform-api
- OAuth: https://developer.apple.com/documentation/apple_ads/implementing-oauth-for-the-apple-search-ads-api
- Calling the API: https://developer.apple.com/documentation/apple_ads/calling-the-apple-search-ads-api
- API functionality: https://developer.apple.com/documentation/apple_ads/using-apple-search-ads-api-functionality
- API 5 changelog: https://developer.apple.com/documentation/apple_ads/apple-search-ads-campaign-management-api-5
At the original research date, API 5 was Apple's current Campaign Management
API and API 5.5 had been released in February 2026. Apple later made Platform
API v1 available. CLI 4.4.0 implements that API under direct asc ads resource
paths and keeps the original v5 commands under asc ads v5 as warning-producing compatibility paths until
Apple's January 26, 2027 retirement date.
At the research date, deprecated Creative Sets were not included as commands
because Apple's v5 documentation marked the collection as deprecated and
exposed no active v5 endpoint under that page. TheincludeDeletedCreativeSetAssets query parameter onGET /v5/creatives/{creativeId} was included.
AdServices Attribution API is out of scope. It is not part of the Apple Ads
Campaign Management API command surface and has different caller requirements.
Platform API v1 in 4.4.0
The cumulative 4.4.0 implementation stack is intended to add all 99 documented
operations under direct asc ads resource paths: this foundation layer registers
13 operations, the campaign layer adds 41, Maps and assets add 21, and reports
and optimization add 24. At each intermediate PR, only the operations registered
by that layer are available; the cumulative stack is what provides the complete
surface.
Ad-account context is carried inX-AP-Context: adAccountId=<ad-account-id>; for account-scoped requests. The--ad-account flag, ASC_ADS_AD_ACCOUNT_ID, and the selected profile'sad_account_id are independent of the legacy v5 --org/org_id context.
The /v1/ad-accounts collection is method-dependent:
| Operation | Context behavior |
|---|---|
POST /v1/ad-accounts |
Creates an account without X-AP-Context; the account context does not exist until the response supplies its ID. |
GET /v1/ad-accounts/{id} |
Requires X-AP-Context: adAccountId=;; the header account must match the path ID. |
PUT /v1/ad-accounts/{id} |
Requires X-AP-Context: adAccountId=;; the header account must match the path ID. |
Authentication commands use the Platform API v1 transport:
| Command | Validation/discovery request |
|---|---|
asc ads auth login --network |
OAuth token exchange, then Platform API v1 GET /v1/me. |
asc ads auth status --validate |
For each stored credential, OAuth token exchange, then Platform API v1 GET /v1/me. |
asc ads auth discover |
Platform API v1 GET /v1/me and GET /v1/acls, returning user and ad-account access. |
All three commands avoid an ad-account context for these requests. The token
exchange remains POST https://appleid.apple.com/auth/oauth2/token with thesearchadsorg scope; a supplied access token skips that exchange. Platform API
v1 users should call asc ads me view and asc ads acls list
when they need the v1 user or ACL resources.
Historical v5 command placement
The original implementation added this command root; in 4.4.0 the historical
v5 leaves move beneath an explicit version group:
asc ads v5 <subcommand> [flags]Common endpoint flags:
- Every Apple Ads endpoint leaf command accepts
--ads-profile NAME. - Every org-scoped endpoint leaf command accepts
--org ORG_ID. asc ads v5 me view,asc ads v5 acls list, andasc ads auth ...do not require--org.- Examples in the endpoint matrix omit
--organd--ads-profileunless those
flags are materially relevant to the endpoint. The implementation still adds
them to the command. - Resource groups with a natural list endpoint execute list by default:
asc ads v5 campaigns,asc ads v5 budget-orders,asc ads v5 ad-groups,asc ads v5 creatives, andasc ads v5 impression-share-reports.
These rules describe the deprecated v5 tree. Platform v1 commands live under
direct asc ads, use --ad-account instead of --org, and follow the v1
payload and response contracts documented indocs/design/apple-ads-platform-api-v1.md.
Root help placement:
- Add
adstocmd/root_usage.gounderANALYTICS & FINANCE COMMANDSafterperformance. This is acquisition/marketing rather than App Store Connect
resource management, but this is the closest existing command group. - Register
ads.AdsCommand()ininternal/cli/registry/registry.go. - Add
adsto registry/root help tests.
Package layout:
internal/appleads/
auth.go
auth_store.go
client.go
client_test.go
endpoints.go
endpoints_test.go
errors.go
pagination.go
internal/cli/ads/
api.go
root.go
auth.go
endpoints.go
endpoints_test.go
resolve.goUse internal/appleads, not internal/asc, because the base URL, auth flow,
error envelope, and pagination model are different from App Store Connect.
Do not implement this as 73 bespoke command functions and 73 bespoke client
methods. Add one EndpointSpec table in internal/appleads/endpoints.go and
generic command/client builders that consume it.
EndpointSpec must include:
type EndpointSpec struct {
Name string
Method string
Path string
CommandPath []string
BodyKind shared.JSONPayloadKind
BodyType string
ResponseType string
RequiresOrg bool
RequiresConfirm bool
PathParams []ParamSpec
QueryParams []ParamSpec
SupportsPaginate bool
DefaultListAlias bool
}
type ParamSpec struct {
Name string
Flag string
Type ParamType
Required bool
Max int
Allowed []string
}Acceptance check: adding a newly documented Apple Ads endpoint requires one
spec row plus docs/tests, not a new hand-written request method.
Authentication Contract
Apple Ads OAuth is separate from App Store Connect JWT auth.
OAuth facts from Apple:
- Token URL:
https://appleid.apple.com/auth/oauth2/token - Grant type:
client_credentials - Scope:
searchadsorg - Client secret: ES256 JWT
- JWT header:
alg=ES256,kid=<key-id> - JWT claims:
iss=<team-id>iat=exp=<expires-at>aud=https://appleid.apple.comsub=<client-id>
- Token response contains
access_token,token_type=Bearer,expires_in=3600, andscope=searchadsorg. - Generate the client secret on demand for each token refresh with a 10-minute
lifetime and 30-second refresh skew. Do not persist the client secret. - Send the token request as
application/x-www-form-urlencodedform values:grant_type,client_id,client_secret, andscope.
Implement this command group:
asc ads auth login --name NAME --client-id CLIENT_ID --team-id TEAM_ID --key-id KEY_ID --private-key PATH [--org ORG_ID] [--ad-account AD_ACCOUNT_ID] [--network] [--skip-validation] [--bypass-keychain] [--local]
asc ads auth status [--verbose] [--validate] [--output table|json]
asc ads auth discover [--ads-profile NAME] [--org ORG_ID] [--ad-account AD_ACCOUNT_ID] [--output table|json]
asc ads auth switch --name NAME
asc ads auth token --confirm [--output text|json]
asc ads auth doctor [--output text|json]
asc ads auth logout [--all --confirm | --name NAME]Mirror the existing asc auth behavior:
--name,--key-id, and--private-keyuse the same meaning and validation
style asasc auth login.--client-idis Apple Ads OAuthclient_id.--team-idis the JWT issuer (iss).--orgstores a default Apple Ads org ID for API calls.--ad-accountstores a default Platform API v1 ad-account ID and, onauth discover, marks that account as active in the output. It is separate
from--org.--private-keyaccepts the EC P-256 PEM Apple documents for Ads. Reuse the
existing private-key parsing helpers because they already support ES256 keys.--networkrequests an access token and calls Platform API v1GET /v1/me.--skip-validationskips JWT and network validation.--networkand--skip-validationare mutually exclusive.--localrequires keychain bypass, exactly likeasc auth login.- Keychain is preferred; config fallback is allowed when bypassing keychain.
auth statussupports--verboseand--validate, matchingasc auth status.auth status --validatevalidates each stored credential through Platform
API v1GET /v1/meand reports failures after rendering the status output.auth discovercalls Platform API v1/v1/meand/v1/aclsto show the
active Ads user and available ad accounts without printing access tokens.
Each ACL row includes its honestad_account_id;--ad-accountcontrols
which row is marked active.- Platform API v1 user and ACL discovery is also available directly through
asc ads me viewandasc ads acls list. auth logoutsupports--alland--name. It requires one of those flags
so bareasc ads auth logoutdoes not clear every stored Ads profile, and--allrequires--confirm.
Add config fields without changing existing App Store Connect credentials:
type AdsCredential struct {
Name string `json:"name"`
ClientID string `json:"client_id"`
TeamID string `json:"team_id"`
KeyID string `json:"key_id"`
PrivateKeyPath string `json:"private_key_path"`
OrgID string `json:"org_id,omitempty"`
AdAccountID string `json:"ad_account_id,omitempty"`
}
type AdsKeychainMetadata struct {
Name string `json:"name"`
ClientID string `json:"client_id"`
TeamID string `json:"team_id"`
KeyID string `json:"key_id"`
OrgID string `json:"org_id,omitempty"`
AdAccountID string `json:"ad_account_id,omitempty"`
ModifiedAt string `json:"modified_at,omitempty"`
}
type AdsConfig struct {
DefaultKeyName string `json:"default_key_name,omitempty"`
Keys []AdsCredential `json:"keys,omitempty"`
KeychainMetadata []AdsKeychainMetadata `json:"keychain_metadata,omitempty"`
OrgID string `json:"org_id,omitempty"`
AdAccountID string `json:"ad_account_id,omitempty"`
}
type Config struct {
// existing fields...
Ads AdsConfig `json:"ads,omitempty"`
}Do not reuse the existing App Store Connect keychain item prefixes. Ads storage
uses:
asc:ads-credential:<name>
asc:ads-metadata:<name>Reuse only private-key parsing/validation helpers from internal/auth.
Environment variables:
ASC_ADS_ACCESS_TOKEN
ASC_ADS_CLIENT_ID
ASC_ADS_TEAM_ID
ASC_ADS_KEY_ID
ASC_ADS_PRIVATE_KEY_PATH
ASC_ADS_PRIVATE_KEY
ASC_ADS_PRIVATE_KEY_B64
ASC_ADS_ORG_ID
ASC_ADS_AD_ACCOUNT_ID
ASC_ADS_PROFILE
ASC_ADS_STRICT_AUTH
ASC_ADS_BYPASS_KEYCHAINResolution order:
- Explicit
--ads-profileif present. ASC_ADS_PROFILEif present.ASC_ADS_ACCESS_TOKENif no profile is selected; it bypasses token exchange but still needs--org,ASC_ADS_ORG_ID, or stored profile org for org-scoped calls.- Complete Ads env credential tuple.
- Default Ads keychain/config profile.
If ASC_ADS_STRICT_AUTH is true, fail when Ads credentials are split across
multiple sources, matching the existing strict-auth principle.
If a profile is selected by --ads-profile or ASC_ADS_PROFILE, ignoreASC_ADS_ACCESS_TOKEN unless ASC_ADS_STRICT_AUTH is true; in strict mode,
selected profile plus access token is a mixed-source error.
Org ID resolution is independent from token resolution:
--orgASC_ADS_ORG_ID- selected Ads profile
org_id ads.org_idin config only when authentication is profile-less
Persist the org ID both on the selected credential and in ads.org_id when Ads
login receives an org ID. This letsASC_ADS_ACCESS_TOKEN users reuse a configured default org without storing Ads
private key material in the active environment. A named profile never inherits
either root Ads context when its own profile omits the value. Switching or
removing the default clears both root context values. Profile-less access-token
or environment authentication can still use the standalone root values.
Platform API v1 ad-account resolution is independent from org resolution:
--ad-accountASC_ADS_AD_ACCOUNT_ID- selected Ads profile
ad_account_id ads.ad_account_idin config when authentication does not select a named profile
Persist an ad-account ID supplied to Ads login alongside the selected profile
and root config. A named profile never inherits the root ad-account ID from
another profile.
Campaign Management API v5 HTTP Client Contract
This section applies only to the deprecated Campaign Management API v5
commands under asc ads v5. Platform API v1 has a separate transport contract
below.
Base URL:
https://api.searchads.apple.com/api/Headers:
Authorization: Bearer <access_token>
Accept: application/json
Content-Type: application/json
X-AP-Context: orgId=<org-id>Do not send X-AP-Context for these two endpoints:
GET /v5/aclsGET /v5/me
For all other endpoints, resolve org ID from --org, ASC_ADS_ORG_ID, the
selected Ads profile, or ads.org_id in config. If it is still empty, fail
with:
Error: --org is required (or set ASC_ADS_ORG_ID or an Ads profile org_id)Use shared.ContextWithTimeout for all Apple Ads API requests so ASC_TIMEOUT
continues to apply.
Use the existing retry/backoff approach for GET and HEAD requests and for 429
responses. Redact Authorization, client_secret, private keys, and access
tokens in ASC_DEBUG=api logging.
Apple Ads error envelope:
{
"error": {
"errors": [
{
"field": "name",
"message": "message",
"messageCode": "CODE"
}
]
}
}Add appleads.APIError with HTTP status, field, message, and messageCode. If
the response is not this envelope, sanitize and return the raw detail the same
way internal/asc does.
Platform API v1 HTTP Client Contract
Base URL:
https://api.ads.apple.com/v1/Platform API v1 uses the same bearer authorization and JSON content headers as
v5. Account-scoped requests add:
X-AP-Context: adAccountId=<ad-account-id>;The ad-account ID is resolved independently from the legacy organization ID.--ad-account, ASC_ADS_AD_ACCOUNT_ID, the selected profile'sad_account_id, and root ads.ad_account_id are the supported sources. The
CLI rejects control characters and semicolons before authentication or network
work so an ID cannot inject additional context fields.
The following requests are context-free: GET /v1/me, GET /v1/acls,GET /v1/orgs/{id}, GET /v1/advertiser-resources, andPOST /v1/ad-accounts. For GET and PUT /v1/ad-accounts/{id}, the context
account must match the path ID. Other
endpoint context requirements are declared by the v1 endpoint metadata.
Platform API v1 retains the shared timeout, retry, rate-limit, pagination, and
sanitized error handling behavior of the client, while selecting the v1 base
URL and endpoint-specific response envelope.
Payload Contract
Every Apple Ads endpoint with an HTTP body uses --file.
Reason: Apple Ads request objects are large, nested, API-specific JSON payloads.
Using --file is already established in this repo for complex JSON payloads
such as Xcode Cloud workflows. It also preserves full API support without
mapping every Apple Ads nested object into unstable CLI flags.
Rules:
- The CLI sends the file content as the HTTP body without wrapping it.
- Object endpoints require a JSON object file.
- Array endpoints require a JSON array file.
(CustomProductPageCreative | DefaultProductPageCreative)accepts a JSON
object file.[int64]delete-bulk endpoints accept a JSON array of numbers.- Do not implement
--file -in this PR.
Extend internal/cli/shared/json_payload.go:
type JSONPayloadKind string
const (
JSONPayloadObject JSONPayloadKind = "object"
JSONPayloadArray JSONPayloadKind = "array"
JSONPayloadAny JSONPayloadKind = "any"
)
func ReadJSONFilePayloadKind(path string, kind JSONPayloadKind) (json.RawMessage, error)Keep ReadJSONFilePayload(path) as the object-only compatibility wrapper.
Required errors:
payload path must be a file
payload file is empty
invalid JSON: ...
payload must be a JSON object
payload must be a JSON arrayPagination Contract
Apple Ads uses offset pagination, not App Store Connect links.next.
List/search commands with limit/offset support:
--limit INT
--offset INT
--paginateDo not add --next to Apple Ads commands. Validate:
--limitmust be1..1000exceptGET v5/custom-reports, where it must be1..50.--offsetmust be>=0.--paginatestarts at--offsetwhen provided.--paginateuses page size--limitwhen provided, otherwise1000
exceptGET v5/custom-reports, where the default page size is50.- Continue until
pagination.startIndex + pagination.itemsPerPage >= pagination.totalResultsor a page returns no data.
Do not add --paginate to find or report endpoints that carry pagination
inside a Selector or ReportingRequest JSON body. Those endpoints keep
pagination fully manual in the payload file. The CLI must not mutate payload
files or rewrite body JSON to advance selector pagination in this PR.
The generic paginated response shape must preserve the Apple envelope:
{
"data": [],
"pagination": {
"itemsPerPage": 1000,
"startIndex": 0,
"totalResults": 0
}
}Output Contract
All endpoint commands bind:
output := shared.BindOutputFlags(fs)Return Apple Ads response JSON exactly as Apple returns it, including data,pagination, and error envelopes. JSON is the canonical agent output.
Use the existing output-registry fallback for Apple Ads raw response types:table and markdown print the same JSON envelope as json until a dedicated
renderer is added in a later PR. Do not add custom Apple Ads table/markdown
renderers in this PR.
Represent successful Apple Ads responses as json.RawMessage or a dedicated
raw envelope type that is not registered with the output registry.
Historical v5 endpoint-to-command matrix
This matrix records the 100% v5 coverage implem