---
title: "Update students"
method: PATCH
path: "/v2/students/"
tags: ["Students"]
---

# Update students

`PATCH /v2/students/`

Update up to DEFAULT_BATCH_MAX_ITEMS students in a single request.

Replaces single-resource PATCH /v2/students/{id}/ from ACCEL-256 — never publicly released.

Each item identifies a student via ``id`` (preferred) or
``attributes.email`` (fallback). The ``email`` field is read-only —
it serves as the lookup identifier when ``id`` is absent, or as
confirmation when ``id`` is present. It cannot be changed via this
endpoint. Updatable attributes: ``first_name``, ``last_name``,
``is_inactive``.

Each item is processed independently — failures on one row do not abort
the rest. Response is 200 if all items succeed, 207 if any fail, 400 if
the request envelope is empty or over the cap.

Per-item failure ``code`` is one of:
- ``not_found`` — student missing in this org
- ``validation_error`` — anonymized/SSO/inactive guard rejected the update,
  or email confirmation did not match the student's current email

To reactivate AND modify other fields, send two PATCHes:

1. First PATCH: ``{"data": [{"id": "...", "attributes": {"is_inactive": false}}]}``
2. Second PATCH: ``{"data": [{"id": "...", "attributes": {"first_name": "..."}}]}``

See ``api_v2/docs/batch-responses.md`` for the envelope shape.

**Required OAuth scope:** `students:write`

## Request body

- StudentUpdateRequestEnvelope — JSON:API envelope for PATCH /v2/students/ batch update. The ``data`` field is always an array — a single-row update is a list of one. Cap enforcement (<=DEFAULT_BATCH_MAX_ITEMS items) lives in the endpoint, NOT here — that gives us HTTP 400 via ``ValidationError`` instead of Pydantic's 422.
  - `data` StudentUpdateData[], required — List of student items to update.
    - `type` 'students', required — Must be "students".
    - `id` string, nullable — Obfuscated student resource id. Required unless attributes.email identifies the row.
    - `attributes` StudentUpdateRequest, required — Request attributes for PATCH /v2/students/. Updatable fields are ``first_name``, ``last_name``, and ``is_inactive``. All are optional individually, but at least one must be provided per item. The ``email`` field is read-only — see field description.
      - `email` string, email, nullable — Student's email address. Read-only via this endpoint — serves as identifier when ``id`` is absent, or confirmation when ``id`` is present. Use a separate workflow to change email.
      - `first_name` string, nullable — Student's given name. Maximum 50 characters.
      - `last_name` string, nullable — Student's family name. Maximum 50 characters.
      - `is_inactive` boolean, nullable — Profile inactive flag. True deactivates; False reactivates. Does NOT touch enrollments.

## Response `200`

OK

- BatchResultEnvelopeStudentResource
  - `data` union[], required — Per-item results in request order.
    - union
      - BatchSucceededItemStudentResource
        - `status` 'succeeded'
        - `id` string, nullable — Resource ID of the created or updated resource.
        - `result` StudentResource, required — JSON:API resource object for a student.
          - `type` string — Always "students".
          - `id` string, required — Opaque student ID. Use in URL paths.
          - `attributes` StudentAttributes, required — Attributes of a student resource object.
            - `email` string, required — Student's email address.
            - `first_name` string, required — Student's given name.
            - `last_name` string, required — Student's family name.
            - `is_inactive` boolean, required — True if the student has been deactivated.
            - `external_id` string, required — Stable UUID5 for cross-system correlation.
            - `date_joined` string, date-time, required — Timestamp when the student joined.
          - `relationships` object, nullable — Related resource links.
          - `links` object, nullable — Self and related links.
      - BatchFailedItem — Wrapper for a failed item in a 207 batch response.
        - `status` 'failed'
        - `id` string, nullable — Echoed resource ID if the input identified a row.
        - `error` BatchItemError, required — Per-item error inside a 207 batch response. Aligned with JSON:API ``ErrorObject`` field naming (``code`` rather than ``reason``) so consumers can reuse error-handling logic across document-level errors (``ErrorObject`` in ``ErrorEnvelope``) and per-item errors (here). Differences from ``ErrorObject``: ``status``/``title`` are omitted because they're redundant for the 207-batch context (HTTP status is on the envelope, and the title is derivable from ``code``).
          - `code` 'duplicate_email' | 'duplicate_in_batch' | 'duplicate_name' | 'validation_error' | 'not_found' | 'internal_error' | 'not_in_domain' | 'already_enrolled' | 'already_published', required — Machine-readable error code.
          - `source` object, nullable — Pointer to the offending input slot, e.g. {"pointer": "/data/0/attributes/email"}.
          - `detail` string, nullable — Human-readable explanation of the error.
  - `summary` BatchSummary, required — Aggregate counts for a 207 batch response. Invariant: ``succeeded + failed == total``. Enforced by ``@model_validator``.
    - `total` integer, required — Total number of items submitted.
    - `succeeded` integer, required — Number of items that succeeded.
    - `failed` integer, required — Number of items that failed.

## Other responses

- `207` — Multi-Status
- `400` — Bad Request
- `401` — Unauthorized
- `403` — Forbidden
- `422` — Unprocessable Entity

---

[API](https://skmtc.net/skilljar/apis/skilljar-api-v2.md) · [All operations](https://skmtc.net/skilljar/apis/skilljar-api-v2/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/skilljar/skilljar-api-v2/revisions/f8025de20169/schema)
