---
title: "Publish courses"
method: POST
path: "/v2/published-courses/"
tags: ["Published Courses"]
---

# Publish courses

`POST /v2/published-courses/`

Publish up to ``BATCH_MAX_ITEMS`` courses to domains in a single request.

Returns 200 if all items succeed, 207 if any fail, 400 if the envelope is empty
or over the cap. Publishing the same course to the same domain twice is a
per-item conflict (``already_published``), not a whole-batch failure.

**Required OAuth scope:** `published-courses:write`

## Request body

- PublishedCourseCreateRequestEnvelope — JSON:API envelope for POST /v2/published-courses (publish one or many). Cap enforcement (<= ``BATCH_MAX_ITEMS``) lives in the endpoint so it surfaces as HTTP 400, matching the v2 batch-response convention.
  - `data` PublishedCourseCreateData[], required — Published-course items to create.
    - `type` 'published-courses', required — Must be "published-courses".
    - `attributes` PublishedCoursePublishAttributes — Optional settings accepted on publish (POST). All omitted fields use model defaults.
      - `slug` string, nullable — URL path on the domain. Auto-generated if omitted. Lowercase letters/numbers/dashes, 2+ parts.
      - `is_hidden` boolean, nullable — Hide from the catalog. Default false.
      - `visible_on_catalog` boolean, nullable — Show on the domain catalog. Default false.
      - `open_access` boolean, nullable — Allow anonymous access. Default false.
      - `strict_enforce_group_visibility` boolean, nullable — Default false.
      - `visibility_override_type` 'GROUP' | 'CATEGORY', nullable — Default GROUP.
      - `access_period_starts_at` string, date-time, nullable — Registration window start. Default null.
      - `access_period_ends_at` string, date-time, nullable — Registration window end. Default null.
      - `restrict_access_start_end_dates` boolean, nullable — Default false.
      - `allow_self_service_reenroll` boolean, nullable — Default false.
      - `unique_progress_per_enrollment` boolean, nullable — Default true.
      - `require_all_prerequisites` boolean, nullable — Default true.
    - `relationships` PublishRelationships, required — Required relationships on publish: the course and the domain.
      - `course` CourseRelationship, required — To-one linkage to the course being published.
        - `data` CourseIdentifier, required — JSON:API resource identifier for a course.
          - `type` 'courses', required — Must be "courses".
          - `id` string, required — Opaque course id.
      - `domain` DomainRelationship, required — To-one linkage to the domain the course is published to.
        - `data` DomainIdentifier, required — JSON:API resource identifier for a domain.
          - `type` 'domains', required — Must be "domains".
          - `id` string, required — Opaque domain id.

## Response `200`

OK

- BatchResultEnvelopePublishedCourseResource
  - `data` union[], required — Per-item results in request order.
    - union
      - BatchSucceededItemPublishedCourseResource
        - `status` 'succeeded'
        - `id` string, nullable — Resource ID of the created or updated resource.
        - `result` PublishedCourseResource, required — JSON:API resource object for a published course.
          - `type` string — Always "published-courses".
          - `id` string, required — Opaque published-course ID. Use in URL paths.
          - `attributes` PublishedCourseAttributes, required — Attributes of a published-course resource object.
            - `external_id` string, required — Stable UUID5 for cross-system correlation.
            - `slug` string, required — URL path of the course on the domain. Unique per domain.
            - `live` boolean, required — Whether the course is currently published (live) on the domain.
            - `is_hidden` boolean, required — Hidden from the catalog; visible only to associated groups.
            - `visible_on_catalog` boolean, required — Whether the course shows on the domain catalog.
            - `open_access` boolean, required — Allow anonymous access (no registration). Disables per-user analytics when true.
            - `strict_enforce_group_visibility` boolean, required — Block access for students the course is not visible to.
            - `visibility_override_type` 'GROUP' | 'CATEGORY', required — Which visibility mode the course uses: GROUP or CATEGORY.
            - `access_period_starts_at` string, date-time, nullable — Start of the registration/enrollment window, or null.
            - `access_period_ends_at` string, date-time, nullable — End of the registration/enrollment window, or null.
            - `restrict_access_start_end_dates` boolean, required — Enforce the access window above.
            - `allow_self_service_reenroll` boolean, required — Let students re-enroll immediately on completion.
            - `unique_progress_per_enrollment` boolean, required — New progress record on each re-enroll.
            - `require_all_prerequisites` boolean, required — Require all prerequisites before enrolling.
            - `created_at` string, date-time, required — Creation timestamp.
            - `modified_at` string, date-time, required — Latest update timestamp.
          - `relationships` object, nullable — Related course and domain linkages.
          - `links` object, nullable — Self link.
      - 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)
