---
title: "Delete groups"
method: DELETE
path: "/v2/groups/"
tags: ["Groups"]
---

# Delete groups

`DELETE /v2/groups/`

Delete up to BATCH_MAX_ITEMS groups in a single request.

Hard-deletes the rows (``StudentGroup`` is not a ``SoftDeletionModel``).
All FK-back relations cascade — memberships, visibility overrides, and
other link tables are removed by the database. Per-row ``.delete()``
fires ``pre_delete`` and ``post_delete`` naturally, preserving the
Salesforce sync receiver and the workflow / analytics cleanup hooks.

Each item is processed independently — a failure on one row does not
abort the rest. Response is 200 if all items deleted, 207 if any failed,
400 if the request envelope is empty or over the cap.

Within-batch duplicate detection is first-wins on ``id``: subsequent
occurrences are pre-marked ``duplicate_in_batch`` (pointing at
``/data/{i}/id``) without reaching the service. This matches the POST
and PATCH endpoints' first-wins semantics.

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

**Required OAuth scope:** `student-groups:write`

## Request body

- GroupDeleteRequestEnvelope — JSON:API envelope for DELETE /v2/groups/ batch delete. The ``data`` field is always an array — single-delete is a list of one. Cap enforcement (<= DEFAULT_BATCH_MAX_ITEMS items) lives in the endpoint, NOT here — matches the create/update contract (400 via ValidationError instead of Pydantic 422).
  - `data` GroupDeleteData[], required — List of group items to delete.
    - `type` 'groups', required — Must be "groups".
    - `id` string, required — Obfuscated group resource id.

## Response `200`

OK

- BatchDeleteResultEnvelope — 207 Multi-Status response envelope for batch-delete operations. Distinct from ``BatchResultEnvelope`` so the generic ``T`` on the latter stays bound to a real resource type and consumers reading ``BatchSucceededItem.result`` keep their non-null contract. Items appear in the same order as the request inputs. ``summary.succeeded`` counts ``BatchDeletedItem`` rows — the field name reflects "operation did not fail" rather than the verb-specific outcome (which is on every item's own ``status`` discriminator).
  - `data` union[], required — Per-item results in request order.
    - union
      - BatchDeletedItem — Wrapper for a successfully deleted item in a 207 batch-delete response. The ``status`` discriminator is ``"deleted"`` (not ``"succeeded"``) so the outcome is explicit on every row without inspecting the rest of the object. There is no ``result`` field: the row has been removed and has no current resource representation to echo. ``id`` semantics depend on the endpoint: - **Resource-keyed deletes** (e.g. ``DELETE /v2/groups/``): the deleted resource's own obfuscated id, echoed from the request. - **Relationship sub-resource deletes** (e.g. ``DELETE /v2/groups/{id}/relationships/published-course-visibility/``): the related-resource id (here, ``published_course_id``), echoed from the request. The deleted ``PublishedCourseGroupVisibility`` row's own obfuscated id is not surfaced because clients identify these rows by the relationship tuple, not by their own id. See ``api_v2/docs/groups.md`` §"Visibility overrides (relationship sub-resource)" for the rationale on the relationship variant.
        - `status` 'deleted'
        - `id` string, required — Obfuscated id echoed from the request. For resource-keyed deletes this is the deleted resource's own id; for relationship sub-resource deletes this echoes the related-resource id (e.g. ``published_course_id``).
      - 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)
