Skip to main content
Documentation

Staff

staff.list

List the staff on the roster, their skills and their workload
Read-onlyRead budget

REST

Shipping
POST /api/v1/staff/list

MCP tool

Live
staff.list

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

Operating contract

The whole bench: who works here, what they are called, what they can do, whether the scheduler may book them, how much live work they are already carrying, and what their login can reach.

TWO IDS COME BACK AND THEY ARE NOT INTERCHANGEABLE. id is the STAFF PROFILE — what jobs.assign, scheduling.time_off and booking.availability all take. user_id is the LOGIN, and it is what leads.assign takes. A person can have one and not the other: bench staff often have no login, and an owner often has no bench profile. Passing the wrong one is refused rather than mis-assigned, but reading the right one saves the round trip.

bookable is narrower than login_state. It says whether the scheduler may put this person on a booking; somebody not bookable can still be assigned work by hand, and somebody with no login at all can still be bookable.

active_jobs is how much unfinished work they are already holding. It is the number behind 'who is free', and it is more honest than the calendar, which shows only what is scheduled.

login_state distinguishes none (no login exists), invited (sent, not accepted), active and suspended. plan_locked is different again and worth saying out loud: their login is blocked because the shop's PLAN no longer covers the seat, which undoes itself on upgrade rather than being anybody's decision.

PAY IS NOT HERE. Rates live on staff.pay_rates, which is its own capability so that reading the roster does not casually hand over what everybody earns.

People who have left are not returned, so somebody missing from this list is no longer on the bench.

This returns the whole roster in one call 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

FieldTypeDescription
bookable_onlyboolean

True returns only people the scheduler may book onto work.

skillstringmax 60 chars

Only people carrying this skill key, as seen on another roster row.

Output

FieldTypeDescription
itemsobject[]

items[].idstring

items[].user_idstring | null

items[].namestring

items[].role_namestring | null

items[].bookableboolean

items[].login_statestring

items[].plan_lockedboolean

items[].skillsstring[]

items[].certificationsstring[]

items[].active_jobsnumber

items[].weekly_days_availablenumber

totalnumber

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/list \
  -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/list", {
  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/list",
    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.list",
    "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.