Posts
Create, read, update, and delete posts
Posts
Posts are the core resource in the Octopost API. A post contains content that can be published to one or more social media platforms simultaneously.
The Post Object
{
"id": "post_abc123",
"content": "Check out our new feature launch!",
"platforms": ["twitter", "bluesky", "linkedin"],
"status": "published",
"scheduled_for": null,
"media": {
"images": [
{
"url": "https://cdn.octopost.ink/uploads/img_001.png",
"alt_text": "Screenshot of the new dashboard"
}
],
"video": null
},
"segments": null,
"platform_results": {
"twitter": {
"success": true,
"post_id": "1234567890",
"post_url": "https://x.com/user/status/1234567890"
},
"bluesky": {
"success": true,
"post_id": "at://did:plc:abc/app.bsky.feed.post/xyz",
"post_url": "https://bsky.app/profile/user.bsky.social/post/xyz"
},
"linkedin": {
"success": false,
"error": "Token expired. Reconnect your LinkedIn account."
}
},
"created_at": "2026-04-03T12:00:00Z",
"updated_at": "2026-04-03T12:05:00Z",
"published_at": "2026-04-03T12:05:00Z"
}Status Values
| Status | Description |
|---|---|
draft | Created but not yet published or scheduled |
scheduled | Queued for publishing at scheduled_for time |
publishing | Currently being published to platforms |
published | Successfully published to at least one platform |
failed | Publishing failed on all platforms |
Create a Post
POST /posts
Creates a new post. The post is created in draft status unless scheduled_for is provided, in which case it is created in scheduled status.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The post content. Platform-specific character limits are validated at publish time. |
platforms | string[] | Yes | Array of platform identifiers to publish to. Valid values: twitter, bluesky, mastodon, linkedin, threads, instagram, facebook, tiktok, youtube, discord, slack, telegram. |
scheduled_for | string | No | ISO 8601 datetime for scheduled publishing. Must be in the future. |
media | object | No | Media attachments. See Media Object below. |
segments | array | No | Thread reply segments. When present, the post becomes a thread: content is the main post, and each item in segments becomes a reply. See Threaded Posts below. Max 24 segments. |
Media Object
| Field | Type | Description |
|---|---|---|
images | array | Array of image objects, each with url (string, required) and alt_text (string, optional). Maximum 4 images. |
video | object | Video object with url (string, required) and thumbnail_url (string, optional). Cannot be combined with images. |
Example: Create a Draft Post
curl -X POST https://api.octopost.ink/v1/posts \
-H "Authorization: Bearer oct_live_abc123" \
-H "Content-Type: application/json" \
-d '{
"content": "Excited to announce our Series A!",
"platforms": ["twitter", "linkedin", "bluesky"]
}'{
"id": "post_def456",
"content": "Excited to announce our Series A!",
"platforms": ["twitter", "linkedin", "bluesky"],
"status": "draft",
"scheduled_for": null,
"media": null,
"platform_results": null,
"created_at": "2026-04-03T14:00:00Z",
"updated_at": "2026-04-03T14:00:00Z",
"published_at": null
}Threaded Posts
Add a segments array to turn any post into a thread. The main content is segment 0; each entry in segments becomes a reply. The whole thread is stored as one post, appears as one entry in queue and schedule views, and publishes atomically.
Platform behavior (selected automatically based on which platforms you're posting to):
- Native reply chain — Twitter, Bluesky, Mastodon, Threads. Each segment is posted as a reply to the previous one.
- Comment on main post — Facebook, Instagram, LinkedIn, YouTube. Main post goes up, subsequent segments are posted as comments on it. (coming soon — currently falls back to sequential numbered posts)
- Native message threads — Slack, Discord, Telegram. Uses each platform's native threading primitive. (coming soon — currently falls back to sequential numbered posts)
- Sequential with
(X/Y)counter — TikTok. Separate posts with a counter suffix.
Canonical use case — avoiding link down-ranking:
curl -X POST https://api.octopost.ink/v1/posts \
-H "Authorization: Bearer oct_live_abc123" \
-H "Content-Type: application/json" \
-d '{
"content": "I shipped a new macOS app for managing Claude rules.",
"platforms": ["twitter", "threads", "bluesky", "mastodon"],
"segments": [
{ "content": "https://rulebook.app" }
]
}'On Twitter, Threads, Bluesky, and Mastodon this posts the main message, then drops the URL as a reply. The main message isn't penalized for containing a link, because it doesn't.
Segment shape:
{
"content": "Reply text (max 5000 chars)",
"media_urls": ["https://..."]
}Each segment can carry its own media (up to 4 images or 1 video per segment on most platforms).
Field behavior:
- Omit
segmentsor pass[]→ the post is a single post, current behavior. segments.length > 0→ the post is a thread.- A thread's character count uses the tightest active native-threading platform (typically 280 for Twitter). Segments posted to platforms with larger limits can be longer.
Example: Create a Scheduled Post with Media
curl -X POST https://api.octopost.ink/v1/posts \
-H "Authorization: Bearer oct_live_abc123" \
-H "Content-Type: application/json" \
-d '{
"content": "New blog post: Building a cross-posting API",
"platforms": ["twitter", "bluesky"],
"scheduled_for": "2026-04-10T09:00:00Z",
"media": {
"images": [
{
"url": "https://example.com/blog-header.png",
"alt_text": "Blog post header image"
}
]
}
}'{
"id": "post_ghi789",
"content": "New blog post: Building a cross-posting API",
"platforms": ["twitter", "bluesky"],
"status": "scheduled",
"scheduled_for": "2026-04-10T09:00:00Z",
"media": {
"images": [
{
"url": "https://example.com/blog-header.png",
"alt_text": "Blog post header image"
}
],
"video": null
},
"platform_results": null,
"created_at": "2026-04-03T14:00:00Z",
"updated_at": "2026-04-03T14:00:00Z",
"published_at": null
}List Posts
GET /posts
Returns a paginated list of posts.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | - | Filter by status: published, scheduled, draft, failed. |
limit | integer | 10 | Number of posts to return. Maximum 100. |
offset | integer | 0 | Number of posts to skip for pagination. |
Example
curl "https://api.octopost.ink/v1/posts?status=published&limit=5&offset=0" \
-H "Authorization: Bearer oct_live_abc123"{
"posts": [
{
"id": "post_abc123",
"content": "Check out our new feature launch!",
"platforms": ["twitter", "bluesky"],
"status": "published",
"scheduled_for": null,
"media": null,
"platform_results": {
"twitter": {
"success": true,
"post_id": "1234567890",
"post_url": "https://x.com/user/status/1234567890"
},
"bluesky": {
"success": true,
"post_id": "at://did:plc:abc/app.bsky.feed.post/xyz",
"post_url": "https://bsky.app/profile/user.bsky.social/post/xyz"
}
},
"created_at": "2026-04-03T12:00:00Z",
"updated_at": "2026-04-03T12:05:00Z",
"published_at": "2026-04-03T12:05:00Z"
}
],
"total": 42
}Get a Post
GET /posts/:id
Returns a single post by ID.
Example
curl https://api.octopost.ink/v1/posts/post_abc123 \
-H "Authorization: Bearer oct_live_abc123"{
"id": "post_abc123",
"content": "Check out our new feature launch!",
"platforms": ["twitter", "bluesky"],
"status": "published",
"scheduled_for": null,
"media": null,
"platform_results": {
"twitter": {
"success": true,
"post_id": "1234567890",
"post_url": "https://x.com/user/status/1234567890"
},
"bluesky": {
"success": true,
"post_id": "at://did:plc:abc/app.bsky.feed.post/xyz",
"post_url": "https://bsky.app/profile/user.bsky.social/post/xyz"
}
},
"created_at": "2026-04-03T12:00:00Z",
"updated_at": "2026-04-03T12:05:00Z",
"published_at": "2026-04-03T12:05:00Z"
}Returns 404 Not Found if the post does not exist.
Update a Post
PUT /posts/:id
Updates a post. Only posts in draft or scheduled status can be updated. Attempting to update a published or publishing post returns 409 Conflict.
Request Body
All fields are optional. Only include the fields you want to change.
| Field | Type | Description |
|---|---|---|
content | string | Updated post content. |
platforms | string[] | Updated list of target platforms. |
scheduled_for | string | null | Updated schedule time (ISO 8601), or null to unschedule. |
media | object | null | Updated media attachments, or null to remove media. |
segments | array | null | Updated thread reply segments. Pass [] to remove all replies and revert to a single post. Omit to keep existing segments unchanged. |
Example
curl -X PUT https://api.octopost.ink/v1/posts/post_def456 \
-H "Authorization: Bearer oct_live_abc123" \
-H "Content-Type: application/json" \
-d '{
"content": "Excited to announce our Series A! Read more on our blog.",
"platforms": ["twitter", "linkedin", "bluesky", "threads"]
}'{
"id": "post_def456",
"content": "Excited to announce our Series A! Read more on our blog.",
"platforms": ["twitter", "linkedin", "bluesky", "threads"],
"status": "draft",
"scheduled_for": null,
"media": null,
"platform_results": null,
"created_at": "2026-04-03T14:00:00Z",
"updated_at": "2026-04-03T14:10:00Z",
"published_at": null
}Delete a Post
DELETE /posts/:id
Deletes a post. Published posts are removed from Octopost but are not deleted from the social platforms they were published to. To delete from platforms, use the platform's native tools.
Example
curl -X DELETE https://api.octopost.ink/v1/posts/post_def456 \
-H "Authorization: Bearer oct_live_abc123"Returns 204 No Content on success.
Publish a Post
POST /posts/:id/publish
Immediately publishes a post to all specified platforms. The post must be in draft or scheduled status. Publishing is asynchronous -- the endpoint returns immediately with status publishing, and platform results are populated as each platform completes.
Use webhooks to be notified when publishing completes, or poll the post endpoint.
Example
curl -X POST https://api.octopost.ink/v1/posts/post_def456/publish \
-H "Authorization: Bearer oct_live_abc123"{
"id": "post_def456",
"content": "Excited to announce our Series A! Read more on our blog.",
"platforms": ["twitter", "linkedin", "bluesky", "threads"],
"status": "publishing",
"scheduled_for": null,
"media": null,
"platform_results": null,
"created_at": "2026-04-03T14:00:00Z",
"updated_at": "2026-04-03T14:15:00Z",
"published_at": null
}After publishing completes, a subsequent GET /posts/post_def456 returns the full platform_results:
{
"id": "post_def456",
"status": "published",
"platform_results": {
"twitter": {
"success": true,
"post_id": "1234567891",
"post_url": "https://x.com/user/status/1234567891"
},
"linkedin": {
"success": true,
"post_id": "urn:li:share:7890",
"post_url": "https://www.linkedin.com/feed/update/urn:li:share:7890"
},
"bluesky": {
"success": true,
"post_id": "at://did:plc:abc/app.bsky.feed.post/def",
"post_url": "https://bsky.app/profile/user.bsky.social/post/def"
},
"threads": {
"success": true,
"post_id": "17890012345",
"post_url": "https://www.threads.net/@user/post/17890012345"
}
},
"published_at": "2026-04-03T14:15:05Z"
}Errors
| Code | Reason |
|---|---|
409 Conflict | Post is already published or currently publishing. |
422 Validation Error | Content exceeds character limit for one or more platforms, or no valid connected accounts found for the specified platforms. |