Grant, change, cancel or switch off a tier
const url = 'https://beta-api.plune.ai/v1/admin/tier-grants';const options = { method: 'POST', headers: { cookie: 'plune_session=<plune_session>', 'Content-Type': 'application/json' }, body: '{"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"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}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).
Authorizations
Section titled “Authorizations ”Request Body required
Section titled “Request Body required ”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
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.
In the future by the database’s clock. Left out, no end.
What a grant carries besides its tier. A free grant carries none.
object
Only with tier: free.
Why, at most 500 bytes — an invoice number, trial, beta, dogfood.
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"}Personal to an account by its address, no end
{ "subject": { }, "tier": "personal", "note": "dogfood"}Cancel
{ "subject": { "kind": "company", "id": "6f1c2b3a-0000-4000-8000-0000000000a5" }, "tier": "free", "note": "cancelled at the customer’s request"}Responses
Section titled “ Responses ”The row written, and what the subject is on now
object
One row of the append-only history. A cancellation is a later row with tier: free, not a change to this one.
object
object
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.
What a grant carries besides its tier. A free grant carries none.
object
The application owner who granted it; null for a payment.
The address of grantedBy; deleted account once the purge has made that account nobody; null for a payment.
object
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.
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.
What a grant carries besides its tier. A free grant carries none.
object
When the paid period ends — or ended, in grace. null without an end, and on Free.
Only in grace and disabled: when the 14 days end.
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", "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
object
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
object
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.
object
Example generated
{ "error": "example"}Headers
Section titled “Headers ”Seconds until the window rolls over