Plan-Variant Entitlements
How to attach billable metrics to plan variants with type-shaped values, giving each tier different feature access, caps, and configurations.
Think of plan-variant entitlements as the feature matrix row for a pricing tier. Each cell says what that tier gets for a specific metric — on/off, a cap, a config blob, or credit-metered access.
enabled, gauge needs capPlan-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
The mistakes developers typically make with this concept — and what to do instead.