---
title: "Apply playlist operations"
method: POST
path: "/playlists/{id}/operations"
tags: ["playlists"]
---

# Apply playlist operations

`POST /playlists/{id}/operations`

Batched spec mutation. Ops apply in order; each sees the state
produced by earlier ops. On any validation failure, nothing is
written. Does not affect running streams — preparation happens
on stream launch. Resources are permanently bound to the workspace
that created them and cannot be moved between workspaces.

## Path parameters

- `id` string, required

## Headers

- `Idempotency-Key` string

## Request body

- ApplyPlaylistOperationsRequest
  - `basedOnVersion` integer, nullable — If set, reject the batch with CONFLICT when playlists.version has advanced past this value.
  - `operations` object[], required
    - `addObjects` object
      - `index` integer, nullable
      - `objectIds` string[], required
      - `position` 'START' | 'END' | 'INDEX', required
    - `removeItems` object
      - `itemIds` string[], required
    - `reorderItems` object
      - `moves` object[], required
        - `index` integer, nullable
        - `itemId` string, required
        - `position` 'START' | 'END' | 'INDEX', required
    - `updateSettings` object
      - `mode` 'sequential' | 'shuffle', nullable
  - `playlistId` string, required

## Response `200`

Successful response.

- Playlist
  - `copiedFrom` string, nullable
  - `createdAt` string, date-time, required
  - `diff` object, required — Snapshot-vs-draft diff (B-13 first-occurrence-by-objectId algorithm; zero-distance items go in `unchanged`, not `moved`; duplicate objectIds matched positionally). When no snapshot exists, diff = { unchanged: items, added: [], removed: [], moved: [] }.
    - `added` object[], required
      - `id` string, required
      - `objectID` string, required
      - `position` integer, required — 0-based; positions for a playlist form {0..N-1} exactly. Enforced by mutation layer (full-rewrite) and unique index.
    - `moved` object[], required
      - `fromPosition` integer, required
      - `item` object, required
        - `id` string, required
        - `objectID` string, required
        - `position` integer, required — 0-based; positions for a playlist form {0..N-1} exactly. Enforced by mutation layer (full-rewrite) and unique index.
      - `toPosition` integer, required
    - `removed` object[], required — May reference deleted PlaylistItem rows; projection-only type avoids the non-null hazard (NB-12).
      - `itemId` string, required — typeid plitm_* — frozen at write; underlying PlaylistItem row may have been deleted.
      - `objectId` string, required — typeid obj_*
      - `position` integer, required
    - `unchanged` object[], required
      - `id` string, required
      - `objectID` string, required
      - `position` integer, required — 0-based; positions for a playlist form {0..N-1} exactly. Enforced by mutation layer (full-rewrite) and unique index.
  - `hasUnpublishedChanges` boolean, required — snapshot != nil && Playlist.version > snapshot.version. Returns false during the B-11 STREAMING+no-snapshot window — deliberate: the stream is live but nothing has been published yet.
  - `id` string, required
  - `items` object[]
    - `id` string, required
    - `object` object, required
      - `duration` integer, nullable
      - `id` string, required
      - `name` string, required
      - `type` 'audio' | 'video' | 'image', required
    - `objectID` string, required
    - `position` integer, required — 0-based; positions for a playlist form {0..N-1} exactly. Enforced by mutation layer (full-rewrite) and unique index.
  - `kind` 'video' | 'audio', required
  - `liveItems` object[], required — Projected from PlaylistSnapshot.items JSON (NB-12) — never FK-hydrated against the live PlaylistItem table. Empty list during the B-11 STREAMING+no-snapshot window.
    - `itemId` string, required — typeid plitm_* — frozen at write; underlying PlaylistItem row may have been deleted.
    - `objectId` string, required — typeid obj_*
    - `position` integer, required
  - `liveVersion` integer, required — PlaylistSnapshot.version — Playlist.version frozen at the last LIVE write. Returns 0 when no snapshot exists OR during the B-11 STREAMING+no-snapshot transient window (WriteInitialPlaylistSnapshot retry).
  - `mode` 'sequential' | 'shuffle', required
  - `publishStatus` 'IDLE' | 'PENDING' | 'PREPARING' | 'FAILED' | 'NOT_LIVE', required — Workflow-state derivation. NB-13: PREPARING wins over PENDING even when a concurrent edit advances Playlist.version while a publish is preparing.
  - `streamID` string, required
  - `updatedAt` string, date-time, required
  - `version` integer, required — Bumped by 1 per committed spec mutation batch. Used for optimistic concurrency and as the snapshot handle for PreparePlaylistWorkflow.

## Other responses

- `401` — Missing or invalid access token.
- `403` — The token lacks the required scope or access to the resource.
- `404` — The resource does not exist or is not visible to the caller.
- `422` — The request body or parameters are invalid.
- `429` — Rate limit exceeded. Retry after the indicated delay.
- `502` — The upstream data service returned an error.

---

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