Skip to main content
PymtHouse tracks per-user usage entitlements as allowances — USD micro balances backed by OpenMeter subscriptions and grants. Every new user starts with a Starter plan allowance; providers can add manual top-ups on top of the subscription balance. All allowance amounts are expressed as USD micros (integer strings; 1000000 = $1.00).
Allowance storage is OpenMeter-backed, not a Postgres wei ledger. Reads use OpenMeter entitlement APIs; grants call OpenMeter createGrant. OPENMETER_URL must be configured.

Authentication

All allowance endpoints use M2M HTTP Basic auth:
Authorization: Basic base64(m2m_id:m2m_secret)
The authenticated client’s app must match the clientId in the path. Mismatches return 404.

Read allowances

GET /api/v1/apps/{clientId}/users/{externalUserId}/allowances
Authorization: Basic base64(m2m_id:m2m_secret)
Returns the user’s allowance breakdown: balance consumed, remaining, and all active grants.
export BASE_URL="https://your-pymthouse.example"
export CLIENT_ID="app_yourClientId"
export M2M_ID="m2m_yourClientId"
export M2M_SECRET="pmth_cs_yourSecret"

curl -sS \
  -u "${M2M_ID}:${M2M_SECRET}" \
  "${BASE_URL}/api/v1/apps/${CLIENT_ID}/users/user-123/allowances" | jq .
Response:
{
  "externalUserId": "user-123",
  "balanceUsdMicros": "4200000",
  "consumedUsdMicros": "800000",
  "lifetimeGrantedUsdMicros": "5000000",
  "grants": [
    {
      "id": "grant-uuid",
      "amountUsdMicros": "5000000",
      "source": "plan_adjustment",
      "createdAt": "2026-04-01T00:00:00.000Z",
      "featureKey": null
    }
  ]
}
FieldDescription
balanceUsdMicrosCurrent remaining entitlement in USD micros.
consumedUsdMicrosConsumed allowance in USD micros this period.
lifetimeGrantedUsdMicrosTotal lifetime granted allowance (Starter + manual top-ups).
grantsList of active grants with source and amount.
grants[].sourceOne of trial, manual, promo, plan_adjustment.
grants[].featureKeyOptional OpenMeter feature key for the grant.
SDK:
const allowances = await client.getUserAllowances("user-123");

Grant a manual top-up

Add additional USD micro balance on top of the user’s existing subscription allowance:
POST /api/v1/apps/{clientId}/users/{externalUserId}/allowances
Authorization: Basic base64(m2m_id:m2m_secret)
Content-Type: application/json
Request body:
{
  "amountUsdMicros": "5000000",
  "source": "manual",
  "featureKey": null
}
FieldRequiredDescription
amountUsdMicrosYesAmount to grant in USD micros (e.g. 5000000 = $5.00). String integer.
sourceNo"manual" (default), "trial", "promo", or "plan_adjustment".
featureKeyNoOptional OpenMeter feature key to scope the grant. null for general balance.
Grants are additive on top of the Starter subscription’s included usage — they do not replace it.
curl -sS -X POST \
  -u "${M2M_ID}:${M2M_SECRET}" \
  -H "Content-Type: application/json" \
  -d '{"amountUsdMicros":"5000000","source":"manual"}' \
  "${BASE_URL}/api/v1/apps/${CLIENT_ID}/users/user-123/allowances"
SDK:
await client.grantUserAllowance("user-123", {
  amountUsdMicros: "5000000",
  source: "manual",
});

Check real-time access balance

For a lightweight gate check before allowing a user action that consumes entitlement:
GET /api/v1/apps/{clientId}/usage/balance?externalUserId={externalUserId}
Authorization: Basic base64(m2m_id:m2m_secret)
Response:
{
  "balanceUsdMicros": "4200000",
  "hasAccess": true,
  "remainingUsdMicros": "4200000",
  "consumedUsdMicros": "800000",
  "lifetimeGrantedUsdMicros": "5000000"
}
FieldDescription
hasAccesstrue when the user has remaining balance from their active plan subscription. Gate signing requests on this field.
balanceUsdMicrosCurrent OpenMeter entitlement balance.
remainingUsdMicrosRemaining balance (may differ from balanceUsdMicros after in-flight consumption).
curl -sS \
  -u "${M2M_ID}:${M2M_SECRET}" \
  "${BASE_URL}/api/v1/apps/${CLIENT_ID}/usage/balance?externalUserId=user-123" | jq .
SDK:
const balance = await client.getUsageBalance("user-123");
if (!balance.hasAccess) {
  throw new Error("User has insufficient balance");
}

Starter plan entitlement

Every app has a Starter plan with a default includedUsdMicros allowance (typically 5000000 = $5.00). New users are automatically subscribed to the Starter plan when provisioned, and the Starter allowance is the baseline grant for every new user. Providers update the Starter allowance via:
PUT /api/v1/apps/{clientId}/starter-plan
Content-Type: application/json

{ "includedUsdMicros": "10000000" }
This triggers an immediate OpenMeter plan sync. See Plans for more on the Starter plan structure.

User subscription status

Read the full OpenMeter subscription state for a user (plan, period, status):
GET /api/v1/apps/{clientId}/users/{externalUserId}/subscription
Authorization: Basic base64(m2m_id:m2m_secret)
SDK:
const subscription = await client.getUserSubscription("user-123");

Error responses

StatusCondition
404 Not FoundclientId path mismatch, or externalUserId not found.
422 Unprocessable EntityamountUsdMicros is not a valid positive integer string.
503 Service UnavailableOpenMeter is unreachable (OPENMETER_URL misconfigured or service down).

Key design decisions

  1. OpenMeter-authoritative. Allowance balances are never stored in Postgres. All reads and grants go through OpenMeter entitlement APIs, keeping the billing source of truth consistent with metering.
  2. Additive grants. POST /allowances calls OpenMeter createGrant on top of the existing Starter subscription — it does not replace or reset the subscription’s included usage.
  3. Separate balance endpoint. GET .../usage/balance is a lightweight gate check that returns only hasAccess and balance totals, suitable for pre-request checks without pulling full allowance detail.

Implementation tasks

  • Call getUsageBalance() (or GET .../usage/balance) before dispatching signed requests to check hasAccess.
  • Use grantUserAllowance() (or POST .../allowances) with source: "manual" for one-time top-ups (e.g. support credits, promotional credits).
  • Use source: "promo" or source: "trial" for time-limited promotional grants when your billing flow distinguishes them.
  • Poll GET /allowances for the full grant history when building a user billing detail view.