---
title: "Patch the edit state for an orientation"
method: PUT
path: "/api/clips/{id}/edit"
tags: ["clips"]
---

# Patch the edit state for an orientation

`PUT /api/clips/{id}/edit`

## Request

- Base URL: `https://cliponit.app`
- URL: `https://cliponit.app/api/clips/{id}/edit`
- Auth: one of:
  - HTTP bearer
  - API key in cookie `session`
  - OAuth 2 (authorization code, authorization URL `https://cliponit.app/oauth/authorize`, token URL `https://cliponit.app/oauth/token`, scopes: `clips`)

Partial update: only the given fields change (`subtitles` merged field by field, lists replaced, `music: null` = removed); the edit row is created with the account subtitle preset if missing. Plan gating (403): `subtitles.overrides` non-empty needs `edit_captions`; `subtitles.lang != "original"` needs `translate`; non-empty `broll`/`music` needs `broll_music`. Validation (400, the message names the field): zones inside 0..1, keyframe zone count must match the layout (vertical/spotlight/centered 1, split 2, trio 3, horizontal 0), cuts `end > start`, B-roll/emojis inside the clip bounds. Returns the full stored edit.

## Path parameters

- `id` string, uuid, required

## Query parameters

- `orientation` 'vertical' | 'horizontal' | 'square' | 'portrait45': vertical = 9:16 1080x1920 (TikTok/Reels/Shorts), horizontal = 16:9 1920x1080 (YouTube), square = 1:1 1080x1080 (feed), portrait45 = 4:5 1080x1350 (Instagram feed). auto-reframe supports vertical|horizontal only.

## Request body

- EditInput: Body of `updateClipEdit`: a PATCH-like merge. Every field optional (absent = unchanged); `subtitles` is merged field by field; `keyframes`/`cuts`/`broll`/`emojis` replace their list; `music: null` removes it.
  - `layout` 'vertical' | 'horizontal' | 'split' | 'trio' | 'spotlight' | 'centered': How source zones fill the output frame. `vertical`: 1 zone cropped to 9:16. `horizontal`: full frame (0 zones). `split`: 2 zones stacked (top, bottom). `trio`: 3 zones stacked. `spotlight`: 1 zone over a blurred full-frame background. `centered`: 1 square zone on a blue (#1e3a8a) background.
  - `keyframes` Keyframe[]
    - `t` number, required
    - `layout` 'vertical' | 'horizontal' | 'split' | 'trio' | 'spotlight' | 'centered', required: How source zones fill the output frame. `vertical`: 1 zone cropped to 9:16. `horizontal`: full frame (0 zones). `split`: 2 zones stacked (top, bottom). `trio`: 3 zones stacked. `spotlight`: 1 zone over a blurred full-frame background. `centered`: 1 square zone on a blue (#1e3a8a) background.
    - `zones` Zone[], required
      - `x` number, required
      - `y` number, required
      - `w` number, required
      - `h` number, required
    - `ease` 'cut' | 'smooth' | '': `cut` = hard switch; `smooth` = zones glide from the previous keyframe over the 0.6 s before `t` (same layout only)
  - `cuts` Cut[]
    - `start` number, required
    - `end` number, required
  - `subtitles` SubtitlesPatch: Partial subtitles patch: every field optional, merged field by field over the current values (or over the built-in defaults for the account preset). Same enums as `Subtitles`.
    - `enabled` boolean
    - `color` 'yellow' | 'white' | 'green' | 'cyan' | 'pink' | 'orange' | 'rainbow'
    - `case` 'normal' | 'upper' | 'lower'
    - `lang` string
    - `overrides` SubtitleOverride[]
      - `start` number, required
      - `end` number, required
      - `text` string, required
    - `style` 'karaoke' | 'boxed' | 'outline' | 'glow' | 'minimal'
    - `font` 'Inter' | 'Montserrat' | 'Bangers' | 'Poppins' | 'Arial'
    - `position` 'bottom' | 'middle' | 'top'
    - `size` 1 | 2 | 3
    - `words_per_line` integer
    - `animate` boolean
    - `hook` Hook: Title card burned at the top of the frame during the first `duration_s` seconds.
      - `enabled` boolean
      - `text` string, nullable: null = the clip title
      - `duration_s` number
      - `style` 'card' | 'plain'
  - `broll` Broll[]
    - `id` string, required
    - `start` number, required
    - `end` number, required
    - `src` 'pexels' | 'upload', required
    - `path` string, required: Server path under data/broll/ (send back unchanged)
    - `preview_url` string, nullable
    - `query` string, nullable
  - `music` Music, nullable: Background music from the user's library (`listMusic`), looped, faded, ducked under the voice.
    - `music_id` string, uuid, required
    - `volume` number
    - `ducking` boolean: Lower the music while someone speaks
    - `fade_s` number: Fade in/out in seconds
  - `emojis` Emoji[]
    - `start` number, required
    - `end` number, required
    - `emoji` string, required
    - `x` number
    - `y` number: 0.58 = just above the subtitles
    - `size` 1 | 2 | 3: 10/14/18 % of the output width

## Response `200`

Stored edit

- Edit: Complete edit state of a clip for one orientation, as returned by `getClip` / `updateClipEdit`.
  - `layout` 'vertical' | 'horizontal' | 'split' | 'trio' | 'spotlight' | 'centered', required: How source zones fill the output frame. `vertical`: 1 zone cropped to 9:16. `horizontal`: full frame (0 zones). `split`: 2 zones stacked (top, bottom). `trio`: 3 zones stacked. `spotlight`: 1 zone over a blurred full-frame background. `centered`: 1 square zone on a blue (#1e3a8a) background.
  - `keyframes` Keyframe[], required
    - `t` number, required
    - `layout` 'vertical' | 'horizontal' | 'split' | 'trio' | 'spotlight' | 'centered', required: How source zones fill the output frame. `vertical`: 1 zone cropped to 9:16. `horizontal`: full frame (0 zones). `split`: 2 zones stacked (top, bottom). `trio`: 3 zones stacked. `spotlight`: 1 zone over a blurred full-frame background. `centered`: 1 square zone on a blue (#1e3a8a) background.
    - `zones` Zone[], required
      - `x` number, required
      - `y` number, required
      - `w` number, required
      - `h` number, required
    - `ease` 'cut' | 'smooth' | '': `cut` = hard switch; `smooth` = zones glide from the previous keyframe over the 0.6 s before `t` (same layout only)
  - `cuts` Cut[], required
    - `start` number, required
    - `end` number, required
  - `subtitles` Subtitles, required: Burned-in subtitles. All fields except the required ones have defaults and are always returned by `getClip`.
    - `enabled` boolean, required
    - `color` 'yellow' | 'white' | 'green' | 'cyan' | 'pink' | 'orange' | 'rainbow', required: Accent colour (current word in karaoke, text otherwise)
    - `case` 'normal' | 'upper' | 'lower', required
    - `lang` string, required: `original` or an ISO 639-1 code (`en`, `es`…) to translate — requires `translate` in the plan
    - `overrides` SubtitleOverride[]
      - `start` number, required
      - `end` number, required
      - `text` string, required
    - `style` 'karaoke' | 'boxed' | 'outline' | 'glow' | 'minimal'
    - `font` 'Inter' | 'Montserrat' | 'Bangers' | 'Poppins' | 'Arial'
    - `position` 'bottom' | 'middle' | 'top'
    - `size` 1 | 2 | 3: 52/64/80 px in 9:16 (×7/8 in 16:9)
    - `words_per_line` integer
    - `animate` boolean: Pop ×1.10 on the current word
    - `hook` Hook: Title card burned at the top of the frame during the first `duration_s` seconds.
      - `enabled` boolean
      - `text` string, nullable: null = the clip title
      - `duration_s` number
      - `style` 'card' | 'plain'
  - `broll` Broll[], required
    - `id` string, required
    - `start` number, required
    - `end` number, required
    - `src` 'pexels' | 'upload', required
    - `path` string, required: Server path under data/broll/ (send back unchanged)
    - `preview_url` string, nullable
    - `query` string, nullable
  - `music` Music, nullable, required: Background music from the user's library (`listMusic`), looped, faded, ducked under the voice.
    - `music_id` string, uuid, required
    - `volume` number
    - `ducking` boolean: Lower the music while someone speaks
    - `fade_s` number: Fade in/out in seconds
  - `emojis` Emoji[], required
    - `start` number, required
    - `end` number, required
    - `emoji` string, required
    - `x` number
    - `y` number: 0.58 = just above the subtitles
    - `size` 1 | 2 | 3: 10/14/18 % of the output width

## Other responses

- `400`: Validation error — the message names the field, e.g. `champ subtitles.size invalide : attendu 1..3`
- `403`: Feature not included in the user's plan (upgrade at /billing)
- `404`: Resource not found or not owned by the user

---

[API](https://skmtc.dev/cliponit/apis/cliponit-api.md) · [All operations](https://skmtc.dev/cliponit/apis/cliponit-api/llms.txt) · [OpenAPI document](https://skmtc.dev/cliponit/apis/cliponit-api/revisions/10e3e4327922?raw)
