Skip to content
MeridFlow AiFlow v8.x • self-hosted

Staff roster

The people an agent will treat as internal. When someone tells an agent "I work here", the verify_staff_member tool checks the code they give against this roster before the agent acts on that claim.

A name, an email, or a phone number is not proof, since anyone who knows a colleague knows those too. The code is the part only the real person has.

The tool does nothing until the roster has someone in it

verify_staff_member can be enabled on any agent, but with an empty roster it answers "not verified" every time. Add at least one person here first.

Codes are stored only as a hash. The plaintext is shown once, when it is created or reissued, and cannot be read back afterwards. Losing one means issuing a replacement.

Every endpoint here requires an admin session token belonging to an Owner or an Admin. See Authentication.

List the roster

GET /api/v1/staff-members

Returns everyone on file, ordered by name. Codes are never included.

curl https://api.your-domain.com/api/v1/staff-members \
  -H "Authorization: Bearer $ADMIN_TOKEN"
import httpx

response = httpx.get(
    "https://api.your-domain.com/api/v1/staff-members",
    headers={"Authorization": f"Bearer {admin_token}"},
)
response.raise_for_status()
const response = await fetch("https://api.your-domain.com/api/v1/staff-members", {
  headers: { Authorization: `Bearer ${adminToken}` },
});
[
  {
    "id": 1,
    "name": "Jane Doe",
    "role": "IT",
    "email": "jane@example.com",
    "phone": "+15551234567",
    "created_at": "2026-08-31T09:00:00Z"
  }
]

Add someone

POST /api/v1/staff-members

Field Type Required Notes
name string Yes What they will give the agent.
role string Yes Repeated back on a successful check, e.g. IT, Founder.
email string No Required unless phone is given.
phone string No Required unless email is given.

At least one of email or phone is required, because the agent matches on whichever the caller offers. For the same reason no two people may share either one: a collision would verify a code against the wrong record. A duplicate is refused with 409 Conflict.

curl -X POST https://api.your-domain.com/api/v1/staff-members \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{"name": "Jane Doe", "role": "IT", "email": "jane@example.com"}'
import httpx

response = httpx.post(
    "https://api.your-domain.com/api/v1/staff-members",
    headers={"Authorization": f"Bearer {admin_token}"},
    json={"name": "Jane Doe", "role": "IT", "email": "jane@example.com"},
)
response.raise_for_status()
code = response.json()["verification_code"]
const response = await fetch("https://api.your-domain.com/api/v1/staff-members", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${adminToken}`,
  },
  body: JSON.stringify({ name: "Jane Doe", role: "IT", email: "jane@example.com" }),
});
const { verification_code: code } = await response.json();

201 Created, with the code included. This is the only response that ever carries it:

{
  "id": 1,
  "name": "Jane Doe",
  "role": "IT",
  "email": "jane@example.com",
  "phone": null,
  "created_at": "2026-08-31T09:00:00Z",
  "verification_code": "414933"
}

Pass that code to the person over a channel you already trust. It is not recoverable.

Update someone's details

PATCH /api/v1/staff-members/{id}

Takes any of name, role, email, or phone. The code is untouched, so changing a job title or a phone number never invalidates it. Clearing both email and phone is refused with 422, and taking an address another member already uses is refused with 409.

Reissue a code

POST /api/v1/staff-members/{id}/code

For a code that was lost or shared too widely. Returns the same shape as create, with a new verification_code. The previous code stops working immediately.

Remove someone

DELETE /api/v1/staff-members/{id}

204 No Content. Their code stops working at once, which is the intended way to handle someone leaving.

Seeding from the command line

scripts/seed_staff.py ships in the backend image for setting up a roster without the API:

python -m scripts.seed_staff \
  --staff "Jane Doe:IT:jane@example.com:+15551234567" \
  --staff "Sam Lee:Founder::+15557654321"

Each repeatable --staff value is Name:Role:email:phone, with a field left blank between two colons when it is unknown. New codes are printed once.

The script syncs, it does not append

The roster ends up matching exactly the list you pass. Anyone omitted is removed. That is what you want when someone leaves, but it means running it with a single --staff clears everyone else. Use the endpoints above, or the dashboard, for one-off edits.

Someone already on file keeps their existing code and has their details refreshed, so re-running never invalidates a code already handed out.

In the dashboard

Staff in the sidebar does all of the above without a terminal. A new code appears once, in a panel you dismiss once you have passed it on.