Skip to content

Grant, change, cancel or switch off a tier

POST
/v1/admin/tier-grants
curl --request POST \
--url https://beta-api.plune.ai/v1/admin/tier-grants \
--header 'Content-Type: application/json' \
--cookie plune_session=<plune_session> \
--data '{ "subject": { "kind": "company", "id": "6f1c2b3a-0000-4000-8000-0000000000a5" }, "tier": "team", "validUntil": "2099-12-31T23:59:59.000Z", "options": { "saml": true }, "note": "annual invoice 2026-114" }'

Writes one row to the append-only history and answers it with the standing it leads to. The newest grant that covers now wins; the old rows stay. Cancelling is tier: free — the subject then keeps the tier’s limits for 14 days (grace); free with disabled: true switches it off at once (Free limits now, nothing deleted for 14 days). The account or company must exist (a company in the Bin does not), and validUntil must be in the future by the database’s clock. Owner-only and session-only, as GET /v1/admin/tiers; everyone else, a bearer token included, gets 404, before the body is read. Ten calls a minute. Not written to the audit log: its entries belong to a project, a grant does not. In the same transaction the grant closes as done the open requests for a plan (#2121) of its subject whose tier is not above the one granted (a free grant closes none); its line in the admin journal names them (closedRequests).

Media type application/json

Personal goes to an account, Team and Enterprise to a company, Free to either. free is also how a tier is cancelled: it takes no validUntil and no options, and with disabled: true it is the forced switch-off.

object
subject
required
One of:
object
kind
required
string
Allowed values: user company
id
required
string
tier
required

Free and Personal are an account’s, Team and Enterprise a company’s; Free may also be a company’s, as the row that cancels its Team.

string
Allowed values: free personal team enterprise
validUntil

In the future by the database’s clock. Left out, no end.

string format: date-time
options

What a grant carries besides its tier. A free grant carries none.

object
saml
boolean
extra_storage_packs
integer
<= 1000
extra_full_months
integer
<= 120
disabled

Only with tier: free.

boolean
note
required

Why, at most 500 bytes — an invoice number, trial, beta, dogfood.

string
>= 1 characters
Examples

Team to a company until a date, with SAML

{
"subject": {
"kind": "company",
"id": "6f1c2b3a-0000-4000-8000-0000000000a5"
},
"tier": "team",
"validUntil": "2099-12-31T23:59:59.000Z",
"options": {
"saml": true
},
"note": "annual invoice 2026-114"
}

The row written, and what the subject is on now

Media type application/json
object
grant
required

One row of the append-only history. A cancellation is a later row with tier: free, not a change to this one.

object
id
required
string format: uuid
subject
required
object
kind
required
string
Allowed values: user company
id
required
string
tier
required

Free and Personal are an account’s, Team and Enterprise a company’s; Free may also be a company’s, as the row that cancels its Team.

string
Allowed values: free personal team enterprise
options
required

What a grant carries besides its tier. A free grant carries none.

object
saml
boolean
extra_storage_packs
integer
<= 1000
extra_full_months
integer
<= 120
validFrom
required
string format: date-time
validUntil
required
string format: date-time
nullable
source
required
string
Allowed values: manual payment
grantedBy
required

The application owner who granted it; null for a payment.

string
nullable
grantedByEmail
required

The address of grantedBy; deleted account once the purge has made that account nobody; null for a payment.

string
nullable
note
required
string
disabled
required
boolean
createdAt
required
string format: date-time
standing
required
object
tier
required

Free and Personal are an account’s, Team and Enterprise a company’s; Free may also be a company’s, as the row that cancels its Team.

string
Allowed values: free personal team enterprise
state
required

Worked out from the grants at the database’s clock, never stored. active: a paid grant covers now. grace: it ended or was cancelled less than 14 days ago, and the limits are still the tier’s. expired: the 14 days are over — Free. disabled: the operator switched it off — Free at once, and nothing is deleted for 14 days. No grant ever is plain Free, active.

string
Allowed values: active grace expired disabled
options
required

What a grant carries besides its tier. A free grant carries none.

object
saml
boolean
extra_storage_packs
integer
<= 1000
extra_full_months
integer
<= 120
validUntil
required

When the paid period ends — or ended, in grace. null without an end, and on Free.

string format: date-time
nullable
graceEndsAt
required

Only in grace and disabled: when the 14 days end.

string format: date-time
nullable
Example
{
"grant": {
"id": "6f1c2b3a-0000-4000-8000-0000000000a6",
"subject": {
"kind": "company",
"id": "6f1c2b3a-0000-4000-8000-0000000000a5"
},
"tier": "team",
"options": {
"saml": true
},
"validFrom": "2026-09-16T09:12:04.151Z",
"validUntil": "2099-12-31T23:59:59.000Z",
"source": "manual",
"grantedBy": "u-shop",
"grantedByEmail": "[email protected]",
"note": "annual invoice 2026-114",
"disabled": false,
"createdAt": "2026-09-16T09:12:04.151Z"
},
"standing": {
"tier": "team",
"state": "active",
"options": {
"saml": true
},
"validUntil": "2099-12-31T23:59:59.000Z",
"graceEndsAt": null
}
}

Not JSON, or a field that does not hold — a tier that does not fit the subject, an end in the past, an option that does not exist, a note over 500 bytes

Media type application/json
object
error
required
string
Example
{
"error": "validation failed — tier: team belongs to a company, not an account"
}

No / invalid session

The caller does not own the application — or there is no such account or company

Media type application/json
object
error
required
string
Example generated
{
"error": "example"
}

Rate-limited: the per-route throttle, or the per-minute request limit of the plan an API token counts by (#1442; the body names the plan, #2092). Retry-After carries the seconds until the window rolls over.

Media type application/json
object
error
required
string
Example generated
{
"error": "example"
}
Retry-After
integer

Seconds until the window rolls over