Skip to main content
POST
/
api
/
calendar
/
posts
Schedule a social post
curl --request POST \
  --url https://app.puffle.ai/api/calendar/posts \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "content": "<string>",
  "title": "<string>",
  "hashtags": [
    "<string>"
  ],
  "channels": [
    "<string>"
  ],
  "scheduled_at": "2023-11-07T05:31:56Z",
  "account_ids": [
    "3c90c3cc-0d44-4b50-8888-8dd25736052a"
  ],
  "status": "draft",
  "media_items": [
    {
      "url": "<string>",
      "type": "image"
    }
  ]
}
'
{ "post": { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "calendar_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "title": "<string>", "content": "<string>", "hashtags": [ "<string>" ], "channels": [ "<string>" ], "scheduled_at": "2023-11-07T05:31:56Z", "account_ids": [ "3c90c3cc-0d44-4b50-8888-8dd25736052a" ], "status": "draft", "late_post_id": "<string>", "media_items": [ { "url": "<string>", "type": "image" } ], "archived_at": "2023-11-07T05:31:56Z", "account_profile_picture": "<string>", "account_display_name": "<string>" } }

Overview

Creates one post. If account_ids resolves to one or more LinkedIn accounts and content is non-empty, the server mirrors the post into Late so it can publish at scheduled_at. If Late’s API rejects the mirror, the local row is still saved with late_post_id: null — retry the mirror by calling updateSocialPost with the same body. Hashtags are stored separately in hashtags[] but appended to content before handing off to Late, since LinkedIn has no native hashtag field.

AI agent notes

Validation before calling:
  • content must be non-empty
  • When status === "scheduled", scheduled_at must be strictly in the future (1-minute grace window)
  • At least one target account — either pass account_ids explicitly, or rely on the channels: ["linkedin"] fallback which picks the caller’s first LinkedIn account
Status semantics:
  • draft — editable, never publishes automatically
  • scheduled — queued with Late, auto-publishes at scheduled_at
  • posted — used for importing already-published posts (e.g., from onboarding); typically the server sets this, not the caller
Media — upload files via uploadSocialMedia first, then pass the returned URLs in media_items[]. LinkedIn allows only one media type per post.After creating, poll listSocialPosts (or syncSocialPostStatus once past scheduled_at) to observe status transitions.

Authorizations

Authorization
string
header
required

Bearer authentication header of the form Bearer <token>, where <token> is your auth token.

Body

application/json

If account_ids resolves to one or more LinkedIn accounts and content is non-empty, the server mirrors the post into Late (creating late_post_id). If Late creation fails, the post is still saved locally with late_post_id: null.

content
string
required

Post body text. Required and non-empty.

Minimum string length: 1
title
string | null
hashtags
string[]

Hashtags without the leading #. Appended to content before sending to Late since LinkedIn has no separate hashtag field.

channels
string[]

Legacy channel list, e.g. ['linkedin']. Fallback targeting when account_ids is empty.

scheduled_at
string<date-time> | null

ISO timestamp. Required when status === 'scheduled'. Must be in the future (1-minute grace window).

Pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
account_ids
string<uuid>[]

Target social account IDs. If empty and channels includes linkedin, the server falls back to the caller's first LinkedIn account.

Pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
status
enum<string>

Defaults to draft. scheduled queues with Late; posted is for importing already-published posts from onboarding.

Available options:
draft,
scheduled,
posted
media_items
object[]

Optional media attachments. Each URL must come from uploadSocialMedia.

Response

Post created.

post
object
required

Canonical social post row.