REST API reference
Base URL https://entitlehub.com/v1. JSON in, JSON out. Authenticate every request with Authorization: Bearer <key>. See API keys. Reads accept a pk_ key; writes require an sk_ key.
POST /v1/check
Authoritative single-entitlement check. Any key.
POST /v1/check
Authorization: Bearer pk_live_…
{ "app_user_id": "user_123", "entitlement": "pro", "sandbox": false }
200 OK
{
"app_user_id": "user_123", "entitlement": "pro", "active": true,
"status": "active", "store": "play", "product": "app_pro_monthly",
"expires_at": "2026-08-01T00:00:00Z", "will_renew": true, "since": "2026-07-01T00:00:00Z"
}
// not entitled → { "active": false, "reason": "no_active_grant" }GET /v1/subscribers/{app_user_id}
A subscriber's active entitlements (the "customer info"). Any key.
GET /v1/subscribers/user_123
Authorization: Bearer pk_live_…
200 OK
{
"app_user_id": "user_123",
"active_entitlements": [
{ "entitlement": "premium", "status": "active", "store": "play",
"product": "app_premium_lifetime", "expires_at": null, "will_renew": false, "since": "…" },
{ "entitlement": "pro", "status": "active", "store": "play",
"product": "app_pro_monthly", "expires_at": "2026-08-01T00:00:00Z", "will_renew": true, "since": "…" }
]
}Only currently-active entitlements are returned; an unknown user returns an empty list (not an error).
POST /v1/subscribers/{app_user_id}/purchases
Record a store purchase → maps product to entitlement(s) and writes a grant. Secret key required. Three input modes:
Google Play (validated)
POST /v1/subscribers/user_123/purchases
Authorization: Bearer sk_live_…
{ "store": "play", "store_product_id": "app_pro_monthly",
"purchase_token": "…", "is_subscription": true }Apple StoreKit 2 (validated)
POST /v1/subscribers/user_123/purchases
Authorization: Bearer sk_live_…
{ "signed_transaction": "<StoreKit2 JWS>" }
// store + product are read from the verified JWSStripe (validated)
POST /v1/subscribers/user_123/purchases
Authorization: Bearer sk_live_…
{ "store": "stripe", "stripe_subscription_id": "sub_1P…" }
// validated via the project's Stripe secret key; store_product_id ← the Stripe Price idTrusted server-report (no store validation)
POST /v1/subscribers/user_123/purchases
Authorization: Bearer sk_live_…
{ "store": "play", "store_product_id": "app_premium_lifetime" }
// only when you've already validated the receipt elsewhere, call server-side onlyAll three return the updated customer-info object (same shape as the subscribers endpoint).
POST /v1/subscribers/{app_user_id}/grant
Grant an entitlement directly: promos, comps, redemption codes. Secret key required.
POST /v1/subscribers/user_123/grant
Authorization: Bearer sk_live_…
{ "entitlement": "premium", "duration_days": 0 }
// duration_days: 0 = lifetime, N = expires in N days. is_sandbox optional.GET /v1/offerings
Your catalog: entitlements and products, for building a paywall. Any key.
GET /v1/offerings
Authorization: Bearer pk_live_…
200 OK
{ "entitlements": [ { "key": "pro", "name": "Pro", ... } ],
"products": [ { "store_product_id": "app_pro_monthly", "type": "subscription",
"price_micros": 2990000, "currency": "USD", "entitlements": ["pro"] } ] }Errors
| Status | Meaning |
|---|---|
401 | Missing / invalid / revoked key. |
403 | Used a pk_ key on a write endpoint (needs sk_). |
400 | Invalid token, unknown/unmapped product, or no Play credential configured. |
A check or subscriber read for a user with no grants returns an inactive/empty result, not an error.
Revoke access
POST /v1/subscribers/{app_user_id}/revoke ends access that was already granted: a support case, a chargeback settled outside the store, abuse, or a manual grant that should not have happened. Omit entitlement to revoke everything they hold. Secret key only, since it takes access away.
curl -X POST https://entitlehub.com/v1/subscribers/user_123/revoke \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"entitlement":"pro"}'Revoking flips the grant to revoked and pulls its expiry to now, so check() stops returning it while the grant and the event ledger stay intact. Revenue already recognized is still real, and "had access, then lost it" is the history you want in a billing dispute. Your webhooks receive an expiration event, not override, because consumers read override as a grant.
DELETE /v1/subscribers/{app_user_id} is the other tool: it erases the subscriber, their grants, their events, and the revenue those events contributed. That is for test residue, like a webhook test that left a phantom sale in your reports. On a real customer, revoke instead.
