Skip to content
Docs / Posts

API reference

Posts

A post is one piece of content going to one or more channels. Each channel gets a target, which tracks whether it published there.

The post object

Attributes

idinteger
The post’s id.
contentstring
The shared text.
statusstring
draft, pending_approval (waiting for an admin), scheduled, publishing, published or failed (at least one channel failed).
scheduled_atstring or null
When it publishes, or published.
authorobject
Who created it: id and name.
latest_reviewobject or null
The last approval decision: decision, comment and reviewer.
mediaarray
Attached media objects, in order.
targets[]array
One per channel: status (pending, publishing, published or failed), its own content and settings, the network’s error if it failed, and external_url, the link to the published post.
The post object
{
  "id": 318,
  "content": "New on the blog: how we plan a month of posts in one afternoon.",
  "status": "scheduled",
  "scheduled_at": "2026-11-02T09:30:00.000000Z",
  "created_at": "2026-10-16T09:02:11.000000Z",
  "updated_at": "2026-10-16T09:02:11.000000Z",
  "author": { "id": 7, "name": "Sam Rivera" },
  "latest_review": null,
  "media": [
    {
      "id": 42,
      "name": "cover.png",
      "mime_type": "image/png",
      "kind": "image",
      "size": 284113,
      "width": 1600,
      "height": 900,
      "duration": null,
      "alt_text": "A calendar with a month of posts",
      "url": "/api/v1/media/42/file",
      "created_at": "2026-10-16T08:58:41.000000Z"
    }
  ],
  "targets": [
    {
      "id": 901,
      "channel_id": 12,
      "status": "pending",
      "content": null,
      "settings": {},
      "error": null,
      "external_url": null,
      "channel": {
        "id": 12,
        "platform": "linkedin",
        "name": "Acme",
        "username": "acme",
        "avatar_url": "https://media.licdn.com/dms/image/acme.png",
        "needs_reconnect": false,
        "posting_times": ["09:00", "12:30", "17:00"],
        "reconnect_by": null,
        "created_at": "2026-09-02T10:14:03.000000Z"
      }
    },
    {
      "id": 902,
      "channel_id": 15,
      "status": "pending",
      "content": "Shorter version for Bluesky.",
      "settings": {},
      "error": null,
      "external_url": null,
      "channel": {
        "id": 15,
        "platform": "bluesky",
        "name": "Acme",
        "username": "acme.bsky.social",
        "avatar_url": null,
        "needs_reconnect": false,
        "posting_times": ["10:00", "16:00"],
        "reconnect_by": null,
        "created_at": "2026-09-02T10:14:03.000000Z"
      }
    }
  ]
}

Create a post

POST/api/public/v1/posts

Saves a draft, or schedules a post. Scheduling checks every channel’s version against that network’s limits, like the composer does, and returns a 422 naming the channel if one doesn’t fit.

If the workspace requires approvals and your role can’t publish directly, a scheduled post is saved as pending_approval and the workspace admins are asked to review it.

Body parameters

statusstringRequired
draft to save it for later, or scheduled to publish it at scheduled_at.
scheduled_atstring
When to publish, in ISO 8601. Required when scheduling. A time in the past publishes it now. Use the queue to pick the next free time.
contentstring
The text every channel uses unless it has its own, up to 10,000 characters. Optional when media is attached.
targetsarray
The channels to post to. Required when scheduling.
targets[].channel_idintegerRequired
A channel from List channels.
targets[].contentstring
This channel’s own text, instead of content.
targets[].settingsobject
This channel’s extra settings, like a YouTube title. See Get post settings.
media_idsarray of integers
Up to 20 files from the media library, in the order they should appear.
curl -X POST https://api.postzora.com/api/public/v1/posts \
  -H "Authorization: Bearer $POSTZORA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "New on the blog: how we plan a month of posts in one afternoon.",
    "status": "scheduled",
    "scheduled_at": "2026-11-02T09:30:00Z",
    "media_ids": [42],
    "targets": [
      { "channel_id": 12 },
      {
        "channel_id": 15,
        "content": "Shorter version for Bluesky."
      }
    ]
  }'
201
{
  "data": {
    "id": 318,
    "content": "New on the blog: how we plan a month of posts in one afternoon.",
    "status": "scheduled",
    "scheduled_at": "2026-11-02T09:30:00.000000Z",
    "created_at": "2026-10-16T09:02:11.000000Z",
    "updated_at": "2026-10-16T09:02:11.000000Z",
    "author": { "id": 7, "name": "Sam Rivera" },
    "latest_review": null,
    "media": [
      {
        "id": 42,
        "name": "cover.png",
        "mime_type": "image/png",
        "kind": "image",
        "size": 284113,
        "width": 1600,
        "height": 900,
        "duration": null,
        "alt_text": "A calendar with a month of posts",
        "url": "/api/v1/media/42/file",
        "created_at": "2026-10-16T08:58:41.000000Z"
      }
    ],
    "targets": [
      {
        "id": 901,
        "channel_id": 12,
        "status": "pending",
        "content": null,
        "settings": {},
        "error": null,
        "external_url": null,
        "channel": {
          "id": 12,
          "platform": "linkedin",
          "name": "Acme",
          "username": "acme",
          "avatar_url": "https://media.licdn.com/dms/image/acme.png",
          "needs_reconnect": false,
          "posting_times": ["09:00", "12:30", "17:00"],
          "reconnect_by": null,
          "created_at": "2026-09-02T10:14:03.000000Z"
        }
      },
      {
        "id": 902,
        "channel_id": 15,
        "status": "pending",
        "content": "Shorter version for Bluesky.",
        "settings": {},
        "error": null,
        "external_url": null,
        "channel": {
          "id": 15,
          "platform": "bluesky",
          "name": "Acme",
          "username": "acme.bsky.social",
          "avatar_url": null,
          "needs_reconnect": false,
          "posting_times": ["10:00", "16:00"],
          "reconnect_by": null,
          "created_at": "2026-09-02T10:14:03.000000Z"
        }
      }
    ]
  }
}

List posts

GET/api/public/v1/posts

Returns the workspace’s posts, latest scheduled_at first, up to 500. Narrow it down with a time window to read more.

Query parameters

fromstring
Only posts scheduled at or after this time.
tostring
Only posts scheduled at or before this time.
statusstring
Only posts with this status.
channel_ids[]array of integers
Only posts going to any of these channels.
curl -G https://api.postzora.com/api/public/v1/posts \
  -H "Authorization: Bearer $POSTZORA_API_KEY" \
  --data-urlencode "from=2026-11-01T00:00:00Z" \
  --data-urlencode "to=2026-11-30T23:59:59Z" \
  --data-urlencode "status=scheduled"
Response
{
  "data": [
    {
      "id": 318,
      "content": "New on the blog: how we plan a month of posts in one afternoon.",
      "status": "scheduled",
      "scheduled_at": "2026-11-02T09:30:00.000000Z",
      "created_at": "2026-10-16T09:02:11.000000Z",
      "updated_at": "2026-10-16T09:02:11.000000Z",
      "author": { "id": 7, "name": "Sam Rivera" },
      "latest_review": null,
      "media": [
        {
          "id": 42,
          "name": "cover.png",
          "mime_type": "image/png",
          "kind": "image",
          "size": 284113,
          "width": 1600,
          "height": 900,
          "duration": null,
          "alt_text": "A calendar with a month of posts",
          "url": "/api/v1/media/42/file",
          "created_at": "2026-10-16T08:58:41.000000Z"
        }
      ],
      "targets": [
        {
          "id": 901,
          "channel_id": 12,
          "status": "pending",
          "content": null,
          "settings": {},
          "error": null,
          "external_url": null,
          "channel": {
            "id": 12,
            "platform": "linkedin",
            "name": "Acme",
            "username": "acme",
            "avatar_url": "https://media.licdn.com/dms/image/acme.png",
            "needs_reconnect": false,
            "posting_times": ["09:00", "12:30", "17:00"],
            "reconnect_by": null,
            "created_at": "2026-09-02T10:14:03.000000Z"
          }
        },
        {
          "id": 902,
          "channel_id": 15,
          "status": "pending",
          "content": "Shorter version for Bluesky.",
          "settings": {},
          "error": null,
          "external_url": null,
          "channel": {
            "id": 15,
            "platform": "bluesky",
            "name": "Acme",
            "username": "acme.bsky.social",
            "avatar_url": null,
            "needs_reconnect": false,
            "posting_times": ["10:00", "16:00"],
            "reconnect_by": null,
            "created_at": "2026-09-02T10:14:03.000000Z"
          }
        }
      ]
    }
  ]
}

Get a post

GET/api/public/v1/posts/{id}

Returns one post. Read it after its time to see how publishing went on each channel: external_url links to the post on the network, and error says why one failed.

curl https://api.postzora.com/api/public/v1/posts/318 \
  -H "Authorization: Bearer $POSTZORA_API_KEY"
Response
{
  "data": {
    "id": 318,
    "content": "New on the blog: how we plan a month of posts in one afternoon.",
    "status": "published",
    "scheduled_at": "2026-11-02T09:30:00.000000Z",
    "created_at": "2026-10-16T09:02:11.000000Z",
    "updated_at": "2026-10-16T09:02:11.000000Z",
    "author": { "id": 7, "name": "Sam Rivera" },
    "latest_review": null,
    "media": [
      {
        "id": 42,
        "name": "cover.png",
        "mime_type": "image/png",
        "kind": "image",
        "size": 284113,
        "width": 1600,
        "height": 900,
        "duration": null,
        "alt_text": "A calendar with a month of posts",
        "url": "/api/v1/media/42/file",
        "created_at": "2026-10-16T08:58:41.000000Z"
      }
    ],
    "targets": [
      {
        "id": 901,
        "channel_id": 12,
        "status": "published",
        "content": null,
        "settings": {},
        "error": null,
        "external_url": "https://www.linkedin.com/feed/update/urn:li:share:7251…",
        "channel": {
          "id": 12,
          "platform": "linkedin",
          "name": "Acme",
          "username": "acme",
          "avatar_url": "https://media.licdn.com/dms/image/acme.png",
          "needs_reconnect": false,
          "posting_times": ["09:00", "12:30", "17:00"],
          "reconnect_by": null,
          "created_at": "2026-09-02T10:14:03.000000Z"
        }
      },
      {
        "id": 902,
        "channel_id": 15,
        "status": "published",
        "content": "Shorter version for Bluesky.",
        "settings": {},
        "error": null,
        "external_url": "https://bsky.app/profile/acme.bsky.social/post/3l6…",
        "channel": {
          "id": 15,
          "platform": "bluesky",
          "name": "Acme",
          "username": "acme.bsky.social",
          "avatar_url": null,
          "needs_reconnect": false,
          "posting_times": ["10:00", "16:00"],
          "reconnect_by": null,
          "created_at": "2026-09-02T10:14:03.000000Z"
        }
      }
    ]
  }
}

Update a post

PUT/api/public/v1/posts/{id}

Replaces the post with the body you send: send every field, not only the ones that change. It takes the same parameters as Create a post.

Posts that are publishing or published can’t change. To retry a failed post, send it again with status set to scheduled: channels it already published to are left alone, so nothing posts twice.

curl -X PUT https://api.postzora.com/api/public/v1/posts/318 \
  -H "Authorization: Bearer $POSTZORA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "New on the blog: how we plan a month of posts in one afternoon.",
    "status": "scheduled",
    "scheduled_at": "2026-11-03T09:30:00Z",
    "media_ids": [42],
    "targets": [
      { "channel_id": 12 },
      {
        "channel_id": 15,
        "content": "Shorter version for Bluesky."
      }
    ]
  }'
Response
{
  "data": {
    "id": 318,
    "content": "New on the blog: how we plan a month of posts in one afternoon.",
    "status": "scheduled",
    "scheduled_at": "2026-11-03T09:30:00.000000Z",
    "created_at": "2026-10-16T09:02:11.000000Z",
    "updated_at": "2026-10-16T09:02:11.000000Z",
    "author": { "id": 7, "name": "Sam Rivera" },
    "latest_review": null,
    "media": [
      {
        "id": 42,
        "name": "cover.png",
        "mime_type": "image/png",
        "kind": "image",
        "size": 284113,
        "width": 1600,
        "height": 900,
        "duration": null,
        "alt_text": "A calendar with a month of posts",
        "url": "/api/v1/media/42/file",
        "created_at": "2026-10-16T08:58:41.000000Z"
      }
    ],
    "targets": [
      {
        "id": 901,
        "channel_id": 12,
        "status": "pending",
        "content": null,
        "settings": {},
        "error": null,
        "external_url": null,
        "channel": {
          "id": 12,
          "platform": "linkedin",
          "name": "Acme",
          "username": "acme",
          "avatar_url": "https://media.licdn.com/dms/image/acme.png",
          "needs_reconnect": false,
          "posting_times": ["09:00", "12:30", "17:00"],
          "reconnect_by": null,
          "created_at": "2026-09-02T10:14:03.000000Z"
        }
      },
      {
        "id": 902,
        "channel_id": 15,
        "status": "pending",
        "content": "Shorter version for Bluesky.",
        "settings": {},
        "error": null,
        "external_url": null,
        "channel": {
          "id": 15,
          "platform": "bluesky",
          "name": "Acme",
          "username": "acme.bsky.social",
          "avatar_url": null,
          "needs_reconnect": false,
          "posting_times": ["10:00", "16:00"],
          "reconnect_by": null,
          "created_at": "2026-09-02T10:14:03.000000Z"
        }
      }
    ]
  }
}

Delete a post

DELETE/api/public/v1/posts/{id}

Deletes the post from Postzora. Copies already published on the networks stay there; delete those on the network itself.

A post that is publishing right now can’t be deleted until it finishes.

curl -X DELETE https://api.postzora.com/api/public/v1/posts/318 \
  -H "Authorization: Bearer $POSTZORA_API_KEY"
204
# No content

Questions about the API? Email [email protected].