---
title: "Edit a piece of content"
method: PATCH
path: "/v1/content/{contentId}"
tags: ["content"]
---

# Edit a piece of content

`PATCH /v1/content/{contentId}`

Rename a piece of content, replace its body, or both.

**Replacement, not a merge:** `body` replaces the artifact entirely, in the same shape the kind takes on create. Send the whole thing, not just the part that changed. Media already attached to the draft is the one exception — you can't send it back, so it's carried over rather than dropped.

**Scheduled pieces are locked:** if a send is queued or already running for this piece, its words go out exactly as written, so an edit to `body` is refused with an explanation. Cancel the schedule in the app first. Renaming is always allowed — a title isn't published anywhere.

**Grouping:** `bucketId` files this piece with the post it was made from. A piece already grouped somewhere else is refused with a 409 rather than moved — ungroup it in the app first.

## Path parameters

- `contentId` string, required

## Request body

- object
  - `title` string — New name for this piece in your library
  - `body` object — The replacement artifact, in the shape this piece's kind uses
    - `text` string — The post's text. `tweet`: a single tweet, at most 280 characters — use `tweets` for a thread and never send both. `linkedin`: the post body.
    - `tweets` string[] — `tweet` only. One entry per tweet, in posting order, each at most 280 characters. Never concatenate a thread into one entry.
    - `subject` string — `newsletter` only. Subject line.
    - `preheader` string — `newsletter` only. Optional preview line shown after the subject.
    - `body` string — `newsletter`: the email body. `x_article`: the article's full CommonMark markdown.
    - `title` string — `x_article` only. The headline as published on X. Separate from the piece's `title`, which only names it in your library.
    - `canonicalUrl` string — `x_article` only. The original post this Article is a version of. It must also appear as a markdown link inside `body`.
    - `media` unknown
  - `bucketId` string, uuid — Group this piece with the post it was made from, so the writer sees them together in Content. Get the id from `POST /v1/posts/{postId}/bucket`. Omit for standalone work that isn't derived from anything.

## Response `200`

Content updated successfully

- object
  - `id` string, required — Unique identifier for this piece of content
  - `kind` string, required — What this piece is: `tweet`, `linkedin`, `newsletter`, or `x_article`
  - `title` string, required — What this piece is called in your library
  - `excerpt` string, required — First readable line of the body, for listing views
  - `status` 'draft' | 'published' | 'archived', required — Whether this piece has been delivered, is still a draft, or was archived
  - `scheduled` boolean, required — Whether a scheduled send is queued against this piece
  - `lockedReason` string, nullable, required — Why this piece can't be edited right now, or null when it can. A queued or in-flight send locks the words, because they go out exactly as written.
  - `publishedAt` string, nullable, required — ISO 8601 timestamp of the first delivery, or null
  - `url` string, nullable, required — Where this piece went live, from the same delivery `publishedAt` came from. Null when it hasn't been delivered, and null by design for a channel that publishes no page: a custom email renders into the message itself, so there is no address to link to. Never guessed — a delivery whose id isn't shaped like its channel reports null rather than a link that would 404.
  - `archivedAt` string, nullable, required — ISO 8601 timestamp of when this piece was archived, or null
  - `bucketId` string, nullable, required — The bucket grouping this piece with the post it was made from, or null when it stands alone. Read it back with `GET /v1/buckets/{bucketId}`.
  - `createdAt` string, required — ISO 8601 timestamp of creation
  - `updatedAt` string, required — ISO 8601 timestamp of the last change
  - `body` object, required — The artifact itself, in the shape its kind uses

## Other responses

- `400` — The draft doesn't match its kind, or a queued send has locked it
- `401` — Invalid or missing API key
- `404` — Content not found, or no such content group in this publication
- `409` — This piece is already grouped with another post. Ungroup it in the Paragraph app first — grouping never moves a piece.
- `500` — Internal server error

## Changes

- **2026-09-01** `c148c610fb95` — 1 info
  - added the non-success response with the status `409`
- **2026-09-01** `2d43a4d31af2` — 2 info
  - added the new optional request property `bucketId`
  - added the required property `bucketId` to the response with the `200` status
- **2026-08-20** `d7ee6288035e` — 1 info
  - added the required property `url` to the response with the `200` status
- **2026-08-19** `795a6c351590` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/paragraph-xyz/apis/paragraph-api/changes/v1/content/:contentId/patch.md)

---

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