App-Store-Connect-CLI Documentation

Fast, scriptable CLI for the App Store Connect API. Automate TestFlight, builds, submissions, signing, analytics, screenshots, subscriptions, and more

RAW Doc

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

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:

bash
asc install-skills

asc install-skills checks out reviewed commit
f52c4f04323bb2dfb21ca8be82e6494e9cd0b4d8 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. Only
git is required; the command does not execute Node.js, npx, an npm package,
or repository scripts.

bash
git --version
asc install-skills

Quick Start

If you want to confirm the binary works before configuring authentication:

bash
asc version
asc --help

1. Install

bash
# Homebrew (recommended)
brew install asc

# Install script (macOS/Linux)
curl -fsSL https://asccli.sh/install | bash
powershell
# Windows (WinGet, once the package is accepted)
winget install asc

# Exact fallback when scripting
winget install --id Rorkai.ASC --exact

The 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

bash
asc auth login \
  --name "MyApp" \
  --key-id "ABC123" \
  --issuer-id "DEF456" \
  --private-key /path/to/AuthKey.p8 \
  --network

Individual API keys have no issuer ID. Omit --issuer-id and pass --key-type individual:

bash
asc auth login \
  --name "MyIndividualKey" \
  --key-id "ABC123" \
  --key-type individual \
  --private-key /path/to/AuthKey.p8

Generate 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:

bash
asc auth login \
  --bypass-keychain \
  --name "MyCIKey" \
  --key-id "ABC123" \
  --issuer-id "DEF456" \
  --private-key /path/to/AuthKey.p8

3. Validate auth

bash
asc auth status --validate
asc auth doctor

4. First command

bash
asc apps list --output table
asc apps list --output json --pretty

Output 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:

bash
export ASC_DEFAULT_OUTPUT=markdown

And explicit flags always win:

bash
asc apps list --output json

Compatibility

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 asc and asc 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=1 or re-run asc 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-only for a concise incident view; summary counts still cover all matched services

Output

  • asc defaults to table in an interactive terminal and json in pipes, files, and CI
  • Use an explicit format when scripting or sharing repro steps: --output json, --output table, or --output markdown
  • Use --pretty with JSON when you want readable output in terminals or bug reports
  • Set a personal default with ASC_DEFAULT_OUTPUT, but remember --output always 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:

bash
asc telemetry status
asc telemetry disable
asc telemetry reset-id

ASC_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 from ASC_DEBUG=api asc ... or asc --api-debug ...

Wall of Apps

See the 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

bash
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

bash
asc builds upload --app "123456789" --ipa "/path/to/MyApp.ipa"
asc builds list --app "123456789" --output table
asc testflight groups list --app "123456789" --output table

For macOS TestFlight distribution, upload the exported .pkg first, then add
the processed build to a beta group:

bash
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)

bash
# 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 2h

Lower-level submission lifecycle commands (for debugging or partial workflows):

bash
# 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" --confirm

Readiness 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:

bash
asc validate --app "123456789" --version "1.2.3" --output json
asc validate --app "123456789" --version "1.2.3" --strict

Add the --deep mode when a release needs checks that Apple
exposes only through its signed-in web app:

bash
asc web auth login --apple-id "[email protected]"
asc validate --app "123456789" --version "1.2.3" --deep

Deep 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

bash
asc review status --app "123456789"
asc review doctor --app "123456789"

Metadata and localization

bash
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 --pretty

Use 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

bash
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:

bash
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 10

VERSION_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:

bash
asc screenshots matrix --plan .asc/screenshots-matrix.json --max-concurrency 2 --output json --pretty

See docs/design/screenshots-matrix.md for
the JSONC schema, isolated output layout, retry behavior, and review contract.

Signing and bundle IDs

bash
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/pulled

signing 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

bash
asc workflow validate --output json
asc workflow run --dry-run testflight_beta VERSION:1.2.3

Raw API requests

bash
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.json

Verified local Xcode -> TestFlight workflow

See docs/WORKFLOWS.md for local compile validation with
asc 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, and
asc 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.

bash
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.3

Replace the example Xcode destination with a simulator installed on the host.

Xcode Cloud workflows and build runs

bash
# 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:

bash
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 json

See 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:

bash
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 json

JSON 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:

bash
asc --help
asc <command> --help
asc <command> <subcommand> --help

Reference hierarchy:

  • asc --help: authoritative command and flag surface
  • docs/COMMANDS.md: generated top-level taxonomy map
  • asc docs show workflows: curated workflow recipes
  • asc docs show reference: repo-local quick reference template used by asc init

For full command families, flags, and discovery patterns, see:

Documentation

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

  1. Replace latest.json with a newer spec file.
  2. Run make update-openapi to regenerate paths.txt.
  3. Run make update-schema-index to regenerate the runtime asc schema index.
  4. Run make check-docs to verify both generated indexes are current.
  5. 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.json SHA-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-purpose DATA_LINKED_TO_YOU to DATA_NOT_LINKED_TO_YOU identity flip. The request uses the existing appDataUsages JSON:API resource and its dataProtection relationship; Apple returned 200, 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_YOU to DATA_LINKED_TO_YOU transition. The temporary EMAIL_ADDRESS/APP_FUNCTIONALITY tuple returned 200, and a fresh GET retained the same usage ID with the new protection. The exact pre-canary DATA_NOT_COLLECTED baseline 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 the DATA_NOT_COLLECTED declaration (201). Direct restore attempts via POST or PATCH to DATA_NOT_COLLECTED returned 409 STATE_ERROR.
  • The direct mutations advanced the remote lastPublished value while published remained true, despite no publish endpoint being called.

Analytics & Sales Reports

  • Although Apple's current Sales Reports endpoint table lists SUBSCRIPTION 1_3, live verification in PR #1842 proved 1_4 succeeds and is required by some accounts, so both are accepted and 1_4 remains 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 app 6759231657 and IAP 6760268101 verified 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/validateFields requests project the first three fields, then add acAccountPassword; POST /sandbox/v2/account/create sends exactly firstName, lastName, acAccountName, acAccountPassword, and storeFront. confirmPassword is UI-only, and the bundle contains no secretQuestion, secretAnswer, or birthDate fields. 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 include id and isInFamily; the UI excludes family members from deletion.
  • Delete status/body, post-delete disappearance, and account-wide mutation acceptance for POST /sandbox/v2/account/delete remain 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 6759231657 and a locally built candidate CLI. The overall-yes, regional-gbr-false, and overall-no CLI steps all exited 0 with exact readbacks; the regional GBR No 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, and nonPersonalServiceApps in 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 empty POST /iris/v2/apiKeys create request, browser P-256 key generation with PKCS#8 private and SPKI public export, public-key registration by v2 PATCH, and v2 inactive-state revocation. Before the detail/generation flow it checks GET /iris/v1/apiAccesses for an APPROVED access resource and obtains the actor ID from initialized page state. This capture contains no /iris/v1/users/ or AuthSession.UserEmail request.
  • 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 6759231657 on 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 under internal/cli/cmdtest/testdata/pricing_schedule_create_409/, byte for byte including Apple's per-response error id.
  • At 23:22 UTC on 2026-09-26, startDate 2026-09-26 returned 201. At 00:03 UTC on 2026-09-27, startDate 2026-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), while 2026-09-26 (the US Pacific date) returned 201. Omitting startDate also returned 201 and restored the original startDate: null / endDate: null interval.
  • Apple's base64 price resource IDs encode interval boundaries as Unix seconds (sd, ed); a captured in-app purchase schedule encodes ed of one interval and sd of the next as the same value, 1771228800 (2026-02-16 00:00 PST, 08:00 UTC).
  • No subscription-specific live capture exists for subscriptionPrices start-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.action with name, identifier, and teamId (portal_client.rb#L483-L492) and sends the full iCloud.-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's listCloudContainers.action.
  • Live capture (2026-09-29, CLI built from 414167918, one maintainer-approved permanent container): the sequence was listTeams.action 200, the visible and hidden preflight reads 200, POST https://developer.apple.com/services-account/v1/cloudContainers 201, then the visible and hidden read-backs 200. The receipt was containerId 7PU5FAD3YR, identifier iCloud.com.rorkai.asc.capture.t1790620906, name asc capture container, prefix YQZQG7N4WG (the team prefix), and hidden:false. A separate asc web icloud-containers list showed 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.json holds the captured request body and status, plus the read-back resource. Its canEdit, canDelete, and responseId values 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 reply and asc 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-running asc web apps create --name "ASC Test 20260216074703" --bundle-id com.rudrank.asc.throwaway20260216074703 --sku asc-test-20260216074703 --auto-rename=false returned HTTP 409 with three errors[] entries whose codes are, in order, ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE, ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE, and ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE.SAME_ACCOUNT. The CLI exited 5 with web apps create failed: web api error (status 409) ... codes=[...].
  • Only the status and the codes were captured. The detail, title, and source of 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-name detail and source pointers to exercise the --auto-rename interaction; those fields are synthetic (inferred, not captured) and are labeled as such in internal/cli/web/web_apps_create_if_exists_test.go.

Web-session app distribution method

  • The current Apple frontend's pricing action supplies an inner apps resource to its request action. The shared Iris transport serializer (module 60285, helper Oe) converts that resource to JSON.stringify({data: resource}) before fetch (it), so the wire request is JSON:API.
  • asc web apps distribution set writes 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/customAppUsers with HTTP 201, an app-scoped list with HTTP 200, and DELETE /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 expected apps resource ID, relationships.appTransferRequest.data: null, and an empty included array. The related URL /iris/v1/apps/{appId}/appTransferRequest returned HTTP 409 with ENTITY_ERROR.RELATIONSHIP.INVALID for 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 by ENTITY_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}/items rejects include=appStoreVersionExperimentV2 with HTTP 400 PARAMETER_ERROR.INVALID even though the public OpenAPI snapshot lists that relationship.

TestFlight text validation (What to Test)

  • A live probe on 2026-09-25 against betaAppLocalizations description on a disposable app (the app had no builds, so betaBuildLocalizations whatsNew could not be written directly) rejected the write with An attribute value has invalid text.: Text contains invalid characters/formats. for: any < (including a, &, the literal text &lt;, 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 betaBuildLocalizations whatsNew (PATCH on an existing localization, disposable app) answered 409 An 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.
  • A live check on 2026-09-25 showed App Store Connect matches filter[locale] on betaAppLocalizations case-insensitively (fr-fr and FR-FR both returned fr-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 with An attribute value is too long.: Review Notes cannot be longer than 4000 characters. (agents trimmed and retried up to 7 times, often measuring bytes with wc -c).
    • Contact fields on create: POST /v1/appStoreReviewDetails sending only contactEmail failed with You must provide a value for the attribute 'contactFirstName' with this request; sending first name, last name, and email failed on 'contactPhone'. An update sending only contactEmail to a detail without a first name also failed on contactFirstName.
    • contactPhone: Rejected 7 digits after +503, and 7- and 11-digit national numbers after +1, with The phone number must be in a valid format. Preface the phone number with '+' followed by the country code (for example, +44 844 209 0611) (betaAppReviewDetails answered The 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 httptest coverage 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 deviceModel rows in GET /v1/buildBundles/{id}/buildBundleFileSizes on every one of five builds of a live iPhone-only app, matching universal apps.
  • fastlane encountered the same intermittent GET /v1/apps/{id}/buildUploads 404 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) and GET /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-v15 payloads expose TestFlight post-actions under post_actions[].deployment_config.testflight_deployment_ids. Two distinct beta_group_ids observed across two post-actions matched the same app's authenticated IRIS betaGroups IDs and the public command output from asc testflight groups list --app APP_ID --paginate --output json (three groups returned). The scan covered 14 products/workflows; all observed beta_tester_ids arrays were empty, so tester-ID equivalence remains unverified.

Developer Portal session (web session)

  • The public schema supports fields[bundleIds]=bundleIdCapabilities and limit[bundleIdCapabilities]=50; their acceptance by the private /services-account/v1 proxy 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 include identifier, dateModified, entitlementGroupName, bundleType, platform, wildcard, dateCreated, bundleIdCapabilitiesSettingOption, seedId, name, platformName, deploymentDataNotice, and responseId.

Developer Portal Services IDs (web session)

  • A 2026-09-26 browser capture accepted PATCH /services-account/v1/bundleIds/{id} for asc web service-ids domains set with an ID-less bundleIdCapabilities child linked to capability APPLE_ID_AUTH and its existing appConsentBundleId. The captured detail response reported paging.total=0 with limit=2147483647 despite 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, then asc web service-ids view --service-id ID --output json, then asc 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 no next link. The sanitized relationship is checked in as internal/web/testdata/service_id_empty_capabilities_relationship.json, with the captured resource ID in its links replaced by service-1.

Developer Portal Website Push IDs (web session)

  • The live listWebsitePushIds.action read returned HTTP 200 with resultCode=0, pageNumber=1, pageSize=1000, and an empty root-level websitePushIdList in the captured account state.
  • The captured frontend (captures/apple-developer-main.js) maps each legacy item to id=item.websitePushId for this resource (offset 849,700), while its generic identifier table reads name and identifier (offset 866,718); the topic picker also reads websitePushId/id and identifier (offsets 1,806,200–1,808,480).
  • A final built-CLI canary using binary SHA-256 fd940938b7477aa27b0f5c793618af0c5c1e30d050f85b6bacf1738378ce31f9 exercised the complete happy path for one uniquely generated owned disposable identifier: baseline list, create, view, delete, and final list all 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; certificateAbsenceIndependentlyVerified is 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/profiles with fields[profiles]=name&include=devices returned HTTP 200 and still put related devices in included, while omitting relationships on each profile unless devices was also in fields[profiles]. fields[devices]=name,udid sparse-filtered the included devices.
    • GET /v1/certificates with fields[certificates]=displayName&include=passTypeId returned HTTP 200. The team had no PASS_TYPE_ID certificates, so included was absent both with that sparse fieldset and with include=passTypeId alone. Non-pass certificates exposed relationships.passTypeId.data=null only when the relationship was in the fieldset (or when no certificate fieldset was sent).

Developer Portal App Groups (web session)

  • deleteApplicationGroup.action was inferred from sibling listApplicationGroups.action / addApplicationGroup.action actions 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/bundleIds with X-HTTP-Method-Override: GET, fields[bundleIds]=bundleIdCapabilities, and limit[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), DeveloperCertificates in local provisioning profiles (22 Apple Development, 11 Apple Distribution, 8 legacy iPhone Developer), leaves extracted with codesign -d --extract-certificates from signed apps and IPAs, and leaves read from X509Certificate elements of flat-package tables of contents (xar --dump-toc), including Xcode's bundled CoreTypes, MobileDevice, MobileDeviceDevelopment, and XcodeSystemResources pkgs.
    • 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, and 6.1.12 (development). Apple's WWDR and Developer ID CPS pages were not machine-readable at the time.

Offline signature verification: xar member payload checksums

  • Checked on 2026-09-29 against pkgutil --check-signature on macOS 27:
    • Flipping one payload byte of Xcode's MobileDeviceDevelopment.pkg made pkgutil report "package is invalid (checksum did not verify)", and pkg-info reported invalid naming Payload.
    • 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-signature still 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.

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 between DATA_LINKED_TO_YOU and DATA_NOT_LINKED_TO_YOU using the appDataUsages JSON:API resource and its dataProtection relationship (live-verified).
  • Tracking, DATA_NOT_COLLECTED, and scope changes must use DELETE (204) + POST (201); direct POST or PATCH to DATA_NOT_COLLECTED fails with 409 STATE_ERROR.
  • Direct mutations advance lastPublished while published remains true without calling a publish endpoint. Treat asc web privacy apply as 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_id or ads.ad_account_id from 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 rank is an unauthenticated storefront read: iOS inspects up to 200 results from the public iTunes /search endpoint, while Apple TV uses the undocumented MZStore search endpoint with X-Apple-Store-Front: <numeric-storefront-id>,33 (available only for countries with a known numeric storefront ID).
  • found: false means only that the app was absent from the returned result window.

Analytics & Sales Reports

  • Sales Reports require YYYY-MM for monthly reports and YYYY for yearly reports on the wire; the CLI accepts YYYY-MM-DD and 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 SUBSCRIPTION 1_3 and 1_4 are accepted (1_4 is the default and live-verified).
  • Use --paginate with asc analytics view --processing-date (filter[processingDate]) to search every report page, or --next <links.next> --paginate to resume. Use --granularity "DAILY,WEEKLY,MONTHLY" to filter by granularity. Raise ASC_TIMEOUT for 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

Tax Categories and Transaction Tax Reports

  • The public API has no tax-category resource or attribute on apps, appInfos, or inAppPurchases. asc capabilities --area monetization reports 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).
    • set requires --confirm, validates category and condition IDs against the catalog, sends the complete condition set (enabledConditions.data=[] when --condition is 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.
  • In-App Purchases (asc web iap tax-category list|view|set|reset):
    • Catalog read uses filter[productType]=ADDON and preserves the raw JSON:API envelope.
    • view reads GET /iris/v2/inAppPurchases/{iapId}?include=inAppPurchaseTaxCategoryInfo. Explicit inAppPurchaseTaxCategoryInfo.data: null means inherited from the parent app (raw discovery envelope preserved). When configured, the CLI reads GET /iris/v1/inAppPurchaseTaxCategoryInfos/{taxInfoId}?include=category,enabledConditions,inAppPurchaseV2&limit[enabledConditions]=100 and verifies the owner IAP ID.
    • set (--confirm) validates against the ADDON catalog, 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 the inAppPurchaseTaxCategoryInfos override and verifies an explicit-null relationship (no-op when already inherited). Browser contract is verified; final CLI live execution remains unverified.

Sandbox Testers

  • List, view, update, and clear-history use the public v2 API through asc sandbox.
  • asc web sandbox create requires --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/validateFields requests followed by POST /sandbox/v2/account/create with firstName, lastName, acAccountName, acAccountPassword, and storeFront (source-backed; live create acceptance is unverified).
  • Web list uses GET /sandbox/v2/provider/account/list?limit=50 (no pagination contract captured; rows include id and isInFamily).
  • asc web sandbox delete (--confirm) sends POST /sandbox/v2/account/delete with {"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.contentStatuses exposes only read-only trader status consequence codes).
  • Declarations use the web-session ppm/complianceform/v1 service with UI headers (X-Csrf-Itc: itc, Origin, and /apps/{id}/distribution/info Referer):
    • asc web apps declarations list: GET /ppm/complianceform/v1/accounts/{accountId}/requirements?contentId={appId}. Prefers requirementData matching contentId, falling back to empty contentId.
    • asc web apps medical-device view: GET /ppm/complianceform/v1/accounts/{accountId}/requirements/{requirementId}/forms?contentId={appId}, reading medicalDeviceData.declaration (no, yes, or absent) alongside $[*]-rooted constraints.
    • asc web apps medical-device set: uses POST .../contents/{appId}/requirements/{requirementId}/forms when medicalDeviceData.declaration is absent and PUT when present. --declared false derives regions from form constraints, preserves existing regional rows, and skips writing when already no + COLLECTED. --declared true [--countries-or-regions EEA,GBR,USA] --confirm limits regions to EEA, GBR, USA (defaulting to all three), updates registrationInfo rows, and verifies readback.
    • asc web apps medical-device region set --app APP_ID --region REGION --input PATH --confirm: sends PUT .../forms with rootfs-anchored declaration, localized supportInfo, and registrationNumber (for USA/EEA). Requires app-level declaration yes and 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 interactive id.apple.com identity verification and is out of scope for unattended CLI writes.

Web-session API keys

  • asc web api-keys list combines 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), following links.next internally (no --paginate). Use asc web auth capabilities --key-id to resolve actor-backed roles when an individual key's roles array is empty.
  • asc web api-keys view --key-id reads GET /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 --confirm checks GET /iris/v1/apiAccesses (APPROVED) plus fail-closed GET /iris/v1/users/{userUUID} and session-email preflight, stages a local 0600 PKCS#8 P-256 private key under --output-dir, sends POST /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 to ApiKey_<KEY_ID>.p8 without replacing existing files, registers the SPKI public key via PATCH /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 --confirm sends PATCH /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}/appAvailabilityV2 returns availableInNewTerritories and links-only relationships.territoryAvailabilities; ?include=availableTerritories&limit[availableTerritories]=200 fails with 400 PARAMETER_ERROR.INVALID. When unconfigured, it can return data: null (treated as absent by asc web apps availability create).
  • Territory availability reads use GET /iris/v2/appAvailabilities/{id}/territoryAvailabilities?include=territory&limit=200 (follow links.next; filter[available]=true returns 400 PARAMETER_ERROR.ILLEGAL, so filter client-side). asc web apps delete uses this collection for its removed-from-sale preflight.
  • On the public API, GET /v1/apps/{id}/appAvailabilityV2 and GET /v2/inAppPurchases/{id}/inAppPurchaseAvailability return 404 NOT_FOUND naming type 'appAvailabilities' or 'inAppPurchaseAvailabilities' when unconfigured (vs 'apps' / 'inAppPurchases' when the parent is missing); asc pricing availability view treats 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}/appPriceSchedule returns a synthetic schedule resource, and GET /v1/appPriceSchedules/{id}/manualPrices returns 404 There is no resource of type 'null' with id '{appId}'. asc pricing current treats that (and a direct schedule 404 or missing current base price) as an expected negative (configured: false or reason: no_current_base_price).
  • asc web removed-apps restore patches /iris/v1/apps/{id} with removed:false, verifies restoration, and only then POSTs /iris/v1/userAppPermissions (GRANT or REVOKE for ALL_SILOABLE_USERS).

App price schedules (`POST /v1/appPriceSchedules`)

  • POST /v1/appPriceSchedules replaces the whole schedule. Known HTTP 409 conflict shapes (fixtures in internal/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 startDate without 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.
  • App Store Connect evaluates "today" and schedule interval boundaries at US Pacific midnight (America/Los_Angeles) with exclusive end dates, and stores an accepted today startDate as null. asc pricing, asc app-setup pricing, asc iap pricing, asc subscriptions pricing, and asc validate all evaluate current/resolved prices and default start dates using the US Pacific date.

Developer Portal iCloud containers

  • asc web icloud-containers list sends POST /services-account/v1/cloudContainers?filter[AND][hidden]=false (or true with --hidden) with X-HTTP-Method-Override: GET, teamId, and urlEncodedQueryParams=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 --confirm requires the exact iCloud. prefix, preflights both visible and hidden collections (failing closed on truncation or existing matches), sends POST /services-account/v1/cloudContainers with {"data":{"type":"cloudContainers","attributes":{"identifier":...,"name":...,"teamId":...}}} (no root teamId), 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/v1 with Apple ID web-session auth and follow links.next internally (no --paginate).
  • Submission scope (GET /iris/v1/resolutionCenterThreads?filter[reviewSubmission]={id}&include=reviewSubmission, used by asc 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 by asc 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. Explicit data: null or HTTP 404 means no draft; a response missing data is malformed and fails closed. asc web review threads --drafts omits signed attachment download URLs.
  • Mutations (POST /iris/v1/resolutionCenterDraftMessages, PATCH/DELETE on the draft resource, and POST /iris/v1/resolutionCenterMessages with createFromDraftMessage for asc web review reply and asc 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, and ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE.SAME_ACCOUNT (captured live on 2026-09-29; detail, title, and source were not captured), so APIError.AllCodes() must inspect every entry in errors[] rather than only the first. asc web apps create --if-exists skip keys on either code and resolves the existing app via the public API (GET /v1/apps?filter[bundleId]=, then filter[sku]=).

Web-session app distribution method

  • distributionType (APP_STORE or CUSTOM) and educationDiscountType (DISCOUNTED, NOT_DISCOUNTED, NOT_APPLICABLE) exist only on /iris/v1/apps/{id} (no sparse fieldset). Missing distributionType is rendered as unknown in tables and omitted in JSON.
  • asc web apps distribution set --app APP_ID --method public|private --confirm preflights GET /iris/v1/apps/{id}, rejects DIRECT_URL or unknown methods, sends one PATCH /iris/v1/apps/{id} with educationDiscountType and conditional distributionType, and verifies via read-back without retries (uncertain outcomes return status: "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 (customAppUsers only; customAppOrganizations is unsupported):
    • asc web apps distribution users list --app APP_ID [--paginate]: reads GET /iris/v1/apps/{appId}/customAppUsers?limit=100 and validates pagination invariants strictly.
    • create --app APP_ID --recipient-apple-id APPLE_ACCOUNT --confirm (POST /iris/v1/customAppUsers -> 201) and delete --app APP_ID --id RECIPIENT_ID --confirm (DELETE /iris/v1/customAppUsers/{recipientID} -> 204) require the app to be in distributionType: 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_ID reads GET /iris/v1/apps/{appId}?include=appTransferRequest (live-verified for data: null). Do not call /iris/v1/apps/{appId}/appTransferRequest directly (returns HTTP 409 ENTITY_ERROR.RELATIONSHIP.INVALID when 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 downloadable attribute on public appStoreVersions (asc versions list/view --output json and asc versions update --downloadable true|false; --downloadable false requires --confirm). Absent downloadable is never defaulted to true.
  • Live-version lookups query filter[appStoreState] and filter[appVersionState] in separate requests (Apple combines them with AND) and prefer appVersionState (READY_FOR_DISTRIBUTION) over appStoreState (READY_FOR_SALE), while checking appStoreState for PREORDER_READY_FOR_SALE, DEVELOPER_REMOVED_FROM_SALE, and REMOVED_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 (with ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE) when the version string is already used.
  • On an unhandled 409, asc versions create makes a best-effort 5s paginated read of GET /v1/apps/{id}/appStoreVersions?filter[platform]=...&limit=200 (which has no sort parameter, so pages are sorted client-side by createdDate) 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 via GET /iris/v1/apps/{id}/appStoreVersions and serially reads GET /iris/v1/appStoreVersions/{appStoreVersionId}/appStoreVersionStateChanges (appStoreState, date, initiator), following links.next internally. Requires the AppStatusHistory role capability; use --version-id or raise ASC_TIMEOUT for apps with long histories.

Web-session review submissions (iris)

  • GET /iris/v1/reviewSubmissions/{id}/items rejects include=appStoreVersionExperimentV2 with HTTP 400 PARAMETER_ERROR.INVALID (live-verified); asc web review show omits it and keeps iris-accepted includes (inAppPurchaseVersion, subscriptionVersion, subscriptionGroupVersion).

TestFlight Distribution

  • externalBuildState cannot be patched on build beta details (--external-testing was removed in 5.0.0); manage external distribution via asc builds add-groups ... --submit --confirm and asc builds remove-groups ... --confirm.
  • POST /v1/builds/{id}/relationships/betaGroups can briefly return a build-specific 404 right after upload; asc publish testflight confirms GET /v1/builds/{id} (stopping on FAILED/INVALID) and retries only that propagation 404 with bounded backoff.
  • asc builds add-groups preflights 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-run performs the read-only resolution/preflight without mutating or submitting (--submit conflicts with --dry-run).

TestFlight text validation (What to Test)

  • betaAppLocalizations.description and betaBuildLocalizations.whatsNew reject <, 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 (including U+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, and U+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, and asc publish testflight --test-notes validate 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 betaAppLocalizations exists for the locale (filter[app] + case-insensitive filter[locale]), creating it if absent and re-checking on POST /v1/betaAppLocalizations HTTP 409 (which can mean either duplicate or invalid locale).

App Store review details (`appStoreReviewDetails`)

  • notes: rejected over 4000 characters; asc review details-create and details-update count Unicode characters of trimmed text with CRLF as 1 (validation.ReviewNotesLength) and exit 2 before sending when over 4000.
  • Contact fields: details-create with default --if-exists fail requires non-blank --contact-first-name and --contact-phone before POST /v1/appStoreReviewDetails (--if-exists skip|update and details-update are exempt).
  • contactPhone: +1 numbers 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 list has no pagination flags).
  • Achievements, leaderboards, and leaderboard sets require creating a release resource to go live; image uploads use reserve → upload → commit.
  • challengesMinimumPlatformVersions on gameCenterDetails is a replace-only PATCH relationship (GET is rejected) that requires live appStoreVersions linkages (rejects gameCenterAppVersions and non-live versions with ENTITY_ERROR.RELATIONSHIP.INVALID.MIN_CHALLENGES_VERSION_MUST_BE_LIVE).
  • Leaderboard-set member localizations have no direct GET; view --id resolves the parent leaderboard and set, then scans the filtered collection. Group challenge relationships are read-only; use asc game-center challenges create --group-id.

Apple Ads Platform API v1

  • Platform API v1 (https://api.ads.apple.com/v1/) is the primary asc ads surface (since 4.4.0). Legacy Campaign Management API v5 (retiring January 26, 2027) lives under asc ads v5 with deprecation warnings (asc ads v5 api request stays v5; asc ads api request is v1).
  • Account-scoped v1 calls send X-AP-Context: adAccountId=<AD_ACCOUNT_ID>; resolved from --ad-account, ASC_ADS_AD_ACCOUNT_ID, profile ad_account_id, or root ads.ad_account_id when no profile is selected (--org / ASC_ADS_ORG_ID are never fallbacks).
  • POST /v1/ad-accounts omits X-AP-Context, whereas GET/PUT /v1/ad-accounts/{id} require X-AP-Context: adAccountId=; matching {id}.
  • asc ads auth login --network, asc ads auth status --validate, and asc ads auth discover call GET /v1/me (and GET /v1/acls for discover) without X-AP-Context (ASC_ADS_ACCESS_TOKEN skips OAuth token exchange).
  • V1 report pagination belongs in the JSON request body (no --paginate flag). 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 deprecated asc ads v5 reports preset warning follows --level: campaigns, ad groups, ads, keywords, and search terms point to their matching asc ads reports apps command; the two ad-group-specific keyword levels point to v1's consolidated keywords or search-terms report.

Authentication & Rate Limiting

  • App Store Connect JWTs are valid for 10 minutes. --api-debug / ASC_DEBUG=api logs X-Rate-Limit to stderr.
  • Retries (ASC_MAX_RETRIES, ASC_BASE_DELAY, ASC_MAX_DELAY, ASC_RETRY_LOG; ASC_MAX_RETRIES=0 disables):
    • 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 uploadOperations PUT parts retry per part on 408/429/500/502/503/504 and transient transport errors within ASC_UPLOAD_TIMEOUT.
    • Unauthenticated public storefront GET commands retry 429/5xx and honor seconds or HTTP-date Retry-After up to ASC_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}; bulk POST /v2/appAvailabilities on already-configured apps is unverified).

Profiles and certificates list filters

  • GET /v1/profiles and GET /v1/certificates reject filter[platform]; only Bundle ID list accepts filter[platform] (including UNIVERSAL).
  • Provisioning profile names generated by signing fetch / signing sync and explicit profiles create --name values are capped at 64 Unicode code points.

Builds

  • GET /v1/apps/{id}/builds has no defined order and rejects sort with 400 PARAMETER_ERROR.ILLEGAL; always query latest builds via GET /v1/builds?filter[app]={id}&sort=-uploadedDate&limit=1. Relationship endpoints and top-level collections frequently accept different sort/filter parameters.
  • Build resources and GET /v1/buildBundles/{id}/buildBundleFileSizes do not identify UIDeviceFamily (iPhone-only builds still list iPad deviceModel rows); asc validate reads UIDeviceFamily from --ipa when 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 404 There is no resource of type 'apps' with id '…'. Top-level reads retry that type 'apps' 404 directly; app-scoped reads first verify GET /v1/apps/{id} once and retry only when the app exists.

Xcode Cloud workflows

  • Public /v1/ciWorkflows quirks:
    • GET /v1/ciWorkflows/{id} omits data linkages on relationships unless ?include=product,repository,xcodeVersion,macOsVersion is requested (all four are required by POST /v1/ciWorkflows), and emits null for optional non-nullable create properties (destination, testConfiguration, filesAndFoldersRule) that workflows duplicate must strip.
    • TestFlight post-actions (post_actions[].deployment_config.testflight_deployment_ids, whose beta_group_ids match public betaGroups IDs; beta_tester_ids equivalence is unverified) and workflow-scoped env vars/secrets exist only on private /ci/api/ payloads (asc web xcode-cloud env-vars).
  • Private /ci/api web-session surfaces:
    • asc web xcode-cloud scm providers list (GET /ci/api/teams/{teamID}/scm-providers-v2) and connection-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: GET and bodyless PUT .../products/{productID}/next-build-number?next_build_number=N (--confirm; requires N > 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=100 on list, no --paginate; detail GET/PUT/DELETE on .../{versionAliasID}). Saves {name,type,build,locked} with trimmed name (1–40 UTF-16 code units) and type in macos_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/subscriptionOfferCodes requires the prices relationship for all modes: FREE_TRIAL must omit subscriptionPricePoint (--prices "DEU,FRA", otherwise 409 ENTITY_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) with planType=MONTHLY (upfront availability stays on subscriptionAvailability).
  • /v1/subscriptionPricePoints/{id}/adjustedEqualizations accepts only filter[planType]=MONTHLY (rejects UPFRONT).
  • Unavailable in USA and SGP; the CLI strips both territories before writing monthly-commitment plan availability.

Subscription Plan Availability

  • GET /v1/subscriptions/{id}/planAvailabilities?include=availableTerritories caps limit[availableTerritories] at 50; complete territory lists require GET /v1/subscriptionPlanAvailabilities/{id}/relationships/availableTerritories (limit=200, cursor-paginated).
  • PATCH /v1/subscriptionPlanAvailabilities/{id} replaces availableTerritories wholesale and allows only availableInNewTerritories as a mutable attribute (planType is create-only, and availableInNewTerritories is unsupported for MONTHLY). set verifies territories via the paginated relationship endpoint and availableInNewTerritories via a fresh resource GET.
  • Removing an approved subscription from sale (emptying availableTerritories) requires the Account Holder web session (asc web subscriptions availability remove-from-sale on /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 --confirm requires confirmation and currently supports only PRIVATE_CLOUD_COMPUTE. It resolves the Bundle ID through POST /services-account/v1/bundleIds (X-HTTP-Method-Override: GET), reads fields[bundleIds]=bundleIdCapabilities + include=bundleIdCapabilities,bundleIdCapabilities.capability (limit[bundleIdCapabilities]=50), fails closed on pagination/linkage ambiguity, sends a single PATCH /services-account/v1/bundleIds/{id} with enabled:false on 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 list sends POST /services-account/v1/bundleIds (X-HTTP-Method-Override: GET) with teamId and urlEncodedQueryParams (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 ID sends POST /services-account/v1/bundleIds/{id} (X-HTTP-Method-Override: GET, body {teamId}) with fields[bundleIds]=name,identifier,platform,seedId,wildcard,~permissions.delete,~permissions.edit and the full capability/app-group/merchant/cloud-container/certificate/consent include graph.

Developer Portal Services IDs (web session)

  • Public OpenAPI BundleIdPlatform omits SERVICES; Services IDs use the private /services-account/v1/bundleIds proxy (X-HTTP-Method-Override for GET/DELETE, direct POST/PATCH for writes) and verify attributes.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}) with include=bundleIdCapabilities,bundleIdCapabilities.capability,bundleIdCapabilities.appConsentBundleId.
    • create --identifier ID --name NAME --confirm: POST /services-account/v1/bundleIds with data.type=bundleIds, attributes.{identifier,name,platform:"SERVICES",seedId,teamId}, and empty bundleIdCapabilities.data (must omit root-level teamId, which Apple rejects on non-override POST). Verifies via detail read-back.
    • rename --service-id ID --name NAME --confirm and domains set: preflight and post-write reads enforce bundleIdCapabilities completeness (no next link, and either exact paging.total or the captured paging.total=0, limit=2147483647 placeholder with resolved references; known navigation-only links like appGroups/bundleId without data are omitted from PATCH). domains set updates APPLE_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: sends POST /services-account/v1/bundleIds/{id} with X-HTTP-Method-Override: DELETE and root-level teamId (omitting it returns HTTP 403 Please 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 list calls legacy POST /services-account/QH65B2/account/ios/identifiers/listWebsitePushIds.action (onlyCountLists=true, pageSize=1000, pageNumber=1, sort=name=asc, teamId, no sidx; no --paginate) and preserves the root websitePushIdList response.
  • view, create, and delete use 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, sends POST /services-account/v1/websitepushIds (data.type=websitepushIds, name, identifier, teamId, empty websitepushIdCapabilities), and settles via detail/legacy-list read-back without retry.
    • delete (--confirm): preflights modern detail (canDelete=true and proven empty capability relationship), sends POST /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|download uses POST /services-account/QH65B2/account/getAgreementHistory and POST /services-account/QH65B2/account/acceptAgreements (HTTP 200 with resultCode=0 on success). accept re-reads getAgreementHistory to verify dateAccepted >= dateEffective. download resolves root-relative agreementDownloadUrl paths 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 download reads /WebObjects/iTunesConnect.woa/ra/paymentConsolidation/providers/{providerId}/sapVendorNumbers/{sapVendorNumber}?year={YYYY}&month={M} (requiring hasVendorTaxReport=true), derives regionCurrencyIds from reportSummaries[].proceedsByRegion[], triggers generation via GET .../reports?...&isVendorTaxReportReq=true, polls /reports/{uuid}/status for readyForDownload, and streams the same-origin HTTPS ZIP under a bounded workflow context into a new 0600 file without retries or leaking sensitive identifiers/URLs.

Pass Type IDs

  • /v1/passTypeIds/{id}/certificates rejects include=passTypeId and fields[passTypeIds] on the live API despite the OpenAPI spec; asc pass-type-ids certificates list omits both.

Sparse Fieldsets Combined with Includes

  • GET /v1/profiles with fields[profiles]=name&include=devices populates included even though devices is omitted from the primary fieldset; fields[devices]=... still sparse-filters included devices. Apple omits per-profile relationships unless devices is also in fields[profiles] (live-verified).
  • GET /v1/certificates accepts fields[certificates]=displayName&include=passTypeId. The probed team had no PASS_TYPE_ID certificates, so included was absent with and without the sparse fieldset; population of related pass types remains unverified. Non-pass certificates expose relationships.passTypeId.data=null only when passTypeId is 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, and subscriptionGroupVersions through reviewSubmissionItems. The CLI preserves both relationship data and included resources in JSON output.
  • API 4.4.1 has no item-detail GET operation (asc review items view and asc review items-get were removed in 4.0.0); list a parent submission's items with asc review items list --submission "SUBMISSION_ID".
  • API 4.4.1 has no marketplace-webhook instance GET operation; asc marketplace webhooks view selects the exact ID across all pages of the collection GET.
  • Review-item updates accept only nullable resolved and removed attributes (--state was removed in 4.0.0; use --resolved, --removed, or their matching --clear-* flags, with --confirm for removed=true).
  • Review-submission updates expose nullable platform, submitted, and canceled values plus matching --clear-* flags (--confirm required for submitted=true or canceled=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, and asc submit cancel read GET /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 remains appStoreVersionExperiments; the CLI selector is appStoreVersionExperimentsV2 (the 3.x --item-type appStoreVersionExperimentV2 alias was removed in 4.0.0, and experiment treatments are not valid review-item create relationships). Review items require appCustomProductPageVersions (appCustomProductPages is rejected).
  • The product-scoped v1 localization, image, submission, and sync command 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 setup and asc subscriptions setup remain 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>".
  • There is no one-command version-scoped replacement for the removed legacy localization sync leaves; reconcile entries through the matching version-localization list/create/update/delete commands.
  • 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.Client methods targeting these legacy resources remain callable during the transition window, with Deprecated: 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-groups combines legacy form endpoints under /services-account/QH65B2/account/ios/identifiers/ (listApplicationGroups.action, addApplicationGroup.action, deleteApplicationGroup.action; application/x-www-form-urlencoded with teamId and scoped csrf/csrf_ts headers, returning resultCode=0) with /services-account/v1/bundleIds:
    • delete (deleteApplicationGroup.action, inferred and live-unverified) first scans all team Bundle IDs via proxied POST /services-account/v1/bundleIds (X-HTTP-Method-Override: GET, fields[bundleIds]=bundleIdCapabilities, limit=200, limit[bundleIdCapabilities]=50, following links.next) and refuses if any APP_GROUPS capability (even disabled) references the group. After delete, it verifies absence across the strictly paginated App Group list.
    • assign, unassign, and set read the Bundle ID with fields[bundleIds]=bundleIdCapabilities and limit[bundleIdCapabilities]=50, fail closed on incomplete/ambiguous capability or nested appGroups pagination, rewrite only APP_GROUPS, and PATCH /services-account/v1/bundleIds/{id} (live proxy acceptance remains unverified). assign rejects a group.* identifier with exit 2 when the team listing proves an opaque resource ID is required. Ambiguous writes settle via read-back without retry and return DeveloperAppGroupUnverifiedError when 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 report untrusted-chain. CMS-only x-signature Mac App Store pkgs report unsupported.

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's archived-checksum (sha1, sha256, sha384, sha512, md5; other algorithms report unsupported unless another member is invalid).
  • 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 because pkgutil --check-signature accepts modified <ea> heap bytes.

CI CD (`docs/CI_CD.md`)

CI/CD Integrations

GitHub Actions

Install asc using the official setup action:

yaml
- uses: rudrankriyam/setup-asc@v1
  with:
    version: latest

- run: asc --help

For end-to-end examples, see:
https://github.com/rudrankriyam/setup-asc

GitLab CI/CD Components

Use the official asc-ci-components repository:

yaml
include:
  - component: gitlab.com/rudrankriyam/asc-ci-components/run@main
    inputs:
      stage: deploy
      job_prefix: release
      asc_version: latest
      command: asc --help

For install/run templates and self-managed examples:
https://github.com/rudrankriyam/asc-ci-components

Bitrise

Use the official setup-asc Bitrise step repository:

yaml
workflows:
  primary:
    steps:
    - git::https://github.com/rudrankriyam/steps-setup-asc.git@main:
        inputs:
        - mode: run
        - version: latest
        - command: asc --help

CircleCI

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:

  1. 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 push with --identity) before building, because the
    private key exists only on this runner.
  2. Every later run: asc signing sync pull decrypts the stored identity and
    profiles.
  3. asc signing keychain install --confirm imports the identity into a new
    dedicated keychain, and asc profiles local install installs each profile.
  4. asc xcode signing plan with --profile infers each target's signing
    settings and an ExportOptions.plist from the profiles, and
    asc xcode signing apply --confirm writes those settings into the project.
  5. asc xcode archive, then asc xcode export with --export-options,
    produce the signed archive and IPA.
  6. asc signing keychain delete --confirm removes 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.

bash
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" --confirm

p12_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 with
asc 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, and
asc 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:

yaml
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 json

configure-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 and
s3:GetObject on the prefix. A job that runs sync push or
sync rotate-password also needs s3:PutObject, and rotation needs
s3: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 and
If-None-Match loses the conflict protection. Receipts include
storage.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:

bash
asc --help
asc <command> --help
asc <command> <subcommand> --help

To regenerate:

bash
make generate-command-docs

Usage Pattern

bash
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; use publish appstore --submit to 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 minified json.
  • Use --output table or --output markdown for explicit human-readable output.
  • Use --output json for explicit machine-readable output.
  • Use --paginate on list commands to fetch all pages automatically.
  • Use --limit and --next for manual pagination control.
  • Any flag that takes a value accepts @env:NAME or @file:PATH to read it from the environment or a file; escape a literal leading @ as @@. --output and the root --profile, --report, and --report-file are always read literally.
  • Prefer explicit flags and deterministic outputs in CI scripts.

High-Signal Examples

text
/* Detailed source-code truncated for AI context efficiency. */

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 main and keep one logical change per branch
  • Do not commit directly to main unless explicitly instructed; prefer PRs
  • Prefer git worktree add for parallel tasks; remove with git worktree remove when done
  • Compare the branch with current main before merging. Do not rebase or merge main into a clean branch merely because main advanced; 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:

bash
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 staging

For 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:

bash
make check-wall-of-apps

Repository 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:

bash
make check-agent-skills

make 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 (use shared_wrappers.go as needed)

Adding a New Command

  1. Add or extend a domain package in internal/cli/<domain>
  2. Implement a command factory (e.g., XCommand() *ffcli.Command)
  3. Register it in internal/cli/registry/registry.go
  4. Write tests in the domain package (or internal/cli/cmdtest for root-level tests)
  5. Update README.md with usage examples

Adding a New API Endpoint

  1. Add method to internal/asc/client.go
  2. Add types for request/response structs
  3. Add helper functions for table/markdown output
  4. Create command in internal/cli/<domain> to expose the endpoint
  5. Write HTTP client tests with mocked responses
  6. 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:

bash
# 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 correctly

Common 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 (and gofumpt via make 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.Context into 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 const values (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,
    return shared.NewReportedError(err); the root prints err once to stderr
    as an Error: 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 UsageFunc on ffcli commands for consistent help formatting
  • When returning flag.ErrHelp, do not call fs.Usage() manually (avoids duplicate output)
  • Help output is written to stderr by default

Testing

Error Checking

  • Do: Use errors.Is() or typed extraction via errors.AsType() (Go 1.26+)
  • Do: Use errors.As() when you need compatibility with older Go versions
  • Do: Simply check err != nil when only verifying an error occurred
  • Don't: Match error strings with strings.Contains(err.Error(), "...") - this is fragile
go
// 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 null when no data is returned
  • CLI commands should exit 0 for 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 with go test -artifacts
  • Use t.Setenv() to set environment variables (auto-cleaned up)
  • Isolate from user config by setting ASC_CONFIG_PATH to a temp path; a temporary HOME alone is not enough, because the upward .asc/config.json search 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, and make test-coverage unset inherited ASC_* variables (except the opt-in test switches ASC_UPDATE_GOLDEN, ASC_SIGNING_RUN_LIVE_TEST, and ASC_SIGNING_KEYCHAIN_INSTALL_LIVE_TEST) and DO_NOT_TRACK and point ASC_CONFIG_PATH at a missing file in a fresh, read-only per-run directory; tests that need any of those inputs set them with t.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-mapping is Apple's Android-to-iOS mapping API, not Android CI.
  • Plugin/lane runtime (Fastfile plugins). asc workflow runs 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 init are 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 (httptest or a stub transport); only the opt-in make test-integration suite 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.

go
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:

go
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/cmdtest for command output and parsing tests
  • Capture stderr for usage text (help output goes to stderr)

Running Tests

bash
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 test

Always 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:

powershell
winget install asc

Keep the exact package identifier documented as the scripting fallback:

powershell
winget install --id Rorkai.ASC --exact

Package Identity

Use this identity for microsoft/winget-pkgs submissions:

yaml
PackageIdentifier: Rorkai.ASC
PackageName: asc
Moniker: asc
Commands:
  - asc

Moniker: 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 public
microsoft/winget-pkgs repository,
not in this repository. For the initial submission, create a portable package
from the Windows release asset:

yaml
PackageIdentifier: Rorkai.ASC
PackageVersion: 1.5.1
DefaultLocale: en-US
ManifestType: version
ManifestVersion: 1.6.0
yaml
PackageIdentifier: 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.0
yaml
PackageIdentifier: 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.0

The release workflow generates these files with
scripts/generate_winget_manifests.py using the SHA256 from
asc_<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 of
    microsoft/winget-pkgs, push branches to that fork, and open PRs against
    microsoft/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 duplicate
microsoft/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:

text
rorkai-asc-<version>

The manifest path in winget-pkgs is:

text
manifests/r/Rorkai/ASC/<version>/

To generate manifests locally for a release asset directory:

bash
python3 scripts/generate_winget_manifests.py \
  --version 1.5.1 \
  --release-dir release \
  --output-dir /tmp/asc-winget

Acceptance Check

After the manifest PR is merged and WinGet sources update, run the manual
WinGet smoke workflow or use a clean Windows machine:

powershell
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 --exact

If winget install asc prompts for disambiguation, keep the README fallback as
the documented automation-safe command:

powershell
winget install --id Rorkai.ASC --exact

WORKFLOWS (`docs/WORKFLOWS.md`)

Workflow Patterns

Use the high-level workflow surfaces deliberately:

  • asc publish appstore: canonical App Store shipping path
  • asc publish testflight: canonical high-level TestFlight publish path
  • asc workflow: user-defined orchestration for repo-specific pipelines

To include store assets in a canonical metadata round-trip, select the asset scopes explicitly:

bash
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 --confirm

App 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-number to choose the next build number for a version
  • asc xcode inject to materialize deployment metadata into generated Xcode
    plist/config files and asset paths before archiving
  • asc xcode build to compile a scheme for an explicit simulator or device
    destination before the archive/export path
  • asc xcode archive to create a deterministic .xcarchive
  • asc xcode export to create a deterministic .ipa
  • asc publish testflight --group ... --wait to upload, wait for processing,
    and add the build to a TestFlight group
  • --submit --confirm on asc publish testflight when 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.

bash
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 json

For 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:

bash
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 json

Use --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:

bash
asc xcode export-options generate \
  --archive-path .asc/artifacts/App.xcarchive \
  --output-path .asc/export-options-app-store.plist \
  --overwrite

The 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):

bash
asc xcode export \
  --archive-path .asc/artifacts/App.xcarchive \
  --ipa-path .asc/artifacts/App.ipa \
  --method release-testing \
  --signing-style manual \
  --team-id TEAM_ID

The 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:

bash
asc xcode export \
  --archive-path .asc/artifacts/App.xcarchive \
  --ipa-path .asc/artifacts/App.ipa \
  --signing-style manual \
  --team-id TEAM_ID

The same flags work when the archive and export are owned by a local publish
flow:

bash
asc publish testflight \
  --app APP_ID \
  --workspace App.xcworkspace \
  --scheme App \
  --version 1.2.3 \
  --group GROUP_ID \
  --signing-style manual \
  --team-id TEAM_ID

An 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:

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 run
asc xcode inject before archive time to fill in the release-specific values
that previously came from Fastlane scripts.

Create .asc/workflow.json:

text
/* Detailed source-code truncated for AI context efficiency. */

Run it:

bash
asc workflow validate --output json
asc workflow run --dry-run testflight_beta VERSION:1.2.3
asc workflow run testflight_beta VERSION:1.2.3

Resumable 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.

json
{
  "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 by
asc workflow:

bash
asc workflow run testflight_external --resume RUN_ID

The upload step is not executed again because its declared outputs were already
persisted in the run-state file.

Notes:

  • VERSION must be a valid next marketing version for your app. If the latest
    App Store version is already READY_FOR_DISTRIBUTION, reusing that same
    version can cause App Store Connect to reject the upload.
  • TESTFLIGHT_GROUP accepts either a beta group name or group ID.
  • Add "ASC_BYPASS_KEYCHAIN": "1" to the top-level env block 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 like archive or publish.
  • Declared outputs keep the exact JSON value the command printed. A numeric
    $.nextBuildNumber of 42 is stored and interpolated as 42, so it can be
    passed straight to CURRENT_PROJECT_VERSION or 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:

jsonc
{
  "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 the
before_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:

json
{
  "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.

bash
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 --confirm

The 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 with
asc 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:

bash
asc game-center leaderboards v2 score-moderations list --leaderboard-id "LEADERBOARD_ID" --include player --paginate

The 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:

bash
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 --confirm

Both 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:

sh
python3 /opt/omarchy-apple-dev/tools/xcodeproj2xtool.py App.xcodeproj --target App

The 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:

sh
asc builds compile --package-path ./omarchy-xtool --product App \
  --platform device --configuration release --app-path ./artifacts/App.app

Package it with an existing local identity and provisioning profile:

sh
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.plist

The 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:

sh
asc builds package --app-path ./artifacts/App.app --ipa-path ./artifacts/Test.ipa --ad-hoc

Ad 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:

sh
asc builds compile --package-path ./omarchy-xtool --product App \
  --platform simulator --configuration debug --app-path ./artifacts/Simulator.app

Simulator 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 reports
adHoc 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:

sh
asc builds upload --app APP_ID --ipa ./artifacts/App.ipa --wait

Neither 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, and
xcode-cloud products app when they select AppInfo kidsAgeBand. Supported
continuation URLs carrying that selector also warn.

Replace reads such as:

bash
asc apps info view --info-id "APP_INFO_ID" --fields kidsAgeBand

with:

bash
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 and
territories-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:

bash
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:

bash
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 json

Named 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 and
GET /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 and
asc 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:

text
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 while
ASC_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: while
ASC_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 by
ASC_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:

text
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:

bash
asc auth logout --all --include-global --confirm
asc auth logout --name "MyKey" --include-global --confirm

Or 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 (see
docs/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 logout without --confirm is now a usage error before any credential is
removed. See migrate-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:

bash
asc auth logout --name "PROFILE" --confirm
asc auth logout --all --confirm

During the compatibility window, existing logout calls still remove credentials
but print this warning to stderr:

text
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:

text
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:

bash
asc testflight groups list --app APP_ID --internal

Combined 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. A
links.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 uses
GET /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 --build compatibility alias described below was removed
in 5.0.0 on every command in the table (and on asc versions attach-build).
--build is now an unknown flag; use --build-id. See migrate-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 --confirm before any
request. See migrate-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 returns
uploadOperations; 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 identifier
com.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:

bash
pip install -U koubou==0.20.0   # or: brew upgrade bitomule/tap/koubou
kou setup-frames

Frames 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 example
    iPad Air 13" - M2 - Space Gray - Portrait is now
    iPad Air 13 - M2 - Space Gray - Portrait.
  • Koubou 0.19.0 made content alignment the true horizontal anchor for text
    and images. Items with alignment: left or right now start or end at their
    position x 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 nullable
downloadable 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:

bash
asc versions list --app "APP_ID" --paginate --output json
asc versions view --version-id "VERSION_ID" --output json

Write it with a new tri-state --downloadable flag on asc versions update:

bash
asc versions update --version-id "VERSION_ID" --downloadable true
asc versions update --version-id "VERSION_ID" --downloadable false --confirm

Leaving --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:

text
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:

text
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 to
cli-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:

bash
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 markdown

The 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:

bash
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.
--password is no longer registered and ASC_MATCH_PASSWORD is no longer
read; use --password-file or ASC_SIGNING_SYNC_PASSWORD. See
migrate-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.

bash
asc web bundle-ids capabilities sync-app-clip \
  --bundle-id "CLIP_BUNDLE_ID" \
  --parent-bundle-id "PARENT_BUNDLE_ID" \
  --capability "PUSH_NOTIFICATIONS" \
  --confirm

The previous invocation shape still parses, but it fails with usage exit code
2 before a web session is resolved or any request is sent, and prints this
migration guidance to stderr:

text
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 required

Because 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 matching
Changed 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 a
Hint: line with the next step in that case, instead of the command's full
usage page:

text
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 --id alias described below was removed in 5.0.0. asc xcode-cloud status --id is now an unknown flag; use --run-id. See
migrate-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:

text
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:

bash
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-notifications

The offline OpenAPI snapshot supports both fields in PATCH /v1/apps/{id} via
AppUpdateRequest.data.attributes: subscriptionStatusUrl and
subscriptionStatusUrlForSandbox. Each URL is paired with its corresponding
subscriptionStatusUrlVersion or subscriptionStatusUrlVersionForSandbox set to
V2. 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 --help for 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 asc command for the workflow, including asc web when the operation lives there.
  • Preview supported workflows with --dry-run. Use --confirm only 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:

bash theme={null}
export ASC_PROFILE="release"
asc auth status --output table

If 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 filtered
asc apps list call below is the scoped network validation for the selected
credential and must succeed.

Resolve the app and release version:

bash theme={null}
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 json

For a build that is already in App Store Connect, resolve a valid processed
build and record its ID:

bash theme={null}
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:

bash theme={null}
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 table

A 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:

bash theme={null}
asc release stage \
  --app "$ASC_APP_ID" \
  --version "$VERSION" \
  --build-id "$ASC_BUILD_ID" \
  --platform IOS \
  --copy-metadata-from "1.2.2" \
  --confirm \
  --output table

Upload and attach a local IPA

Preview the upload and preparation plan:

bash theme={null}
asc publish appstore \
  --app "$ASC_APP_ID" \
  --ipa "./build/ios/ipa/MyApp.ipa" \
  --version "$VERSION" \
  --platform IOS \
  --wait \
  --dry-run \
  --output table

After checking the plan, run the preparation command and request structured
output:

bash theme={null}
asc publish appstore \
  --app "$ASC_APP_ID" \
  --ipa "./build/ios/ipa/MyApp.ipa" \
  --version "$VERSION" \
  --platform IOS \
  --wait \
  --output json

Do 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:

bash theme={null}
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:

bash theme={null}
asc validate iap --app "$ASC_APP_ID" --output json
asc validate subscriptions --app "$ASC_APP_ID" --output json

Resolve the exact product resourceId and its current version ID for every
product included in this release. Use asc iap versions list and
asc subscriptions versions list to find versions in
PREPARE_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:

bash theme={null}
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 json

These 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:

bash theme={null}
asc validate iap --app "$ASC_APP_ID" --output json
asc validate subscriptions --app "$ASC_APP_ID" --output json

For 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:

bash theme={null}
asc validate \
  --app "$ASC_APP_ID" \
  --version "$VERSION" \
  --platform IOS \
  --output json

Require 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:

bash theme={null}
asc review submit \
  --app "$ASC_APP_ID" \
  --version "$VERSION" \
  --build-id "$ASC_BUILD_ID" \
  --platform IOS \
  --dry-run \
  --output table

After 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:

bash theme={null}
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 json

The 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, or
PENDING_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

bash theme={null}
asc analytics sales \
  --vendor "YOUR_VENDOR_NUMBER" \
  --type SALES \
  --subtype SUMMARY \
  --frequency DAILY \
  --date "2024-01-20"

Download Weekly Sales Report

bash theme={null}
asc analytics sales \
  --vendor "YOUR_VENDOR_NUMBER" \
  --type SALES \
  --subtype SUMMARY \
  --frequency WEEKLY \
  --date "2024-01-21"

Download Monthly Sales Report

bash theme={null}
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
The vendor number is found in App Store Connect under **Sales and Trends > Reports**. Set it as `ASC_VENDOR_NUMBER` environment variable to avoid repeating it.

Analytics Reports

Request custom analytics reports for detailed app metrics.

Create Analytics Request

Request ongoing analytics access and reuse an existing active request when possible:
text
```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
```
View all analytics requests for your app:
text
```bash  theme={null}
asc analytics requests --app "YOUR_APP_ID"
```
Get detailed status of a request:
text
```bash  theme={null}
asc analytics view --request-id "REQUEST_ID"
```
Once the request is processed, inspect the available reports and instances:
text
```bash  theme={null}
asc analytics view --request-id "REQUEST_ID"
```

Download Analytics Data

Download the actual report data:

bash theme={null}
asc analytics download \
  --request-id "REQUEST_ID" \
  --instance-id "INSTANCE_ID" \
  --output "./analytics-report.csv"

Analytics Report Segments

Segment analytics by various dimensions:

bash theme={null}
asc analytics segments view \
  --segment-id "SEGMENT_ID" \
  --output json

Common segments include:

  • App version
  • Device type
  • Platform version
  • Territory
  • Source type

Financial Reports

Access payment and financial information:

Download Financial Report

bash theme={null}
asc finance reports \
  --vendor "YOUR_VENDOR_NUMBER" \
  --report-type FINANCIAL \
  --region "ZZ" \
  --date "2024-01"

Report Regions

Financial reports are organized by region:

bash theme={null}
asc finance reports \
  --vendor "YOUR_VENDOR_NUMBER" \
  --report-type FINANCIAL \
  --region "US" \
  --date "2024-01"

Common region codes:

  • US - United States
  • EU - European Union
  • JP - Japan
  • AU - Australia
  • CN - China

Complete Reporting Workflow

Here's a complete workflow for downloading reports:

text
/* Detailed source-code truncated for AI context efficiency. */

Automated Daily Reports (CI/CD)

GitHub Actions Example

yaml theme={null}
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-*.txt

Report Data Processing

Parse Sales Report

Sales reports are typically tab-delimited:

bash theme={null}
# 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 -10

Convert to JSON

python theme={null}
#!/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:

bash theme={null}
python convert_sales.py sales-report.txt

Troubleshooting

"Vendor number not found"

Problem: Invalid or missing vendor number.

Solution: Find your vendor number in App Store Connect:

  1. Go to Sales and Trends
  2. Click Reports
  3. Your vendor number is displayed at the top

Set it as an environment variable:

bash theme={null}
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:

bash theme={null}
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:

bash theme={null}
export ASC_TIMEOUT=180s
asc analytics sales --vendor "..." --date "..." --output report.txt

Best Practices

  1. Automate daily downloads: Set up scheduled jobs to download reports automatically

  2. Store reports securely: Reports contain sensitive financial data—encrypt and restrict access

  3. Parse and analyze: Don't just download—parse reports and feed into analytics platforms

  4. Set up monitoring: Alert on anomalies like sudden drop in downloads or revenue

  5. Use environment variables: Store vendor number and other config in environment variables

  6. Handle missing reports gracefully: Not all reports are available for all dates

  7. Archive historical data: Keep long-term archives for trend analysis

  8. Validate data: Cross-check totals with App Store Connect web interface


Guides App Store Submission (`guides/app-store-submission.mdx`)

App Store Submission

Publish with publish appstore --submit, then use validate or submit for 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 path
  • asc release stage - prepare a version without submitting it
  • asc validate - canonical readiness check before submission
  • asc submit status - inspect submission or version review state
  • asc 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:

  1. A processed build uploaded to App Store Connect
  2. App Store version created with required metadata
  3. All localizations configured with required fields
  4. Screenshots uploaded for at least one device type
  5. Privacy information and app review details completed

Canonical Publish Workflow

Create an App Store version if it doesn't exist:
text
```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
```
The CLI automatically validates that required localization fields are present:
text
* 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"
```
List builds for your app version:
text
```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
```
Publish with the canonical App Store flow:
text
```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

bash theme={null}
asc validate --app "YOUR_APP_ID" --version "1.0.0"

Monitoring Submission Status

Using submission ID:
text
```bash  theme={null}
asc submit status --id "SUBMISSION_ID"
```

Using version ID:

```bash  theme={null}
asc submit status --version-id "VERSION_ID"
```
View detailed version information:
text
```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 team

Managing Submissions

Cancel a Submission

If you need to cancel a submission before review:

bash theme={null}
asc submit cancel --id "SUBMISSION_ID" --confirm

Or cancel using version ID:

bash theme={null}
asc submit cancel --version-id "VERSION_ID" --confirm
You can only cancel submissions that are in `WAITING_FOR_REVIEW` state. Once review begins, you cannot cancel.

Resubmit After Changes

If you need to make changes after submitting:

```bash theme={null} asc submit cancel --version-id "VERSION_ID" --confirm ``` Update metadata, upload new screenshots, or attach a different build as needed. ```bash theme={null} asc publish appstore \ --app "YOUR_APP_ID" \ --ipa "./build/MyApp.ipa" \ --version "1.0.0" \ --submit \ --confirm ```

Complete Example: iOS App Submission

Here's a complete workflow for publishing an iOS app:

text
/* 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:

text
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:

bash theme={null}
asc apps info edit \
  --app "YOUR_APP_ID" \
  --locale "es-ES" \
  --copy-from-locale "en-US"

Then update locale-specific fields:

bash theme={null}
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:

bash theme={null}
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:

bash theme={null}
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:

  1. Check rejection details in App Store Connect web interface
  2. Address the issues mentioned in the rejection
  3. Upload a new build if code changes are needed, or just update metadata
  4. Resubmit with the canonical publish command:
bash theme={null}
asc publish appstore \
  --app "YOUR_APP_ID" \
  --ipa "./build/MyApp.ipa" \
  --version "1.0.0" \
  --submit \
  --confirm

Platform-Specific Considerations

macOS Apps

For macOS apps, specify the MAC_OS platform:

bash theme={null}
asc publish appstore \
  --app "YOUR_APP_ID" \
  --ipa "./build/MyMacApp.ipa" \
  --version "1.0.0" \
  --platform MAC_OS \
  --submit \
  --confirm

tvOS Apps

For tvOS apps, use the TV_OS platform:

bash theme={null}
asc publish appstore \
  --app "YOUR_APP_ID" \
  --ipa "./build/MyTVApp.ipa" \
  --version "1.0.0" \
  --platform TV_OS \
  --submit \
  --confirm

visionOS Apps

For visionOS apps, use the VISION_OS platform:

bash theme={null}
asc publish appstore \
  --app "YOUR_APP_ID" \
  --ipa "./build/MyVisionApp.ipa" \
  --version "1.0.0" \
  --platform VISION_OS \
  --submit \
  --confirm

Best Practices

  1. Always use --confirm flag: Prevents accidental submissions

  2. Test with TestFlight first: Distribute to internal testers before submitting to the App Store

  3. Validate metadata beforehand: Use asc localizations list to verify all required fields are present

  4. Monitor submission status: Check status regularly during the review process

  5. Keep builds ready: Have builds processed and ready before starting the submission workflow

  6. Use version IDs for lifecycle automation: Version IDs are reliable for submit status and submit cancel

  7. Handle stale submissions: The CLI automatically cleans up orphaned submissions, but verify in case of errors


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 under
asc 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:

bash theme={null}
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" \
  --network

For CI, prefer secrets and environment variables over checked-in config:

bash theme={null}
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 json

Use a read-only API command for CI validation so the run resolves the exported
ASC_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:

bash theme={null}
asc ads me view --output json
asc ads acls list --output json
asc ads apps search --ad-account "987654" --query "Example" --output json

The 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:

bash theme={null}
asc ads reports apps campaigns \
  --ad-account "987654" \
  --file report.json \
  --output json

Platform 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. Use
asc 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:

bash theme={null}
asc ads acls list --output json

Use the ACL response to confirm the ad account ID, then pin it explicitly for
the rest of the session:

bash theme={null}
export ASC_ADS_AD_ACCOUNT_ID="987654"
asc ads apps search --ad-account "$ASC_ADS_AD_ACCOUNT_ID" --query "Example" --limit 1 --output json

When operating multiple Apple Ads accounts, avoid relying on the ambient
default. Pass both profile and ad account:

bash theme={null}
asc ads apps search \
  --ads-profile "Marketing" \
  --ad-account "987654" \
  --query "Example" \
  --output json

Safe Read-Only Smoke Test

Use this sequence before an automation run or after rotating credentials:

bash theme={null}
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 json

Expected 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:

bash theme={null}
asc ads auth status --validate --output json

Campaign and Ad Group Inventory

Query campaigns first, then inspect ad groups under one confirmed campaign ID:

bash theme={null}
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 json

For a full v1 query, keep pagination in the request body:

bash theme={null}
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 json

Do 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:

json platform-report.json
{
  "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"
  }
}
bash theme={null}
asc ads reports apps campaigns \
  --ad-account "987654" \
  --file platform-report.json \
  --output json

The v1 report response keeps Apple's raw result and pagination fields.
Change offset and pageSize in the file to fetch another page. Run
asc 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:

json reporting-request.json
{
  "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:

bash theme={null}
asc ads v5 reports campaigns \
  --org "123456" \
  --file reporting-request.json \
  --output json

For 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:

bash theme={null}
asc ads api request \
  --method POST \
  --path v1/campaigns/query \
  --ad-account "987654" \
  --file campaign-query.json \
  --output json

Raw v1 requests only accept Platform API v1 paths or
https://api.ads.apple.com/v1/... URLs. DELETE requests and risky mutation
payloads require --confirm; multipart asset upload must use
asc ads assets upload.

For legacy troubleshooting only, keep asc ads v5 api request with its v5 path
and --org context:

bash theme={null}
asc ads v5 api request \
  --method POST \
  --path v5/campaigns/find \
  --org "123456" \
  --file selector.json \
  --output json

Raw 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:

  1. Run the matching --help command and confirm required flags.
  2. Resolve account access with asc ads acls list --output json.
  3. Run a read-only resource check:
    asc ads apps search --ad-account "987654" --query "Example" --limit 1 --output json.
  4. Pass --ads-profile and --ad-account explicitly when more than one account is possible.
  5. Store request bodies in reviewed JSON files; avoid shell-escaped inline JSON.
  6. Create paused or clearly named test resources when validating workflow shape.
  7. Print or log the target IDs before using --confirm.
  8. Prefer deleting temporary parent campaigns only after confirming Apple allows the cleanup.

Example guarded delete:

bash theme={null}
asc ads campaigns view --ad-account "987654" --campaign "campaign-id" --output json
asc ads campaigns delete --ad-account "987654" --campaign "campaign-id" --confirm

Troubleshooting Auth and Source Precedence

Use the auth doctor first:

bash theme={null}
asc ads auth doctor --output json
asc ads auth status --verbose --output json

Common fixes:

  • Missing ad account: pass --ad-account, export ASC_ADS_AD_ACCOUNT_ID, or store --ad-account during login.
  • Wrong profile: pass --ads-profile "Marketing" or export ASC_ADS_PROFILE.
  • Keychain prompts in CI: export ASC_ADS_BYPASS_KEYCHAIN=1.
  • Mixed sources: set ASC_ADS_STRICT_AUTH=1 to fail when both profile and env token/key sources are present.
  • Token-only runs: ASC_ADS_ACCESS_TOKEN still 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.

Apple's OpenAPI 4.5.1 documents the core Asset Library and placement resources. Apple controls media compatibility, asset eligibility, and permissions. Successful upload or placement requests do not establish App Review approval or storefront publication.

Find a library and inspect its assets

bash theme={null}
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 specs

Use 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

bash theme={null}
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 createdDate

Filter 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.

bash theme={null}
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

bash theme={null}
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:00

Videos 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.

bash theme={null}
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_ASSET

Supply 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

bash theme={null}
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 --confirm

swap 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

bash theme={null}
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 --confirm

Reordering 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 asc commands together
  • Run shell scripts and commands
  • Pass data between steps
  • Handle errors with hooks
  • Execute conditional steps
  • Compose reusable sub-workflows
Workflows intentionally execute arbitrary shell commands. Only run workflow files you trust, especially when using `--file`. Treat `.asc/workflow.json` like code: review it before running.

Quick Start

Create a Workflow File

Create .asc/workflow.json in your project:

json theme={null}
{
  "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

bash theme={null}
asc workflow run beta BUILD_ID:abc123 GROUP_ID:def456

List Available Workflows

bash theme={null}
asc workflow list

Validate Workflow File

bash theme={null}
asc workflow validate

Workflow Structure

Basic Workflow

json theme={null}
{
  "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):

  1. Runtime parameters
  2. Workflow env
  3. Global env
  4. System environment

Precedence for steps inside a called sub-workflow (highest to lowest):

  1. The calling step's with
  2. Runtime parameters
  3. Calling workflow's env
  4. Global env
  5. Called workflow's own env
  6. 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

json theme={null}
"before_all": "asc auth status"

after_all: Runs after successful workflow

json theme={null}
"after_all": "echo Success! && notify-send 'Workflow Complete'"

error: Runs when workflow fails

json theme={null}
"error": "echo Failed! && notify-send 'Workflow Failed'"

Step Types

Command Steps

Execute shell commands:

json theme={null}
{
  "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):

json theme={null}
{
  "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.

A skipped step is not a failure: the run still reports `"status": "ok"` and exits `0`, with `"status": "skipped"` on that entry in `steps[]`. A mutating step gated on a non-boolean value never runs while CI stays green.

Sub-Workflow Steps

Call another workflow:

json theme={null}
{
  "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):

json theme={null}
{
  "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

json theme={null}
{
  "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:

bash theme={null}
asc workflow run beta DISTRIBUTE:true

Leave DISTRIBUTE off to resolve the latest build without touching the group:

bash theme={null}
asc workflow run beta

App Store Submission Workflow

json theme={null}
{
  "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:

bash theme={null}
asc workflow run release SUBMIT:true VERSION:1.0.0 VERSION_ID:abc123

SUBMIT: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 the
ensure_ipa step above. Drop SUBMIT:true for a metadata-only dry pass:

bash theme={null}
asc workflow run release VERSION:1.0.0 VERSION_ID:abc123

Complete Release Workflow

text
/* Detailed source-code truncated for AI context efficiency. */

Run with:

bash theme={null}
asc workflow run release SUBMIT:true VERSION:2.0.0

create_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

yaml theme={null}
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-*.log

GitLab CI

yaml theme={null}
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: manual

Advanced Features

Dry Run Mode

Preview workflow execution without running commands:

bash theme={null}
asc workflow run beta --dry-run

Shows:

  • Steps that would be executed
  • Environment variables
  • Command interpolation
  • Workflow order

Private Workflows

Mark workflows as private (only callable from other workflows):

json theme={null}
{
  "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"}
      ]
    }
  }
}
bash theme={null}
# Works
asc workflow run main

# Fails - private workflow
asc workflow run helper

List including private workflows:

bash theme={null}
asc workflow list --all

Custom Workflow File

Use a different workflow file:

bash theme={null}
asc workflow run beta --file ./custom-workflows.json

Pretty JSON Output

bash theme={null}
asc workflow run beta --pretty

Best Practices

  1. Use descriptive names: Name workflows and steps clearly

  2. Add descriptions: Document what each workflow does

  3. Validate before running: Always run asc workflow validate after editing

  4. Use environment variables: Parameterize workflows for reusability

  5. Handle errors: Define error hooks to clean up on failure

  6. Test with dry-run: Use --dry-run to preview execution

  7. Keep workflows focused: Break complex workflows into composable sub-workflows

  8. Version control workflows: Store .asc/workflow.json in git

  9. Document required parameters: Note required KEY:VALUE parameters in descriptions

  10. Use private workflows: Extract common logic into private sub-workflows

Security Considerations

* Only run workflows from trusted sources * Review workflow files before execution * Avoid hardcoding secrets—use environment variables * In CI/CD, use encrypted secrets for credentials * Don't run untrusted workflows on PRs with access to secrets * Set `ASC_READ_ONLY=1` for audit, reporting, and agent sessions that must never write

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.

bash theme={null}
ASC_READ_ONLY=1 asc workflow run audit

Safe Practices

json theme={null}
// 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:

bash theme={null}
asc workflow list

Variable 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 echo statements

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

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:

bash theme={null}
asc signing fetch \
  --app APP_ID \
  --bundle-id com.example.app \
  --profile-type IOS_APP_STORE \
  --output .asc/signing/app-store \
  --format json \
  --pretty

Install the profile by file rather than copying it to a guessed Xcode directory:

bash theme={null}
asc profiles local install \
  --path .asc/signing/app-store/PROFILE.mobileprovision \
  --output json

The .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:

bash theme={null}
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:

bash theme={null}
asc certificates list \
  --certificate-type IOS_DISTRIBUTION \
  --paginate \
  --output json \
  --pretty > .asc/signing/distribution-certificates-after-ambiguous-create.json

When a matching certificate exists, retrieve that resource into a fresh response file:

bash theme={null}
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:

bash theme={null}
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:

bash theme={null}
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:

bash theme={null}
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 \
  --pretty

For an app with embedded targets, use one strict target manifest so the repository update covers all bundle identifiers in one commit:

json
{
  "schemaVersion": 1,
  "targets": [
    {"bundleId": "com.example.app"},
    {"bundleId": "com.example.app.widget"}
  ]
}
bash theme={null}
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 json

The 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.

bash theme={null}
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.json

The 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:

bash theme={null}
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
  done

The 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, one
signing 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.

text
/* 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 reports certificateCreationState: "reused" and
    has no p12Path, so the jq -e lookup stops the script under set -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-certificate fetches one bundle ID and cannot be combined
    with --match-extensions. For an app with extensions, run a second
    asc signing fetch --match-extensions --create-missing into another output
    directory after the certificate exists. Pass every profileFile from its
    results to profiles local install and to xcode signing plan as repeated
    --profile flags. Push the identity for all of those bundle IDs with
    signing sync push --bundle-id com.example.app --match-extensions, or with a
    --targets-file manifest that lists each bundle ID as in
    Shared App Store identity; the manifest takes
    bundle IDs, not profile paths.
  • .asc/signing/bootstrap holds the private key, CSR, and .p12. Do not upload
    it as an artifact. The .p12 password 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 plan exits 0 with ready: false when a target has no matching
    profile, so the script checks ready. xcode signing apply edits the
    checked-out project file and refuses a blocked or stale plan.
  • sync push can 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 runs
signing sync pull, signing keychain install, profiles local install,
xcode signing plan --profile, xcode signing apply, xcode archive, and
xcode 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:

bash theme={null}
: "${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.json

Planning 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:

bash theme={null}
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

bash theme={null}
asc localizations list --app "YOUR_APP_ID" --type app-info

Filter by locale:

bash theme={null}
asc localizations list --app "YOUR_APP_ID" --type app-info --locale "en-US,es-ES"

Update App Info Localization

bash theme={null}
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:

bash theme={null}
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:

bash theme={null}
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

bash theme={null}
asc localizations list --version "VERSION_ID"

Filter by specific locales:

bash theme={null}
asc localizations list --version "VERSION_ID" --locale "en-US,ja"

Update Version Localization

bash theme={null}
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

bash theme={null}
asc localizations update \
  --version "VERSION_ID" \
  --locale "en-US" \
  --marketing-url "https://example.com/myapp" \
  --support-url "https://support.example.com"

Update Promotional Text

bash theme={null}
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

Download all version localizations to a directory:
text
```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
```
Download only certain locales:
text
```bash  theme={null}
asc localizations download \
  --version "VERSION_ID" \
  --locale "en-US,es-ES" \
  --path "./localizations"
```
Download app-level metadata:
text
```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:

strings theme={null}
/* 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

Validate files without uploading:
text
```bash  theme={null}
asc localizations upload \
  --version "VERSION_ID" \
  --path "./localizations" \
  --dry-run
```
Upload all edited localizations:
text
```bash  theme={null}
asc localizations upload \
  --version "VERSION_ID" \
  --path "./localizations"
```
Upload only certain locales:
text
```bash  theme={null}
asc localizations upload \
  --version "VERSION_ID" \
  --locale "en-US,es-ES" \
  --path "./localizations"
```
Upload app-level metadata:
text
```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

bash theme={null}
asc localizations search-keywords list --localization-id "LOCALIZATION_ID"

Update Keywords

bash theme={null}
asc localizations update \
  --version "VERSION_ID" \
  --locale "en-US" \
  --keywords "productivity,calendar,tasks,todo,organizer,planner,schedule"
Keywords are limited to 100 characters total (including commas). Focus on relevant, high-traffic search terms.

Preview and Screenshot Sets

List Preview Sets

bash theme={null}
asc localizations preview-sets list --localization-id "LOCALIZATION_ID"

Get Preview Set Details

bash theme={null}
asc localizations preview-sets view --id "PREVIEW_SET_ID"

List Screenshot Sets

bash theme={null}
asc localizations screenshot-sets list --localization-id "LOCALIZATION_ID"

Get Screenshot Set Details

bash theme={null}
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:

bash theme={null}
#!/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 table

Multi-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 - French
  • de-DE - German
  • it - Italian
  • ja - Japanese
  • ko - Korean
  • pt-BR - Portuguese (Brazil)
  • pt-PT - Portuguese (Portugal)
  • ru - Russian
  • zh-Hans - Chinese (Simplified)
  • zh-Hant - Chinese (Traditional)
  • ar-SA - Arabic
  • nl-NL - Dutch
  • th - Thai
  • vi - Vietnamese

Add a New Language

```bash theme={null} asc app-setup info set \ --app "YOUR_APP_ID" \ --locale "ja" \ --name "アプリ名" \ --subtitle "日本語のサブタイトル" ``` ```bash theme={null} asc localizations create \ --version "VERSION_ID" \ --locale "ja" \ --description "日本語の説明" \ --keywords "生産性,ツール" \ --whats-new "新機能" ``` See the [Screenshots Guide](/guides/screenshots) for uploading locale-specific screenshots.

Bulk Language Setup

Set up multiple languages at once:

bash theme={null}
#!/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

  1. Download before editing: Always download current localizations before making changes

  2. Use dry-run: Test uploads with --dry-run to catch errors before applying changes

  3. Separate app info from version metadata: Update persistent metadata (name, subtitle) via app-setup info set; create new version locales with localizations create, then edit existing version metadata with localizations update

  4. Optimize keywords: Research popular search terms and use all 100 characters effectively

  5. Keep what's new concise: Focus on 3-5 key updates that matter to users

  6. Maintain consistency: Keep terminology and tone consistent across all locales

  7. Use professional translation: For important markets, use professional translation services

  8. 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:

bash theme={null}
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:

bash theme={null}
# 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:

strings theme={null}
/* 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:

bash theme={null}
asc localizations update \
  --version "VERSION_ID" \
  --locale "en-US" \
  --description "Required description" \
  --keywords "required,keywords" \
  --whats-new "Required what's new text"

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:

  1. App Store Workflow: Upload and manage screenshots in App Store Connect
  2. 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:

bash theme={null}
asc screenshots sizes

For the full matrix including all device types:

bash theme={null}
asc screenshots sizes --all
For most iOS submissions, you need:
  • 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

Export RGB PNG or JPEG images without alpha channels or transparency. Screenshot and Asset Library image uploads reject PNG alpha channels and transparency before reserving an upload. Even an alpha channel whose pixels are all opaque must be removed when exporting.
text
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
```
Select the localization with the app, version string, and locale. No localization ID lookup is needed:
text
```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.
```bash theme={null} asc screenshots upload \ --app "YOUR_APP_ID" \ --version "1.2.3" \ --locale "en-US" \ --path "./screenshots/ipad" \ --device-type "IPAD_PRO_3GEN_129" ```

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 Pro
  • IPAD_97 - 9.7" iPad

Apple Watch:

  • APPLE_WATCH_SERIES_7 - 45mm Series 7
  • APPLE_WATCH_SERIES_4 - 44mm Series 4
  • APPLE_WATCH_SERIES_3 - 42mm Series 3

Mac:

  • DESKTOP

Apple TV:

  • APPLE_TV

List Existing Screenshots

bash theme={null}
asc screenshots list --version-localization "LOCALIZATION_ID"

Filter client-side by device type:

bash theme={null}
asc screenshots list \
  --version-localization "LOCALIZATION_ID" \
  --output json | jq '.data[] | select(.attributes.displayType == "IPHONE_65")'

Download Screenshots

Download all screenshots for a localization:

bash theme={null}
asc screenshots download \
  --version-localization "LOCALIZATION_ID" \
  --output-dir "./downloaded-screenshots"

Delete Screenshots

Delete a specific screenshot:

bash theme={null}
asc screenshots delete --id "SCREENSHOT_ID" --confirm
Deleting screenshots is permanent. Download backups before deleting.

Local Screenshot Automation

Please report issues at: [https://github.com/rorkai/App-Store-Connect-CLI/issues/new/choose](https://github.com/rorkai/App-Store-Connect-CLI/issues/new/choose)

Complete Local Workflow

Launch your app in iOS Simulator, then capture screenshots:
text
```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.
Add device frame around raw screenshots:
text
```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
```
Create an HTML page to review all screenshots:
text
```bash  theme={null}
asc screenshots review-generate \
  --framed-dir "./screenshots/framed" \
  --output-dir "./screenshots/review"
```
```bash theme={null} asc screenshots review-open --output-dir "./screenshots/review" ```
text
This opens a browser with an interactive gallery for review.
Mark screenshots as ready for upload:
text
```bash  theme={null}
asc screenshots review-approve \
  --all-ready \
  --output-dir "./screenshots/review"
```

Or approve selectively in the browser UI.
Build a deterministic upload plan from the approved review artifacts:
text
```bash  theme={null}
asc screenshots plan \
  --app "123456789" \
  --version "1.2.3"
```
Apply the reviewed upload plan explicitly:
text
```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:

json theme={null}
{
  "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:

bash theme={null}
asc screenshots run --plan .asc/screenshots.json

Available Frame Devices

List supported devices for framing:

bash theme={null}
asc screenshots list-frame-devices --output json

Supported 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
bash theme={null}
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 bottom

Each 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:

bash theme={null}
asc screenshots frame --input-dir ./screenshots/raw --output-dir ./screenshots/framed \
  --device ipad-air-13 --frame-color blue --title "Plan every trip" --resume

To 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:

bash theme={null}
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:

bash theme={null}
#!/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:

bash theme={null}
asc screenshots upload --resume ".asc/reports/screenshots-upload/failures-123.json"

Preview Videos

Upload app preview videos:

Upload Preview Video

bash theme={null}
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:

bash theme={null}
asc screenshots upload \
  --app "YOUR_APP_ID" \
  --version "1.2.3" \
  --path "./screenshots" \
  --device-type "IPHONE_65"

Recommended directory layout:

text
screenshots/
  en-US/
    iphone/
      01-home.png
      02-settings.png
  fr-FR/
    iphone/
      01-home.png
  ja/
    iphone/
      01-home.png

The 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 with
asc screenshots upload --resume <artifact>.

Troubleshooting

"Invalid image dimensions"

Problem: Screenshot dimensions don't match the device type.

Solution: Verify required dimensions:

bash theme={null}
asc screenshots sizes

Ensure 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:

  1. Launch iOS Simulator
  2. Open your app in the simulator
  3. Run the capture command

"Frame device not found"

Problem: Invalid device name for framing.

Solution: List available devices:

bash theme={null}
asc screenshots list-frame-devices

Use exact device names from the output.

"Upload failed - file too large"

Problem: Image file size exceeds limits.

Solution: Optimize images:

bash theme={null}
# Using ImageMagick
mogrify -quality 85 -resize '1242x2688>' screenshots/*.png

# Using pngquant for compression
pngquant --quality=80-95 screenshots/*.png

Best Practices

  1. Use consistent naming: Name screenshots clearly (01-home.png, 02-features.png)

  2. Optimize file sizes: Compress screenshots to reduce upload time while maintaining quality

  3. Follow Apple guidelines:

    • No UI chrome (status bars are optional)
    • Show actual app content
    • Use device-appropriate assets
  4. Localize screenshots: Create locale-specific screenshots showing localized UI

  5. Test on real devices: Capture from actual devices when possible for best quality

  6. Version control: Keep screenshot sources in git for easy updates

  7. Automate where possible: Use the local workflow tools to streamline repetitive tasks

  8. Review before upload: Always review screenshots in context before uploading to App Store Connect


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

View all beta groups for your app:
text
```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
```
Create an external beta group:
text
```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
```
Enable public link for external distribution:
text
```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

Add a new tester to a beta group:
text
```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"
```
Export existing testers to CSV:
text
```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.
List all testers for an app:
text
```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"
```
Invite a tester to start testing:
text
```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

List recent builds:
text
```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
```
Add a build to one or more groups:
text
```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"
```
Check which groups have access to a build:
text
```bash  theme={null}
asc testflight distribution view --build-id "BUILD_ID"
```

Feedback and Crash Reports

Get crash submission details:
text
```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"
```
Get screenshot submission with annotations:
text
```bash  theme={null}
asc testflight feedback view --submission-id "SUBMISSION_ID"
```
Get detailed crash logs for analysis:
text
```bash  theme={null}
asc testflight crashes log --crash-log-id "CRASH_LOG_ID"
```
Delete processed crash submissions:
text
```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:

bash theme={null}
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:

bash theme={null}
# 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

  1. Verify the tester is added to the group:

    bash theme={null}
    asc testflight testers list --app "YOUR_APP_ID" --group "GROUP_ID"
  2. Check if invitation was sent:

    bash theme={null}
    asc testflight testers view --id "TESTER_ID"
  3. Resend invitation:

    bash theme={null}
    asc testflight testers invite --app "YOUR_APP_ID" --email "[email protected]"

Build Not Appearing for Testers

  1. Verify build is assigned to the group:

    bash theme={null}
    asc testflight distribution view --build-id "BUILD_ID"
  2. Check build processing status:

    bash theme={null}
    asc builds info --build-id "BUILD_ID"
  3. 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:

bash theme={null}
asc testflight groups view --id "GROUP_ID"

If you hit limits, create additional groups:

bash theme={null}
asc testflight groups create --app "YOUR_APP_ID" --name "Beta Testers Group 2"

Best Practices

  1. Organize by testing focus: Create separate groups for different testing purposes (QA, feature testing, general beta)

  2. Use CSV import for bulk operations: Export your current tester list, make changes, and re-import

  3. Enable feedback collection: Turn on feedback for external groups to collect valuable user input

  4. Monitor crash reports: Regularly check crash submissions and address issues before App Store release

  5. Set public link limits: For public beta links, set reasonable limits to control your beta population size

  6. Track metrics: Use beta tester usage metrics to identify your most active testers


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 under
asc 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:

  • ffcli commands with shared.DefaultUsageFunc
  • explicit long flags
  • TTY-aware output defaults through shared.BindOutputFlags
  • non-interactive behavior only
  • --confirm for deletes and bulk deletes
  • --paginate for 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:

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. The
includeDeletedCreativeSetAssets query parameter on
GET /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 in
X-AP-Context: adAccountId=<ad-account-id>; for account-scoped requests. The
--ad-account flag, ASC_ADS_AD_ACCOUNT_ID, and the selected profile's
ad_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 the
searchadsorg 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:

text
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, and asc ads auth ... do not require
    --org.
  • Examples in the endpoint matrix omit --org and --ads-profile unless 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, and asc 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 in
docs/design/apple-ads-platform-api-v1.md.

Root help placement:

  • Add ads to cmd/root_usage.go under ANALYTICS & FINANCE COMMANDS after
    performance. This is acquisition/marketing rather than App Store Connect
    resource management, but this is the closest existing command group.
  • Register ads.AdsCommand() in internal/cli/registry/registry.go.
  • Add ads to registry/root help tests.

Package layout:

text
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.go

Use 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:

go
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.com
    • sub=<client-id>
  • Token response contains access_token, token_type=Bearer,
    expires_in=3600, and scope=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-urlencoded form values:
    grant_type, client_id, client_secret, and scope.

Implement this command group:

text
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-key use the same meaning and validation
    style as asc auth login.
  • --client-id is Apple Ads OAuth client_id.
  • --team-id is the JWT issuer (iss).
  • --org stores a default Apple Ads org ID for API calls.
  • --ad-account stores a default Platform API v1 ad-account ID and, on
    auth discover, marks that account as active in the output. It is separate
    from --org.
  • --private-key accepts the EC P-256 PEM Apple documents for Ads. Reuse the
    existing private-key parsing helpers because they already support ES256 keys.
  • --network requests an access token and calls Platform API v1 GET /v1/me.
  • --skip-validation skips JWT and network validation.
  • --network and --skip-validation are mutually exclusive.
  • --local requires keychain bypass, exactly like asc auth login.
  • Keychain is preferred; config fallback is allowed when bypassing keychain.
  • auth status supports --verbose and --validate, matching asc auth status.
  • auth status --validate validates each stored credential through Platform
    API v1 GET /v1/me and reports failures after rendering the status output.
  • auth discover calls Platform API v1 /v1/me and /v1/acls to show the
    active Ads user and available ad accounts without printing access tokens.
    Each ACL row includes its honest ad_account_id; --ad-account controls
    which row is marked active.
  • Platform API v1 user and ACL discovery is also available directly through
    asc ads me view and asc ads acls list.
  • auth logout supports --all and --name. It requires one of those flags
    so bare asc ads auth logout does not clear every stored Ads profile, and
    --all requires --confirm.

Add config fields without changing existing App Store Connect credentials:

go
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:

text
asc:ads-credential:<name>
asc:ads-metadata:<name>

Reuse only private-key parsing/validation helpers from internal/auth.

Environment variables:

text
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_KEYCHAIN

Resolution order:

  1. Explicit --ads-profile if present.
  2. ASC_ADS_PROFILE if present.
  3. ASC_ADS_ACCESS_TOKEN if no profile is selected; it bypasses token exchange but still needs
    --org, ASC_ADS_ORG_ID, or stored profile org for org-scoped calls.
  4. Complete Ads env credential tuple.
  5. 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, ignore
ASC_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:

  1. --org
  2. ASC_ADS_ORG_ID
  3. selected Ads profile org_id
  4. ads.org_id in 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 lets
ASC_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:

  1. --ad-account
  2. ASC_ADS_AD_ACCOUNT_ID
  3. selected Ads profile ad_account_id
  4. ads.ad_account_id in 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:

text
https://api.searchads.apple.com/api/

Headers:

text
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/acls
  • GET /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:

text
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:

json
{
  "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:

text
https://api.ads.apple.com/v1/

Platform API v1 uses the same bearer authorization and JSON content headers as
v5. Account-scoped requests add:

text
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's
ad_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, and
POST /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:

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:

text
payload path must be a file
payload file is empty
invalid JSON: ...
payload must be a JSON object
payload must be a JSON array

Pagination Contract

Apple Ads uses offset pagination, not App Store Connect links.next.

List/search commands with limit/offset support:

text
--limit INT
--offset INT
--paginate

Do not add --next to Apple Ads commands. Validate:

  • --limit must be 1..1000 except GET v5/custom-reports, where it must be
    1..50.
  • --offset must be >=0.
  • --paginate starts at --offset when provided.
  • --paginate uses page size --limit when provided, otherwise 1000
    except GET v5/custom-reports, where the default page size is 50.
  • Continue until pagination.startIndex + pagination.itemsPerPage >= pagination.totalResults or 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:

json
{
  "data": [],
  "pagination": {
    "itemsPerPage": 1000,
    "startIndex": 0,
    "totalResults": 0
  }
}

Output Contract

All endpoint commands bind:

go
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