---
title: "Add visibility overrides for a group"
method: POST
path: "/v2/groups/{id}/relationships/published-course-visibility/"
tags: ["Groups"]
---

# Add visibility overrides for a group

`POST /v2/groups/{id}/relationships/published-course-visibility/`

Idempotently add visibility overrides for the group.

Per JSON:API to-many relationship POST semantics, this is **idempotent**:
if a row already exists for the
``(group, published_course, is_visible)`` tuple, it is returned as
``succeeded`` — no separate ``already_exists`` error code.

Within-batch duplicate detection is first-wins on the
``(published_course_id, is_visible)`` tuple: the first occurrence is
processed; subsequent occurrences are pre-marked ``duplicate_in_batch``
with the JSON:API source pointer at ``attributes/published_course_id``.

Returns 404 if the group is not in the caller's organization. The
group lookup runs **before** the envelope guard (empty / over-cap) so
a missing group is always reported as 404 regardless of body content
— clients can't infer group existence from the 400-vs-404 boundary.

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

## Path parameters

- `id` string, required

## Request body

- VisibilityOverrideAddRequestEnvelope — JSON:API envelope for POST visibility-overrides. The ``data`` field is always an array — single-add is a list of one. Cap enforcement (<= DEFAULT_BATCH_MAX_ITEMS items) lives in the endpoint, NOT here — matches the create/update/delete contract (400 via ValidationError instead of Pydantic 422).
  - `data` VisibilityOverrideAddData[], required — List of visibility-override items to add.
    - `type` 'visibility-overrides', required — Must be "visibility-overrides".
    - `attributes` VisibilityOverrideAddRequest, required — Request attributes for an item in POST visibility-overrides. ``is_visible`` defaults to ``True`` (allowlist case — make the course visible to the group despite default-hidden). Setting ``False`` adds a blocklist entry (hide the course from this group despite default-visible). The underlying model supports both via its ``(student_group, published_course, is_visible)`` unique constraint, so ``(group, course, True)`` and ``(group, course, False)`` can coexist — but in practice clients should pick one.
      - `published_course_id` string, required — Obfuscated id of an existing PublishedCourse in the caller's organization.
      - `is_visible` boolean — True for allowlist (show despite default-hidden); False for blocklist.

## Response `200`

OK

- BatchResultEnvelopeVisibilityOverrideResource
  - `data` union[], required — Per-item results in request order.
    - union
      - BatchSucceededItemVisibilityOverrideResource
        - `status` 'succeeded'
        - `id` string, nullable — Resource ID of the created or updated resource.
        - `result` VisibilityOverrideResource, required — JSON:API resource object for a visibility override.
          - `type` 'visibility-overrides' — Always "visibility-overrides".
          - `id` string, required — Opaque visibility-override id.
          - `attributes` VisibilityOverrideAttributes, required — Attributes of a visibility-override resource object.
            - `published_course_id` string, required — Obfuscated id of the PublishedCourse this override applies to.
            - `is_visible` boolean, required — True if the course is force-visible to the group; False if force-hidden.
            - `created_at` string, date-time, required — Timestamp when the override was created.
            - `updated_at` string, date-time, required — Timestamp when the override was last updated.
      - 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
- `404` — Not Found
- `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)
