Skip to main content
Documentation

Staff

staff.roles

List the roles this shop grants its staff and what each opens
Read-onlyRead budget

REST

Shipping
POST /api/v1/staff/roles

MCP tool

Live
staff.roles

Exposed on: REST API · Shop MCP server · Claude connector · Dash (in-app copilot) · Zapier. Part of the Staff domain.

Operating contract

The shop's roles, what each one unlocks, and how many people hold it.

permissions is the list of section keys the role grants. A key ending .access is FULL — open the section and act in it. A key ending .view is READ-ONLY: the same pages, and every action refused. That one-character difference is the entire read-only mechanism in this product, so read the suffix rather than the prefix.

is_system marks a built-in role (owner, admin, tech) that the shop did not compose and cannot delete.

member_count counts everybody holding the role in any login state — active, invited or suspended — because a pending invite still occupies the role.

READ ONLY, AND EMPHATICALLY SO. There is no capability that creates a role, edits one, or moves somebody onto a different one. @/lib/auth/sections records that widening the act-question predicate is 'the one edit that silently turns every read-only login in the product into a full one'; a machine that could compose roles could grant itself everything one hop away. The live matrix at /team/roles/matrix re-derives its own claims from the catalog, the nav and each route's real guard, and is the place to check what a role actually reaches.

This returns every role the org has and does not page, so nothing is truncated.

Who may call it

Permission
shop.manageThe caller must hold shop.manage at the ACT level. A read-only dashboard grant on the same section is refused.
Plan
Every planNo plan gate. Available on every Service VIN plan.
Retries
naturalNaturally idempotent — running it twice leaves the same world as running it once. A retrying integration needs no key.
Rate class
readCounted against the read budget — the widest of the four.

Input

This capability takes no arguments.

Output

FieldTypeDescription
itemsobject[]

items[].idstring

items[].keystring

items[].namestring

items[].is_systemboolean

items[].member_countnumber

items[].permissionsobject[]

items[].permissions[].keystring

items[].permissions[].namestring

items[].permissions[].categorystring

Examples

Built from this capability's own schema — required fields and the ones carrying a default, and nothing invented. Paste one and it validates.

curl
export SERVICEVIN_API_KEY=svk_live_…

curl -X POST https://www.servicevin.com/api/v1/staff/roles \
  -H "Authorization: Bearer $SERVICEVIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

TypeScript (fetch)
const res = await fetch("https://www.servicevin.com/api/v1/staff/roles", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SERVICEVIN_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({}),
});

// Success and failure are both envelopes. Switch on error.code, never
// on error.message — the codes are stable, the messages are for people.
const payload = await res.json();
if (!res.ok) throw new Error(payload.error.code);
const data = payload.data;

Python (requests)
import os, requests

res = requests.post(
    "https://www.servicevin.com/api/v1/staff/roles",
    headers={"Authorization": f"Bearer {os.environ['SERVICEVIN_API_KEY']}"},
    json={},
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(payload["error"]["code"])
data = payload["data"]

MCP tools/call — https://www.servicevin.com/api/mcp
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "staff.roles",
    "arguments": {}
  }
}

Refusals

The four gates run in this order on every surface, and the order is not arbitrary — see Authentication.

StatusCodeWhen
404not_foundThe id is unknown, or the feature is not enabled for this account. Deliberately the same answer for both.
403forbiddenThis login does not hold shop.manage.
422validation_errorAn argument was wrong. The message names the field.
429rate_limitedToo many read calls. Back off and retry.
500internal_errorSomething failed on our side. Nothing was changed.