Docs
Docs/API Reference/Posts

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

StatusDescription
draftCreated but not yet published or scheduled
scheduledQueued for publishing at scheduled_for time
publishingCurrently being published to platforms
publishedSuccessfully published to at least one platform
failedPublishing 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

FieldTypeRequiredDescription
contentstringYesThe post content. Platform-specific character limits are validated at publish time.
platformsstring[]YesArray of platform identifiers to publish to. Valid values: twitter, bluesky, mastodon, linkedin, threads, instagram, facebook, tiktok, youtube, discord, slack, telegram.
scheduled_forstringNoISO 8601 datetime for scheduled publishing. Must be in the future.
mediaobjectNoMedia attachments. See Media Object below.
segmentsarrayNoThread 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

FieldTypeDescription
imagesarrayArray of image objects, each with url (string, required) and alt_text (string, optional). Maximum 4 images.
videoobjectVideo 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 segments or 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

ParameterTypeDefaultDescription
statusstring-Filter by status: published, scheduled, draft, failed.
limitinteger10Number of posts to return. Maximum 100.
offsetinteger0Number 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.

FieldTypeDescription
contentstringUpdated post content.
platformsstring[]Updated list of target platforms.
scheduled_forstring | nullUpdated schedule time (ISO 8601), or null to unschedule.
mediaobject | nullUpdated media attachments, or null to remove media.
segmentsarray | nullUpdated 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

CodeReason
409 ConflictPost is already published or currently publishing.
422 Validation ErrorContent exceeds character limit for one or more platforms, or no valid connected accounts found for the specified platforms.