Operations

An operation is one request your software makes to the commerce provider. For a purchase, the important order is verify → connect the owner → read access.

These calls keep separate questions separate: a valid receipt, its owner, and today’s access can have different answers. Follow Alice through those steps →

Find the call you need#

Check what a provider supports#

Before connecting, ask which services, stores, and API formats this backend supports. This public answer contains no customer purchase data.

Use it to choose a compatible provider before sending purchases.

Compare the example and IAPKit

Different scope, declared explicitly

Run the flow locally

openiap-commerce-protocol-example

The descriptor names a fictional fixture store and makes no complete-profile claim. All six REST operations let you study the flow, including account deletion; they do not establish support for real stores or the full protocol.

Read code: capabilities
See the service implementation

IAPKit

IAPKit declares profiles, both API bindings, and support per store. Apple and Google have subscription events; Meta and Amazon do not. Read the descriptor before choosing a store integration.

Read code: PROVIDER_CAPABILITIES
How to check this behavior

Example. Local checks validate the teaching flow and published vectors. They are not a full provider conformance report. Inspect reference

IAPKit. Capability tests compare the served descriptor, store mappings, and implementation declarations. Inspect reference

API details · providerCapabilities
Operation
providerCapabilities
REST request
GET /commerce/v1/capabilities
Responsibility
Core: every provider supports discovery.
Credential
None: public discovery

Check the purchase evidence#

Send the store’s evidence and receive isValid. A valid purchase is not yet connected to an app user and does not by itself grant access.

Alice’s receipt is valid. Next, authorize and record who owns it.

Compare the example and IAPKit

Same question: is the evidence valid?

Run the flow locally

openiap-commerce-protocol-example

verifyFixture recognizes one fictional purchase. A valid result saves it in SQLite with no user attached. You can watch Alice’s access stay locked after verification.

Read code: verifyPurchase
See the service implementation

IAPKit

The handler sends evidence to the Apple, Google, Meta, or Amazon verifier and returns the same isValid answer. Store-specific work stays behind this call.

Read code: verifyPurchaseVerdict
How to check this behavior

Example. The Verify step checks isValid and confirms that Alice still has no access. Run npm test. Inspect reference

IAPKit. The route suite checks unreachable verification and retry behavior using mocked store I/O. Inspect reference

API details · verifyPurchase
Operation
verifyPurchase
REST request
POST /commerce/v1/purchases/verify
Responsibility
verification profile
Credential
verification role

Connect the purchase to its owner#

Your authenticated backend selects Alice and asks the provider to associate the verified purchase with her. Repeating this request keeps the same owner.

Continue on bound: true. A false result covers both unknown evidence and an ownership conflict; it never tells Bob whose purchase exists.

Compare the example and IAPKit

Same owner after a retry

Run the flow locally

openiap-commerce-protocol-example

One SQLite transaction assigns the unowned purchase to Alice and queues the access event. Repeating Alice’s request succeeds; Bob’s conflicting claim returns bound: false.

Read code: bindPurchase
See the service implementation

IAPKit

The protocol handler calls bindUserAsServer. Convex checks the project’s server credential and records ownership without moving a purchase already owned by another user.

Read code: bindUserAsServer
How to check this behavior

Example. The Bind step repeats Alice’s request and rejects Bob’s claim. Run npm test. Inspect reference

IAPKit. Binding tests cover repeated and conflicting claims against the subscription mutation handlers. Inspect reference

API details · bindPurchase
Operation
bindPurchase
REST request
POST /commerce/v1/purchases/bind
Responsibility
accountLifecycle profile
Credential
server role

Read which products the user can access#

Ask for Alice’s current productIds. Your backend unlocks a feature only when its product is in that list. The answer includes supporting subscription records, without purchase tokens.

Premium in the list: allow Premium. Empty list: no current access. A request failure is not an empty list or a new grant.

Compare the example and IAPKit

Same access rule, different storage

Run the flow locally

openiap-commerce-protocol-example

isEntitled reads the subscription state and expiry. SQLite holds the records; entitlements returns only products Alice can use now. Turning renewal off does not end paid access.

Read code: isEntitled
See the service implementation

IAPKit

isEntitledAt applies the same rule to Convex records. entitlementsV2 reads the current time and returns allowed products without exposing store purchase tokens.

Read code: isEntitledAt
How to check this behavior

Example. The state checks test before, at, and after expiry with a controlled clock. Run npm test. Inspect reference

IAPKit. Account-read tests check token omission, server authorization, and time advancing past expiry. Inspect reference

API details · entitlements
Operation
entitlements
REST request
GET /commerce/v1/entitlements
Responsibility
entitlements profile
Credential
server role

Read the subscription’s current status#

Use this for a subscription status screen or an “any active subscription?” check. It returns active and, when available, a supporting subscription record.

Canceling renewal can leave active: true until expiry. For product-specific access, use entitlements.

Compare the example and IAPKit

Explain why access continues

Run the flow locally

openiap-commerce-protocol-example

subscriptionStatus returns an active subscription or a record explaining the inactive state. In the Cancel step, willRenew becomes false while active remains true.

Read code: subscriptionStatus
See the service implementation

IAPKit

subscriptionStatusV2 selects a subscription and applies the same access deadline. The protocol handler turns that record into the shared response shape.

Read code: subscriptionStatusV2
How to check this behavior

Example. Cancel and Expire compare the status response with current entitlements. Run npm test. Inspect reference

IAPKit. The contract vectors compare status and access over REST and GraphQL with an in-memory database substitute. Inspect reference

API details · subscriptionStatus
Operation
subscriptionStatus
REST request
GET /commerce/v1/subscriptions/status
Responsibility
entitlements profile
Credential
server role

Remove the user’s identity#

When Alice deletes her account, ask the provider to remove her identity from its subscription records and protocol event store. Repeating the request is safe.

accepted acknowledges the request. A provider may report an erasure job. Your backend and other event recipients must erase their own copies separately.

Compare the example and IAPKit

Delete the account in both implementations

Run the flow locally

openiap-commerce-protocol-example

Step 7 removes identity from purchases and event records atomically. The app erases delivered copies and remembers the deletion so late signed events cannot restore the user.

Read code: eraseUser
See the service implementation

IAPKit

eraseUser starts a project-scoped job. The worker removes identity from purchases, subscriptions, and events in batches, waiting for claimed deliveries before cleaning those events.

Read code: drainSubscriptionUserErasurePage
How to check this behavior

Example. Run bun verify-erasure.mjs. It checks active-account erasure, in-flight delivery, repeated jobs, late events, and restart. Inspect reference

IAPKit. Erasure request tests cover server authorization and repeated requests, including already completed jobs. Inspect reference

API details · eraseUser
Operation
eraseUser
REST request
POST /commerce/v1/users/erase
Responsibility
accountLifecycle profile
Credential
server role

The same operation meanings apply over REST and GraphQL. Repeated requests are idempotent: they do not repeat the business effect. Providers must report an error when they cannot produce a complete answer.

Exact inputs, outputs, and behavior: SPEC.md §4.