The access-mapping protections and soldWithoutAccess field below are unreleased
source changes. Deploy the product-access migration and API together before relying
on these checks. Existing store imports are not automatically remapped.
Catalog
Create and map store products to access tiers, with explicit handling for consumables and purchases sold without access.
A catalog product represents one store SKU, and on Google Play one base plan. Apple and Google products are separate rows; map them to the same entitlement identifier when they unlock the same access tier.
Authentication#
The app-scoped management routes below accept a dashboard session, an authorized MCP token, or an owner/admin-issued setup token. They do not accept an SDK publishable key or a backend secret key. Setup tokens expire after 24 hours. Use a token scoped to this app; never put it in a mobile app.
For backend read-only catalog access with a secret key, use GET /v1/catalog.
For a customer's actual access, use
GET /v1/customers/{appUserId}/entitlements, not the catalog's entitlement definitions.
Retrieve the catalog#
GET /v1/apps/{appId}/catalog
curl "https://api.cashsdk.com/v1/apps/$CASHSDK_APP_ID/catalog" \
-H "Authorization: Bearer $CASHSDK_SETUP_TOKEN"
The response contains products, entitlements, and groups. Product fields include:
| Field | Meaning |
|---|---|
id | Generated CashSDK row ID; use this in PATCH paths |
identifier | Exact store product identifier |
store | app-store or play-store |
basePlanId | Play base-plan ID, or null |
type | auto_renewable, non_renewing, non_consumable, or consumable |
duration | Subscription period such as P1M, or null |
entitlementIds | CashSDK entitlement row IDs, not identifiers |
soldWithoutAccess | Explicit acknowledgement that an unmapped purchase intentionally grants no access |
status | active products participate in the mapping readiness check |
price, currency | Catalog price in minor units and currency; display the device store's localized price |
Catalog entitlements are definitions with id, identifier, name, and rank.
They are not a customer's active entitlement snapshot.
Create an entitlement first#
POST /v1/apps/{appId}/catalog/entitlements
curl -X POST "https://api.cashsdk.com/v1/apps/$CASHSDK_APP_ID/catalog/entitlements" \
-H "Authorization: Bearer $CASHSDK_SETUP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"identifier":"pro","name":"Pro","rank":1}'
Save the returned id for mapping. Your app checks the identifier (pro).
Create a product#
POST /v1/apps/{appId}/catalog/products
{
"identifier": "com.example.pro.monthly",
"store": "app-store",
"type": "auto_renewable",
"duration": "P1M",
"entitlementIds": ["ENTITLEMENT_ROW_ID"]
}
For Play, use store: "play-store" and the exact basePlanId when applicable.
Create a separate row for each base plan. The unique identity is app + store +
identifier + base plan; reusing it returns 409.
Active subscriptions, passes and lifetime products require an entitlement mapping.
Consumables credit balances and need no mapping. For an intentional tip or donation,
set soldWithoutAccess: true; do not use this to bypass an unfinished access setup.
The flag does not remove existing entitlement links or suppress their grants.
Map an imported product#
PATCH /v1/apps/{appId}/catalog/products/{productId}
Sync can import products before their access tiers are known. Read the catalog, select the exact row (including its Play base plan), and PATCH it instead of creating a duplicate:
{ "entitlementIds": ["ENTITLEMENT_ROW_ID"] }
entitlementIds replaces the entire mapping set. Include existing IDs when adding
another tier. Other omitted fields remain unchanged. An agent can do this through
update_product with productId and entitlementIds.
Readiness and safe edits#
An active access product without a mapping returns HTTP 400 with
code: "entitlements_required", affected product identifiers, and remediation when:
- creating or editing that product;
- deleting its last linked entitlement;
- saving app settings whose resulting environment is Production, including going live.
The dashboard's Products → Needs mapping filter identifies these products. The checklist and both store diagnostics exempt consumables, inactive rows and products explicitly sold without access.
Existing Production apps are not automatically switched to Sandbox, and store sync remains allowed to import unmapped rows. Fix the reported mapping before launching those products. These readiness checks do not certify store credentials, receipt settlement, notifications, or a real purchase on a device.