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).