Paragraph Docs
Developers

Drafted content

Upload X posts, LinkedIn posts, newsletters, and X Articles to a publication's library over the REST API.

Alongside long-form posts, a Paragraph publication keeps a library of short-form content: X posts and threads, LinkedIn posts, one-off emails, and X Articles. Writers see it in the app under Content, and the writing agent fills it as it works.

The /v1/content endpoints let you fill it too. Draft something anywhere (a script, a CI job, your own editor) and it lands in the writer's library ready to review.

Uploading creates a draft. Nothing is posted, emailed, or scheduled, and no draft you upload can go out without the writer sending it from the app.

Kinds and their bodies

Every piece has a kind, and body carries the artifact in the shape that kind uses. title is what the piece is called in the library, and isn't published anywhere.

KindBody
tweettext for a single post, or tweets for a thread (one entry per tweet, at most 280 characters each). Send one or the other, never both.
linkedintext
newslettersubject, body, and an optional preheader
x_articletitle (the headline X publishes), body as CommonMark markdown, and an optional canonicalUrl

Bodies are validated the same way the app validates them, so a thread with a 400-character entry or an Article missing its headline comes back with the same explanation you'd see while editing.

Drafts created through the API are text-only. Media has to be uploaded to X or LinkedIn first, using the publication's own connection to those platforms, which the API can't do yet. Add images to a draft in the app.

Create a draft

curl -X POST "https://public.api.paragraph.com/api/v1/content" \
  -H "Authorization: Bearer your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "tweet",
    "title": "Thread on writing in public",
    "body": { "tweets": ["Writing in public changes what you write.", "Here is what changed for me."] }
  }'
{
  "id": "7f3a2c18-5b9e-4c21-9a0d-8e6b1f4d2a55",
  "kind": "tweet",
  "title": "Thread on writing in public",
  "excerpt": "Writing in public changes what you write.",
  "status": "draft",
  "scheduled": false,
  "lockedReason": null,
  "publishedAt": null,
  "url": null,
  "archivedAt": null,
  "createdAt": "2026-08-19T09:12:44.108Z",
  "updatedAt": "2026-08-19T09:12:44.108Z",
  "body": { "tweets": ["Writing in public changes what you write.", "Here is what changed for me."] }
}

List your library

curl "https://public.api.paragraph.com/api/v1/content?kind=tweet&status=draft&limit=20" \
  -H "Authorization: Bearer your-api-key"

Filter with kind and status, and page with cursor. status takes all (the default), draft, published, or archived:

  • published means the piece has been delivered somewhere. A delivered piece keeps that status even if it's archived afterwards.
  • archived is what the writer has put away and never sent.
  • all is everything except the pieces archived without being sent.

A delivered piece also carries url, the address it went live at, alongside the publishedAt of the same delivery. It's null for anything still a draft, and null by design for a channel that publishes no page: a one-off email renders into the message itself, so there's nowhere to link to. A thread reports the link to its first post.

Listing leaves bodies out. Fetch a single piece to read one:

curl "https://public.api.paragraph.com/api/v1/content/7f3a2c18-5b9e-4c21-9a0d-8e6b1f4d2a55" \
  -H "Authorization: Bearer your-api-key"

Edit a draft

PATCH renames a piece, replaces its body, or both. body replaces the artifact entirely, in the same shape POST takes, so send the whole thing rather than the part that changed. Media attached to the draft in the app is the one exception: you can't send it back, so it's carried over rather than dropped.

curl -X PATCH "https://public.api.paragraph.com/api/v1/content/7f3a2c18-5b9e-4c21-9a0d-8e6b1f4d2a55" \
  -H "Authorization: Bearer your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"body": {"text": "Rewritten, and shorter."}}'

A piece with a send already queued goes out exactly as written, so editing its body is refused with an explanation, and lockedReason says so on every read. Cancel the schedule in the app first. Renaming is always allowed.

Archive and restore

Archiving puts a piece away without deleting it, and drops it out of the writer's Content surface. Anything still proposing the piece is dismissed alongside it, so nothing is left offering to send something that's been filed away.

curl -X POST "https://public.api.paragraph.com/api/v1/content/7f3a2c18-5b9e-4c21-9a0d-8e6b1f4d2a55/archive" \
  -H "Authorization: Bearer your-api-key"

Restoring brings the draft back. It doesn't bring the dismissed suggestions back with it.

curl -X POST "https://public.api.paragraph.com/api/v1/content/7f3a2c18-5b9e-4c21-9a0d-8e6b1f4d2a55/restore" \
  -H "Authorization: Bearer your-api-key"

Errors

StatusWhat it means
400The body doesn't match its kind, or a queued send has locked the piece. msg says which.
401Missing or invalid API key.
404No such piece in this publication, or the publication has no agent workspace yet (open it once at app.paragraph.com).

On this page