Edit a Social Post

Change a draft or scheduled post before it goes out

Published ·Updated

PATCH
/v1/social/posts/{postId}
curl -X PATCH "https://api.orshot.com/v1/social/posts/<POST_ID>" \
  -H "Authorization: Bearer <ORSHOT_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "<CONTENT>",
    "media_urls": [],
    "media_url": "<MEDIA_URL>",
    "accounts": []
  }'

Overview#

Changes a post that Orshot is still holding — its caption, its media, which accounts it targets, or when it fires. The post keeps its ID.

This is what makes a review loop possible: save a post as a draft, show it to someone, apply their edit, then publish it. Editing does not re-render anything and costs no credits.

Only works while the post is still held. Check is_editable on the post, or status: draft and scheduled can be edited, everything else is already at its platforms and is immutable.

Endpoint
https://api.orshot.com/v1/social/posts/{postId}

Request#

JavaScript
await fetch("https://api.orshot.com/v1/social/posts/512", {
  method: "PATCH",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer <ORSHOT_API_KEY>",
  },
  body: JSON.stringify({
    content: "Our new pricing is live. Same features, simpler tiers.",
  }),
});
JSON
{
  "data": {
    "post": {
      "id": 512,
      "status": "draft",
      "content": "Our new pricing is live. Same features, simpler tiers.",
      "media_urls": ["https://storage.orshot.com/.../launch.png"],
      "platforms": ["linkedin", "twitter"],
      "accounts": [
        {
          "account_id": 15,
          "platform": "linkedin",
          "username": "acme-inc",
          "name": "Acme Inc",
          "status": "draft"
        }
      ],
      "is_editable": true,
      "scheduled_for": null,
      "published_at": null,
      "created_at": "2026-08-11T17:24:09.752914+00:00",
      "render_id": 889021
    },
    "platforms": [
      {
        "account_id": 15,
        "platform": "linkedin",
        "username": "acme-inc",
        "status": "draft"
      }
    ]
  }
}

Request Parameters#

Only the fields you send are changed. Anything you leave out keeps its current value.

ParameterTypeRequiredDescription
contentStringNoReplacement caption, max 5000 characters
media_urlsArrayNoReplacement media. Send an empty array to remove the media
media_urlStringNoSingle-media shorthand for media_urls
accountsArrayNoReplacement target account IDs. Replaces the existing set rather than adding to it. On a post saved without an account (needs_accounts: true), this is what sends it on its way
platformsArrayNoReplacement platform ids for a post that has no account yet
statusStringNodraft or scheduled. Omit to keep the post's current timing
scheduled_forStringNoNew ISO 8601 publish time, at least 60 seconds ahead. Required with status: "scheduled"
timezoneStringNoIANA timezone for the schedule
platformOptionsObjectNoPer-account platform options, keyed by account ID. Sending it rebuilds the post (see below)
tiktokSettingsObjectNoReplacement TikTok options, same as creating a post
tagsArrayNoReplacement labels. Up to 10

Timing uses the same status grammar as creating a post. Leave it out entirely and the post keeps the timing it already has, so editing a caption never disturbs a schedule.

status: "published" is rejected here — editing only changes a held post. Sending it is a separate, explicit call.

Turning a scheduled post back into a draft#

JavaScript
await fetch("https://api.orshot.com/v1/social/posts/512", {
  method: "PATCH",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer <ORSHOT_API_KEY>",
  },
  body: JSON.stringify({ status: "draft" }),
});

The post stops being scheduled and is held until you publish it explicitly.

Rescheduling#

JavaScript
body: JSON.stringify({
  status: "scheduled",
  scheduled_for: "2030-02-01T09:00:00Z",
});

Changing where a post goes#

JavaScript
body: JSON.stringify({
  accounts: [15, 22, 31],
  platformOptions: {
    "15": { firstComment: "Full changelog in the comments." },
  },
});

How editing works#

Changing only the caption, the schedule, or both is an in-place update: the queued post is edited where it stands. Nothing is torn down and nothing is republished.

Changing which accounts a post targets, its media, or its platformOptions cannot be done in place, so Orshot rebuilds the post: a replacement is created with your changes and the old one removed. Your post ID does not change either way, and nothing is published as part of an edit.

On that rebuild path, a scheduled post has its old schedule cancelled before the replacement is created, so there is never a window in which two copies could fire. If the replacement then fails, the edit returns an error and the post is no longer scheduled. Read it back and re-schedule.

If you add accounts, Orshot may create extra posts for them next to the one you edited.

Error Responses#

Post already sent (400)#

JSON
{
  "error": "Only draft or scheduled posts can be edited",
  "message": "This post has already been sent to its platforms. Create a new post instead."
}

Schedule too soon (400)#

JSON
{
  "error": "scheduled_for must be at least 60 seconds in the future"
}

Publishing by edit (400)#

JSON
{
  "error": "status \"published\" cannot be set by editing",
  "message": "Editing only changes a held post. Publish it with POST /v1/social/posts/{postId}/publish."
}

No accounts (400)#

JSON
{
  "error": "accounts must be a non-empty array of account IDs"
}

A post that has no account yet can be edited without accounts; it keeps waiting. Giving it accounts is what publishes or schedules it for real.

Account IDs are not numbers (400)#

JSON
{
  "error": "accounts must contain numeric account IDs"
}

Plan required (403)#

JSON
{
  "error": "Social publishing requires a paid plan",
  "code": "plan_required",
  "message": "Publishing is not included in this workspace.",
  "action": { "label": "Open workspace", "path": "" }
}

Only when the edit gives the post accounts. Editing a waiting post needs no plan.

Discard in progress (409)#

JSON
{
  "error": "This post is being discarded",
  "message": "Wait for the discard to finish before changing it."
}

Time passed (422)#

JSON
{
  "error": "The scheduled time has passed.",
  "code": "time_passed",
  "message": "Set a new time and the post goes out.",
  "action": { "label": "Open post", "path": "/socials/posts" }
}

A waiting post whose time went by while it had no account: send scheduled_for (or status) along with the accounts.

Not found (404)#

JSON
{
  "error": "Post not found"
}

Rate Limits#

  • 20 requests per minute per workspace, shared by the publishing and post endpoints. Analytics has its own limit
Was this page helpful?

Ready to automate?

Start rendering images, PDFs and videos from your templates in under 2 minutes. Free plan, no credit card.

Get your API key
  • Image, PDF and video generation via API
  • Visual editor with AI and smart layouts
  • Zapier, Make, MCP and 40+ integrations
  • White-label embed for your own app
  • 100 free credits a month, no credit card required