Workspace settings
On this page
- Before you call
- Feature switches
- GET /api/v1/brands/{brand_id}/features
- PUT /api/v1/brands/{brand_id}/features/{key}
- Members
- PATCH /api/v1/brands/{brand_id}/members/{user_id}
- POST /api/v1/brands/{brand_id}/members/{user_id}/photo
- DELETE /api/v1/brands/{brand_id}/members/{user_id}/photo
- POST /api/v1/brands/{brand_id}/members/{user_id}/reset-password
- POST /api/v1/brands/{brand_id}/members/{user_id}/set-password
- Guests
- GET /api/v1/brands/{brand_id}/guests
- POST /api/v1/brands/{brand_id}/guests/{user_id}/make-member
- DELETE /api/v1/brands/{brand_id}/guests/{user_id}
- Your OPVS address
- GET /api/v1/me/mail/address/availability
- PUT /api/v1/me/mail/address
- Machine updates
- POST /api/v1/machines/{machine_id}/updates
- POST /api/v1/machines/updates
- Related
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.
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 |
|
Change a feature switch, members and guests | a workspace 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). 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.
GET /api/v1/brands/{brand_id}/features
Returns every switch with its current state and what the feature holds.
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 |
|---|---|---|
| boolean | Whether the workspace uses the current sidebar and Settings |
| boolean | Whether the feature is on |
| boolean |
|
| string | Why it has this value: |
| object or null |
|
| string or null | When an admin last set it |
| object | Counts of what the feature holds, such as |
Errors
Status | When | Resolution |
|---|---|---|
| no token, or the token is not valid | send |
| the token lacks | request a token with |
|
| use the token's own workspace id |
|
| drop |
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.
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 |
|---|---|---|
| no token, or the token is not valid | send a live token |
| the token lacks | change the switch in the dashboard, or have your workspace's policy widened to allow |
| you are not an admin of this workspace | ask an admin |
|
| use the token's own workspace id |
| turning on a feature with | change plan under Billing & plans first |
|
| send one of the four keys and |
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.
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 |
|---|---|---|
| another role value | send |
| the body has neither field | send |
| the token lacks | use an admin's |
| the person is not an active member | check |
| 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.
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 |
|---|---|---|
| another file type | convert the image |
| the file is over 5 MB | resize it |
| the token lacks | use an admin's |
| the person is not an active member | check |
| picture storage is unavailable | try again later |
DELETE /api/v1/brands/{brand_id}/members/{user_id}/photo
Removes a member's picture.
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 |
|---|---|---|
| the token lacks | use an admin's |
| the person is not an active member | check |
POST /api/v1/brands/{brand_id}/members/{user_id}/reset-password
Emails the member a link to choose a new password.
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 |
|---|---|---|
| the token lacks | use an admin's |
| the person is not an active member | check |
| 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.
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 |
|---|---|---|
| the person has never signed in with a password | invite them, or send a reset link |
| the token lacks | use an admin's |
| the person is not an active member | check |
| the person is in more than one workspace, or is an OPVS staff or service account | use |
|
| send a longer 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.
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 |
|---|---|---|
| no token, or the token is not valid | send a live token |
| the token lacks | use an admin's |
| 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.
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 |
|---|---|---|
| the person is already a member | nothing to do |
| an invitation is waiting | wait for them to accept; Settings → Members → Invitations lists it |
| the token lacks | use an admin's |
| 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.
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 |
|---|---|---|
| the token lacks | use an admin's |
| the person is not a guest of this workspace | list guests first |
| 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.
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 |
|---|---|---|
| no token, or the token is not valid | send a live token |
| the token's person has no OPVS address | none to change |
|
| 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.
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 |
|---|---|---|
| the token's person has no OPVS address | none to change |
| the name is taken, or is still forwarding for someone else | check availability first and pick another name |
| the name is your current one | nothing to do |
| the address is not on | it cannot be changed |
|
| 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.
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 |
|---|---|---|
| you are not an admin of this workspace | ask an admin |
| no machine with that id in your workspace | check the id under Settings → Machines |
| 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.
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 |
|---|---|---|
| no token, or the token is not valid | send a live token |
| you are not an admin of this workspace | ask an admin |