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
}
]
}
| Field | Description |
|---|
balanceUsdMicros | Current remaining entitlement in USD micros. |
consumedUsdMicros | Consumed allowance in USD micros this period. |
lifetimeGrantedUsdMicros | Total lifetime granted allowance (Starter + manual top-ups). |
grants | List of active grants with source and amount. |
grants[].source | One of trial, manual, promo, plan_adjustment. |
grants[].featureKey | Optional 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
}
| Field | Required | Description |
|---|
amountUsdMicros | Yes | Amount to grant in USD micros (e.g. 5000000 = $5.00). String integer. |
source | No | "manual" (default), "trial", "promo", or "plan_adjustment". |
featureKey | No | Optional 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"
}
| Field | Description |
|---|
hasAccess | true when the user has remaining balance from their active plan subscription. Gate signing requests on this field. |
balanceUsdMicros | Current OpenMeter entitlement balance. |
remainingUsdMicros | Remaining 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
| Status | Condition |
|---|
404 Not Found | clientId path mismatch, or externalUserId not found. |
422 Unprocessable Entity | amountUsdMicros is not a valid positive integer string. |
503 Service Unavailable | OpenMeter is unreachable (OPENMETER_URL misconfigured or service down). |
Key design decisions
- 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.
- 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.
- 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.