Edit a Social Post
Change a draft or scheduled post before it goes out
Published ·Updated
/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": []
}'const res = await fetch("https://api.orshot.com/v1/social/posts/<POST_ID>", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
"content": "<CONTENT>",
"media_urls": [],
"media_url": "<MEDIA_URL>",
"accounts": []
}),
});
const data = await res.json();import requests
response = requests.patch(
"https://api.orshot.com/v1/social/posts/<POST_ID>",
headers={"Authorization": "Bearer <ORSHOT_API_KEY>"},
json={
"content": "<CONTENT>",
"media_urls": [],
"media_url": "<MEDIA_URL>",
"accounts": []
},
)
data = response.json()$ch = curl_init("https://api.orshot.com/v1/social/posts/<POST_ID>");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer <ORSHOT_API_KEY>",
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"content" => "<CONTENT>",
"media_urls" => [],
"media_url" => "<MEDIA_URL>",
"accounts" => []
]));
$data = json_decode(curl_exec($ch), true);
curl_close($ch);require "net/http"
require "json"
uri = URI("https://api.orshot.com/v1/social/posts/<POST_ID>")
req = Net::HTTP::Patch.new(uri)
req["Authorization"] = "Bearer <ORSHOT_API_KEY>"
req["Content-Type"] = "application/json"
req.body = {
"content": "<CONTENT>",
"media_urls": [],
"media_url": "<MEDIA_URL>",
"accounts": []
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)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.
https://api.orshot.com/v1/social/posts/{postId}Request#
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.",
}),
});{
"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.
| Parameter | Type | Required | Description |
|---|---|---|---|
content | String | No | Replacement caption, max 5000 characters |
media_urls | Array | No | Replacement media. Send an empty array to remove the media |
media_url | String | No | Single-media shorthand for media_urls |
accounts | Array | No | Replacement 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 |
platforms | Array | No | Replacement platform ids for a post that has no account yet |
status | String | No | draft or scheduled. Omit to keep the post's current timing |
scheduled_for | String | No | New ISO 8601 publish time, at least 60 seconds ahead. Required with status: "scheduled" |
timezone | String | No | IANA timezone for the schedule |
platformOptions | Object | No | Per-account platform options, keyed by account ID. Sending it rebuilds the post (see below) |
tiktokSettings | Object | No | Replacement TikTok options, same as creating a post |
tags | Array | No | Replacement 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#
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#
body: JSON.stringify({
status: "scheduled",
scheduled_for: "2030-02-01T09:00:00Z",
});Changing where a post goes#
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)#
{
"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)#
{
"error": "scheduled_for must be at least 60 seconds in the future"
}Publishing by edit (400)#
{
"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)#
{
"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)#
{
"error": "accounts must contain numeric account IDs"
}Plan required (403)#
{
"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)#
{
"error": "This post is being discarded",
"message": "Wait for the discard to finish before changing it."
}Time passed (422)#
{
"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)#
{
"error": "Post not found"
}Rate Limits#
- 20 requests per minute per workspace, shared by the publishing and post endpoints. Analytics has its own limit
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