> Source: https://wexa.ai/docs/api/project-mode

# Project mode

```
GET /v1/projects/{projectId}/mode
PUT /v1/projects/{projectId}/mode
```

A project's mode decides how much the platform does on its own. It is not a display preference: it
changes which calls need a human decision before they take effect, so setting it wrongly changes the
controls, quietly.

Two routes read and write it, and this page covers the failure it exists to warn you about.

## Two states, four names

There are exactly **two** states. Each has two accepted spellings, and the two spellings for a state
are synonyms rather than variants:

| Send | Stored as | Shown in the console as |
|---|---|---|
| `auto` | `auto` | **Simple** |
| `simple` | `auto` | **Simple** |
| `manual` | `manual` | **Advanced** |
| `advanced` | `manual` | **Advanced** |

`auto` — *Simple* — lets the platform organise the project's ontology itself. An ontology commit
goes straight through.

`manual` — *Advanced* — puts you in charge of it. The same commit becomes a proposal, and an
ontology commit by a non-admin raises an approval a project admin has to decide before it takes
effect. [Governance](/docs/api/governance) shows that sequence end to end.

## `GET /v1/projects/{projectId}/mode`

```bash
curl -sS -H "Authorization: Bearer $FABRIC_API_KEY" \
  https://fabric.wexa.ai/v1/projects/proj_abc/mode
```

```json
{ "project_id": "proj_abc", "mode": "auto", "ui_label": "Simple" }
```

Any project-scoped credential can read it — no admin role required. The `{projectId}` in the path
must be the one the credential is scoped to; another project's id is refused rather than answered:

```json
{ "error": "forbidden", "error_description": "project outside token scope" }
```

The gateway reads the mode through to the platform, the source of truth, with a short cache.
If that is briefly unreachable it falls back to the last value this process saw, or the
configured default — so a tool call never fails just because the mode could not be re-read.

## `PUT /v1/projects/{projectId}/mode`

```bash
curl -sS -X PUT https://fabric.wexa.ai/v1/projects/proj_abc/mode \
  -H "Authorization: Bearer $FABRIC_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode":"manual"}'
```

```json
{ "project_id": "proj_abc", "mode": "manual", "ui_label": "Advanced" }
```

The response is the same shape as the read, reflecting the new state, and a subsequent `GET`
returned the same thing — the write is applied immediately, not eventually.

Writing requires an **admin role** on the project. A project-scoped member is refused:

```json
{ "error": "forbidden", "error_description": "changing project mode requires an admin role" }
```

The scope check comes first, so another project's id is refused before the role is even considered.

A successful write is recorded in the audit stream as `project.mode.changed`, with the new mode in
the payload — so "who put this project into Simple mode, and when" is answerable from
`GET /v1/audit`.

## The failure that looks like a success

A malformed body — one that is not JSON at all — is a real `400`:

```json
{ "error": "invalid_request", "error_description": "…" }
```

That is the only input error this route actually reports.

## A safe write

Because a wrong value is indistinguishable from a right one by status code alone, the write is worth
wrapping in a check:

```bash
want=manual
got=$(curl -sS -X PUT "$FABRIC_URL/v1/projects/$PROJECT/mode" \
        -H "Authorization: Bearer $FABRIC_ADMIN_TOKEN" \
        -H "Content-Type: application/json" \
        -d "{\"mode\":\"$want\"}" | jq -r .mode)

[ "$got" = "$want" ] || { echo "mode is '$got', asked for '$want'" >&2; exit 1; }
```

Note that this compares against the stored spelling. Asking for `advanced` stores `manual`, so
compare against `manual` — or compare `ui_label` against `Advanced`, which is stable across both
spellings.

## Per-route ledger

| Case | Result |
|---|---|
| `GET` on the token's own project | `200`, `auto` / Simple |
| `GET` on another project | `403 project outside token scope` |
| `PUT` `{"mode":"manual"}` | `200`, `manual` / Advanced; `GET` confirmed |
| `PUT` `{"mode":"advanced"}` | `200`, stored as `manual` / Advanced |
| `PUT` `{"mode":"simple"}` | `200`, stored as `auto` / Simple |
| `PUT` `{"mode":"Advanced"}` | `200`, silently stored as `auto` / Simple |
| `PUT` on another project | `403 project outside token scope` |

Where the platform cannot persist the mode, the gateway holds it in its own store instead, so a restart returns the project to Simple.
