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.
statusstringdraft,pending_approval(waiting for an admin),scheduled,publishing,publishedorfailed(at least one channel failed).scheduled_atstring or null- When it publishes, or published.
authorobject- Who created it:
idandname. latest_reviewobject or null- The last approval decision:
decision,commentandreviewer. mediaarray- Attached media objects, in order.
targets[]array- One per channel:
status(pending,publishing,publishedorfailed), its owncontentandsettings, the network’serrorif it failed, andexternal_url, the link to the published post.
{
"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.
pending_approval and the workspace admins are asked to review it.Body parameters
statusstringRequireddraftto save it for later, orscheduledto publish it atscheduled_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."
}
]
}'const response = await fetch('https://api.postzora.com/api/public/v1/posts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTZORA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
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.' },
],
}),
})
const { data } = await response.json()import os
import requests
response = requests.post(
"https://api.postzora.com/api/public/v1/posts",
headers={"Authorization": f"Bearer {os.environ['POSTZORA_API_KEY']}"},
json={
"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.",
},
],
},
)
data = response.json()["data"]{
"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"const response = await fetch(`https://api.postzora.com/api/public/v1/posts?${new URLSearchParams({
from: '2026-11-01T00:00:00Z',
to: '2026-11-30T23:59:59Z',
status: 'scheduled',
})}`, {
headers: {
Authorization: `Bearer ${process.env.POSTZORA_API_KEY}`,
},
})
const { data } = await response.json()import os
import requests
response = requests.get(
"https://api.postzora.com/api/public/v1/posts",
headers={"Authorization": f"Bearer {os.environ['POSTZORA_API_KEY']}"},
params={
"from": "2026-11-01T00:00:00Z",
"to": "2026-11-30T23:59:59Z",
"status": "scheduled",
},
)
data = response.json()["data"]{
"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"const response = await fetch('https://api.postzora.com/api/public/v1/posts/318', {
headers: {
Authorization: `Bearer ${process.env.POSTZORA_API_KEY}`,
},
})
const { data } = await response.json()import os
import requests
response = requests.get(
"https://api.postzora.com/api/public/v1/posts/318",
headers={"Authorization": f"Bearer {os.environ['POSTZORA_API_KEY']}"},
)
data = response.json()["data"]{
"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."
}
]
}'const response = await fetch('https://api.postzora.com/api/public/v1/posts/318', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.POSTZORA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
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.' },
],
}),
})
const { data } = await response.json()import os
import requests
response = requests.put(
"https://api.postzora.com/api/public/v1/posts/318",
headers={"Authorization": f"Bearer {os.environ['POSTZORA_API_KEY']}"},
json={
"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.",
},
],
},
)
data = response.json()["data"]{
"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"const response = await fetch('https://api.postzora.com/api/public/v1/posts/318', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.POSTZORA_API_KEY}`,
},
})import os
import requests
response = requests.delete(
"https://api.postzora.com/api/public/v1/posts/318",
headers={"Authorization": f"Bearer {os.environ['POSTZORA_API_KEY']}"},
)# No contentQuestions about the API? Email [email protected].