# Workspace settings

Source: https://opvs.ai/docs/guides/workspace-settings

> The calls behind Settings in the dashboard: which optional features a workspace uses, the people in it, your own OPVS address, and updates for your machines.

The calls behind **Settings** in the dashboard: which optional features a workspace uses, the people in it, your own OPVS address, and updates for your machines. The dashboard uses the same routes. For what each settings page does, see [Settings: where each setting lives](https://opvs.ai/help/find-your-way-around/find-a-setting.md).

## Before you call

Every route takes a personal access token, `Authorization: Bearer $OPVS_PAT`, and acts on the workspace the token belongs to. `{brand_id}` in a path must be that same workspace.

Group

Who may call

Token scope

Read feature switches

any active member

`brand:read`

Change a feature switch, members and guests

a workspace admin

`brand:admin`

Your OPVS address

the token's own person

any

Machine updates

a workspace admin

any

`brand:read` is in every workspace's default scope ceiling. `brand:admin` is not: a platform admin has to widen your workspace's policy first (see [Authentication](/developers/authentication)). Without it, make these changes in the dashboard.

A token that lacks a route's scope gets `403` with a `detail` that names the missing scope and says whether your workspace allows it.

## Feature switches

A workspace has four optional features: `skills`, `feed`, `machines` and `crm`. Turning one off hides it from the dashboard and deletes nothing. See [Turn a feature on or off](https://opvs.ai/help/find-your-way-around/turn-a-feature-on-or-off.md).

### `GET /api/v1/brands/{brand_id}/features`

Returns every switch with its current state and what the feature holds.

```bash
curl "https://api.opvs.ai/api/v1/brands/1/features" \
  -H "Authorization: Bearer $OPVS_PAT"

# Response 200, application/json (two of the four switches shown)
# {"new_navigation": true,
#  "switches": {
#    "skills": {"on": true, "locked_by_plan": false, "source": "data_default",
#               "changed_by": null, "changed_at": null,
#               "holds": {"installed_packages": 59}},
#    "feed":   {"on": true, "locked_by_plan": false, "source": "data_default",
#               "changed_by": null, "changed_at": null,
#               "holds": {"feed_sources": 8}}}}

# Without a token the route returns a 401 error.
```

Add `?format=yaml` or `?format=md` for a smaller answer.

**Response**

Field

Type

Meaning

`new_navigation`

boolean

Whether the workspace uses the current sidebar and Settings

`switches.<key>.on`

boolean

Whether the feature is on

`switches.<key>.locked_by_plan`

boolean

`true` when the plan does not include the feature; it cannot be turned on

`switches.<key>.source`

string

Why it has this value: `plan_default`, `data_default` (the workspace already had data for it) or `admin` (an admin set it)

`switches.<key>.changed_by`

object or null

`{user_id, name}` of the admin who last set it

`switches.<key>.changed_at`

string or null

When an admin last set it

`switches.<key>.holds`

object

Counts of what the feature holds, such as `installed_packages`, `feed_sources`, `machines` or `crm_boards`

**Errors**

Status

When

Resolution

`401`

no token, or the token is not valid

send `Authorization: Bearer $OPVS_PAT` with a live token

`403`

the token lacks `brand:read`

request a token with `brand:read`

`404` `Brand not found`

`{brand_id}` is not a workspace you belong to

use the token's own workspace id

`422`

`format` is not `yaml` or `md`

drop `format`, or use one of the two values

### `PUT /api/v1/brands/{brand_id}/features/{key}`

Turns one feature on or off for everyone in the workspace, and returns every switch as it now stands. `{key}` is `skills`, `feed`, `machines` or `crm`.

```bash
curl -X PUT "https://api.opvs.ai/api/v1/brands/1/features/feed" \
  -H "Authorization: Bearer $OPVS_PAT" \
  -H "Content-Type: application/json" \
  -d '{"on": false}'

# Response 200: the same shape as GET /features, with
#   switches.feed.on = false, source = "admin" and changed_by naming you.
# A feature your plan does not include returns a 409 error when you turn it on.
```

**Request body:** `on` (boolean, required).

**Errors**

Status

When

Resolution

`401`

no token, or the token is not valid

send a live token

`403`

the token lacks `brand:admin`

change the switch in the dashboard, or have your workspace's policy widened to allow `brand:admin`

`403` `Brand admin access required`

you are not an admin of this workspace

ask an admin

`404` `Brand not found`

`{brand_id}` is not a workspace you belong to

use the token's own workspace id

`409` `This feature is not included in the workspace plan`

turning on a feature with `locked_by_plan: true`

change plan under Billing & plans first

`422`

`{key}` is not one of the four, or `on` is missing

send one of the four keys and `{"on": true}` or `{"on": false}`

## Members

Every route in this group needs `brand:admin` and an admin of the workspace. `{user_id}` must be an active member.

### `PATCH /api/v1/brands/{brand_id}/members/{user_id}`

Changes a person's role or position in the workspace.

```bash
curl -X PATCH "https://api.opvs.ai/api/v1/brands/1/members/482" \
  -H "Authorization: Bearer $OPVS_PAT" \
  -H "Content-Type: application/json" \
  -d '{"role": "admin", "position": "Head of Sales"}'

# Response 200
# {"success": true}

# Making the only admin a member returns a 409 error.
```

**Request body:** `role` (string, optional): `admin` or `member`. `position` (string, optional): free text; send `null` to clear it. Send at least one.

**Errors**

Status

When

Resolution

`400` `Role must be 'admin' or 'member'`

another role value

send `admin` or `member`

`400` `No fields to update`

the body has neither field

send `role`, `position` or both

`403`

the token lacks `brand:admin`, or you are not an admin

use an admin's `brand:admin` token

`404` `Member not found`

the person is not an active member

check `{user_id}` against Members

`409` `The last workspace admin cannot be changed to a member`

demoting the only admin

make someone else an admin first

### `POST /api/v1/brands/{brand_id}/members/{user_id}/photo`

Uploads a picture for a member. Send the file as multipart field `file`: PNG, JPG or GIF, up to 5 MB.

```bash
curl -X POST "https://api.opvs.ai/api/v1/brands/1/members/482/photo" \
  -H "Authorization: Bearer $OPVS_PAT" \
  -F "file=@portrait.png"

# Response 200
# {"photo": "https://files.opvs.ai/…/portrait.png"}

# A file over 5 MB returns a 400 error.
```

**Errors**

Status

When

Resolution

`400` `File must be PNG, JPG or GIF`

another file type

convert the image

`400` `File too large (max 5MB)`

the file is over 5 MB

resize it

`403`

the token lacks `brand:admin`, or you are not an admin

use an admin's `brand:admin` token

`404` `Member not found`

the person is not an active member

check `{user_id}`

`503` `Image storage not configured`

picture storage is unavailable

try again later

### `DELETE /api/v1/brands/{brand_id}/members/{user_id}/photo`

Removes a member's picture.

```bash
curl -X DELETE "https://api.opvs.ai/api/v1/brands/1/members/482/photo" \
  -H "Authorization: Bearer $OPVS_PAT"

# Response 200
# {"photo": null}

# A person who is not an active member returns a 404 error.
```

**Errors**

Status

When

Resolution

`403`

the token lacks `brand:admin`, or you are not an admin

use an admin's `brand:admin` token

`404` `Member not found`

the person is not an active member

check `{user_id}`

### `POST /api/v1/brands/{brand_id}/members/{user_id}/reset-password`

Emails the member a link to choose a new password.

```bash
curl -X POST "https://api.opvs.ai/api/v1/brands/1/members/482/reset-password" \
  -H "Authorization: Bearer $OPVS_PAT"

# Response 200
# {"success": true, "message": "Password reset email sent to ada@example.com"}

# If the email cannot be sent the route returns a 502 error.
```

**Errors**

Status

When

Resolution

`403`

the token lacks `brand:admin`, or you are not an admin

use an admin's `brand:admin` token

`404` `Member not found`

the person is not an active member

check `{user_id}`

`502` `Failed to send password reset email`

the email could not be sent

try again later

### `POST /api/v1/brands/{brand_id}/members/{user_id}/set-password`

Sets a member's password directly. This works only for a person who belongs to this workspace and no other; for everyone else, send a reset link.

```bash
curl -X POST "https://api.opvs.ai/api/v1/brands/1/members/482/set-password" \
  -H "Authorization: Bearer $OPVS_PAT" \
  -H "Content-Type: application/json" \
  -d '{"password": "correct-horse-battery", "send_email": true}'

# Response 200
# {"success": true, "message": "Password updated for ada@example.com"}

# A person who belongs to another workspace too returns a 409 error.
```

**Request body:** `password` (string, required, at least 8 characters). `send_email` (boolean, optional, default `false`): also email the person that their password changed.

**Errors**

Status

When

Resolution

`400` `User has no auth account`

the person has never signed in with a password

invite them, or send a reset link

`403`

the token lacks `brand:admin`, or you are not an admin

use an admin's `brand:admin` token

`404` `Member not found`

the person is not an active member

check `{user_id}`

`409` `Send a reset link instead of setting this person's password.`

the person is in more than one workspace, or is an OPVS staff or service account

use `reset-password`

`422`

`password` is shorter than 8 characters

send a longer password

`502` `Failed to set password`

the sign-in service did not accept the change

try again later

## Guests

A guest is someone outside the workspace who can open some of its boards or docs. Every route here needs `brand:admin` and an admin of the workspace.

### `GET /api/v1/brands/{brand_id}/guests`

Lists every guest and each board or docs space they can open.

```bash
curl "https://api.opvs.ai/api/v1/brands/1/guests" \
  -H "Authorization: Bearer $OPVS_PAT"

# Response 200
# {"guests": [{"user_id": 913, "email": "sam@example.com", "full_name": "Sam Reyes",
#              "image": null,
#              "access": [{"kind": "board", "resource_id": "6f1c2a7e-3b0d-4c55-9e1a-2d8f4b7c9a10",
#                          "name": "Launch plan", "role": "editor"}]}],
#  "total": 1}

# Without a token the route returns a 401 error.
```

**Response:** `guests[].access[].kind` is `board` or `docs`; `role` is the guest's level on that board or space, or `null`.

**Errors**

Status

When

Resolution

`401`

no token, or the token is not valid

send a live token

`403`

the token lacks `brand:admin`, or you are not an admin

use an admin's `brand:admin` token

`502` `AgentBoard returned an invalid response`

the boards service answered badly

try again later

### `POST /api/v1/brands/{brand_id}/guests/{user_id}/make-member`

Invites a guest to join the workspace as a member. Their guest access stays until they accept.

```bash
curl -X POST "https://api.opvs.ai/api/v1/brands/1/guests/913/make-member" \
  -H "Authorization: Bearer $OPVS_PAT"

# Response 201
# {"id": 2207, "email": "sam@example.com", "status": "pending",
#  "expires_at": "2026-10-22T16:00:00Z", "created_at": "2026-10-15T16:00:00Z"}

# A guest who already has a pending invitation returns a 400 error.
```

**Errors**

Status

When

Resolution

`400` `User is already a member of this brand`

the person is already a member

nothing to do

`400` `An invitation is already pending for this email`

an invitation is waiting

wait for them to accept; Settings → Members → Invitations lists it

`403`

the token lacks `brand:admin`, or you are not an admin

use an admin's `brand:admin` token

`404` `Guest not found`

the person is not a guest of this workspace

list guests first

### `DELETE /api/v1/brands/{brand_id}/guests/{user_id}`

Removes a guest from every board and docs space in the workspace. Cards, comments and pages they wrote stay.

```bash
curl -X DELETE "https://api.opvs.ai/api/v1/brands/1/guests/913" \
  -H "Authorization: Bearer $OPVS_PAT"

# Response 200
# {"success": true, "boards_removed": 2, "docs_removed": 1}

# A person who is not a guest returns a 404 error.
```

**Errors**

Status

When

Resolution

`403`

the token lacks `brand:admin`, or you are not an admin

use an admin's `brand:admin` token

`404` `Guest not found`

the person is not a guest of this workspace

list guests first

`502` `AgentBoard returned an invalid response`

the boards service answered badly

try again later

## Your OPVS address

Every person gets an `@opvs.run` address when they sign up. These routes act on the token's own person.

### `GET /api/v1/me/mail/address/availability`

Checks whether a new name for your address is free. `name` is 3 to 30 lowercase letters, numbers or hyphens, starting and ending with a letter or number.

```bash
curl "https://api.opvs.ai/api/v1/me/mail/address/availability?name=ada-lovelace" \
  -H "Authorization: Bearer $OPVS_PAT"

# Response 200
# {"name": "ada-lovelace", "domain": "opvs.run", "available": true}

# A name shorter than 3 characters returns a 422 error.
```

**Errors**

Status

When

Resolution

`401`

no token, or the token is not valid

send a live token

`404` `Not found`

the token's person has no OPVS address

none to change

`422`

`name` breaks the naming rule

use 3 to 30 lowercase letters, numbers or hyphens

### `PUT /api/v1/me/mail/address`

Changes your address. Mail to the old address is forwarded to the new one for 30 days.

```bash
curl -X PUT "https://api.opvs.ai/api/v1/me/mail/address" \
  -H "Authorization: Bearer $OPVS_PAT" \
  -H "Content-Type: application/json" \
  -d '{"name": "ada-lovelace"}'

# Response 200
# {"address": "ada-lovelace@opvs.run",
#  "forwarding_from": "ada.l@opvs.run",
#  "forwards_until": "2026-11-14T16:00:00Z"}

# A name someone else holds returns a 409 error.
```

**Errors**

Status

When

Resolution

`404` `Not found`

the token's person has no OPVS address

none to change

`409` `This OPVS address is not available`

the name is taken, or is still forwarding for someone else

check availability first and pick another name

`409` `This is already your OPVS address`

the name is your current one

nothing to do

`409` `This mailbox cannot be renamed`

the address is not on `opvs.run`

it cannot be changed

`422`

`name` breaks the naming rule

use 3 to 30 lowercase letters, numbers or hyphens

## Machine updates

Ask OPVS to update the software on your machines. A request is queued, never run at once: OPVS updates each machine when no build is running on it. Both routes need an admin of the workspace.

### `POST /api/v1/machines/{machine_id}/updates`

Requests an update for one machine.

```bash
curl -X POST "https://api.opvs.ai/api/v1/machines/8d2e6b1a-4f3c-4e9a-b7d5-1c0f9a2e3b44/updates" \
  -H "Authorization: Bearer $OPVS_PAT"

# Response 202
# {"id": "c1a9e7f0-2b6d-4d38-8e51-7a4b3c2d1e90",
#  "machine_id": "8d2e6b1a-4f3c-4e9a-b7d5-1c0f9a2e3b44", "machine_name": "build-01",
#  "status": "queued", "requested_by": {"user_id": 482, "name": "Ada Lovelace"},
#  "requested_at": "2026-10-15T16:00:00Z", "wait_for_idle": true, "error": null}

# A machine that is not yours returns a 404 error.
```

**Errors**

Status

When

Resolution

`403`

you are not an admin of this workspace

ask an admin

`404` `Not found`

no machine with that id in your workspace

check the id under Settings → Machines

`409` `This machine shares its sidecar installation with another brand and remains operator-managed.`

the machine is shared with another workspace

OPVS updates it for you; no request is needed

### `POST /api/v1/machines/updates`

Requests an update for every machine in the workspace. Machines that cannot be requested come back with `status: "failed"` and an `error`; the rest are queued.

```bash
curl -X POST "https://api.opvs.ai/api/v1/machines/updates" \
  -H "Authorization: Bearer $OPVS_PAT"

# Response 202 (abridged: one machine, four of its fields)
# {"requests": [{"machine_id": "8d2e6b1a-4f3c-4e9a-b7d5-1c0f9a2e3b44", "machine_name": "build-01",
#                "status": "queued", "error": null}],
#  "queued": 1, "failed": 0, "requested_at": "2026-10-15T16:00:00Z"}

# Without a token the route returns a 401 error.
```

**Errors**

Status

When

Resolution

`401`

no token, or the token is not valid

send a live token

`403`

you are not an admin of this workspace

ask an admin

## Related

- [Settings: where each setting lives](https://opvs.ai/help/find-your-way-around/find-a-setting.md)
- [Turn a feature on or off](https://opvs.ai/help/find-your-way-around/turn-a-feature-on-or-off.md)
- [Authentication](/developers/authentication)
