Skip to main content

Projects

The isolation and billing boundary every resource belongs to.

Overview

An account holds multiple projects — one per client, or per environment. Every resource in every other module belongs to exactly one project. API keys (API Keys) are project-scoped by default, so a key from one project can never read another project's resources. A project also exposes its own usage meter, the re-billing view for tokens and cost.

See the OpenAPI spec for the full endpoint and schema reference, or browse it rendered under API Reference → Projects.

Data Model

Project

FieldTypeDescription
idstringPublic project ID (proj_ prefix).
namestringHuman-readable label.
statusstringLifecycle status.
trace_content_retention_daysinteger, nullableDays of trace and generation content retention before a daily sweep purges it. null (the default) disables retention — see Content retention.
trace_content_modestringfull (the default) stores trace and generation content; none is zero-retention — see Content retention.
created_atstring (date-time)
updated_atstring (date-time)

Key Concepts

Project usage

GET /v1/projects/{project_id}/usage returns token/cost usage grouped across dimensions, backing the re-billing view a customer embeds for their own end users.

Token counts describe LLM calls. They are not the only thing metered, so every bucket — and the top-level total — also carries components, the amounts actually measured:

{
"key": "storage",
"cost_usd": null,
"input_tokens": 0,
"total_tokens": 0,
"components": [
{ "component": "gb_day", "unit": "gb_day", "quantity": 0.4, "cost_usd": null }
]
}

Read components whenever the bucket is not LLM usage: a storage, api_request or compute_execution bucket has no tokens to report, so its token fields are legitimately zero while quantity is what it measured. A cost_usd of null means the component was not priced — the quantity was still measured, and null never means free.

meter_type narrows the rollup to one meter. That is what to reach for with group_by=model, whose dimension otherwise mixes model ids with platform SKUs (gb_day sits next to a model name, because for platform meters the SKU is the model field). The applied filter comes back on the response, null when unfiltered.

naturali get-project-usage \
--project-id proj_V1StGXR8Z5jdHi6B \
--group-by model \
--meter-type llm_tokens

Content retention

Two project-level controls govern what happens to the content of traces and generations — the prompts, tool arguments, tool results and error payloads a run records. Both are set with PATCH /v1/projects/{project_id}.

They answer different questions, and the difference matters when you have to describe your data handling to someone else:

FieldGuaranteeContent on disk?
trace_content_retention_daysContent is purged once it is older than the windowYes, until the sweep runs
trace_content_mode: noneContent is never writtenNo, ever

trace_content_retention_days bounds how long content stays. A daily sweep content-purges everything past the window using the same path as an on-demand purge, so a swept record survives as an auditable skeleton with content_redacted_at set — see Purging trace content. null (the default) disables the sweep, and content is kept until purged explicitly.

trace_content_mode: none is zero-retention: content is never written in the first place, for every agent in the project. That is a stronger claim than deletion — content that was never stored cannot be missed by a sweep or survive in a backup — and it is what you want when the content itself must not land on disk at all.

The project mode is a floor, not a default

An agent may tighten to none under a storing project, but cannot loosen a none project back to full; that is refused with 400 invalid_trace_content_mode. Otherwise a project-wide zero-retention mandate could be escaped just by creating a new agent.

Switching a project to none stops future writes; it does not erase content already recorded. To clear the backlog, purge it explicitly or set a retention window and let the sweep do it.

Note that these settings govern what naturali stores. What the model provider does with the content it receives is governed by your agreement with that provider — for a BYOK provider, directly with them.

Deletion

Deleting a project with dependent resources returns 409 conflict; pass force=true to delete everything underneath it — destructive and irreversible.

Examples

naturali create-project --name acme-corp

Putting a project into zero-retention, with a 90-day window for anything already recorded:

naturali update-project \
--project-id proj_V1StGXR8Z5jdHi6B \
--trace-content-mode none \
--trace-content-retention-days 90

Send trace_content_retention_days: null to turn the sweep off again. Omitting the field leaves the current window untouched — null and absent are different instructions.