On this page

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:

SendStored asShown in the console as
autoautoSimple
simpleautoSimple
manualmanualAdvanced
advancedmanualAdvanced

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 shows that sequence end to end.

GET /v1/projects/{projectId}/mode

curl -sS -H "Authorization: Bearer $FABRIC_API_KEY" \
  https://fabric.wexa.ai/v1/projects/proj_abc/mode
{ "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:

{ "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

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"}'
{ "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:

{ "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:

{ "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:

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

CaseResult
GET on the token's own project200, auto / Simple
GET on another project403 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 project403 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.