Skip to content
  • There are no suggestions because the search field is empty.

Creating, editing, and removing People (Person objects) with the API

Beyond looking people up and seeing what they have checked out, the API can create a person record, edit their details, manage their group membership, and remove them. This article covers that full lifecycle, plus the same safety features the asset endpoints use: advisory warnings, an Idempotency-Key so a retry never creates a duplicate, and a dry-run mode so you can test a call before it changes anything.

Beyond looking people up and seeing what they have checked out, the API can create a person record, edit their details, manage their group membership, and remove them. This article covers that full lifecycle, plus the same safety features the asset endpoints use: advisory warnings, an Idempotency-Key so a retry never creates a duplicate, and a dry-run mode so you can test a call before it changes anything.

Examples use curl; set KEY to your API key and PANEL to your panel domain first:

export KEY="k12_live_xxxxxxxxxxxx_yyyy..."
export PANEL="https://<your-panel-domain>"

The permission you need

Creating, editing, removing, and managing group membership for people all need a key with the people:write permission. It's separate from people:read (which only views people), so a reporting key that reads people doesn't also have to be able to change them. An administrator sets a key's permissions under Settings → API Keys.

Creating a person

POST to the people collection. Only a first name and last name are required:

curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"first_name": "Alex", "last_name": "Rivera", "email": "arivera@school.org"}' \
  "$PANEL/api/public/v1/people"

A successful create returns 201 Created with the new person, including the id the panel assigned them:

{
  "id": 5210,
  "first_name": "Alex",
  "last_name": "Rivera",
  "email": "arivera@school.org",
  "org_unit": { "id": 12, "name": "High School" },
  "groups": [],
  "warnings": []
}

You can send more in the same call — notes and the AD (Active Directory) fields — and you can place the person in a specific org unit by id or name:

curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{
        "first_name": "Alex",
        "last_name": "Rivera",
        "email": "arivera@school.org",
        "org_unit": {"name": "High School"},
        "ad_domain": "SCHOOL",
        "ad_winlogin": "arivera",
        "notes": "Fall onboarding"
      }' \
  "$PANEL/api/public/v1/people"

A few things are decided for you, on purpose:

  • A new person lands in the org root if you don't name an org_unit — and once created, they're immediately added to any dynamic groups whose rules they match, so membership is correct right away.
  • You can't set system-owned fields. The cloud/directory sync identifiers (Google, Entra/Azure, Verkada) are assigned by the sync, never accepted from a create. Sending them does no harm; they're simply ignored.

Email must be unique

Email is optional, but if you supply one it has to be unique within your organization. If someone already has that email, the create is rejected with 409 and hands you back the existing person(s) as candidates, so your script can switch to editing the record it already has instead of creating a duplicate:

{
  "error": "email_conflict",
  "message": "A person with email 'arivera@school.org' already exists (id 5210).",
  "candidates": [
    { "id": 5210, "first_name": "Alex", "last_name": "Rivera", "email": "arivera@school.org",
      "org_unit": "High School", "groups": ["Students"] }
  ]
}

This is the same candidate shape used for ambiguous lookups — read the id from candidates and continue.

Never create the same person twice: Idempotency-Key

If you POST a create, don't hear back, and retry, you'd normally risk creating the person twice. To prevent that, send an Idempotency-Key header — any unique string you generate for the request (a UUID is ideal):

curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7c1e6a4d-9f88-4d21-bb0f-5f2b8c149a3e" \
  -d '{"first_name": "Sam", "last_name": "Lee"}' \
  "$PANEL/api/public/v1/people"

If a request with that same key arrives again, the API returns the person it already created (with 200 instead of 201) rather than making a second one.

For a person with an email, the key is optional — the email's uniqueness is already a backstop. For a person without an email, an Idempotency-Key is required: with no email to dedupe on, it's the only thing that keeps a retry from duplicating. An email-less create with no key is rejected with 400.

Editing a person

PATCH the person with just the fields you want to change — first name, last name, email, notes, the AD fields, or their org unit:

curl -X PATCH -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"email": "a.rivera@school.org", "org_unit": {"name": "Middle School"}}' \
  "$PANEL/api/public/v1/people/5210"

The response is the updated person, with a warnings list (below). Changing the email follows the same uniqueness rule as create — a collision returns 409 with the conflicting person as a candidate.

Warnings: edits to synced people

If a person is synced from a directory (Google Workspace, Entra/Azure, or Verkada), editing a field that sync manages is allowed, but your change may be overwritten the next time the sync runs. Rather than blocking the edit, the API applies it and returns a non-blocking warnings entry so you know:

{
  "id": 5210,
  "first_name": "Alexis",
  "warnings": [
    { "code": "sync_managed_field", "field": "first_name",
      "message": "This person's first_name is managed by sync; your change may be overwritten on the next sync." }
  ]
}

Notes are never managed by sync, so editing a synced person's notes never warns. Warnings appear only on write responses (create and edit); plain GET and list responses don't carry them.

Managing group membership

To add a person to a group, POST to their groups collection, naming the group by id or name:

curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"group": {"name": "Robotics Club"}}' \
  "$PANEL/api/public/v1/people/5210/groups"

To remove them, DELETE the group from the person (by name or id):

curl -X DELETE -H "Authorization: Bearer $KEY" \
  "$PANEL/api/public/v1/people/5210/groups/Robotics%20Club"

Adding returns the updated person; removing returns 204 No Content. Both are idempotent — adding a group the person already has, or removing one they don't, still succeeds, so your automation can safely retry.

Two rules to know:

  • The group must already exist in your organization. Asking to add an unknown group returns an error rather than creating it — this keeps a typo from silently adding a new group to your org. (A name that matches several groups returns the candidates so you can retry with an id.)
  • Only static groups can be edited this way. A dynamic group fills itself from its rules, so adding or removing a member by hand would just be undone on the next rebuild. Targeting a dynamic group returns 422 group_is_dynamic — to change who's in it, change the attributes its rule matches on (for example, the person's org unit).

Removing a person

A DELETE permanently removes the person — there is no trashcan for people, and the removal also clears their login and activity history. Because it can't be undone, the API guards it the same way the web interface does, and refuses in two cases:

curl -X DELETE -H "Authorization: Bearer $KEY" \
  "$PANEL/api/public/v1/people/5210"
  • 409 person_sync_blocked — the person is synced from a cloud service (Google or Entra/Azure). Remove them in that service first, or the next sync just re-creates them.
  • 409 person_has_checkouts — the person still has one or more assets checked out. Check those devices in first (this keeps the check-in history intact); then the delete will succeed.

When neither guard applies, the delete succeeds and returns 204 No Content. Re-deleting a person who's already gone returns a normal 404.

Test first with dry-run

Before a call changes anything, add ?dry_run=true to see what would happen — the resulting person and any warnings, or which guard would block a delete — without saving a thing. It works on create, edit, and delete:

# Would this create warn me about anything? Does it look right?
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"first_name": "Jordan", "last_name": "Kim", "email": "jkim@school.org"}' \
  "$PANEL/api/public/v1/people?dry_run=true"

# Would deleting this person be blocked?
curl -X DELETE -H "Authorization: Bearer $KEY" \
  "$PANEL/api/public/v1/people/5210?dry_run=true"

A dry-run create echoes the would-be values with "would_create": true. Nothing is written, no id is assigned, and no idempotency key is consumed.