# IAPKit > Open-source purchase validation and entitlement infrastructure for the > OpenIAP ecosystem, managed by OpenIAP. Hosted at https://kit.openiap.dev. > One Bearer-authed endpoint for Apple / Google / Horizon / Amazon; > harmonized response shape with `{ store, isValid, state, productId?, environment?, clientPayload? }` so your backend has a single code path for > entitlement + refund detection. IAPKit lives in the OpenIAP monorepo as a Bun + Hono server, Convex backend, and React SPA deployed behind one origin. The official hosted service is free under fair-use safeguards on shared, community-funded capacity. It is best-effort, not unlimited or SLA-backed. High-volume apps should contact hyo@hyo.dev before launch, help fund shared capacity through GitHub Sponsors or OpenCollective, or self-host the MIT-licensed server for dedicated capacity. ## API quick reference Base URL: https://kit.openiap.dev Purchase verification: `Authorization: Bearer openiap-kit_pk_` MCP/admin: `Authorization: Bearer openiap-kit_sk_` App-facing product and inbound webhook helpers use a publishable key path where mobile/store transports require it. Account-level v2 reads require a secret key held by the authenticated developer backend. Administrative product, subscription analytics, MCP, and store-sync requests keep the secret in the Bearer header and out of URLs. - [POST /v1/purchase/verify](https://kit.openiap.dev/docs/api) — verify an in-app purchase; body is a tagged union on `store` - [GET /v1/products/{apiKey}?platform=IOS&includeClientPayload=true&limit=25](https://kit.openiap.dev/docs/products) — list public product payloads in bounded cursor pages (platform required, limit 1-50, follow `nextCursor` while `hasMore`) - [GET /v1/products/{apiKey}/{productId}/client-payload?platform=IOS](https://kit.openiap.dev/docs/products) — fetch `{ clientPayload }`; returns 404 when the product is missing/Draft/Removed or no payload exists - [GET/PUT/DELETE /v1/products/client-payload/{productId}?platform=IOS](https://kit.openiap.dev/docs/products) — read durable editor state or create/update/remove a payload from CI or MCP with `Authorization: Bearer `; publishable keys receive 403 - [GET /v1/products and catalog/store-sync writes](https://kit.openiap.dev/docs/products) — header-authenticated GET is client-safe; POST/DELETE and `/v1/products/sync/{ios|android}` require `Authorization: Bearer `; secret-key-in-path requests receive 410 - [GET /v2/subscriptions/status?userId=...](https://kit.openiap.dev/docs/api) — secret-only, tokenless account status selected by an authenticated developer backend - [GET /v2/subscriptions/entitlements?userId=...](https://kit.openiap.dev/docs/api) — secret-only, tokenless active product snapshot; supports up to 200 indexed subscription rows and fails closed on overflow - [POST /v2/subscriptions/user-erasure](https://kit.openiap.dev/docs/api) — schedule durable removal of an app user ID from IAPKit subscription and commerce-event rows; erase downstream copies separately - [GET /v1/subscriptions/{list|metrics|revenue}](https://kit.openiap.dev/docs/api) — administrative subscription data with `Authorization: Bearer ` - [POST /v1/webhooks/{apiKey}](https://kit.openiap.dev/docs/webhooks) — lifecycle webhook receiver. Paste this URL into App Store Connect (Production + Sandbox) AND Google Cloud Pub/Sub push subscription. Auto-detects ASN v2 vs Pub/Sub by payload shape. **POST-only**; opening in a browser returns 404 — that's expected. - [GET /v1/openapi](https://kit.openiap.dev/v1/openapi) — machine-readable OpenAPI spec - [GET /v2/openapi](https://kit.openiap.dev/v2/openapi) — secret-only account API OpenAPI spec - [GET /v1](https://kit.openiap.dev/v1) — Redoc UI for the OpenAPI spec - [GET /health](https://kit.openiap.dev/health) — no-store liveness metadata (`ok`, status, service, API version, public revision, environment, timestamp) with no Convex round-trip - [POST /mcp](https://kit.openiap.dev/docs/ai-assistants/codex-plugin) — MCP Streamable HTTP endpoint for Codex, Claude Code, and other MCP clients. Uses an IAPKit secret admin key, not a mobile publishable key and not an OpenAI, ChatGPT, Anthropic, or Claude API key. Also mounted at `/api/v1/*` for backwards compatibility. `/v1/verify-purchase` is an alias of `/v1/purchase/verify`. Pick `/v1/purchase/verify` for new code. Apple ASN v2 and Google RTDN update IAPKit's canonical subscription state. IAPKit does not relay those events through SSE, WebSockets, push, or long polling. The app authenticates to its developer backend; that backend resolves the opaque IAPKit user ID and calls v2 with a server-held secret key. It returns only the access decision or tokenless fields the app needs. Respect the `Retry-After` header on `429` and `503`, coalesce concurrent backend reads, and use a bounded project-and-user-scoped cache. Existing v1 SDK helpers remain available for shipped-client compatibility. ## Request shapes (discriminated on `store`) - Apple — `{ store: "apple", jws, expectedProductId?, includeClientPayload? }` where `jws` is a StoreKit 2 JWS (≤ 16 KB) - Google — `{ store: "google", purchaseToken, expectedProductId?, includeClientPayload? }` (token ≤ 2 KB) - Horizon — `{ store: "horizon", userId, sku }` (≤ 256 chars each). IAPKit holds the App ID + App Secret server-side and composes the `OC|APP_ID|APP_SECRET` access token per-request. - Amazon — `{ store: "amazon", userId, receiptId, sandbox?, expectedProductId? }` where `userId` and `receiptId` come from Amazon Appstore RVS. Production uses the project-held RVS shared secret; sandbox requires the project's explicit App Tester / Cloud Sandbox opt-in and never sends that production secret. ## Success response ```json { "store": "amazon", "isValid": true, "state": "ENTITLED", "productId": "premium_monthly", "environment": "Sandbox" } ``` Handled Amazon results identify the selected `Sandbox` or `Production` environment. Match the store-verified `productId` before fulfillment. For Apple/Google only, `includeClientPayload: true` may add a top-level `clientPayload` when verification is valid, the store supplies a verified productId, and that exact platform/product has a payload: ```json { "store": "apple", "isValid": true, "state": "ENTITLED", "productId": "premium_monthly", "clientPayload": { "format": "toml", "body": "[access]\nmax_items = 10", "version": 3, "updatedAt": 1784160000000 } } ``` Default requests, invalid receipts, missing payloads, Horizon, and Amazon omit `clientPayload`. Entitlement decisions must use `isValid`, `state`, and the store-verified `productId`. Require that ID to match the product the app expected; never substitute a client-supplied ID when the verified value is missing, and never use payload contents alone. Harmonized `state` values (truthy `isValid`): `ENTITLED`, `PENDING_ACKNOWLEDGMENT`, `READY_TO_CONSUME`. Falsey `isValid`: `PENDING`, `CONSUMED`, `CANCELED`, `EXPIRED`, `INAUTHENTIC`, `UNKNOWN`. ## Product client payloads - Shape: `{ format: "toml" | "json" | "text", body, version, updatedAt }` - Body is required, nonblank, and ≤ 16 KiB measured as UTF-8 bytes. - JSON must parse to a non-null, non-array object; TOML syntax is validated. - Payloads are public app data. Never store secrets, credentials, or private server rules. - Store Sync never pushes or overwrites them. Resetting the local catalog retains them. - They are returned by app requests, not sent through APNs/FCM, and do not display an OS notification. - The decoded body limit is 16 KiB. The payload-write JSON envelope is separately capped at 128 KiB so fully escaped 16 KiB text still fits; the general product-management envelope remains 64 KiB. - Mobile apps use `openiap-kit_pk_...` publishable keys. Payload writes use `openiap-kit_sk_...` secret keys or authenticated dashboard sessions. - A matching IAPKit catalog row must exist before a payload write. After creating a product directly in App Store Connect or Play Console, run a pull sync and wait for its job to succeed, or create the row with secret-authenticated `POST /v1/products`; early writes return `PRODUCT_NOT_FOUND`. - Secret-key `GET /v1/products/client-payload/{productId}?platform=...` returns the current payload plus durable `expectedVersion`, including the revision after deletion. MCP exposes it as `iapkit_get_client_payload`. ## Dashboard order lookup The authenticated project dashboard includes a read-only Orders tab for live Apple and Google order lookup. It reuses the project's store credentials, returns the order plus best-effort subscription status, and never stores or logs the order ID or result. Apple lookup is production-only and needs the App Apple ID; Google requires the Play service account's View financial data permission. This operator tool does not accept API keys and is not part of `/v1`. ## Version compatibility Hosted `/v1` responses evolve additively. Existing fields keep their meaning, new response data is optional or request-gated, and a breaking contract would use `/v2` while `/v1` keeps serving. SDKs report their compile-time spec in `X-OpenIAP-Spec` for rollout measurement only; the header never changes receipt verification. Gate on `isValid`, require the store-verified `productId`, and allow optional metadata to be absent or unknown. ## Status codes - `200` — verification ran; require `isValid`, an operation-appropriate `state`, and an exact store-verified `productId` match - `400 INVALID_INPUT` — malformed body / unknown store / oversized field - `400 INVALID_API_KEY` — well-formed key that fails project lookup (unknown or rotated) - `413 PAYLOAD_TOO_LARGE` — request body exceeds the 32 KB edge cap - `401 MISSING_API_KEY` — no `Authorization` header - `403 INSUFFICIENT_SCOPE` — publishable key used for an administrative operation - `403 INVALID_API_KEY` — wrong scheme or malformed key (format check only) - `410 SECRET_API_KEY_IN_URL` — move the secret to `Authorization: Bearer ...` on the canonical route - `429 RATE_LIMITED` — API-key, source-IP, or process bucket empty; inspect `X-RateLimit-Scope` and honor `Retry-After` - `503 SERVICE_BUSY` — the API-key, source-IP, or process verification share is full; inspect `X-Concurrency-Scope` and retry with jittered backoff - `500 UNKNOWN_ERROR` — quote the `X-Correlation-Id` header in a support ticket ## Response headers - `X-Correlation-Id` — UUIDv4, matches the stdout log line - `X-RateLimit-Limit` — bucket capacity (default 600 per key) - `X-RateLimit-Remaining` — tokens left in the bucket - `X-RateLimit-Scope` — rejecting `key`, source `ip`, or process `global` bucket on `RATE_LIMITED` - `X-Concurrency-Limit` / `X-Concurrency-Remaining` — verification slots for the reported axis after the request reaches the in-flight guard - `X-Concurrency-Scope` — `key`, trusted source `ip`, or process `global` - `Retry-After` (429 / 503) — seconds to wait Default protection is 600 burst / 10 req/sec per key, 600 / 5 req/sec per source IP, 5,000 / 100 req/sec per process, 8 concurrent verify handlers per API key, 16 per trusted source IP, and 32 per process. The key and source shares make simple credential rotation insufficient to monopolize the process from one network source. Fly Proxy separately limits the complete service to 80 soft / 120 hard concurrent requests per machine. One million requests per day average about 11.6 req/sec before peaks, so apps at that scale must coordinate capacity or self-host rather than assuming DAU implies safe request volume. ## Docs - [/docs](https://kit.openiap.dev/docs) — full in-dashboard documentation - [/docs/quickstart](https://kit.openiap.dev/docs/quickstart) — signup → project → API key → first verify - [/docs/products](https://kit.openiap.dev/docs/products) — catalog sync, client payload editing, retrieval, caching, and limits - [/docs/orders](https://kit.openiap.dev/docs/orders) — read-only Apple and Google order support tooling - [/docs/verification/apple](https://kit.openiap.dev/docs/verification/apple) — bundle ID, Issuer ID, Key ID, .p8 - [/docs/verification/google](https://kit.openiap.dev/docs/verification/google) — package name, service account JSON - [/docs/verification/horizon](https://kit.openiap.dev/docs/verification/horizon) — App ID + App Secret (write-only) - [/docs/verification/amazon](https://kit.openiap.dev/docs/verification/amazon) — RVS shared secret, App Tester sandbox, and background rechecks - [/docs/api](https://kit.openiap.dev/docs/api) — request shapes, responses, errors, headers, and Amazon RVS payloads - [/docs/compatibility](https://kit.openiap.dev/docs/compatibility) — additive `/v1` policy and older-SDK behavior - [/docs/webhooks](https://kit.openiap.dev/docs/webhooks) — inbound Apple ASN v2 and Google RTDN setup - [/docs/operations](https://kit.openiap.dev/docs/operations) — fair use, capacity, rate and concurrency limits, logs, `/health`, graceful shutdown - [OpenIAP Commerce Protocol webhook contract](https://openiap.dev/commerce-protocol/webhooks) — vendor-neutral signed outbound delivery semantics; IAPKit setup remains in the local guide above - [/docs/ai-assistants](https://kit.openiap.dev/docs/ai-assistants) — how to point Codex / Claude / Cursor / etc. at this file - [/docs/ai-assistants/codex-plugin](https://kit.openiap.dev/docs/ai-assistants/codex-plugin) — Codex plugin setup and self-hosted IAPKit MCP server option - [/docs/ai-assistants/claude-plugin](https://kit.openiap.dev/docs/ai-assistants/claude-plugin) — Claude Code plugin setup (marketplace install or claude mcp add) - [/docs/release-notes](https://kit.openiap.dev/docs/release-notes) — changelog