---
title: Plan-Variant Entitlements
description: How to attach billable metrics to plan variants with type-shaped values, giving each tier different feature access, caps, and configurations.
order: 10
---

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

> **Mental Model:** 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.

## 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 needs `cap`
- 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

## Diagram

A plan variant has a row of entitlement attachments, one per billable metric. Each attachment carries a typed value. Customers subscribe to a plan variant and inherit all its attachments.

```mermaid
flowchart TD
    PV[Plan Variant<br/>e.g. Pro Monthly] --> A1[sso: enabled=true]
    PV --> A2[max_seats: cap=50]
    PV --> A3[model_access: config=...]
    PV --> A4[api_call: metered]
    C[Customer] -->|subscribes| PV
```

## How it works

1. You create a [billable metric](/docs/concepts/metering) with a `type` and `default_value`.
2. You attach that metric to a plan variant with a **value override** — the specific value this tier provides.
3. Customers subscribing to that variant inherit the attached value. If no attachment exists for a metric, the metric's `default_value` applies.

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
```

```bash
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`):

```json
{
  "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
```

```bash
curl https://api.quotastack.io/v1/plans/plan_01/variants/var_pro_monthly/entitlements \
  -H "X-API-Key: qs_live_..."
```

Response:

```json
{
  "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}
```

```bash
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}
```

```bash
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.
