All posts
AUTOMATION

App Store Server API: verifying iOS transactions and checking subscription status server-side

A practical guide to the App Store Server API — how it differs from the App Store Connect API, how to authenticate, which endpoints matter for subscription entitlement management, and how to wire it into a reliable three-layer validation architecture.

By the AppsOps team · · 8 min read

Apple's developer ecosystem includes two server-side APIs with confusingly similar names. The App Store Connect API is a management API: it lets you read and write data in App Store Connect — pricing tiers, metadata, builds, users. The App Store Server API is a transaction API: it lets your backend server look up the real-time status of a specific customer's subscription, retrieve their full transaction history, or extend a renewal date. They share the same JWT authentication scheme but call completely different base URLs and serve entirely different purposes.

Understanding the App Store Server API is increasingly important as Apple pushes developers away from the legacy verifyReceipt endpoint (which Apple has signalled will eventually be deprecated) and toward a model where server-side subscription management is a first-class concern. This guide covers what the API does, how authentication works, which endpoints matter for subscription apps, and how to wire it into a reliable entitlement system. If you haven't generated an App Store Connect API key yet, the 5-minute API key setup guide covers that first step — you'll use the same key pair here.

What the App Store Server API does (and what it doesn't)

At its core, the App Store Server API answers one class of question: what has this specific customer purchased, and is it still valid? You pass Apple an originalTransactionId — the stable identifier assigned to the first transaction of a subscription — and Apple returns signed, verifiable data about the current state of that subscription.

What the API doesn't do: it does not give you aggregate revenue figures, sales trends, or anything about your app's performance as a whole. That is the domain of the App Store Connect API and the Sales and Trends reports. The Server API is deliberately narrow — its only concern is the transactional state of individual customers.

The production endpoint base URL is https://api.storekit.itunes.apple.com. The sandbox equivalent (for testing with sandbox Apple IDs) is https://api.storekit-sandbox.itunes.apple.com. It's a common mistake to test against the production URL and receive "transaction not found" errors — make sure your environment maps correctly.

Why server-side verification still matters with StoreKit 2

StoreKit 2 introduced client-side signed transactions — JWS tokens that your app can verify locally against Apple's public key without a server round-trip. This leads some developers to skip the server entirely. For simple apps, that works. For subscription businesses, it leaves meaningful gaps:

The right architecture combines both layers. Use StoreKit 2 on the client for instant, low-latency access gating. Use the App Store Server API on your backend to independently validate entitlements, power support tooling, and handle any flow that runs outside an active user session. Neither layer alone is sufficient for a serious subscription app.

Authentication: the same JWT scheme as App Store Connect API

Authentication uses the same JSON Web Token approach as the App Store Connect API — if you've already set up a key for pricing automation or data pulls, you can reuse it here with no changes to the key itself. The difference is in the JWT payload: the aud (audience) claim must be "appstoreconnect-v1", and you must add a bid claim containing the bundle ID of the specific app you're querying.

A minimal Python example using PyJWT:

import jwt, time

payload = {
    "iss": ISSUER_ID,          # from App Store Connect → Users and Access → Keys
    "iat": int(time.time()),
    "exp": int(time.time()) + 3600,
    "aud": "appstoreconnect-v1",
    "bid": "com.yourcompany.yourapp",   # your app's bundle ID
}
token = jwt.encode(
    payload,
    PRIVATE_KEY_PEM,           # the .p8 file contents as a string
    algorithm="ES256",
    headers={"kid": KEY_ID}    # from the same page as ISSUER_ID
)
# Use as: Authorization: Bearer {token}

Tokens are valid for up to 3,600 seconds. Cache and reuse the same token until it's close to expiry — generating a new one per request is unnecessary overhead.

3,600 s maximum JWT lifetime — cache and reuse within that window

The five endpoints subscription developers use most

Apple's App Store Server API covers a range of use cases. The table below focuses on the endpoints that matter for subscription entitlement management:

Endpoint Method What it returns Primary use case
/inApps/v1/subscriptions/{originalTransactionId} GET Active and most-recent status for all subscriptions under this original transaction Real-time entitlement check; support ticket resolution
/inApps/v2/history/{originalTransactionId} GET Paginated list of all signed transactions (newest first) Entitlement migration; purchase history audit
/inApps/v1/transactions/{transactionId} GET A single signed transaction by its transaction ID Validating a specific purchase before granting access
/inApps/v1/subscriptions/extend/{originalTransactionId} PUT Confirmation and updated expiry date Compensating users for outages; retention tooling
/inApps/v1/notifications/test POST A test notification dispatched to your registered Server Notifications URL Testing your notification handler without a real purchase event

For day-to-day subscription management, /inApps/v1/subscriptions/{originalTransactionId} is the workhorse. It returns a data array — one entry per subscription group — each containing the latest renewal information and a status integer you can act on directly.

Decoding the subscription status response

The subscription status endpoint wraps its payload in signed JWS tokens — the same format StoreKit 2 uses on the client — to make responses tamper-evident. Each lastTransactions entry contains a status integer and two JWS strings: signedTransactionInfo and signedRenewalInfo. Decode them against Apple's public keys (available at https://appleid.apple.com/auth/keys) to get the underlying JSON.

The status codes map as follows:

Status 3 and 4 deserve close attention. Research from RevenueCat has shown that a significant share of "churned" subscribers are actually in billing retry — their cards declined but they haven't actively cancelled. Maintaining access during status 4 (Grace Period) and optionally status 3 can recover a meaningful fraction of these users before they even realize there was a problem. The grace period and billing retry deep-dive covers the setup and the revenue impact in detail.

On JWS decoding: Don't implement JWS certificate chain verification from scratch. Apple publishes official server libraries for Swift, Java, Python, Node.js, and Go at developer.apple.com/documentation/appstoreserverapi. These libraries handle certificate chain validation against Apple's root CA automatically — a step that's easy to skip and hard to get right manually.

Paginating transaction history

The /inApps/v2/history endpoint returns transactions newest-first, with up to 20 records per page. For long-tenured subscribers — or apps migrating away from legacy receipt validation — there may be dozens or hundreds of renewal entries. The response includes a hasMore boolean and a revision cursor. Pass revision as a query parameter on subsequent requests to fetch the next page.

Useful filter parameters:

If you are migrating from the legacy verifyReceipt endpoint, the history endpoint is the authoritative replacement. The migration pattern is: for each subscriber record, look up their originalTransactionId, fetch their complete history, find the latest renewal entry, and use that as the new canonical entitlement anchor.

Wiring the API into a reliable entitlement system

The recommended architecture for production subscription apps treats the App Store Server API as one layer of a three-part entitlement stack:

  1. StoreKit 2 on the client (speed): Transaction.currentEntitlements gives you a signed, locally verifiable entitlement list within milliseconds of app launch. Use this for the first access decision — it's fast enough to avoid any perceptible gate.
  2. App Store Server API on your backend (trust): After the client sends you the originalTransactionId, your server independently calls /inApps/v1/subscriptions/{id} to confirm the status. Cache the result for 5–10 minutes to avoid unnecessary API calls, and refresh it when you receive a Server Notification event.
  3. App Store Server Notifications for real-time events: Server Notifications v2 push events — renewals, cancellations, refunds, billing recovery — to your endpoint the instant they happen. Handling these correctly keeps your entitlement database in sync without polling.

This layered approach means your access gates are fast (StoreKit 2), your backend records are trustworthy (server API validation), and your database stays current (event-driven Server Notifications). Each layer handles the failure modes the other two can't catch.

Rate limits and common errors

Apple does not publish specific numeric rate limits for the App Store Server API, but the API returns standard HTTP 429 responses when volume thresholds are exceeded. The right operational pattern is: query on-demand (not continuously), cache responses aggressively, and rely on Server Notifications for real-time updates rather than polling the status endpoint on a timer. If you are operating at significant scale, third-party subscription platforms such as RevenueCat, Adapty, and Purchasely maintain their own managed connections to Apple's API and surface a normalized status endpoint to your backend — offloading rate limit management entirely.

The error codes most likely to appear in early integrations:

Sources and further reading

Share this post

Ready to put this into practice?

AppsOps is the first App Store ops dashboardPPP-fair pricing for 175 App Store territories, AI metadata localization in 39 languages, AI screenshot localization for 14 Apple device classes, and one-click App Store Connect API push — all from one dashboard, all for $19/month.

Try AppsOps free — no card →

Related reading