---
title: "Edit Operator"
method: PUT
path: "/operator/{operator_id}"
tags: ["operator"]
---

# Edit Operator

`PUT /operator/{operator_id}`

## Request

- Base URL: `https://api.verdun.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.verdun.ai/operator/{operator_id}`
- Auth: HTTP bearer

## Path parameters

- `operator_id` string, required

## Request body

- EditOperatorRequest
  - `first_name` string, required: The first name of the operator
  - `last_name` string, required: The last name of the operator
  - `email` string, required: The email address of the operator
  - `role` 'owner' | 'operator' | 'site_manager' | 'read_only' | 'account_manager' | 'monitoring_manager' | 'location_manager' | 'support', required
  - `permissions` object, nullable: Per-permission overrides. Omit (null) to leave unchanged.
  - `parent_ids` string[], nullable: Full set of parent (client) IDs the operator may access. Omit (null) to leave unchanged. A site_manager must be given exactly one. For role=location_manager the client is DERIVED from the effective site and rewritten on every edit; send it omitted, empty, or exactly the derived parent — a different one is a 409.
  - `site_ids` string[], nullable: Full set of per-site grants (narrowing within granted parents). Omit (null) to leave unchanged; send [] to clear all site narrowing. Every site must belong to a granted parent. For role=location_manager the EFFECTIVE set must be exactly one site — so converting a multi-site parent manager into the role must narrow in the same request, else 409; converting OUT keeps the rows.
  - `account_id` string, uuid, nullable: The one account an account_manager is bound to. Omit (null) to leave the existing grant unchanged, matching parent_ids/site_ids. REQUIRED (stored or sent) for role=account_manager and rejected for every other role; converting away from account_manager clears the grant automatically. Any parent_ids/site_ids sent alongside role=account_manager are ignored — that access is derived from the account.

## Response `200`

Successful Response

- EditOperatorResponse
  - `id` string, uuid, required: UUID of operator
  - `first_name` string, required: The first name of the operator
  - `last_name` string, required: The last name of the operator
  - `email` string, required: The email address of the operator
  - `role` 'owner' | 'operator' | 'site_manager' | 'read_only' | 'account_manager' | 'monitoring_manager' | 'location_manager' | 'support', required
  - `last_seen_at` string, date-time, nullable: The time the operator was last seen
  - `last_logged_in` string, date-time, nullable: The time the operator last logged in
  - `created_at` string, date-time, required: The time the operator was created
  - `permissions` object, nullable: Per-permission overrides ({OperatorPermission value: bool}); null means role defaults apply
  - `is_primary_owner` boolean: The account anchor: cannot be removed or demoted, and only they may edit their own record. Not settable via the API.
  - `parents` OperatorParent[], required: The parents of the operator
    - `id` string, uuid, required: Unique client id
    - `company_name` string, required: Name of the client
    - `type` 'Monitoring Dealer' | 'End User' | 'Internal', required
    - `primary_contact_name` string, required: Name of the primary contact
    - `primary_contact_phone_number` string, required: Phone number of the primary contact
    - `primary_contact_email` string, required: Email of the primary contact
    - `notes` string, nullable: Notes about the client
  - `sites` OperatorSite[]: Per-site access grants (site-manager / site-scoped operators). Empty means access follows `parents` (all sites). Exactly one entry for a location_manager, whose `parents` entry is derived from it.
    - `id` string, uuid, required: Unique site id
    - `name` string, required: Name of the site
    - `parent_id` string, uuid, required: The client (parent) this site belongs to
  - `accounts` OperatorAccount[]: Account access grant (account managers). At most one entry. When present, `parents` and `sites` are empty: both are DERIVED from the account, and the operator sees whichever sites currently carry this account_id — including sites added later. Empty for every other role.
    - `id` string, uuid, required: Unique account id
    - `name` string, required: Name of the account
    - `parent_id` string, uuid, required: The client (parent) this account belongs to
  - `coverage_groups` OperatorCoverageGroup[]: Standing coverage-group membership. The operator's site access is the UNION of these groups' grants and the `parents`/`sites` rows above, which are an ADDITIVE exception list ('also sees...'). SCOPE ONLY, NEVER PERMISSION: a member gains sites, never a capability. Empty for site_manager / account_manager / location_manager (structurally scoped, refused membership) and for owner / monitoring_manager (already unconfined, so a membership would be inert).
    - `id` string, uuid, required: Unique coverage group id
    - `name` string, required: Display name of the group
    - `color` string, required: Display color used in schedule grids and chips
    - `site_count` integer, nullable: Distinct live sites the group currently covers (parent grants expanded). Null on /auth/me, which omits the aggregate: present on the admin operator payloads.
  - `all_sites` boolean: EFFECTIVE all-sites access (operator / read_only only; false for every other role). When true the user sees every client and site, including future ones, and their coverage groups and parent/site rows are retained but inert. Set via PUT /operator/{id}/site-access.
  - `deactivated_at` string, date-time, nullable: When this user was deactivated (POST /operator/{id}/deactivate), or null while active. A deactivated user cannot log in and holds no seats or coverage groups; their records are kept.

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/verdun/apis/verdun-api.md) - [All operations](https://skmtc.dev/verdun/apis/verdun-api/llms.txt) - [OpenAPI document](https://skmtc.dev/verdun/apis/verdun-api/revisions/a19366be50a3?raw)
