Publish to Social
Publish images or videos directly to connected social accounts
Published ·Updated
Publish content to your connected social media accounts. Use this when you already have a media URL (e.g., from a previous render) and want to post it without rendering again.
To publish as part of a render in a single call, use the publish parameter on Render from Studio Template instead.
Endpoint#
POST /social/publish#
https://api.orshot.com/v1/social/publishRequest Example#
await fetch("https://api.orshot.com/v1/social/publish", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
accounts: [1, 2],
content: "Check out our latest design!",
media_url: "https://storage.orshot.com/.../image.png",
}),
});Find your account IDs using the List Social Accounts endpoint, or from the Social Accounts page in your workspace sidebar.
Choosing when it goes out#
One field decides this: status. It takes the same three values the API gives back to you, so what you ask for and what you read back are the same vocabulary.
status | What happens |
|---|---|
published | Posts immediately. This is the default when you omit status |
draft | Held by Orshot. Reaches no platform until you publish it explicitly |
scheduled | Queued for scheduled_for. Requires that field |
Schedule a Post#
await fetch("https://api.orshot.com/v1/social/publish", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
accounts: [1],
content: "This will be posted later!",
media_url: "https://storage.orshot.com/.../image.png",
status: "scheduled",
scheduled_for: "2030-01-15T10:00:00Z",
timezone: "America/New_York",
}),
});scheduled_for must be at least 60 seconds in the future, and is only valid with status: "scheduled" — pairing it with a draft or an immediate post is rejected rather than quietly ignored.
Save as Draft#
await fetch("https://api.orshot.com/v1/social/publish", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
accounts: [1, 2, 3],
content: "Draft post — will publish later from the dashboard",
media_url: "https://storage.orshot.com/.../image.png",
status: "draft",
}),
});A draft is held by Orshot and reaches no platform until you publish it. From there you can read it back, edit it, publish it or discard it — see List Social Posts for finding a post's ID.
Platform-Specific Options#
await fetch("https://api.orshot.com/v1/social/publish", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
accounts: [1, 2, 3],
content: "New product launch!",
media_url: "https://storage.orshot.com/.../image.png",
platformOptions: {
"1": { firstComment: "Follow us for more updates!" },
"2": { title: "Product Launch", link: "https://acme.com/launch" },
},
tiktokSettings: { autoAddMusic: true },
}),
});Request Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
accounts | Array | Yes | Array of account IDs to publish to (see List Social Accounts) |
content | String | No | Caption/text for the post (max 5000 characters) |
media_url | String | No | URL of the image or video to publish |
media_urls | Array | No | Multiple media URLs for carousel posts (use instead of media_url) |
status | String | No | published (default), draft or scheduled — see above |
scheduled_for | String | No | ISO 8601 timestamp, at least 60 seconds ahead. Required with status: "scheduled", invalid otherwise |
timezone | String | No | IANA timezone string for scheduling (e.g., "America/New_York", "Europe/London") |
platformOptions | Object | No | Per-account options keyed by account ID (see below). platform_options also accepted |
tiktokSettings | Object | No | Root-level TikTok options. For photo carousels, { "autoAddMusic": true } asks TikTok to add recommended music. |
Platform Options#
Pass platform-specific options keyed by account ID:
| Platform | Option | Description |
|---|---|---|
firstComment | Auto-post a first comment | |
disableLinkPreview | Disable the link preview card | |
title | Pin title | |
link | Destination URL for the pin |
TikTok recommended music is set at the root level via tiktokSettings, not per account — it applies to every TikTok photo post in the request.
Earlier field names#
Before status existed, timing was expressed with isDraft: true and schedule: { scheduledFor }. Both still work and need no migration. status takes precedence if you send both.
The one combination that no longer passes is isDraft: true together with a schedule — it used to be accepted and stored a draft carrying a schedule that could never fire. Say which one you mean instead.
Response Example#
{
"data": {
"post_id": 42,
"status": "published",
"platforms": [
{
"account_id": 1,
"platform": "twitter",
"username": "acmehq",
"name": "Acme HQ",
"status": "published",
"url": "https://twitter.com/acmehq/status/1234567890"
},
{
"account_id": 2,
"platform": "linkedin",
"username": "acme-inc",
"name": "Acme Inc",
"status": "published",
"url": "https://linkedin.com/feed/update/urn:li:share:1234567890"
}
]
}
}Response Fields#
| Field | Type | Description |
|---|---|---|
post_id | Integer | Unique identifier for the post |
post_ids | Array | Provider post IDs, one per created post |
status | String | Overall status: published, scheduled, drafted, partial, processing, or failed |
platforms | Array | Per-account results |
platforms[].account_id | Integer | Exact connected account targeted by this result |
platforms[].platform | String | Platform name |
platforms[].username | String | Account username |
platforms[].name | String | Account display name |
platforms[].status | String | published, scheduled, drafted, failed, or an in-flight provider status like pending or publishing |
platforms[].url | String | Link to the published post (when available) |
platforms[].error | String | Error message (only on failure) |
platforms[].action | String | Suggested recovery action on failure (e.g. reconnect) |
A partial status means some accounts succeeded and others failed — check individual platforms[].status for details. A processing status means all targeted platforms publish asynchronously (e.g. Instagram, TikTok) and the post is still in flight.
Format Compatibility#
Not all formats work on every platform:
| Format | Unsupported Platforms |
|---|---|
| All platforms (not supported) | |
| MP4, WebM, GIF | Google Business |
| PNG, JPG, WebP | YouTube (video-only platform) |
If some accounts are incompatible with the media format, they are skipped and returned with status: "failed" and a reason in the error field.
Rate Limits#
This endpoint is rate limited to 20 requests per minute per workspace. Rate limit headers are included in every response:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per minute |
X-RateLimit-Remaining | Remaining requests in the current window |
Retry-After | Seconds to wait (only on 429 responses) |
Error Responses#
| Code | Description |
|---|---|
| 400 | Missing or empty accounts array |
| 400 | Content exceeds 5000 characters |
| 400 | Invalid schedule.scheduledFor timestamp |
| 400 | Scheduled time must be in the future |
| 400 | No valid connected accounts found for the given IDs |
| 401 | Invalid or expired API key |
| 403 | Missing Authorization header |
| 403 | Social publishing requires a paid plan |
| 422 | No compatible platforms for the media format |
| 422 | Publish attempted but failed on every targeted account |
| 429 | Rate limit exceeded |
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 50+ integrations
- White-label embed for your own app
- 30 free credits — no credit card required