Plan-Variant Entitlements
How to attach billable metrics to plan variants with type-shaped values, giving each tier different feature access, caps, and configurations.
Quick take
- Each attachment links one billable metric to one plan variant with a type-shaped value
- Value shape must match the metric type: boolean needs
enabled, gauge needscap - Customers inherit entitlements from their subscription's plan variant — no per-customer overrides (yet)
- Metered metrics don't need explicit attachments — they're governed by credit grants + metering rules
Plan-Variant Entitlements
A plan-variant entitlement is the link between a plan variant and a billable metric. It declares what a specific pricing tier gives its customers — SSO access (boolean), a seat cap (gauge), a config blob (static), or simply credit-metered access.
When a customer subscribes to a plan variant, they inherit all its entitlement attachments. Your app reads these values to gate features, enforce limits, and apply configuration.
How it works
- You create a billable metric with a
typeanddefault_value. - You attach that metric to a plan variant with a value override — the specific value this tier provides.
- Customers subscribing to that variant inherit the attached value. If no attachment exists for a metric, the metric’s
default_valueapplies.
The value you attach must match the metric’s type:
| Metric type | Required value shape | Example |
|---|---|---|
boolean | {"enabled": <bool>} | {"enabled": true} |
gauge | {"cap": <non-negative integer>} | {"cap": 50} |
static | {"config": <any object>} | {"config": {"rpm": 1000}} |
metered | {} or omit | {} |
Metered metrics don’t need explicit attachments — they’re governed by credit grants and metering rules. You can still attach them to mark that a variant “includes” a metered metric, but the value has no effect on credit behavior.
Attaching a metric to a plan variant
POST /v1/plans/{plan_id}/variants/{variant_id}/entitlements
curl -X POST https://api.quotastack.io/v1/plans/plan_01/variants/var_pro_monthly/entitlements \
-H "X-API-Key: qs_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: attach-sso-pro" \
-d '{
"billable_metric_key": "sso",
"value": {"enabled": true}
}'
Response (201 Created):
{
"plan_id": "plan_01",
"variant_id": "var_pro_monthly",
"billable_metric_key": "sso",
"value": {"enabled": true},
"created_at": "2026-05-24T10:00:00Z"
}
Listing variant entitlements
GET /v1/plans/{plan_id}/variants/{variant_id}/entitlements
curl https://api.quotastack.io/v1/plans/plan_01/variants/var_pro_monthly/entitlements \
-H "X-API-Key: qs_live_..."
Response:
{
"data": [
{
"billable_metric_key": "sso",
"value": {"enabled": true},
"metric_type": "boolean",
"created_at": "2026-05-24T10:00:00Z",
"updated_at": "2026-05-24T10:00:00Z"
},
{
"billable_metric_key": "max_seats",
"value": {"cap": 50},
"metric_type": "gauge",
"created_at": "2026-05-24T10:01:00Z",
"updated_at": "2026-05-24T10:01:00Z"
}
]
}
Updating an entitlement value
PATCH /v1/plans/{plan_id}/variants/{variant_id}/entitlements/{billable_metric_key}
curl -X PATCH https://api.quotastack.io/v1/plans/plan_01/variants/var_pro_monthly/entitlements/max_seats \
-H "X-API-Key: qs_live_..." \
-H "Content-Type: application/json" \
-d '{
"value": {"cap": 100}
}'
The new value must still match the metric’s type. You cannot change the metric key — only the value.
Detaching an entitlement
DELETE /v1/plans/{plan_id}/variants/{variant_id}/entitlements/{billable_metric_key}
curl -X DELETE https://api.quotastack.io/v1/plans/plan_01/variants/var_pro_monthly/entitlements/sso \
-H "X-API-Key: qs_live_..."
Returns 204 No Content. After detaching, customers on this variant fall back to the metric’s default_value.
Validation rules
The API enforces type-safe values at write time:
| Metric type | Validation | Error on violation |
|---|---|---|
boolean | value must contain "enabled" (boolean) | 422 — invalid value shape |
gauge | value must contain "cap" (non-negative integer) | 422 — invalid value shape |
static | value must contain "config" (any JSON object) | 422 — invalid value shape |
metered | No shape requirement | — |
Error responses
| Status | Condition | Meaning |
|---|---|---|
404 | Plan, variant, or metric not found | The referenced resource doesn’t exist. |
409 | Metric already attached to this variant | Duplicate. Use PATCH to update or DELETE first. |
422 | Value doesn’t match metric type | Shape violation — see validation table above. |
Worked example: Free vs Pro
A SaaS app defines four billable metrics:
| Metric key | Type | Default value |
|---|---|---|
sso | boolean | {"enabled": false} |
max_seats | gauge | {"cap": 5} |
api_call | metered | {} |
model_access | static | {"config": {"models": ["gpt-3.5"]}} |
The Free variant attaches:
| Metric | Value | Effect |
|---|---|---|
sso | {"enabled": false} | No SSO |
max_seats | {"cap": 5} | 5 seats |
model_access | {"config": {"models": ["gpt-3.5"]}} | Basic models only |
The Pro variant attaches:
| Metric | Value | Effect |
|---|---|---|
sso | {"enabled": true} | SSO enabled |
max_seats | {"cap": 50} | 50 seats |
model_access | {"config": {"models": ["gpt-4", "claude-sonnet", "gpt-3.5"]}} | All models |
Neither variant explicitly attaches api_call — it’s metered, so access is governed by whether the customer has credits and an active metering rule.
When a customer subscribes to the Pro variant, your app can query their plan-variant entitlements and enforce: SSO is on, up to 50 seats, and the full model list is available. When they downgrade to Free, the values update accordingly.
Common mistakes
Don't attach the same metric to a variant twice
The unique constraint rejects it with a 409. Detach first, or use PATCH to update the value.
Don't use gauge with a float cap
Cap must be a non-negative integer. The API rejects fractional values with a 422.
Loading…