← all adapters
apple-appstoreconnect-style
simulates App Store Connect API v3
restadapter v0.1.015 routes
verification
use it
# add to stunt.yaml
services:
apple-appstoreconnect:
adapter: embedded:apple-appstoreconnect-style
# then
$ stunt upThe embedded: source extracts this adapter from the binary — no git clone, fully offline.
how to mock the App Store Connect API locally
Install stunt, paste the service block above into stunt.yaml, run stunt up — then point your client at localhost instead of App Store Connect API. It works offline, keeps state across requests and restarts, and every response is synthetic: the route shapes, status codes, pagination, and webhook delivery follow the real App Store Connect API v3 surface — the data is fake, the behavior is real.
backing stores
apps
api surface (15)
generated from the adapter manifest — exactly what boots, nothing more · covers 0% of the real API's 1263 routes (derived from spec App Store Connect OpenAPI Specification @ 4.4.1)
15 routes
GET/v1/apps
POST/v1/apps
POST/v1/appStoreVersionSubmissions
GET/v1/users
GET/v1/salesReports
GET/v1/apps/{id}
PATCH/v1/apps/{id}
GET/v1/apps/{id}/appStoreVersions
POST/v1/apps/{id}/appStoreVersions
GET/v1/apps/{id}/builds
GET/v1/apps/{id}/appPrices
GET/v1/builds/{id}
GET/v1/appStoreVersions/{id}
PATCH/v1/appStoreVersions/{id}
GET/v1/appStoreVersions/{id}/builds
VM-verified behaviors (20)
- ✓a rejected credential answers Apple's 401 errors array, not a bare status
- ✓the JOSE header must declare ES256 and carry a kid
- ✓structural validity is not enough: the exact JWT string must be registered
- ✓the seeded app reads back through list and single-get, with 404 NOT_FOUND for unknown ids
- ✓create assigns numeric ids, persists into the list, and dedupes bundleId
- ✓bracketed filter[...], leading-dash sort, and fields[apps] projections apply before paging
- ✓PATCH /v1/apps/{id} renames and refuses bundleId collisions
- ✓limit/cursor paging surfaces meta.paging and a links.next cursor
- ✓limit defaults to 50 and clamps at the documented maximum 200
- ✓a malformed cursor is total: 200 with data null (Apple answers 400 — deviation)
- ✓versionString is required and duplicate versionStrings collide per app
- ✓the app's first version adopts its unattached build
- ✓PATCH works only in the editable states (PREPARE_FOR_SUBMISSION, REJECTED)
- ✓submission drives WAITING_FOR_REVIEW → IN_REVIEW → READY_FOR_SALE on the clock
- ✓simulate_fail rejects, and a REJECTED version stays editable and resubmittable
- ✓a fresh app's build reads PROCESSING and the processingState filter agrees
- ✓the 3s window settles VALID and GET /v1/builds/{id} agrees
- ✓a simulate_fail app settles its build INVALID
- ✓users seed once and answer the bracketed role/username filters and sort
- ✓salesReports echoes the bracketed report filters with DAILY/SALES defaults
Not implemented (7)
- No TestFlight beta testing (betaGroups, betaTesters, buildBetaDetails)
- No IAP or subscriptions (inAppPurchasesV2, subscriptionGroups)
- No provisioning surface — bundleIds, certificates, devices, profiles
- No app metadata: localizations, ageRating, categories, phased release
- No customer reviews endpoints (customerReviews and responses)
- No user invitations or user CRUD (GET /v1/users list only)
- No App Analytics (analyticsReportRequests, metrics) — salesReports only
Differs from the real API (7)
- JWT signature crypto NOT verified — structural plus registry check only
- Review is clock-driven: WAITING_FOR_REVIEW +1s to IN_REVIEW, +3s to READY_FOR_SALE
- Builds settle PROCESSING to VALID at +3s — no separate in-flight state
- simulate_fail extension: app create forces build INVALID, version create REJECTED
- A first build is minted at app create — no build upload path
- A malformed cursor answers 200 with data null; real ASC answers 400
- salesReports returns a JSON:API object; real ASC streams a gzip TSV and 400s on missing filters
want more of the App Store Connect API surface? adapters are YAML + Starlark — the authoring guide covers adding routes, fixtures, and state machines. Open an issue for anything big first, or just file a PR — merged routes ship in the next release.