Client API
All endpoints on this page take X-Kasskeep-Key: pk_.... app_user_id is 1 to 200 characters.
POST /v1/receipts
Section titled “POST /v1/receipts”Submit a store transaction. Returns {"customer": ...}.
| Field | Type | Notes |
|---|---|---|
app_user_id |
string | Required |
platform |
ios, android, test |
Required. Picks the channel |
ios.signed_transaction |
string | StoreKit 2 Transaction.jwsRepresentation |
android.product_id |
string | Play product or subscription id |
android.purchase_token |
string | Purchase.purchaseToken |
android.type |
inapp, subs |
A hint for which Play API to query first |
test.purchase_id |
string | Resubmit a test store purchase |
{ "app_user_id": "6f1c2a3b-4d5e-4f60-8a71-92b3c4d5e6f7", "platform": "ios", "ios": { "signed_transaction": "eyJhbGciOiJFUzI1NiIs..." } }{ "app_user_id": "6f1c2a3b-4d5e-4f60-8a71-92b3c4d5e6f7", "platform": "android", "android": { "product_id": "coins_100", "purchase_token": "...", "type": "inapp" } }| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_signed_transaction |
Bad signature or chain, wrong bundle id or environment (see message) |
| 400 | environment_not_allowed |
The app does not accept this environment |
| 400 | invalid_purchase_token |
Google does not know the token |
| 403 | app_account_token_mismatch |
The transaction is bound to a different user id |
| 403 | transaction_belongs_to_another_user |
Already recorded for another user |
| 422 | android_not_configured |
The app has no package name or service account |
| 502 | verification_unavailable |
Apple certificate revocation check or Google API temporarily failed; retry |
Pending Android purchases are not recorded until they complete.
GET /v1/customers/:app_user_id
Section titled “GET /v1/customers/:app_user_id”The user’s subscriptions and purchases. Optional ?environment=production|sandbox|test.
{ "customer": { "app_user_id": "6f1c...", "subscriptions": [ { "product_id": "pro_monthly", "platform": "ios", "environment": "production", "original_transaction_id": "2000000123", "status": "active", "will_renew": true, "current_period_start": "2026-10-01T12:00:00.000Z", "current_period_end": "2026-11-01T12:00:00.000Z", "ownership": "purchased" } ], "purchases": [ { "product_id": "coins_100", "platform": "ios", "environment": "production", "transaction_id": "2000000456", "purchased_at": "2026-10-02T08:00:00.000Z", "quantity": 1, "status": "owned" } ] }}Subscription status values are listed in Data model. With a secret key the response carries more detail, see Server API.
POST /v1/customers/:app_user_id/restore
Section titled “POST /v1/customers/:app_user_id/restore”Submit everything the store currently owns for this user. Returns {"customer": ...}.
| Field | Type | Notes |
|---|---|---|
platform |
ios, android, test |
Required |
ios.signed_transactions |
string[] | Up to 500 |
android.purchases |
{product_id, purchase_token, type}[] |
Up to 500 |
test.purchase_ids |
string[] | Optional; without it all test purchases are resynced |
Transactions that belong to someone else or that Google does not know are skipped without an error. With an App Store API key, Kasskeep also completes the history from Apple.
POST /v1/checkout
Section titled “POST /v1/checkout”Create a hosted checkout for a web payment channel. See Stripe.
| Field | Type | Notes |
|---|---|---|
app_user_id |
string | Required |
channel |
string | stripe |
product_id |
string | Must be mapped for the channel |
success_url, cancel_url |
string | https on a host in checkout_return_hosts |
quantity |
integer | 1 to 100, default 1 |
Response: {"url": "...", "session_id": "cs_..."}.
| Status | Code |
|---|---|
| 400 | unknown_channel, checkout_not_supported, return_url_not_allowed, product_not_configured |
| 422 | return_hosts_not_configured, stripe_not_configured |
| 502 | stripe_unavailable, stripe_auth_failed, stripe_rejected |
POST /v1/test-store/purchases
Section titled “POST /v1/test-store/purchases”Buy from the test store. Requires the test store to be on.
| Field | Type | Notes |
|---|---|---|
app_user_id |
string | Required |
product_id |
string | Required |
type |
inapp, subs |
Required |
quantity |
integer | 1 to 100; subscriptions 1 |
outcome |
success, cancelled, pending, failed |
Default success |
price_micros, currency |
integer, [A-Z]{3} |
Optional price override |
Response: {outcome, purchase, customer}.
GET /v1/test-store/products
Section titled “GET /v1/test-store/products”?ids=a,b&type=inapp|subs. Returns {products: [{product_id, type, title, description, price_micros, currency, environment}], subscription_period_seconds} for the allowed ids, at most 200.
GET /v1/customers/:app_user_id/ad-rewards
Section titled “GET /v1/customers/:app_user_id/ad-rewards”Recent rewards, newest first. ?limit=1..200 (default 50), ?environment=production|test. Returns {app_user_id, ad_rewards: [...]}. For display and support only; grant rewards from the webhook.
POST /v1/ads/test/rewards
Section titled “POST /v1/ads/test/rewards”A test reward. See Test mode.
| Field | Type | Notes |
|---|---|---|
app_user_id |
string | Required |
ad_unit |
string | Your ad unit name |
reward_type |
string | Optional |
reward_amount |
number | Optional, 0 to 1e9 |
custom_data |
string | Optional, up to 8192 characters |
outcome |
completed, skipped, failed |
Default completed |
network |
test, admob, applovin, levelplay |
Default test |
Response: {status, reward_event_id, ad_reward}.