---
title: "Create web packages"
method: POST
path: "/v2/web-packages/"
tags: ["Web Packages"]
---

# Create web packages

`POST /v2/web-packages/`

Create up to ``BATCH_MAX_ITEMS`` web packages in a single request.

Each item supplies a ``content_url`` that the server fetches and re-hosts
**asynchronously**. The response therefore describes rows in state
``PROCESSING`` — poll ``GET /v2/web-packages/{id}`` until ``state`` becomes
``READY`` or ``ERROR``. A malformed archive surfaces as ``state=ERROR``, not
as an error on this response.

Returns 200 if all items succeed, 207 if any fail, 400 if the envelope is
empty or over the cap.

Rate limited more tightly than a plain row-writing batch endpoint: every
accepted item queues an outbound fetch of up to ``REMOTE_FETCH_MAX_BYTES``,
so this endpoint converts one request into real egress. Actual concurrent
fetching is additionally bounded by Celery worker concurrency.

No within-batch deduplication is applied — ``WebPackage`` has no natural
uniqueness key, and two identical ``content_url`` values are a legitimate
request for two distinct packages.

**Required OAuth scope:** `web-packages:write`

## Request body

- WebPackageCreateRequestEnvelope — JSON:API envelope for POST /v2/web-packages/. The ``data`` field is always an array — single-create is a list of one. Cap enforcement (<= ``BATCH_MAX_ITEMS`` items) lives in the endpoint, NOT here — that gives us HTTP 400 via ``ValidationError`` instead of Pydantic's 422, per the v2 batch-response convention.
  - `data` WebPackageCreateData[], required — List of web package items to create.
    - `type` 'web-packages', required — Must be "web-packages".
    - `attributes` WebPackageCreateRequest, required — Request attributes for POST /v2/web-packages/. ``content_url`` uses the shared ``ContentUrl`` type from GE-253536 rather than redeclaring the https/well-formed rule. That check is syntactic only and runs at schema-parse time (422); the authoritative network safety runs at fetch time in the worker.
      - `content_url` string, required — https:// URL of the package archive. The server fetches and re-hosts it asynchronously; the created resource starts in state=PROCESSING.
      - `title` string, required — Title for the package.

## Response `200`

OK

- BatchResultEnvelopeWebPackageResource
  - `data` union[], required — Per-item results in request order.
    - union
      - BatchSucceededItemWebPackageResource
        - `status` 'succeeded'
        - `id` string, nullable — Resource ID of the created or updated resource.
        - `result` WebPackageResource, required — JSON:API resource object for a web package.
          - `type` 'web-packages' — Always "web-packages".
          - `id` string, required — Opaque web package ID. Use in URL paths.
          - `attributes` WebPackageAttributes, required — Attributes of a web package resource object. ``type`` and ``state`` are deliberately typed as loose ``str`` rather than ``Literal``, matching ``LessonAttributes.type``: a new choice added to ``WebPackage.PACKAGE_TYPE_CHOICES`` must never break serialization of existing rows. The valid values are documented in the field descriptions. Note ``type`` lives inside ``attributes`` even though JSON:API reserves that name at the resource level. This follows the established v2 precedent (``LessonAttributes.type``) and is what GE-249998 AC2 asks for.
            - `type` string, required — Package format: SCORM, SCORM_2004, XAPI, AICC, or HTML.
            - `state` string, required — Ingestion state: PROCESSING while the package is being fetched and unpacked, READY once usable, ERROR if the archive could not be processed. Poll this field after create.
            - `title` string, required — Best-guess title parsed from the package manifest.
            - `display_name` string, required — Server-derived label shown in the dashboard. Recomputed on every save from the title, state and archive filename — it is not directly settable.
            - `base_path` string, required — Base location in storage of the unpacked package content.
            - `created_at` string, date-time, required — Timestamp when the web package was created.
            - `modified_at` string, date-time, required — Timestamp when the web package was last modified.
          - `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
- `404` — Not Found
- `422` — Unprocessable Entity
- `429` — Too Many Requests

---

[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)
