Users
The account a credential resolves to.
Overview
A user is a naturali account: an email address, a display name, and the
projects it is a member of. It is the identity behind both
credential types — a session issued by Auth and an
API key — so GET /v1/users/me answers "who am I" the same way
whichever one you present.
The email address is the credential, not a profile field: sign-in codes go to it, and it is therefore not editable through this module.
See the OpenAPI spec for the full endpoint and schema reference, or browse it rendered under API Reference → Users.
Data Model
User
| Field | Type | Description |
|---|---|---|
id | string | Public user ID (user_ prefix). |
email | string | The address sign-in codes are sent to. Read-only here. |
name | string, nullable | Display name. |
email_verified | boolean | Whether the address has completed a sign-in code exchange. |
roles | string[] | Platform roles, from a fixed catalog. Empty for almost every account; admin marks a naturali operator. |
created_at | string (date-time) |
UserBilling
Returned by
GET /v1/users/me/billing. Not part
of the User shape — see Plan and credit.
| Field | Type | Description |
|---|---|---|
plan | string | The rung the account is on: free, pro, business or enterprise. |
retention_days | integer, nullable | The longest content-retention window a project of this account may set. null when nothing bounds it. |
credit_balance_usd | number | Model credit left, in USD. Negative when the account has overspent. |
earned_balance_usd | number | Marketplace earnings, in USD, less withdrawals. |
paid_balance_usd | number | What may pay a marketplace fee: purchased credit plus earnings, less the usage included and granted credit did not cover and the fees paid. |
spend_reconciled_at | string (date-time), nullable | When spend was last read from the meter. null when it never has been. |
storage_gb | number | Indexed storage the account is holding, in the metered gigabytes the ceiling is enforced in. |
storage_limit_gb | number, nullable | The pool the plan allows. null where a contract sets it, or where the plan sets none. |
storage_sampled_at | string (date-time), nullable | When the oldest sample behind storage_gb was taken. null when a project has never been sampled. |
TopUp
Returned by
POST /v1/users/me/top-ups. See
Buying credit.
| Field | Type | Description |
|---|---|---|
id | string | The payment's or the checkout's ID at the payment provider. |
amount_usd | number | What is credited once paid, charged in US dollars or its equivalent in reais. |
status | string | paid — the saved card was charged and the credit added. checkout — nothing is charged until the person pays at checkout_url. |
checkout_url | string, nullable | The hosted page that takes the card. null when paid. |
expires_at | string (date-time), nullable | When an unpaid checkout lapses. null when paid. |
PaymentMethod
Returned by
GET /v1/users/me/payment-method.
See Auto-recharge.
| Field | Type | Description |
|---|---|---|
brand | string | Card brand, such as visa. |
last4 | string | Last four digits. |
exp_month | integer | Expiry month. |
exp_year | integer | Expiry year. |
currency | string | brl for a card issued in Brazil, else usd. See Charge currency. |
exchange_rate | number, nullable | Units of currency per US dollar for a charge made now. null while the rate can't be read. |
AutoRecharge
Returned by
GET /v1/users/me/auto-recharge.
| Field | Type | Description |
|---|---|---|
enabled | boolean | Whether the balance is topped up automatically. |
amount_usd | number, nullable | What each recharge buys. null while off. |
below_usd | number, nullable | The balance under which a recharge is charged. null while off. |
last_attempted_at | string (date-time), nullable | When a recharge was last charged, whatever its outcome. |
UserUsage
Returned by
GET /v1/users/me/usage. See
Runs and the allowance.
| Field | Type | Description |
|---|---|---|
cycle_from | string (date-time) | Start of the cycle counted — the first instant of the current UTC calendar month. |
runs | integer, nullable | Runs this cycle across every project the account pays for. null when a project's meter could not be read. |
runs_included | integer, nullable | Runs the plan includes per cycle. null on a contract plan. |
projects | ProjectRuns[] | Per-project breakdown: project_id and its own nullable runs. |
Key Concepts
One identity, two credentials
GET /v1/users/me resolves a session
access token and a nat_sk_… API key to the same account — a project-scoped
key included, which resolves to the user who minted it. That makes it the
canonical "is this credential live?" probe: a revoked key answers 401, and a
valid one answers with the account it belongs to.
Create a provider checks a
fresh credential with it.
Plan and credit
GET /v1/users/me/billing answers
the figures the platform enforces against:
-
plangates features and resource counts. A request outside the rung is refused with403. It is the plan in effect: an upgrade applies at once, a downgrade when the month ends, and until thennext_plannames the plan coming. At the turn, active channels past the new allowance are disabled. -
retention_daysis the longest window a project may keep trace and generation content for. APATCHasking for a wider one is refused with403 plan_limit_reachednaming this same number, so it is worth reading before you set a window rather than after — see Content retention. On a custom plan it is the contract's figure, andnullmeans nothing bounds it. -
credit_balance_usdis model credit for naturali-managed models. While it is negative, a managed generation is refused with402 insufficient_credit. A zero balance still generates — the generation that crosses zero is absorbed and settled against the next top-up. Generations on your own provider credentials are billed by your vendor and never touch this balance. A paid plan's included credit is for its share of the month: an upgrade adds the difference for the days left at once, and it expires when the month ends. -
paid_balance_usdis what pays for priced marketplace listings: purchased credit plus marketplace earnings, less the model usage your included and granted credit did not cover and the fees already paid. Included and granted credit never count, so a call to a priced listing is refused with402 insufficient_creditwhile it is zero or less.earned_balance_usdis what your own listings have earned. -
storage_gbandstorage_limit_gbare how much indexed storage the account holds and how much its plan allows. An ingest past the limit is refused with403 plan_limit_reachedandresource: "storage", whosedetailsname both — so this is where to read them before the refusal.The figure is the account's, not a project's: the pool is shared across every project the account pays for, so one project may hold all of it. Per project, read the
gb_daycomponent of project usage. And it is measured in gigabytes the meter counts — the raw file plus every chunk's text and its embedding vector — so it is not the size of what you uploaded; see Documents.
Storage is only as current as storage_sampled_at, on the same terms: each
project's footprint is measured every few minutes rather than on your request,
and the timestamp is the oldest of the samples behind the total, because a sum
is only as current as its stalest part. null means a project the account pays
for has never been sampled — the figure still reports what the known samples add
up to, it just is not current as of anything. A brand-new project reads that way
until the next measurement.
The balance is only as current as spend_reconciled_at: spend is read from
the meter on a schedule rather than per generation, so a very recent generation
may not be in the figure yet. Read the two fields together; null means spend
has never been read for at least one of the account's projects.
All three are the account's own. The plan that gates a project is that project's billing owner's, so a member of someone else's project reads their own rung here, not that project's.
It is a separate call rather than fields on GET /v1/users/me because the two
answer different questions: identity is fixed for the life of a credential,
while a balance moves while a session is open.
Buying credit
POST /v1/users/me/top-ups buys
amount_usd of credit, from $5 up, in whole cents. The full amount is added to
credit_balance_usd — the card fee is not deducted.
- With
saved_card: truethe saved card is charged at once:statusispaidand the credit is already in the balance. If the bank asks to confirm the payment, or declines it, nothing is charged and the answer is a checkout instead. - Otherwise
statusischeckout: send the person paying tocheckout_url. After paying, the browser returns to Account → Billing in the console. Creating a checkout charges nothing; an unpaid one lapses atexpires_at. The card paid with there becomes the account's saved card, replacing any other.
Purchased credit never expires and is not refundable.
Charge currency
Credit and prices are in US dollars. A card issued in Brazil is charged the
equivalent in reais, at the Banco Central's latest closing PTAX selling rate
with no spread; every other card pays US dollars. A saved card's currency
decides it for every charge on that card: top-ups, auto-recharge, upgrades and
statements. The payment page shows reais to a person paying from Brazil.
The balance receives the USD amount either way. A statement keeps the rate of
the moment its charge was prepared. While the rate can't be read, a charge in
reais answers 503 exchange_rate_unavailable and nothing is charged.
Auto-recharge
Auto-recharge tops up the balance on a saved card before managed models stop.
POST /v1/users/me/payment-methodopens a hosted page that saves a card and charges nothing. Saving another card replaces it.PUT /v1/users/me/auto-rechargewithamount_usd(from $5) andbelow_usd. It is refused with409 payment_method_requireduntil a card is saved.
The balance is checked every 15 minutes, and the card is charged at most once
an hour. A declined charge turns auto-recharge off and emails the account.
DELETE /v1/users/me/payment-method
forgets the card and turns auto-recharge off with it.
Runs and the allowance
A run is one agent generation or one tool call — the unit every plan is
priced in. Every tool call counts, including the ones an agent makes inside a
generation: a reply that calls three tools is four runs. A client tool, which
your own code runs, counts nothing.
GET /v1/users/me/usage counts them
for the current billing cycle across every project the account is the billing
owner of, and reports the number its plan includes.
On Free the allowance is enforced. An account that has used it is refused
403 plan_limit_reached with resource: "runs" on everything that starts a
generation and on a direct tool call, on
its own provider credential as much as on a managed model, until
the month turns or it upgrades — see
A plan's run allowance can stop generation.
On Pro and Business nothing is refused: runs past the allowance are billed at
the overage rate. Enterprise is not counted.
The other thing that stops a managed-model generation is a credit balance below
zero — that is credit_balance_usd, a different quantity,
and it never applies to your own credential.
This route counts live; the refusal reads a figure counted every few minutes. So the two can differ by a burst: this is the more current number, and the refusal quotes the one it actually enforced.
runs is null when at least one project's meter could not be read. A total
missing a project would understate it, and an understated count read against an
allowance claims headroom that may not exist, so the answer is "unknown" rather
than "fewer" — the projects breakdown names which one could not be counted.
The read asks the meter once per project, so it is heavier than the balance
read. Poll it on the order of minutes. For one project's own usage in more
detail — by model, agent, provider or day — use
GET /v1/projects/{project_id}/usage.
Editing the account
PATCH /v1/users/me takes name,
usage_alerts, or both. At least one is required, so a request that
misspelled a field is refused with a 400 instead of answered with a silent
200 that changed nothing. Send name: null to clear the name.
Usage alerts
With usage_alerts on — the default for a new account — the account is emailed
when any of these reaches 75%, 90% and 100%:
| Resource | Measured as |
|---|---|
| Model credit | Spent this month ÷ (spent this month + balance left). A top-up lowers it; a balance at or below zero is 100%. |
| Runs | Runs this month ÷ the runs the plan includes. |
| Indexed storage | The account's storage ÷ the plan's storage. |
Each level is sent once. Falling back below a level — a top-up, the month turning, deleted storage — re-arms it. Figures are read every 15 minutes, so an alert can arrive up to that long after the crossing. A plan with no published figure for a resource is not alerted on it. The same setting is the checkbox under Account → Billing in the console.
Restoring what billing stopped
When the credit balance goes below zero, or a Free plan's runs for the month run out, the platform stops what would keep spending on its own: schedule triggers are disabled, eval runs cancelled, orchestration runs and workflow tasks paused. Nothing is turned back on for you.
GET /v1/users/me/stops lists each
stopped item that is still waiting on you, newest first. For each one:
POST /v1/users/me/stops/{stop_id}/restorere-enables the trigger or resumes the run or task. It is refused with409 stop_ceiling_closedwhileblocked_byis set — top up fordebt, upgrade or wait for the month to turn forrun_allowance. A cancelled eval run (restorable: false) is refused with409 stop_not_restorable: start a new run from its eval instead.DELETE /v1/users/me/stops/{stop_id}stops listing it without turning it back on.
An item you already turned back on yourself is marked restored when you restore it. The same list is under Account → Billing in the console.
Changing the plan
PUT /v1/users/me/plan takes free,
pro or business.
GET /v1/users/me/plan/quote
says what it would charge now, changing nothing.
- An upgrade costs the price difference over the rest of the month, in US
dollars before any conversion to reais. The
saved card is charged at once, the plan applies, and
charged_usdsays how much. With no saved card, or when the bank asks to confirm the payment or declines it, nothing is charged andcheckout_urlis a page that charges the upgrade and saves the card: the plan applies once it is paid, and an unpaid page lapses atexpires_at. The month's statement nets the payment out as aprepaidline. - A downgrade charges nothing and applies when the month ends;
next_plannames it until then.
A contract plan answers 409 plan_by_contract: ask us to change it.
Monthly statements
GET /v1/users/me/statements
lists what the account owes for each closed billing month, newest first. A
statement is written within an hour of the month closing and never changes. Each
has one line per plan held that month, plus a run overage line when runs went
past the allowance:
| Line | Amount |
|---|---|
subscription | The plan's monthly price × the share of the month it was held (quantity). A sign-up or a plan change mid-month is billed by the time on each plan. |
run_overage | Runs past the allowance of the highest plan held that month, at that plan's rate per 1,000 runs. |
prepaid | What was already charged at an upgrade that month, as a negative amount. |
total_usd is charged to the saved card within an hour of the statement, and
payment_status follows it: unpaid until the charge starts (or while no card
is saved), processing, then paid or failed. The provider retries a
declined charge; when the last retry fails, the plan moves to Free at the end
of the month and the account is emailed. A statement is not a tax invoice. A month spent only
on Free has no statement; contract plans and storage overage are not stated.
GET /v1/users/me/statements/{statement_id}
reads one. The same list is under Account → Billing in the console.
Roles
roles are roles on naturali itself, not on a project — project access is
membership. The catalog has
one entry today:
| Role | Grants | Granted by |
|---|---|---|
admin | Operator access to the platform. | admin |
roles is read-only here: it appears on GET /v1/users/me, and changing it —
like everything else done to an account other than your own — needs the admin
platform role and an operator route, which this API does not document.
What a user is not
A naturali user is a tenant of naturali. Access to a project comes from project membership, and who pays for a project is that project's billing owner — a separate column, so a project can be shared without the payer becoming ambiguous.
Examples
Read the current account
- CLI
- SDK
- curl
naturali get-current-user
const { data: me } = await naturali.users.getCurrentUser();
curl https://api.naturali.ai/v1/users/me \
-H "Authorization: Bearer $NATURALI_TOKEN"
Set a display name
- CLI
- SDK
- curl
naturali update-current-user --name "Ada Lovelace"
const { data: me } = await naturali.users.updateCurrentUser({
body: { name: 'Ada Lovelace' },
});
curl -X PATCH https://api.naturali.ai/v1/users/me \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "Ada Lovelace" }'
Send { "name": null } to clear it again.
Turn usage alerts off
- CLI
- SDK
- curl
naturali update-current-user --usage-alerts false
const { data: me } = await naturali.users.updateCurrentUser({
body: { usage_alerts: false },
});
curl -X PATCH https://api.naturali.ai/v1/users/me \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "usage_alerts": false }'
Read the plan and credit balance
- CLI
- SDK
- curl
naturali get-current-user-billing
const { data: billing } = await naturali.users.getCurrentUserBilling();
if (billing.credit_balance_usd < 0) {
throw new Error('Managed models are paused until the balance is topped up.');
}
curl https://api.naturali.ai/v1/users/me/billing \
-H "Authorization: Bearer $NATURALI_TOKEN"
Upgrade to Pro
- CLI
- SDK
- curl
naturali get-current-user-plan-quote --plan pro
naturali set-current-user-plan --plan pro
const { data: quote } = await naturali.users.getCurrentUserPlanQuote({
query: { plan: 'pro' },
});
console.log(`Upgrading charges $${quote.charge_usd} now`);
const { data: change } = await naturali.users.setCurrentUserPlan({
body: { plan: 'pro' },
});
if (change.checkout_url) {
console.log(`Pay at ${change.checkout_url}`);
} else {
console.log(`On ${change.plan}, charged $${change.charged_usd}`);
}
curl "https://api.naturali.ai/v1/users/me/plan/quote?plan=pro" \
-H "Authorization: Bearer $NATURALI_TOKEN"
curl -X PUT https://api.naturali.ai/v1/users/me/plan \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "plan": "pro" }'
Top up credit
- CLI
- SDK
- curl
naturali create-current-user-top-up --amount-usd 50 --saved-card true
const { data: topUp } = await naturali.users.createCurrentUserTopUp({
body: { amount_usd: 50, saved_card: true },
});
if (topUp.status === 'checkout') {
console.log(`Finish paying at ${topUp.checkout_url}`);
}
curl -X POST https://api.naturali.ai/v1/users/me/top-ups \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "amount_usd": 50, "saved_card": true }'
Turn on auto-recharge
- CLI
- SDK
- curl
naturali create-current-user-payment-method
naturali set-current-user-auto-recharge --amount-usd 20 --below-usd 5
const { data: page } = await naturali.users.createCurrentUserPaymentMethod();
console.log(`Save a card at ${page.checkout_url}`);
// Once the card is saved:
await naturali.users.setCurrentUserAutoRecharge({
body: { amount_usd: 20, below_usd: 5 },
});
curl -X POST https://api.naturali.ai/v1/users/me/payment-method \
-H "Authorization: Bearer $NATURALI_TOKEN"
curl -X PUT https://api.naturali.ai/v1/users/me/auto-recharge \
-H "Authorization: Bearer $NATURALI_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "amount_usd": 20, "below_usd": 5 }'
Read this cycle's runs
- CLI
- SDK
- curl
naturali get-current-user-usage
const { data: usage } = await naturali.users.getCurrentUserUsage();
// `runs` is null when a project's meter could not be read — not zero.
if (usage.runs !== null && usage.runs_included !== null) {
console.log(`${usage.runs} of ${usage.runs_included} runs this cycle`);
}
curl https://api.naturali.ai/v1/users/me/usage \
-H "Authorization: Bearer $NATURALI_TOKEN"
Restore what billing stopped
- CLI
- SDK
- curl
naturali list-current-user-stops
naturali restore-current-user-stop --stop-id sst_V1StGXR8Z5jdHi6B
const { data: stops } = await naturali.users.listCurrentUserStops();
if (stops.blocked_by === null) {
for (const stop of stops.data.filter((s) => s.restorable)) {
await naturali.users.restoreCurrentUserStop({
path: { stop_id: stop.id },
});
}
}
curl https://api.naturali.ai/v1/users/me/stops \
-H "Authorization: Bearer $NATURALI_TOKEN"
curl -X POST https://api.naturali.ai/v1/users/me/stops/sst_V1StGXR8Z5jdHi6B/restore \
-H "Authorization: Bearer $NATURALI_TOKEN"
List monthly statements
- CLI
- SDK
- curl
naturali list-current-user-statements
const { data: statements } = await naturali.users.listCurrentUserStatements();
for (const statement of statements.data) {
console.log(statement.cycle, statement.total_usd);
}
curl https://api.naturali.ai/v1/users/me/statements \
-H "Authorization: Bearer $NATURALI_TOKEN"