---
title: "Update a project"
method: PATCH
path: "/v1/projects/{project_id}"
tags: ["Projects"]
---

# Update a project

`PATCH /v1/projects/{project_id}`

## Request

- Base URL: `https://api.naturali.ai`
- URL: `https://api.naturali.ai/v1/projects/{project_id}`
- 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`)

Rename or archive a project, and/or change its content-retention settings (`trace_content_retention_days`, `trace_content_mode`), its execution ceilings (`max_concurrent_runs`, `max_chain_generations`, `max_orchestration_run_depth`), its priced-model gate (`require_priced_model`), its project-scope guardrails (`guardrail_ids`) and managed conversion (`managed_conversion`). Archiving is reversible; resources are retained.
Requires the `admin` role in the project (an `owner` has it too). These are the terms every member works under, which is why setting them sits above the role that works under them.
The ceilings and the priced-model gate are uncapped by plan: each one only ever narrows what the project may spend, so setting one takes on a restriction rather than claiming an entitlement. On the three ceilings `null` clears the project's own bound and omission leaves it alone — they are different instructions.
`guardrail_ids` is the floor under every tool call by every agent in the project, including tools added later. The list is replaced wholesale, so send the ids you want to keep; `[]` detaches every one. An id naming no guardrail in the project responds `400` with `guardrail_not_found`, whose `details.missing` lists the ids. It is uncapped by plan, like the ceilings: a guardrail can only tighten what runs.
The two retention controls answer different questions. The window bounds how long content *stays* — a daily sweep purges anything past it, leaving auditable skeletons behind. `trace_content_mode: none` means content is never *written*, which is the stronger guarantee: it cannot be missed by a sweep or survive in a backup.

Your plan sets the longest window you may keep content for. A wider one — `null` included, which keeps content indefinitely — responds `403` with `plan_limit_reached`, whose `details` carry the `plan` and the `limit` in days. Anything shorter is always allowed. Moving to a plan with a shorter window takes effect at the end of the billing cycle, and content already stored is then purged by age like everything else.

## Request body

- ProjectUpdate: At least one field must be present.
  - `name` string
  - `status` 'active' | 'archived'
  - `trace_content_retention_days` integer, nullable: Days of trace and generation content retention before the daily sweep purges it. Send `null` to disable retention (content is then kept until purged on demand). Omitting the field leaves the current window unchanged — `null` and absent are different instructions. Bounded by your plan: a window longer than the plan allows, and `null` on any plan that sets a window, respond `403` `plan_limit_reached`.
  - `trace_content_mode` 'full' | 'none': Set `none` for zero-retention: content is never written for any agent in this project. Tightening to `none` does not erase content already on disk — purge it explicitly, or set a retention window to have the sweep do it.
  - `max_concurrent_runs` integer, nullable: Orchestration runs driven at once. Send `null` to lift the limit. Omitting the field leaves it unchanged.
  - `max_chain_generations` integer, nullable: Generations one continuation chain may hold. Send `null` to drop back to the platform-wide ceiling. Omitting the field leaves it unchanged.
  - `max_orchestration_run_depth` integer, nullable: Nesting levels a run tree may reach. Send `null` to drop back to the platform-wide bound. Omitting the field leaves it unchanged.
  - `require_priced_model` boolean: Refuse a generation whose model carries no price, before the provider is called. Uncapped by plan — it only ever narrows what the project may spend.
  - `guardrail_ids` string[]: Guardrails attached at the project scope, replaced wholesale. Send `[]` to detach every one; omitting the field leaves them unchanged. Uncapped by plan — a guardrail can only tighten what runs.
  - `managed_conversion` boolean: Turn managed conversion off or back on. Off removes the managed ingestion rules; rules of your own stay.

## Response `200`

Project updated.

- Project
  - `id` string, required: Public project ID (proj_ prefix).
  - `name` string, required
  - `status` 'active' | 'archived', required
  - `role` 'owner' | 'admin' | 'member', required: What the member may do in this project. `owner` — everything, including deleting it and paying for it; exactly one per project. `admin` — everything a `member` may do, plus managing who else is in the project and how the project itself is configured. `member` — every read and write on the project's own resources, including minting project API keys. When returned on a `Project`, this is *your* role, not a property of the project: it is what lets a client hide an action the request would refuse.
  - `owner_user_id` string, required: The user who pays for the project — its billing owner, and the one member holding the `owner` role. Membership decides who may act in a project; this decides who is charged for it.
  - `transfer_to_user_id` string, nullable, required: The member a pending transfer would make the owner, until they accept or decline it; `null` when none is pending.
  - `trace_content_retention_days` integer, nullable, required: How long trace and generation content is kept before a daily sweep purges it. `null` (the default) disables retention, so content is kept until purged on demand. A swept record is content-purged the same way an on-demand purge does it — the row survives as an auditable skeleton with `content_redacted_at` set.
  - `trace_content_mode` 'full' | 'none', required: Whether trace and generation content is persisted at all. `full` (the default) stores it; `none` is zero-retention — content is never written, for every agent in this project. `none` is a floor, not a default: an agent may tighten itself to `none`, but cannot loosen a `none` project back to `full`.
  - `max_concurrent_runs` integer, nullable, required: Orchestration runs of this project driven at once. `null` (the default) is unlimited. Enforced when a run is claimed — runs past the limit wait for a slot rather than failing.
  - `max_chain_generations` integer, nullable, required: Generations one continuation chain in this project may hold before it stops being resumed. `null` (the default) leaves the platform-wide ceiling in force. The budget actually applied is the smallest of that ceiling, this number, and the agent's own — an agent author can be stricter than this, never looser.
  - `max_orchestration_run_depth` integer, nullable, required: Nesting levels a run tree in this project may reach before the next child is refused. `null` (the default) leaves the platform-wide bound in force; the bound applied is the smaller of the two.
  - `require_priced_model` boolean, required: Whether a generation whose model carries no price is refused before the provider is called. `false` by default, which runs the model and meters it at no cost. Mainly of use with your own providers — see [Requiring a priced model](/docs/modules/projects#requiring-a-priced-model).
  - `guardrail_ids` string[], required: Guardrails attached at the project scope — the floor under every tool call by every agent in the project, including tools added later. Empty by default. See [Attaching one](/docs/modules/guardrails#attaching-one).
  - `paused_at` string, date-time, nullable, required: When the project was paused; `null` while it runs. See [Pausing a project](/docs/modules/projects#pausing-a-project).
  - `pause_reason` string, nullable, required: The reason the pause named; `null` while the project runs, or when the pause named none.
  - `managed_conversion` boolean, required: Whether scanned PDFs, images and audio are converted to text on ingest with no setup. `true` by default. See [Managed conversion](/docs/modules/documents#managed-conversion).
  - `created_at` string, date-time, required
  - `updated_at` string, date-time, required

## Other responses

- `400`: A malformed field, or `guardrail_not_found` for a `guardrail_ids` entry naming no guardrail in the project.
- `401`: Missing or invalid credentials.
- `403`: The credential is scoped to a different project, or the caller's role in the project does not carry this action.
- `404`: The resource does not exist (existence is not leaked).

---

[API](https://skmtc.dev/naturali/apis/naturali-ai-api.md) · [All operations](https://skmtc.dev/naturali/apis/naturali-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/naturali/apis/naturali-ai-api/revisions/e4905b78f422?raw)
