# Edit a Social Post

> Change a draft or scheduled post before it goes out

- **URL**: https://orshot.com/docs/api-reference/social-post-update

---

## 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.

```markdown tab="Endpoint"
https://api.orshot.com/v1/social/posts/{postId}
```

## Request

**Request**
```js
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.",
  }),
});
```

**Response**
```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.

| 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 |
| `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                                  |

Timing uses the same `status` grammar as [creating a post](https://orshot.com/docs/api-reference/social-publish). 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

```js
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

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

### Changing where a post goes

```js
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, or **its media**, 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 widen `accounts` across accounts that were connected through different profiles, the extra accounts are created as additional posts alongside 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"
}
```

### Not found (404)

```json
{
  "error": "Post not found"
}
```

## Rate Limits

- 20 requests per minute per workspace, shared across all social endpoints