---
title: "Update assets"
method: PATCH
path: "/v2/assets/"
tags: ["Assets"]
---

# Update assets

`PATCH /v2/assets/`

Update up to ``BATCH_MAX_ITEMS`` assets in a single request.

``title`` is the only writable attribute; it is persisted to the asset's display
name. ``type``, ``state``, ``status_msg`` and ``download_url`` are server-owned
and a client that sends them has them silently ignored rather than rejected.

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

**Required OAuth scope:** `assets:write`

## Request body

- AssetUpdateRequestEnvelope — JSON:API envelope for PATCH /v2/assets/. The ``data`` field is always an array — single-update is a list of one. Cap enforcement lives in the endpoint, matching the create-endpoint convention.
  - `data` AssetUpdateData[], required — List of asset items to update.
    - `type` 'assets', required — Must be "assets".
    - `id` string, required — Obfuscated ID of the asset to update.
    - `attributes` AssetUpdateRequest, required — Request attributes for PATCH /v2/assets/. ``title`` is the only writable field; it is persisted to the model's ``display_name`` column. ``type``, ``state``, ``status_msg`` and ``download_url`` are server-owned and are **rejected** when sent, not silently ignored — see the ``extra="forbid"`` note below. The v2 surface is split on this (quizzes, groups, published_courses and lessons forbid; web_packages, certificates, students, courses, enrollments and domains do not); assets follow the stricter half deliberately. The source ticket's AC4 names ``title`` *and* ``description``. ``Asset`` has neither column — ``display_name`` is its writable label — so ``description`` is dropped rather than backed by a new column no other surface consumes.
      - `title` string, required — New label for the asset. Persisted to the asset's display name.

## Response `200`

OK

- BatchResultEnvelopeAssetResource
  - `data` union[], required — Per-item results in request order.
    - union
      - BatchSucceededItemAssetResource
        - `status` 'succeeded'
        - `id` string, nullable — Resource ID of the created or updated resource.
        - `result` AssetResource, required — JSON:API resource object for an asset, as returned on list.
          - `type` 'assets' — Always "assets".
          - `id` string, required — Opaque asset ID. Use in URL paths.
          - `attributes` AssetAttributes, required — Attributes of an asset resource object, as returned on list. ``type`` and ``state`` are deliberately typed as loose ``str`` rather than ``Literal``, matching ``WebPackageAttributes`` and ``LessonAttributes.type``: a new choice added to ``Asset.ASSET_TYPE_CHOICES`` or ``ASSET_STATE_CHOICES`` must never break serialization of existing rows. The valid values are documented in the field descriptions. Note ``type`` here is the **asset kind**, not the JSON:API resource type. Both are spelled ``type`` — the resource-level one is always ``"assets"``, while this one distinguishes a PDF from a video. Living inside ``attributes`` is what keeps them apart on the wire, and follows the ``LessonAttributes.type`` precedent. ``download_url`` is **not** here: signing a URL costs a round trip per row, so it is exposed only on the single-resource response (see ``AssetDetailAttributes``).
            - `type` string, required — Asset kind: PDF, FILE, HTML, VIDEO_BOTR, AUDIO_BOTR, or TEMPLATE. Server-determined from the fetched content — a value supplied on create seeds the row but is provisional and may be corrected during ingest.
            - `state` string, required — Ingestion state: PROCESSING while the asset is being fetched, stored and (for video/audio) handed to JW Platform; READY once that handoff has been accepted; ERROR if it could not be completed. Poll this field after create. READY means accepted for processing, not that transcoding has finished.
            - `title` string, required — Human-readable label. Server-derived from the uploaded filename when not set explicitly.
            - `status_msg` string, required — Reason the asset reached ERROR, or empty when there is none. Generic by design — it never names the host or address a fetch was attempted against.
            - `created_at` string, date-time, required — Timestamp when the asset was created.
            - `modified_at` string, date-time, required — Timestamp when the asset 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

## Changes

- **2026-09-01** `a3c13d9cb9b7` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/skilljar/apis/skilljar-api-v2/changes/v2/assets/patch.md)

---

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