REST API
The endpoints a client app (or the iOS / Android SDK) calls. All paths are relative to your Metera deployment's base URL. Admin / dashboard endpoints are not covered here.
Authentication
Every request needs:
Authorization: Bearer <app API key> | The app's API key (app_…). |
X-App-User-Id: <appUserId> | The current user's ID. Optional on calls that already carry appUserId, but harmless to always send. |
All request and response bodies are JSON.
POST /v1/users/identify
Create or look up a user, optionally merging an anonymous identity into a known one. When anonymousId differs from appUserId, that user's subscriptions, purchases and entitlement grants move onto appUserId.
{
"appUserId": "user@example.com",
"anonymousId": "$meteraAnonymous:ABC",
"attributes": { "email": "user@example.com" }
}Response: { "appUserId", "merged": bool, "entitlements": {…}, "quota": {…}|null }
GET /v1/users/{appUserId}/entitlements
The canonical "customer info" — active entitlements, subscriptions and quota. This is what gates features. Returns 400 if the user does not exist yet.
{
"appUserId": "user@example.com",
"entitlements": {
"pro": {
"identifier": "pro",
"isActive": true,
"willRenew": true,
"periodType": "NORMAL",
"expirationDate": "2026-06-10T12:00:00.000Z",
"productIdentifier": "xyz.pytron.app.pro.monthly",
"store": "IOS"
}
},
"activeSubscriptions": ["xyz.pytron.app.pro.monthly"],
"latestExpirationDate": "2026-06-10T12:00:00.000Z",
"quota": null
}GET /v1/offerings
The remote paywall configuration: which products to show, grouped into packages. Editable in the dashboard with no app release.
{
"currentOfferingId": "default",
"offerings": [{
"identifier": "default",
"displayName": "Default",
"isCurrent": true,
"packages": [{
"identifier": "$rc_monthly",
"displayName": "Monthly",
"productId": "xyz.pytron.app.pro.monthly",
"productType": "SUBSCRIPTION",
"billingPeriod": "MONTHLY"
}]
}]
}POST /v1/receipts/ios
Verify and sync a StoreKit 2 transaction. Metera independently re-verifies with the App Store Server API — the client is never trusted. Request: { "appUserId": "...", "transactionId": "2000000812345678" }. Returns 200 (synced) or 422 (Apple could not verify).
POST /v1/receipts/android
Verify and sync a Google Play purchase. Metera re-verifies the purchase token with the Play Developer API and acknowledges it. Request: { "appUserId": "...", "purchaseToken": "<token>" }. Returns 200 or 422.
Outbound webhooks
Metera can push signed webhooks to your backend when a subscription or entitlement changes — useful to keep your own DB in sync. Configure the endpoint in the dashboard; each delivery is signed so you can verify it came from Metera.
Errors
Non-2xx responses carry an error field — either a string or { "code", "message" }. Treat 4xx as terminal; 5xx and network failures are safe to retry with backoff (both SDKs do).