Admin

Grant a user a plan

Puts the account on a rung of the infra ladder. Needs the admin role. PUT because an account is on exactly one rung at a time, but the change is recorded rather than overwritten: the plan history is append-only and the newest line is the account's plan. Setting the rung it already holds still writes a line — a re-confirmation is an act worth a record, and the reason given with it would otherwise have nowhere to go. Read the history back with GET on this path. An upgrade takes effect at once. A downgrade is the same call with a lower rung and takes effect when the month turns: until then plan stays the rung held and next_plan names the new one. At the turn, active channels past the new rung's allowance are disabled, oldest first; an upgrade re-enables them. A rung whose content-retention window a contract sets — enterprise — requires retention_days, since nothing else can answer what that window is and an unanswered one bounds nothing. Every other rung sets its own and refuses the field.

put/v1/admin/users/{user_id}/plan

Request

  • Base URL: https://api.naturali.ai
  • URL: https://api.naturali.ai/v1/admin/users/{user_id}/plan
  • Auth: one of:
    • HTTP bearer
    • OAuth 2 (authorization code, authorization URL https://api.naturali.ai/authorize, token URL https://api.naturali.ai/token, scopes: mcp:access)

Path parameters

user_idstring required

Request body

plan'free' | 'pro' | 'business' | 'enterprise' required

The rung to put the account on.

retention_daysinteger nullable

The content-retention window the contract negotiated, in days. Required on enterprise, whose window the pricing table leaves to a contract: without one nothing bounds what the account's projects store, so it is refused rather than defaulted. Send null for a contract that keeps content indefinitely — that is a decision the field records, not the absence of one. Refused on every other rung, whose window the plan itself sets.

reasonstring nullable

Why the account was moved. Recorded on the line and never edited, so it is the only account of intent the history carries.

Example request

{
  "plan": "business",
  "retention_days": 365,
  "reason": "annual contract"
}

Response

The account, on its new plan.

idstring required

Public user ID (user_ prefix).

emailstring email required
namestring nullable
email_verifiedboolean required
rolesstring[] required

Roles on naturali itself, not on a project. Empty for almost every account; admin marks a naturali operator. Changed only through PUT /v1/admin/users/{user_id}/roles, by a caller holding the role that grants each one.

usage_alertsboolean required

Whether the account is emailed when its model credit, runs or indexed storage reaches 75%, 90% and 100%. On for a new account.

created_atstring date-time required
plan'free' | 'pro' | 'business' | 'enterprise' required

The rung in effect: the highest the account held this month. free for an account with no subscription. It gates the feature lines and the resource counts of the pricing table.

next_plan'free' | 'pro' | 'business' | 'enterprise' required

The rung the account moves to when the month turns; differs from plan only while a downgrade is pending.

retention_daysinteger nullable required

The longest content-retention window this account's projects may keep content for, in days. On a plan whose window a contract sets, this is the figure recorded with the grant; null means nothing bounds it. Resolved the same way the refusal is — the widest window held during the current billing cycle.

credit_balance_usdnumber required

Model credit left, in US dollars — the ledger's sum, less the part of a past cycle's allowance that lapsed. 0 for an account that has never been credited, and negative for one that has overspent, which refuses its managed generations.

earned_balance_usdnumber required

Marketplace earnings: the publisher's share of fees its consumers funded, less withdrawals. Never lapses.

paid_balance_usdnumber required

What may fund a marketplace fee: purchased credit plus earnings, less the usage included and granted credit did not cover and the fees paid. Included and granted credit never fund a fee, so a call to a priced installed tool or agent is refused with 402 insufficient_credit while this is not positive.

spend_reconciled_atstring date-time nullable required

When this account's spend was last read off the meter, which is how current credit_balance_usd is. null when it has never been read for at least one of the account's projects, or the account has none. The same figure the account reads about itself at GET /v1/users/me/billing.

Example response

{
  "id": "user_V1StGXR8Z5jdHi6B",
  "email": "ana@acme.com",
  "name": "Ana Silva",
  "email_verified": true,
  "roles": [],
  "usage_alerts": true,
  "created_at": "2026-07-17T00:00:00.000Z",
  "plan": "business",
  "next_plan": "pro",
  "retention_days": 90,
  "credit_balance_usd": 25,
  "spend_reconciled_at": "2026-07-17T00:15:00.000Z",
  "storage": {
    "used": 12.4137,
    "limit": 30,
    "sampled_at": "2026-07-17T00:15:00.000Z"
  }
}

Changes

No recorded changes to this operation across all 1 revision of this API.