# Entitlements

> List your workspace's plan limits and feature flags. Entitlements are enforced when you create API keys, connectors, knowledge bases, private apps, upload files, or run tasks. Most limits are checked ...

**URL:** https://inference.sh/docs/api/rest/entitlements
**Last updated:** 2026-11-10

---

List your workspace's plan limits and feature flags. Entitlements are enforced when you create API keys, connectors, knowledge bases, private apps, upload files, or run tasks. Most limits are checked at request time; **concurrent task** limits are enforced by the task scheduler. See [Concurrent task limits](#concurrent-task-limits).

Use **`GET /entitlements`** to see entitlement rows (limits and sources). Use **`GET /entitlements/usage`** to see current usage vs limits.

### Organizations

When your team belongs to an organization, the **organization workspace** (`type: org`, same ID as the org) owns the company subscription. Its primary `team_plan` and any add-on rows on that workspace materialize **org-scoped** tier entitlements (`source: tier`). While those rows are active, they **replace** each member team's own tier limits for the same resources — member teams keep their Starter `team_plan` row for bookkeeping, but org-scope tier wins at enforcement time.

Countable limits (for example `api_keys`, `seats`, `storage_mb`, `concurrency`) **pool usage across every team in the org** when the winning entitlement row is org-scoped. `GET /entitlements/usage` from a member team reflects that pooled usage against the org-wide cap.

The organization workspace does **not** receive the automatic Starter `team_plan` at creation. With no primary plan on the org workspace, there are no org-scope tier rows until you subscribe from that workspace or a complimentary contract is assigned there. Member teams still have Starter until an org plan is in effect.

Add-ons purchased on the organization workspace are org-scoped the same way as the base plan. See [Organizations — Billing](/docs/org/organizations#billing) and [Subscription API](/docs/api/rest/subscription#get-subscription).

---

## List entitlements

`GET /entitlements`

Returns all entitlement rows for the API key's workspace. Requires the `billing:read` scope.

**Example:**

```bash
curl https://api.inference.sh/entitlements \
  -H "Authorization: Bearer inf_your_key"
```

**Response** (array of entitlement objects):

```json
[
  {
    "id": "ent_abc123",
    "team_id": "team_xyz",
    "resource": "api_keys",
    "type": "limit",
    "enabled": true,
    "unlimited": false,
    "limit": 5,
    "source": "tier",
    "enforcement": "block"
  }
]
```

### Fields

| Field | Type | Description |
|-------|------|-------------|
| `resource` | string | What is limited — see [Resources](#resources) below |
| `type` | string | Entitlement type from the plan definition |
| `enabled` | boolean | Whether the resource or feature is enabled |
| `unlimited` | boolean | When `true`, no numeric cap applies |
| `limit` | number | Maximum allowed (when not unlimited) |
| `source` | string | Origin: `tier`, `addon`, `trial`, `whitelist`, or `override` |
| `enforcement` | string | `block` (hard deny when exceeded) or `warn` (allow with warning). Omitted on entitlement rows defaults to `warn`. Plan definitions in `GET /plans` can set `enforcement` per limit — the **Starter** tier uses `block` on `concurrency`, `rate_per_min`, and `triggers`. |
| `expires_at` | string | ISO timestamp when a trial or override expires (optional) |

When multiple rows exist for the same `resource`, the API resolves the **highest-priority source**: `override` > `whitelist` > `trial` > `tier` / `addon`. Rows from `tier` and `addon` are **merged** for the same resource — boolean features use OR, numeric limits are summed, any unlimited row makes the merged result unlimited, and any `block` row makes the merged `enforcement` `block`.

---

## Get usage

`GET /entitlements/usage`

Returns current usage vs limits for countable resources, inflight task concurrency, and boolean feature gates. Requires the `billing:read` scope.

Unlike `GET /entitlements`, this endpoint includes **live usage counts** — for example how many API keys exist, how much storage is used, and how many tasks are currently in flight.

**Example:**

```bash
curl https://api.inference.sh/entitlements/usage \
  -H "Authorization: Bearer inf_your_key"
```

**Response** (array of usage objects):

```json
[
  {
    "resource": "api_keys",
    "label": "api keys",
    "type": "limit",
    "usage": 3,
    "limit": 5,
    "unlimited": false
  },
  {
    "resource": "storage_mb",
    "label": "storage",
    "type": "limit",
    "unit": "mb",
    "usage": 128,
    "limit": 1024,
    "unlimited": false
  },
  {
    "resource": "concurrency",
    "label": "concurrent tasks",
    "type": "limit",
    "usage": 2,
    "limit": 3,
    "unlimited": false
  },
  {
    "resource": "feature:webhooks",
    "label": "webhooks",
    "type": "boolean",
    "enabled": true
  },
  {
    "resource": "feature:seedance",
    "label": "seedance video generation",
    "type": "boolean",
    "enabled": false
  }
]
```

### Usage fields

| Field | Type | Description |
|-------|------|-------------|
| `resource` | string | Resource identifier (same values as [Resources](#resources)) |
| `label` | string | Human-readable name (for example `concurrent tasks`, `api keys`) |
| `unit` | string | Display unit from the plan (for example `mb`, `days`) — optional |
| `type` | string | `limit` (numeric cap) or `boolean` (feature gate) |
| `usage` | number | Current usage (limit-type resources only) |
| `limit` | number | Maximum allowed when not unlimited (limit-type only) |
| `unlimited` | boolean | When `true`, no numeric cap applies (limit-type only) |
| `enabled` | boolean | Whether the feature is enabled (boolean-type only) |

When no entitlement row exists for a limit-type resource, the response sets `unlimited: true` and still returns the current `usage`.

### What is counted

| Resource | Usage source |
|----------|--------------|
| `api_keys` | Number of API keys for the workspace |
| `storage_mb` | Total uploaded file storage in megabytes |
| `seats` | Number of workspace members |
| `triggers` | Trigger subscriptions linked to agents |
| `concurrency` | Inflight tasks — queued (`received`, `queued`) plus in-progress (`dispatched`, `preparing`, `serving`, `setting_up`, `running`, `cancelling`, `uploading`) |
| `sandboxes` | [Managed sandboxes](/docs/api/rest/sandboxes) not `stopped` or `failed` |
| `sandbox_vcpus` | vCPUs of those sandboxes, added up |

### Concurrent task limits

The `concurrency` resource caps how many tasks your workspace can have in flight at once. Limits come from your subscription plan (see `limits.concurrency` on [GET /plans](/docs/api/rest/subscription#list-plans)) and merge with add-on rows the same way as other entitlements.

Unlike `api_keys`, `storage_mb`, and most other limits, concurrency is **not** checked when you call `POST /tasks`. The task is accepted and enters **Queued**; the scheduler enforces the cap when it assigns a worker. That is different from `task_executions`, which is checked at task creation and returns `limit_exceeded` (402) when the billing-period cap is reached.

| `enforcement` on the entitlement row | Scheduler behavior when at the cap |
|--------------------------------------|------------------------------------|
| `warn` (default when unset) | Task is dispatched anyway; the over-limit condition is logged server-side |
| `block` | Task stays **Queued** until another run completes and frees a slot |

The **Starter** tier sets `enforcement: block` on `concurrency` (limit of 1). Paid tiers use `warn` unless a plan or entitlement row sets `block`. Inspect the `enforcement` field on `GET /entitlements` rows or on each limit in `GET /plans`.

Inspect live inflight usage with `GET /entitlements/usage` (`resource: "concurrency"`). In the web app, [settings → workspace → subscription](https://app.inference.sh/settings/team/billing/subscription) shows the same row.

Store apps can also declare per-app caps (`max_concurrency`, `max_concurrency_per_team` on the [store listing](/docs/api/rest/store#get-app-store-listing)). Those are separate scheduler limits scoped to one app, not your workspace-wide plan cap.

Boolean features (for example `feature:webhooks`, `feature:byok`, `feature:seedance`) appear only when an entitlement row exists for that feature.

In the web app, [settings → workspace → subscription](https://app.inference.sh/settings/team/billing/subscription) shows the same usage rows with progress bars: yellow at 80% of a finite limit, orange at the cap.

---

## Resources

Common `resource` values returned by `GET /entitlements`:

| Resource | Typical use |
|----------|-------------|
| `api_keys` | Number of API keys |
| `connectors` | MCP connectors |
| `knowledge_bases` | Knowledge entries |
| `private_apps` | Private app deployments |
| `storage_mb` | Total uploaded file storage |
| `task_executions` | Task runs per billing period |
| `concurrency` | Concurrent tasks (inflight runs) |
| `rate_per_min` | API rate per minute |
| `seats` | Workspace members |
| `triggers` | Agent trigger subscriptions (event, schedule, or manual) |
| `sandboxes` | Managed sandboxes running or waiting to run. Enforced; see [Sandbox limits](/docs/extend/sandbox-limits#plan-limits) |
| `sandbox_vcpus` | vCPUs those sandboxes hold together. Enforced |
| `sandbox_max_vcpus` | Largest sandbox a workspace may create (vCPUs; memory is capped at 2 GiB per vCPU of that size). Enforced |
| `sandbox_starts_per_min` | Sandbox creates and starts (of stopped or failed sandboxes) per minute. Enforced with `429` `rate_limited` |
| `sandbox_kept_disks` | Stopped sandboxes whose engine keeps their disk. Soft limit — past it a stop still keeps the disk; see [Sandbox limits](/docs/extend/sandbox-limits#plan-limits) |
| `sandbox_snapshots` | Ready [sandbox snapshots](/docs/api/rest/sandboxes#snapshots-and-forks) in the workspace. Soft limit — past it a snapshot is still taken; see [Sandbox limits](/docs/extend/sandbox-limits#plan-limits) |
| `retention_days` | Data retention period in days (plan policy; not usage-counted) |
| `feature:scopes` | Custom API key scopes |
| `feature:webhooks` | Webhooks |
| `feature:byok` | Bring your own keys |
| `feature:seedance` | Seedance video generation |
| `feature:marketplace_publish` | Submit to the marketplace (`POST /store/listings/draft`, `POST /store/check`, `POST /store/submit`). Granted per workspace by invitation; no row or `enabled: false` blocks new publish actions with `feature_not_available` (403). Request access with [Entitlement requests](#entitlement-requests). See [Store API — Publishing invitation](/docs/api/rest/store#publishing-invitation). |
| `feature:forms` | Create and manage workspace forms (`POST /forms`, `PATCH` / `PUT /forms/{id}`). Granted per workspace by invitation; missing or disabled returns `feature_not_available` (403). See [Forms API](/docs/api/rest/forms). |
| `feature:sandboxes` | [Managed sandboxes](/docs/api/rest/sandboxes). Open by default, but the paying workspace needs a purchase in the last `billing.recent_purchase_days` days (default 30); without one, create answers `402` `purchase_required` with `meta.requirement`, `meta.within_days` and `meta.billing_page`. An enabled row with source `override` waives the purchase. A row with `enabled: false` refuses create and start with `feature_not_available` (403). |
| `feature:publish_apps` | Legacy boolean kept for compatibility; marketplace gating uses `feature:marketplace_publish` |
| `feature:auto_recharge` | Auto-recharge billing |
| `feature:invoices` | Invoice billing |
| `feature:team_billing` | Workspace billing features |

### Limit vs feature gate semantics

How the API treats a missing entitlement row depends on the resource type and where it is checked:

| Context | No entitlement row | Row with `enabled: false` |
|---------|-------------------|---------------------------|
| `limit` resources (for example `api_keys`, `concurrency`) | No restriction — `GET /entitlements/usage` reports `unlimited: true` | Uses `limit` / `unlimited`, not `enabled` |
| `boolean` plan features (standard entitlement checks) | No restriction | Feature blocked |
| Store app `required_feature` | **Denied** — explicit grant required | **Denied** |

Capacity limits follow an opt-out model: no row means no cap. **Store app feature gates** follow an opt-in model: your workspace must have a boolean entitlement row with `enabled: true` for the listed `required_feature`. A missing row is not treated as unrestricted access.

---

## When limits block a request

Hard-blocked entitlements return API errors before the operation completes. On the **Starter** tier, `concurrency`, `rate_per_min`, and `triggers` use `enforcement: block` — exceeding those limits returns `limit_exceeded` (402) at request time (`rate_per_min`, `triggers`) or keeps tasks **Queued** (`concurrency`; see [Concurrent task limits](#concurrent-task-limits)). Other Starter limits (for example `storage_mb`, `seats`) use the default `warn` mode until you upgrade.

| Code | HTTP | Meaning |
|------|------|---------|
| `limit_exceeded` | 402 | Numeric limit reached |
| `feature_not_available` | 403 | Boolean feature disabled on your tier |
| `payment_required` | 402 | Insufficient prepaid balance (separate from plan limits) |
| `payment_method_required` | 402 | A run certain to cost $0, with no balance and no saved payment method. See [Billing: free runs](/docs/api/rest/billing#free-runs) |
| `entitlement_unavailable` | 500 | The entitlement lookup failed, so plan limits could not be verified. Retriable. |

When the API cannot read entitlement rows (for example on a transient database error), paths that check plan limits return `entitlement_unavailable` (HTTP 500) with detail `could not verify plan limits, try again`. The server did not determine that you are over a cap or missing a feature. Retry after a short delay. The underlying error is logged server-side and not included in the response. Gated features fail closed on this error.

### Error metadata

When a plan limit blocks a request, structured metadata is attached to the error response. Clients that drive upgrade flows (the web app, belt CLI) read these fields from the raw response body.

| Field | Description |
|-------|-------------|
| `resource` | Resource identifier (for example `api_keys`, `concurrency`) |
| `resource_label` | Human-readable label (for example `concurrent tasks`) |
| `limit` | Plan limit that was exceeded |
| `current` | Usage at the time of the request |
| `upgrade_available` | Always `true` for entitlement errors. The CLI appends an upgrade URL; the web app opens the upgrade modal |
| `addon_plan_id` | Optional — plan ID for an addon that unlocks the blocked feature (present when a store app requires a purchasable addon) |
| `addon_plan_name` | Optional — display name of the addon plan |
| `addon_plan_price` | Optional — monthly price in cents for the addon plan |
| `requestable` | When `true`, the blocked resource is a feature gate your team may ask to be granted (see [Entitlement requests](#entitlement-requests)) |
| `request_state` | When your team already has a request for this resource: `open`, `accepted`, `declined`, or `withdrawn` |

Errors are RFC 9457 `application/problem+json` with a human-readable `detail` and the metadata fields under a top-level `meta` extension member (RFC 9457 allows extension members on problem details):

```json
{
  "type": "https://api.inference.sh/errors/limit_exceeded",
  "title": "Payment Required",
  "status": 402,
  "detail": "You have reached the limit of 3 concurrent tasks",
  "meta": {
    "resource": "concurrency",
    "resource_label": "concurrent tasks",
    "limit": 3,
    "current": 3,
    "upgrade_available": true
  }
}
```

When a store app blocks on a purchasable addon (for example `feature:seedance`), `meta` also includes the addon fields:

```json
{
  "type": "https://api.inference.sh/errors/feature_not_available",
  "title": "Forbidden",
  "status": 403,
  "detail": "this app requires the seedance video generation add-on.",
  "meta": {
    "resource": "feature:seedance",
    "resource_label": "seedance video generation",
    "upgrade_available": true,
    "addon_plan_id": "plan_seedance_addon",
    "addon_plan_name": "Seedance",
    "addon_plan_price": 999
  }
}
```

Official SDKs expose the raw problem body on `APIError.response_body` (Python) or `InferenceError.responseBody` (JavaScript). Parse `meta` from the JSON when you need structured entitlement fields. The **belt** / **infsh** CLI checks `meta.upgrade_available` on problem+json responses to append an upgrade URL to the error message. When `addon_plan_id` is present, the URL points to [settings → workspace → add-ons](https://app.inference.sh/settings/team/billing/addons); otherwise it points to the subscription page. You can also inspect limits proactively with `GET /entitlements/usage`.

### Web app upgrade modal

When a workspace API call returns `limit_exceeded` or `feature_not_available` with `upgrade_available: true`, the app opens the upgrade modal and passes error metadata (`resource`, `resource_label`, `limit`, `current`, and `detail`). The modal lists self-serve plans above your current tier and highlights the **recommended** plan — the cheapest tier that lifts the blocked resource. For numeric limits it shows `current / limit` only when `limit` is greater than zero.

When `addon_plan_id` is present (for example a blocked `feature:seedance` run on a store app), the web app shows an **add-on purchase** modal instead of a tier upgrade, with the addon name and monthly price from `addon_plan_name` and `addon_plan_price`. The primary CTA reads **subscribe - $X/mo** and routes to [settings → workspace → add-ons](https://app.inference.sh/settings/team/billing/addons) to complete checkout (same flow as the add-ons page cards). Most add-ons do not require a base subscription; some set `required_plan_ids` on the plan (see [Subscription API: add-on subscriptions](/docs/api/rest/subscription#add-on-subscriptions)).

### Feature-gated store apps

Some store apps declare a `required_feature` on their listing (for example `feature:seedance`). Before dispatch, the API resolves that entitlement for your workspace and requires an explicit grant: a boolean row with `enabled: true`. If no row exists, or the row has `enabled: false`, task dispatch returns `feature_not_available` (403) before the run starts.

This is stricter than capacity limits, where a missing row means no restriction. For gated store apps, access is denied unless the feature is positively enabled on your workspace.

Check `GET /entitlements/usage` for boolean feature status, or inspect `required_feature` on `GET /store/apps/{appId}`. See [Store API — Get app store listing](/docs/api/rest/store#get-app-store-listing).

See [billing and plan limits in the REST overview](/docs/api/rest/overview#billing-and-plan-limits) for modal behavior, web app links, and credit balance errors.

---

## Entitlement requests

Workspaces can ask to be **granted** invitation-only feature gates (`feature:*` resources). Capacity limits (`api_keys`, `concurrency`, …) are not requestable; upgrade your plan instead.

Each team holds at most **one open request per resource**. After a decision (`accepted`, `declined`, or `withdrawn`), the team may ask again. `feature_not_available` errors carry `requestable` and, when applicable, `request_state` so clients can show waitlist or review status.

`GET /me/entitlement-requests` or `POST /me/entitlement-requests/list` lists your team's requests (`billing:read`).

`POST /me/entitlement-requests` opens a request (`billing:write`). Requires a selected workspace.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `resource` | string | Yes | Requestable feature gate (for example `feature:marketplace_publish`, `feature:forms`) |
| `requested` | object | No | Defaults to `{"type":"boolean","enabled":true}`. Feature gates must be boolean; limit-shaped requests are rejected. |
| `form` | string | No | Form ref (`namespace/name`) or form ID to submit first |
| `data` | object | No | Answers for `form` (validated like `POST /forms/{id}/submissions`) |

When `form` is set, the API submits the form (or reuses your standing submission) and stores `submission_id` on the request for review.

`POST /me/entitlement-requests/{id}/withdraw` cancels an open request (`billing:write`).

**Common errors:** `already_entitled` when the gate is already granted; `request_open` when another open request exists for the same resource; validation errors when `resource` is not requestable.

→ [Forms API](/docs/api/rest/forms) · [Store API — Publishing invitation](/docs/api/rest/store#publishing-invitation)

---

## Related

- [Forms API](/docs/api/rest/forms) — JSON Schema forms and submissions

- [REST overview](/docs/api/rest/overview) — authentication and response format
- [Tasks API](/docs/api/rest/tasks) — run apps (may return `payment_required`)
- [Troubleshooting](/docs/extend/troubleshooting#plan-limits-and-billing-errors) — common limit and balance errors
