Project mode
Read a project's mode with GET /v1/projects/{projectId}/mode and set it with PUT on the same path. Any project-scoped credential can read; writing needs an administrator role.
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" }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" }Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
projectId | string, in the path | Yes | Must be the project the credential is scoped to; another project's id is refused (403, project outside token scope). |
mode | string, in the PUT body | Yes, on PUT | auto or simple stores auto (Simple); manual or advanced stores manual (Advanced). Lower case only. Any other value is not rejected: it is stored as auto and answered with 200. |
Errors
| Status | Code | Raised when |
|---|---|---|
| 403 | forbidden | A PUT comes from a caller without an administrator role (changing project mode requires an admin role). The scope check comes first. |
| 400 | invalid_request | The PUT body is not JSON. |
More details
GET /v1/projects/{projectId}/mode
PUT /v1/projects/{projectId}/mode
For when and how to use it, read Project mode.
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 |
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": "…" }Any other unrecognised value is not an error. It is normalised to auto and answered with 200, as the Advanced row of the ledger below shows:
{ "project_id": "proj_abc", "mode": "auto", "ui_label": "Simple" }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
| 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.
Every tool call works the same way: How every tool call works.