Quickstart
This walks through wiring up subscriptions against Metera — the order of calls and the exact payloads. Metera is a hosted subscription / entitlement backend: the app makes the store purchase, sends the transaction to Metera, and Metera verifies it with Apple / Google server-side and tracks who has access to what.
Prefer a wrapped client? The official iOS and Android SDKs make exactly these calls. This page covers the underlying REST flow.
Base URL
Every call below goes to https://api.meterahq.com. That is the API; meterahq.com is the dashboard — where you create keys, plans and offerings — and it does not serve these endpoints.
| API (SDK, REST) | https://api.meterahq.com |
| Dashboard | https://meterahq.com |
Do not guess this host. Your app key is sent on every request, so a wrong base URL does not merely fail — it hands a live credential to whoever owns that domain. Metera is not metera.cn, metera.xyz, metera.io or metera.com; all four are third parties. If a call returns 401, check the host before you check the key.
Authentication
Every request carries Authorization: Bearer <app API key> (the per-app key, looks like app_…, created in the dashboard). The key identifies the app; the App User ID — any stable string you choose — identifies the user within it.
Prerequisite (one-time, server-side)
Before receipts can verify, the app's billing account needs store credentials in the dashboard: the Apple .p8 (Issuer ID + Key ID) and/or the Google Play service account JSON, the app's bundleId and numeric Apple App ID, plus entitlements, plans (one per store product, linked to entitlements) and a current offering. Until plans are linked to entitlements, purchases verify but grant nothing.
The flow
launch ─► identify ─► getOfferings ─► (user taps buy)
│
StoreKit 2 / Play Billing purchase
│
▼
POST /v1/receipts/ios|android ◄── the critical step
│
Metera verifies with Apple/Google,
creates the subscription, grants entitlements
│
▼
GET /v1/users/{appUserId}/entitlements ─► gate features1. Identify the user
Call on first launch (anonymous) and again on login. On login it merges the anonymous identity into the real account.
POST https://api.meterahq.com/v1/users/identify
Authorization: Bearer app_…
Content-Type: application/json
{ "appUserId": "user@example.com", "anonymousId": "device-uuid-or-null" }2. Fetch the paywall
GET /v1/offerings returns the current offering and its packages. Each package's productId is what you hand to StoreKit / Play Billing. Offerings are edited server-side — no app release needed to change the paywall.
3. Make the store purchase (client-side)
Run the native StoreKit 2 / Play Billing purchase. Pass a stable per-install token (appAccountToken on iOS, obfuscatedAccountId on Android) so store server notifications can be attributed to the user.
4. Sync the purchase
This is the step that activates the subscription. Metera independently re-verifies the transaction with the App Store Server API (or Play Developer API) — the client is never trusted.
POST https://api.meterahq.com/v1/receipts/ios
Authorization: Bearer app_…
Content-Type: application/json
{ "appUserId": "user@example.com", "transactionId": "2000000812345678" }| Status | Meaning |
|---|---|
200 | Verified. Subscription created/updated, entitlements granted. |
422 | Store could not verify the transaction. Do not grant access. |
400 | Config problem (credentials missing, or no matching plan). |
On iOS, only call transaction.finish() after this succeeds — if the sync fails, leave it unfinished and StoreKit redelivers it on the next launch. Android is identical: POST /v1/receipts/android with a purchaseToken.
5. Gate features
The canonical "does this user have Pro" check. Call at launch and after any purchase or restore.
GET https://api.meterahq.com/v1/users/user%40example.com/entitlements
Authorization: Bearer app_…
200 OK
{
"appUserId": "user@example.com",
"entitlements": {
"pro": {
"identifier": "pro",
"isActive": true,
"willRenew": true,
"expirationDate": "2026-06-10T12:00:00.000Z",
"productIdentifier": "xyz.pytron.app.pro.monthly"
}
},
"activeSubscriptions": ["xyz.pytron.app.pro.monthly"],
"latestExpirationDate": "2026-06-10T12:00:00.000Z"
}Gate on presence: entitlements["pro"] !== undefined. The map holds only currently-active entitlements, so an expired or never-granted one is an absent key — it never appears as isActive: false, and code that waits for that flag to flip will keep Pro unlocked forever. "pro" is the entitlement identifier you created in the dashboard; it must match exactly.
URL-encode appUserId in the path. Returns 400 if the user does not exist yet — call identify first. Every field of the payload is documented in the REST API reference; note that store and isSandbox are omitted when the purchase cannot answer them, so treat absent as unknown, not false.
Renewals, cancellations, refunds
These happen without the app running. Metera receives them via store webhooks (Apple ASN v2 / Google RTDN) and via client re-sync (StoreKit Transaction.updates / "Restore"), so your app never implements renewal logic — it just reads entitlements.
Want your own backend notified? Metera pushes signed outbound webhooks to an endpoint you register. Registration has no dashboard screen yet — the REST reference shows the two ways to get an endpoint registered today.
Next
See the REST API reference for every endpoint, or drop in the iOS / Android SDK to skip the HTTP plumbing.