Skip to content

Plans API

Base: /org/plans

Address plans by key.

Every plan object includes archived_at: null, or the UTC time the plan was archived (2026-03-01T00:00:00.000Z). Archiving does not change existing subscriptions, move subscribers, or send a webhook. Grants on an archived plan can still be edited.

Create plan

POST /org/plans
{
  "key": "pro",
  "name": "Pro",
  "kind": "catalog",
  "grants": [
    { "feature": "sso", "kind": "flag" },
    { "feature": "csv_export", "kind": "flag" },
    { "feature": "seats", "kind": "limit", "limit": 10 },
    { "feature": "storage_gb", "kind": "limit", "limit": null },
    { "feature": "support_tier", "kind": "config", "value": "priority" }
  ]
}
Field Required Rules
name yes 1–255
kind yes catalog | custom
key yes unique per org; max 64; lowercase letters, numbers, underscores, dashes; immutable
grants no array of grants; default []

Grant object

Feature kind Body
flag { "feature": "<key>", "kind": "flag" }
limit { "feature": "<key>", "kind": "limit", "limit": <int≥0 \| null> }
config { "feature": "<key>", "kind": "config", "value": <value> }

limit: null = unlimited. A config value must match the feature's value_type (an enum's must be one of its options) and can't be null; to leave a config unset on a plan, leave the grant out. See config value types. Grant kind must match the catalog feature’s kind. Duplicate feature keys in one request → 422.

201 — plan object (archived_at is null)

409 — key already exists

422 — unknown feature key, kind mismatch, config value of the wrong type, invalid grant, or an unknown field on the plan or on a grant. Properties that are not in the tables above are rejected.

List plans

GET /org/plans

Query (optional):

Param Description
kind catalog or custom
include_archived true or 1 includes archived plans. false, 0, or omitted leaves them out. Any other value → 422, one detail at include_archived: include_archived must be true, false, 1, or 0.

Unknown query parameters on this route are 422, one detail each. ?archived=true → Unknown query parameter archived; did you mean include_archived?

200 { "plans": [ … ] } — archived plans left out unless include_archived is true or 1

Get plan

GET /org/plans/{key}

200 — plan, including when it is archived

{
  "key": "pro",
  "name": "Pro",
  "kind": "catalog",
  "grants": [
    { "feature": "csv_export", "kind": "flag" },
    { "feature": "seats", "kind": "limit", "limit": 10 },
    { "feature": "sso", "kind": "flag" }
  ],
  "created_at": "2026-01-15T12:00:00.000Z",
  "updated_at": "2026-01-15T12:00:00.000Z",
  "archived_at": null
}

Update plan

PATCH /org/plans/{key}
{
  "name": "Pro (2026)",
  "grants": [
    { "feature": "sso", "kind": "flag" },
    { "feature": "seats", "kind": "limit", "limit": 25 }
  ]
}
Field Notes
name optional
grants optional; replaces full grant list when present
key / kind immutable after create

Unknown fields on the plan or on a grant are 422. archived_at is not a body field: Unknown field archived_at; use POST /org/plans/{key}/archive or /unarchive.

200 — plan

Warning

Changing grants immediately affects entitlement resolution for all active subscribers on this plan. That includes an archived plan: its grants still drive existing subscribers.

Archive plan

POST /org/plans/{key}/archive

No body. Sets archived_at if it was null. Idempotent: archiving a plan that is already archived returns it unchanged (archived_at and updated_at stay as they were).

200 — plan

{
  "key": "pro",
  "name": "Pro",
  "kind": "catalog",
  "grants": [],
  "created_at": "2026-01-15T12:00:00.000Z",
  "updated_at": "2026-04-01T00:00:00.000Z",
  "archived_at": "2026-04-01T00:00:00.000Z"
}

404 — plan not found

Existing subscriptions stay on this plan. No webhook.

Unarchive plan

POST /org/plans/{key}/unarchive

No body. Sets archived_at to null. Idempotent: unarchiving a plan that is not archived returns it unchanged.

200 — plan (archived_at is null)

404 — plan not found

No webhook.

Delete plan

DELETE /org/plans/{key}

204 — deleted

409 — any subscription (including canceled) still references this plan. Message: This plan is still in subscription history; archive it instead (POST /org/plans/{key}/archive).