New here? Read the API guide first: it explains keys, errors and limits. Every request needs Authorization: pz_live_YOUR_KEY. Download the whole reference as OpenAPI 3.1.
Endpoints
Account: GET /is-connected
Channels and customers: GET /integrations, GET /groups, GET /integration-settings/{id}, POST /integration-trigger/{id}, GET /social/{integration}, DELETE /integrations/{id}
Media: POST /upload, POST /upload-from-url, GET /media
Posts: GET /find-slot, GET /find-slot/{id}, GET /posts, POST /posts, GET /posts/{id}, PUT /posts/{id}/date, PUT /posts/{id}/status, PUT /posts/{id}/settings, POST /posts/{id}/retry, GET /posts/{id}/missing, PUT /posts/{id}/release-id, DELETE /posts/{id}, DELETE /posts/group/{group}
Analytics: GET /analytics/{integration}, GET /analytics/post/{postId}
Notifications and networks: GET /notifications, GET /networks, GET /rules/{identifier}
Account
Check your key.
GET /is-connected
Check that your key works. Answers {"connected": true} when the key is valid. Use it to test a new key or a connection in an automation tool.
Example request
curl "https://app.postozo.com/public/v1/is-connected" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
{
"connected": true
}
Channels and customers
The social accounts connected to your workspace, and the customers (clients) they belong to.
GET /integrations
List connected channels. Every channel (a connected account, Page or profile) in the workspace. Use the id as the channel id in posts. customer is only there when the channel belongs to a customer.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
group | query | string | No | Only channels of this customer id (Postiz calls customers "groups"). |
Example request
curl "https://app.postozo.com/public/v1/integrations" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
[
{
"id": "de53f029-255d-4104-b090-974efcca2c86",
"name": "acme",
"identifier": "demo",
"picture": null,
"disabled": false,
"refreshNeeded": false,
"inBetweenSteps": false,
"profile": "acme",
"customer": {
"id": "c62b4bac-2eac-4174-aeb3-2265c1355be7",
"name": "Bean There Cafe"
}
},
{
"id": "f451da5e-2cd3-4674-bbbb-dc6bc5e12b55",
"name": "acme-news",
"identifier": "demo",
"picture": null,
"disabled": false,
"refreshNeeded": false,
"inBetweenSteps": false,
"profile": "acme-news"
}
]
identifier is the network id (see GET /networks). refreshNeeded or inBetweenSteps true means the channel must be reconnected in the app before it can post. disabled true means it is switched off.
GET /groups
List customers. Customers group channels, for example 1 customer per client of an agency. Sorted by name.
Example request
curl "https://app.postozo.com/public/v1/groups" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
[
{
"id": "c62b4bac-2eac-4174-aeb3-2265c1355be7",
"name": "Bean There Cafe"
}
]
GET /integration-settings/{id}
Rules and settings for 1 channel. The posting rules of the channel's network: the character limit, whether it needs media, how many files a post may have, whether follow-up comments work, the settings a post can carry (some are required, for example a title on YouTube) and the helper tools you can run with POST /integration-trigger/{id}. Read this before you post to a network for the first time.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Channel id. |
Example request
curl "https://app.postozo.com/public/v1/integration-settings/de53f029-255d-4104-b090-974efcca2c86" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
{
"output": {
"rules": "Demo network for testing. Max 1000 characters. Supports threads and media.",
"maxLength": 1000,
"needs": null,
"maxMedia": 10,
"comments": true,
"settings": [
{
"key": "audience",
"label": "Audience",
"type": "select",
"options": [
"Everyone",
"Followers"
],
"help": "Only for testing settings."
}
],
"tools": [
{
"methodName": "echo",
"description": "Returns what you send. For testing tool calls.",
"dataSchema": [
{
"key": "text",
"type": "string",
"description": "Any text"
}
]
}
]
}
}
needs is null, "media" (at least 1 photo or video) or "video". settings is the text "No additional settings required" when the network has none. Each setting has a key to use in the post's settings, a type (text, select, toggle, date and others) and required when it must be filled.
POST /integration-trigger/{id}
Run a network helper tool. Some settings need a value from the network, for example a Pinterest board or a Reddit flair. The tools for a channel are listed in GET /integration-settings/{id} under tools.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Channel id. |
Body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
methodName | string | Yes | The tool name (methodName from the settings answer). |
data | object | No | Input for the tool, keys from its dataSchema. |
Example request
curl -X POST "https://app.postozo.com/public/v1/integration-trigger/de53f029-255d-4104-b090-974efcca2c86" \
-H "Authorization: pz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"methodName": "echo",
"data": {
"text": "hi"
}
}'
Example response (200)
{
"output": {
"echo": "hi"
}
}
output is whatever the tool returns, for example a list of boards.
GET /social/{integration}
Get a sign-in link to connect a channel. Returns the network's sign-in address. Open it in a browser, sign in to the network and allow access; the new channel then appears in the workspace. Only for networks that connect with a sign-in link. Networks that connect with a form (Bluesky, Mastodon, Telegram and others) return 400; connect those in the app.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
integration | path | string | Yes | Network id, for example x or linkedin-page (see GET /networks). |
customer | query | string | No | Put the new channel under this customer id. |
refresh | query | string | No | Channel id to reconnect instead of adding a new channel. |
Example request
curl "https://app.postozo.com/public/v1/social/x" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
{
"url": "https://x.com/i/oauth2/authorize?response_type=code&client_id=YOUR_X_CLIENT_ID&redirect_uri=https%3A%2F%2Fapp.postozo.com%2Fintegrations%2Fsocial%2Fx&scope=tweet.read+tweet.write+users.read+offline.access+media.write+like.read&state=OvsF9Xk5X4HuLRulFx90tuUx&code_challenge=SNCg1EXirhq2ueAp5gifrqkwsl9uVOy42lEiL1hxEFA&code_challenge_method=S256"
}
A network that connects with a form
curl "https://app.postozo.com/public/v1/social/demo" \
-H "Authorization: pz_live_YOUR_KEY"
{
"error": "This network connects with a form, not a link. Use the app to connect it.",
"msg": "This network connects with a form, not a link. Use the app to connect it."
}
The link only works when the server has keys for that network. Without them the answer is 400 with a message that says which keys are missing.
DELETE /integrations/{id}
Disconnect a channel. Removes the channel. Its drafts, queued, failed and unconfirmed posts are deleted too. Posts that are already live stay on the network. Needs an owner or admin key (the workspace API key counts as admin).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Channel id. |
Example request
curl -X DELETE "https://app.postozo.com/public/v1/integrations/a326eb7f-3bbe-4886-8a5c-06fb3c572859" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
{
"success": true
}
Media
Upload images and videos to use in posts.
POST /upload
Upload a file. Send 1 image or video as multipart form data in the field file. Accepted: JPG, PNG, GIF, WEBP, MP4, MOV and WEBM. The type is read from the file itself, not the name. Use the returned id (and path) in a post.
Body (multipart/form-data):
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | The image or video. |
Example request
curl -X POST "https://app.postozo.com/public/v1/upload" \
-H "Authorization: pz_live_YOUR_KEY" \
-F "file=@photo.png"
Example response (200)
{
"id": "281e94f5-fe31-40ed-8718-8dd3d1fbcd6d",
"name": "photo.png",
"originalName": "photo.png",
"path": "https://app.postozo.com/uploads/ee50beea-d095-49ff-a79c-cb7c6768b73d/2026-09/281e94f5-fe31-40ed-8718-8dd3d1fbcd6d.png",
"thumbnail": "https://app.postozo.com/uploads/ee50beea-d095-49ff-a79c-cb7c6768b73d/2026-09/281e94f5-fe31-40ed-8718-8dd3d1fbcd6d.png",
"alt": "",
"type": "image",
"status": "ready"
}
path is the public address of the file. thumbnail is the same address for images and null for videos.
POST /upload-from-url
Import a file from a public URL. Downloads an image or video from a public http or https address into the media library. The address must open the file itself, not a page about it. Addresses on private networks are refused.
Body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Public address of the file. |
Example request
curl -X POST "https://app.postozo.com/public/v1/upload-from-url" \
-H "Authorization: pz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://app.postozo.com/uploads/ee50beea-d095-49ff-a79c-cb7c6768b73d/2026-09/281e94f5-fe31-40ed-8718-8dd3d1fbcd6d.png"
}'
Example response (200)
{
"id": "0b6914a1-8a0c-4d05-a4d6-1caacd698cbf",
"name": "281e94f5-fe31-40ed-8718-8dd3d1fbcd6d.png",
"originalName": "281e94f5-fe31-40ed-8718-8dd3d1fbcd6d.png",
"path": "https://app.postozo.com/uploads/ee50beea-d095-49ff-a79c-cb7c6768b73d/2026-09/281e94f5-fe31-40ed-8718-8dd3d1fbcd6d.png",
"thumbnail": "https://app.postozo.com/uploads/ee50beea-d095-49ff-a79c-cb7c6768b73d/2026-09/281e94f5-fe31-40ed-8718-8dd3d1fbcd6d.png",
"alt": "",
"type": "image",
"status": "ready"
}
The file name is taken from the last part of the address.
GET /media
List the media library. Files in the workspace library, newest first, 40 per page. Client members get an empty list.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
page | query | integer | No | Page number, starting at 1. Default 1. |
search | query | string | No | Only files whose name or alt text contains this text. |
Example request
curl "https://app.postozo.com/public/v1/media?page=1" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
[
{
"id": "281e94f5-fe31-40ed-8718-8dd3d1fbcd6d",
"name": "photo.png",
"originalName": "photo.png",
"path": "https://app.postozo.com/uploads/ee50beea-d095-49ff-a79c-cb7c6768b73d/2026-09/281e94f5-fe31-40ed-8718-8dd3d1fbcd6d.png",
"thumbnail": "https://app.postozo.com/uploads/ee50beea-d095-49ff-a79c-cb7c6768b73d/2026-09/281e94f5-fe31-40ed-8718-8dd3d1fbcd6d.png",
"alt": "",
"type": "image",
"status": "ready"
}
]
There is no total count. When a page has fewer than 40 files, it is the last page.
Posts
Create, read, move, retry and delete posts.
GET /find-slot
Next free time for any channel. The next free posting time across all channels. It uses the posting times set on each channel (9:00 when none are set). Posting times are times of day in local time: the tzOffsetMinutes you send, or else the time zone in the profile of the person the key belongs to (for the workspace API key: the owner; UTC when none is set; daylight saving time is followed). It skips times that already have a queued or draft post on those channels, starts at least 5 minutes from now and looks 90 days ahead. The MCP tool find_free_slot gives the same answer for the same input.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
tzOffsetMinutes | query | integer | No | Your time zone as minutes to add to UTC to get local time, from -840 to 840: 330 for India (UTC+5:30), -300 for New York in winter. Leave it out to use the profile time zone. Another value gives 400. |
Example request
curl "https://app.postozo.com/public/v1/find-slot?tzOffsetMinutes=330" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
{
"date": "2026-10-01T03:30:00.000Z"
}
The date is UTC. In the example, a channel posting time of 9:00 in India (UTC+5:30) is 03:30 UTC.
GET /find-slot/{id}
Next free time for 1 channel. Same as GET /find-slot, for 1 channel.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Channel id. |
tzOffsetMinutes | query | integer | No | Minutes to add to UTC to get local time, as in GET /find-slot. Default: the profile time zone. |
Example request
curl "https://app.postozo.com/public/v1/find-slot/de53f029-255d-4104-b090-974efcca2c86" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
{
"date": "2026-09-30T09:00:00.000Z"
}
The example workspace has its profile time zone set to UTC, so 9:00 is 09:00 UTC.
An id that is not an id
curl "https://app.postozo.com/public/v1/find-slot/not-an-id" \
-H "Authorization: pz_live_YOUR_KEY"
{
"error": "One of the ids is not valid.",
"msg": "One of the ids is not valid.",
"fix": "Check the id. Copy it again from the list it came from."
}
GET /posts
List posts between 2 dates. Returns 1 row per channel for every post whose time falls between startDate and endDate, oldest first. Follow-up comments are not listed; read them with GET /posts/{id}. There is no paging: ask for a shorter date range to get fewer rows.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
startDate | query | string | No | ISO 8601 date. Default: 30 days ago. |
endDate | query | string | No | ISO 8601 date. Default: 30 days from now. |
customer | query | string | No | Only posts on channels of this customer id. |
Example request
curl "https://app.postozo.com/public/v1/posts?startDate=2026-09-28T00%3A00%3A00.000Z&endDate=2026-10-05T00%3A00%3A00.000Z" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
{
"posts": [
{
"id": "5001b946-c142-4ab7-b376-188c71272521",
"content": "Main post",
"publishDate": "2026-10-01T09:30:00.000Z",
"releaseURL": null,
"releaseId": null,
"state": "QUEUE",
"status": "queued",
"error": null,
"intervalInDays": null,
"group": "98c49d36-7dfd-439b-a857-e9a8e7e3f4a2",
"creationMethod": "API",
"settings": {
"__type": "demo",
"audience": "Followers"
},
"tags": [
{
"tag": {
"id": "48bae191-d9eb-4adb-9ee0-be9c797a8f16",
"name": "Launch",
"color": "#612bd3"
}
}
],
"image": [
{
"id": "281e94f5-fe31-40ed-8718-8dd3d1fbcd6d",
"path": "https://app.postozo.com/uploads/ee50beea-d095-49ff-a79c-cb7c6768b73d/2026-09/281e94f5-fe31-40ed-8718-8dd3d1fbcd6d.png",
"alt": ""
}
],
"integration": {
"id": "de53f029-255d-4104-b090-974efcca2c86",
"providerIdentifier": "demo",
"name": "acme",
"picture": null
}
}
]
}
state is the Postiz value: DRAFT, QUEUE (queued, publishing or waiting for the network), PUBLISHED or ERROR (failed or could not be confirmed). status is the exact state: draft, queued, publishing, pending, published, error or unconfirmed. error is { "message", "fix" } when the post failed. id is the per-channel post id and group the id of the whole post.
POST /posts
Create or edit a post. Creates 1 post for 1 or more channels. The body is the Postiz body: 1 entry in posts per channel, each with its own text in value. The 1st item of value is the post, the next items are follow-up comments (a thread on X, Threads, Bluesky and Mastodon) posted after it, each delay minutes after the one before. HTML in content is turned into plain text. Every channel is checked against its network's rules before anything is saved; on a problem nothing is saved and the answer is 400 with a list in errors. To edit a post, send the same body with groupId (or group in a posts entry): channels that are already published or publishing are kept as they are.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key | header | string | No | Optional. Any unique value up to 255 characters, for example a UUID. Send the same key again (same workspace, same body, within 24 hours) and you get the first answer again instead of a 2nd post. See the notes below. |
Body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
type | string | No | draft (save only), schedule (publish at date), now (publish right away) or review (send to the client for approval first). Default schedule. Posts for a customer with approval turned on go to review even with schedule. |
date | string | No | When to publish, ISO 8601 with a time zone, for example 2026-10-01T09:30:00+05:30. Ignored for now. When left out, the current time is used. For schedule and review it cannot be more than 1 minute in the past. |
posts[] | array | Yes | 1 entry per channel. |
posts[].integration | object | Yes | The channel: { "id": "CHANNEL_ID" }. channelId at the entry level also works. |
posts[].integration.id | string | Yes | Channel id. |
posts[].value[] | array | Yes | The post, then its follow-up comments, in order. |
posts[].value[].content | string | Yes | The text. HTML is turned into plain text. |
posts[].value[].image | array | No | Media from POST /upload: [{ "id": "MEDIA_ID" }]. path and alt are also read. media works too. |
posts[].value[].delay | number | No | Minutes to wait after the item before. Ignored on the 1st item. |
posts[].settings | object | No | Network settings for this channel, keys from GET /integration-settings/{id}. __type is ignored. |
posts[].group | string | No | Id of an existing post to edit. |
tags | array | No | Tags. A string is matched to an existing tag by id or name. An object like { "value": "launch", "label": "Launch" } is matched by value, and a tag named label is created when none matches. |
shortLink | boolean | No | Replace links with short links that count clicks. Default false. |
inter | integer | No | Repeat the post every N days (1 to 365). repeatDays also works. |
groupId | string | No | Id of an existing post to edit. |
creationMethod | string | No | Shown in the post history: api, cli, mcp or agent. Default: how you signed in. |
Example request
curl -X POST "https://app.postozo.com/public/v1/posts" \
-H "Authorization: pz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "schedule",
"date": "2026-10-01T09:30:00.000Z",
"shortLink": false,
"tags": [
{
"value": "launch",
"label": "Launch"
}
],
"posts": [
{
"integration": {
"id": "de53f029-255d-4104-b090-974efcca2c86"
},
"value": [
{
"content": "<p>Main post</p>",
"image": [
{
"id": "281e94f5-fe31-40ed-8718-8dd3d1fbcd6d"
}
]
},
{
"content": "First comment",
"delay": 5
}
],
"settings": {
"audience": "Followers"
}
}
]
}'
Example response (200)
[
{
"postId": "5001b946-c142-4ab7-b376-188c71272521",
"group": "98c49d36-7dfd-439b-a857-e9a8e7e3f4a2",
"integration": "de53f029-255d-4104-b090-974efcca2c86",
"status": "queued",
"previewUrl": "https://app.postozo.com/p/98c49d36-7dfd-439b-a857-e9a8e7e3f4a2?t=662b7122e80a48658ffbc75ededf1efc"
}
]
1 row per channel. postId is the per-channel post id, group the id of the whole post (use either in the other post endpoints), status the channel state (draft or queued), and previewUrl a link anyone can open to see the post and, for review posts, approve it.
A simpler body (also accepted)
curl -X POST "https://app.postozo.com/public/v1/posts" \
-H "Authorization: pz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "draft",
"content": "Same text on 2 channels",
"channels": [
"de53f029-255d-4104-b090-974efcca2c86",
"f451da5e-2cd3-4674-bbbb-dc6bc5e12b55"
],
"thread": [
{
"content": "A follow-up comment",
"delay": 2
}
]
}'
[
{
"postId": "c5eb1c30-583d-4b70-9c97-1ecd59a9eaef",
"group": "5b76fde2-9551-494e-b9be-a4aebc2a984e",
"integration": "de53f029-255d-4104-b090-974efcca2c86",
"status": "draft",
"previewUrl": "https://app.postozo.com/p/5b76fde2-9551-494e-b9be-a4aebc2a984e?t=847d5b8457654b21830a6b1a6797f701"
},
{
"postId": "fff25ca6-1f78-4355-a0fe-ea180f8cddb8",
"group": "5b76fde2-9551-494e-b9be-a4aebc2a984e",
"integration": "f451da5e-2cd3-4674-bbbb-dc6bc5e12b55",
"status": "draft",
"previewUrl": "https://app.postozo.com/p/5b76fde2-9551-494e-b9be-a4aebc2a984e?t=847d5b8457654b21830a6b1a6797f701"
}
]
In the simpler body, channels is a list of channel ids or of objects { "channelId", "content", "media", "settings", "thread" } that change the post for 1 channel. media is a list of media ids.
A post that breaks a network rule (content is 1,200 characters)
curl -X POST "https://app.postozo.com/public/v1/posts" \
-H "Authorization: pz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"schedule","date":"2026-10-01T09:30:00.000Z","posts":[{"integration":{"id":"de53f029-255d-4104-b090-974efcca2c86"},"value":[{"content":"xxxx..."}],"settings":{}}]}'
{
"error": "Please fix these before saving.",
"msg": "Please fix these before saving.",
"errors": [
{
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"channelName": "acme (Demo)",
"message": "acme (Demo): the post is too long, 1200 of 1000 characters."
}
]
}
With an Idempotency-Key (send it again and you get this same answer)
curl -X POST "https://app.postozo.com/public/v1/posts" \
-H "Authorization: pz_live_YOUR_KEY" \
-H "Idempotency-Key: order-1001" \
-H "Content-Type: application/json" \
-d '{
"type": "draft",
"posts": [
{
"integration": {
"id": "de53f029-255d-4104-b090-974efcca2c86"
},
"value": [
{
"content": "Only once, even if I retry"
}
],
"settings": {}
}
]
}'
[
{
"postId": "8e1f0c7a-3b5d-4f2e-9a61-2d7c4b8e5f10",
"group": "1c9e7d5b-2a4f-4e8d-b6c3-7f0a9e2d4b61",
"integration": "de53f029-255d-4104-b090-974efcca2c86",
"status": "draft",
"previewUrl": "https://app.postozo.com/p/1c9e7d5b-2a4f-4e8d-b6c3-7f0a9e2d4b61?t=3b7d9f1a5c2e4b6d8f0a1c3e5b7d9f2a"
}
]
The 2nd request with the same key and body gets status 200, this body and the header Idempotent-Replayed: true. No 2nd post is made.
The same Idempotency-Key with a different body
curl -X POST "https://app.postozo.com/public/v1/posts" \
-H "Authorization: pz_live_YOUR_KEY" \
-H "Idempotency-Key: order-1001" \
-H "Content-Type: application/json" \
-d '{
"type": "draft",
"posts": [
{
"integration": {
"id": "de53f029-255d-4104-b090-974efcca2c86"
},
"value": [
{
"content": "Some other text"
}
],
"settings": {}
}
]
}'
{
"error": "This Idempotency-Key was already used with a different request body.",
"msg": "This Idempotency-Key was already used with a different request body.",
"code": "idempotency_key_reused",
"fix": "Use a new Idempotency-Key for a new post. Send the same key only to repeat the same request."
}
Limit: 90 requests an hour per workspace on this endpoint (replays with an Idempotency-Key count too); over it the answer is 429 with a Retry-After header. Idempotency-Key: the key is kept for 24 hours per workspace. The same key with the same body (the order of the JSON keys does not matter) returns the first answer. The same key with a different body is 409. While the first request is still running, a 2nd 1 with the same key is 409 "A request with this Idempotency-Key is still running." Only answers that worked are kept: when the first request fails (for example 400), nothing is kept and you can fix the body and send the same key again. Without the header, 2 requests make 2 posts. A post is never sent twice to a network: if the network does not answer, the post is checked on the network or marked "could not confirm" instead of being sent again.
GET /posts/{id}
Get 1 post. The whole post: every channel with its text, media, settings, state, live link and error with a fix, the follow-up comments, the comments from your team and client, and the history. The id can be a per-channel post id or the group id.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Post id or group id. |
Example request
curl "https://app.postozo.com/public/v1/posts/98c49d36-7dfd-439b-a857-e9a8e7e3f4a2" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
{
"id": "98c49d36-7dfd-439b-a857-e9a8e7e3f4a2",
"status": "scheduled",
"approvalStatus": null,
"approvedBy": null,
"publishAt": "2026-10-01T09:30:00.000Z",
"repeatDays": null,
"shortLink": false,
"creationMethod": "api",
"createdAt": "2026-09-29T18:23:39.694Z",
"updatedAt": "2026-09-29T18:23:39.694Z",
"tags": [
{
"id": "48bae191-d9eb-4adb-9ee0-be9c797a8f16",
"name": "Launch",
"color": "#612bd3"
}
],
"shareUrl": "https://app.postozo.com/p/98c49d36-7dfd-439b-a857-e9a8e7e3f4a2?t=662b7122e80a48658ffbc75ededf1efc",
"channels": [
{
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"channelName": "acme",
"provider": "demo",
"picture": null,
"username": "acme",
"root": {
"id": "5001b946-c142-4ab7-b376-188c71272521",
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"position": 0,
"content": "Main post",
"media": [
{
"id": "281e94f5-fe31-40ed-8718-8dd3d1fbcd6d",
"alt": "",
"url": "https://app.postozo.com/uploads/ee50beea-d095-49ff-a79c-cb7c6768b73d/2026-09/281e94f5-fe31-40ed-8718-8dd3d1fbcd6d.png",
"mime": "image/png",
"name": "photo.png",
"size": 70,
"type": "image",
"width": 1,
"height": 1
}
],
"settings": {
"audience": "Followers"
},
"delay": 0,
"state": "queued",
"releaseId": null,
"releaseUrl": null,
"publishedAt": null,
"error": null,
"held": null,
"heldAt": null,
"attempts": 0
},
"thread": [
{
"id": "de2b4583-65d9-4f69-8489-e9109664369a",
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"position": 1,
"content": "First comment",
"media": [],
"settings": {},
"delay": 5,
"state": "queued",
"releaseId": null,
"releaseUrl": null,
"publishedAt": null,
"error": null,
"held": null,
"heldAt": null,
"attempts": 0
}
]
}
],
"comments": [],
"activity": [
{
"actor": "API",
"action": "created",
"detail": "schedule via api",
"created_at": "2026-09-29T18:23:39.700Z"
}
]
}
status is the state of the whole post: draft, review, scheduled, changes (the client asked for changes), published, partial (some channels published, some failed) or error. channels[].root.state is the state on 1 channel. held is set when publishing waits because of billing or a plan limit.
PUT /posts/{id}/date
Move a post. Moves every channel of the post that is a draft, queued or failed to a new time. Failed channels are queued again. Channels that are published stay as they are.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Post id or group id. |
Body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
date | string | Yes | New time, ISO 8601. Not more than 1 minute in the past. |
Example request
curl -X PUT "https://app.postozo.com/public/v1/posts/98c49d36-7dfd-439b-a857-e9a8e7e3f4a2/date" \
-H "Authorization: pz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"date": "2026-10-02T09:30:00.000Z"
}'
Example response (200)
{
"id": "98c49d36-7dfd-439b-a857-e9a8e7e3f4a2",
"status": "scheduled",
"approvalStatus": null,
"approvedBy": null,
"publishAt": "2026-10-02T09:30:00.000Z",
"repeatDays": null,
"shortLink": false,
"creationMethod": "api",
"createdAt": "2026-09-29T18:23:39.694Z",
"updatedAt": "2026-09-29T18:23:39.694Z",
"tags": [
{
"id": "48bae191-d9eb-4adb-9ee0-be9c797a8f16",
"name": "Launch",
"color": "#612bd3"
}
],
"shareUrl": "https://app.postozo.com/p/98c49d36-7dfd-439b-a857-e9a8e7e3f4a2?t=662b7122e80a48658ffbc75ededf1efc",
"channels": [
{
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"channelName": "acme",
"provider": "demo",
"picture": null,
"username": "acme",
"root": {
"id": "5001b946-c142-4ab7-b376-188c71272521",
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"position": 0,
"content": "Main post",
"media": [
{
"id": "281e94f5-fe31-40ed-8718-8dd3d1fbcd6d",
"alt": "",
"url": "https://app.postozo.com/uploads/ee50beea-d095-49ff-a79c-cb7c6768b73d/2026-09/281e94f5-fe31-40ed-8718-8dd3d1fbcd6d.png",
"mime": "image/png",
"name": "photo.png",
"size": 70,
"type": "image",
"width": 1,
"height": 1
}
],
"settings": {
"audience": "Followers"
},
"delay": 0,
"state": "queued",
"releaseId": null,
"releaseUrl": null,
"publishedAt": null,
"error": null,
"held": null,
"heldAt": null,
"attempts": 0
},
"thread": [
{
"id": "de2b4583-65d9-4f69-8489-e9109664369a",
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"position": 1,
"content": "First comment",
"media": [],
"settings": {},
"delay": 5,
"state": "queued",
"releaseId": null,
"releaseUrl": null,
"publishedAt": null,
"error": null,
"held": null,
"heldAt": null,
"attempts": 0
}
]
}
],
"comments": [],
"activity": [
{
"actor": "API",
"action": "created",
"detail": "schedule via api",
"created_at": "2026-09-29T18:23:39.700Z"
}
]
}
The whole post, as in GET /posts/{id}.
PUT /posts/{id}/status
Switch between draft and scheduled. draft stops queued and failed channels and keeps them as drafts. schedule checks the drafts against the network rules and queues them for the post's time (or now, when that time has passed).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Post id or group id. |
Body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
status | string | Yes | draft or schedule. |
Example request
curl -X PUT "https://app.postozo.com/public/v1/posts/5b76fde2-9551-494e-b9be-a4aebc2a984d/status" \
-H "Authorization: pz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "schedule"
}'
Example response (200)
{
"id": "5b76fde2-9551-494e-b9be-a4aebc2a984d",
"status": "scheduled",
"approvalStatus": null,
"approvedBy": null,
"publishAt": "2026-10-01T09:30:00.000Z",
"repeatDays": null,
"shortLink": false,
"creationMethod": "api",
"createdAt": "2026-09-29T18:23:39.694Z",
"updatedAt": "2026-09-29T18:23:39.694Z",
"tags": [
{
"id": "48bae191-d9eb-4adb-9ee0-be9c797a8f16",
"name": "Launch",
"color": "#612bd3"
}
],
"shareUrl": "https://app.postozo.com/p/98c49d36-7dfd-439b-a857-e9a8e7e3f4a2?t=662b7122e80a48658ffbc75ededf1efc",
"channels": [
{
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"channelName": "acme",
"provider": "demo",
"picture": null,
"username": "acme",
"root": {
"id": "5001b946-c142-4ab7-b376-188c71272521",
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"position": 0,
"content": "Main post",
"media": [
{
"id": "281e94f5-fe31-40ed-8718-8dd3d1fbcd6d",
"alt": "",
"url": "https://app.postozo.com/uploads/ee50beea-d095-49ff-a79c-cb7c6768b73d/2026-09/281e94f5-fe31-40ed-8718-8dd3d1fbcd6d.png",
"mime": "image/png",
"name": "photo.png",
"size": 70,
"type": "image",
"width": 1,
"height": 1
}
],
"settings": {
"audience": "Followers"
},
"delay": 0,
"state": "queued",
"releaseId": null,
"releaseUrl": null,
"publishedAt": null,
"error": null,
"held": null,
"heldAt": null,
"attempts": 0
},
"thread": [
{
"id": "de2b4583-65d9-4f69-8489-e9109664369a",
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"position": 1,
"content": "First comment",
"media": [],
"settings": {},
"delay": 5,
"state": "queued",
"releaseId": null,
"releaseUrl": null,
"publishedAt": null,
"error": null,
"held": null,
"heldAt": null,
"attempts": 0
}
]
}
],
"comments": [],
"activity": [
{
"actor": "API",
"action": "created",
"detail": "schedule via api",
"created_at": "2026-09-29T18:23:39.700Z"
}
]
}
The whole post, as in GET /posts/{id}.
PUT /posts/{id}/settings
Change network settings of 1 channel. Merges new settings into 1 channel's post, for example a YouTube title. Only before it publishes. With a group id, the first channel of the post is changed, so send the per-channel post id when the post has more than 1 channel.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Per-channel post id (or group id). |
Body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
settings | object | Yes | Keys from GET /integration-settings/{id}. Keys you leave out keep their value. |
Example request
curl -X PUT "https://app.postozo.com/public/v1/posts/5001b946-c142-4ab7-b376-188c71272521/settings" \
-H "Authorization: pz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"settings": {
"audience": "Everyone"
}
}'
Example response (200)
{
"id": "98c49d36-7dfd-439b-a857-e9a8e7e3f4a2",
"status": "scheduled",
"approvalStatus": null,
"approvedBy": null,
"publishAt": "2026-10-01T09:30:00.000Z",
"repeatDays": null,
"shortLink": false,
"creationMethod": "api",
"createdAt": "2026-09-29T18:23:39.694Z",
"updatedAt": "2026-09-29T18:23:39.694Z",
"tags": [
{
"id": "48bae191-d9eb-4adb-9ee0-be9c797a8f16",
"name": "Launch",
"color": "#612bd3"
}
],
"shareUrl": "https://app.postozo.com/p/98c49d36-7dfd-439b-a857-e9a8e7e3f4a2?t=662b7122e80a48658ffbc75ededf1efc",
"channels": [
{
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"channelName": "acme",
"provider": "demo",
"picture": null,
"username": "acme",
"root": {
"id": "5001b946-c142-4ab7-b376-188c71272521",
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"position": 0,
"content": "Main post",
"media": [
{
"id": "281e94f5-fe31-40ed-8718-8dd3d1fbcd6d",
"alt": "",
"url": "https://app.postozo.com/uploads/ee50beea-d095-49ff-a79c-cb7c6768b73d/2026-09/281e94f5-fe31-40ed-8718-8dd3d1fbcd6d.png",
"mime": "image/png",
"name": "photo.png",
"size": 70,
"type": "image",
"width": 1,
"height": 1
}
],
"settings": {
"audience": "Everyone"
},
"delay": 0,
"state": "queued",
"releaseId": null,
"releaseUrl": null,
"publishedAt": null,
"error": null,
"held": null,
"heldAt": null,
"attempts": 0
},
"thread": [
{
"id": "de2b4583-65d9-4f69-8489-e9109664369a",
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"position": 1,
"content": "First comment",
"media": [],
"settings": {},
"delay": 5,
"state": "queued",
"releaseId": null,
"releaseUrl": null,
"publishedAt": null,
"error": null,
"held": null,
"heldAt": null,
"attempts": 0
}
]
}
],
"comments": [],
"activity": [
{
"actor": "API",
"action": "created",
"detail": "schedule via api",
"created_at": "2026-09-29T18:23:39.700Z"
}
]
}
The whole post, as in GET /posts/{id}.
POST /posts/{id}/retry
Retry failed channels. Queues again, right now, every channel of the post that failed or could not be confirmed, and follow-up comments that failed. Only retry an "unconfirmed" channel after you checked the post is not already live on the network.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Post id or group id. |
Example request
curl -X POST "https://app.postozo.com/public/v1/posts/374f5b42-c4e3-42ab-a357-99e1b5662d9e/retry" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
{
"id": "374f5b42-c4e3-42ab-a357-99e1b5662d9e",
"status": "scheduled",
"approvalStatus": null,
"approvedBy": null,
"publishAt": "2026-09-29T18:23:45.838Z",
"repeatDays": null,
"shortLink": false,
"creationMethod": "api",
"createdAt": "2026-09-29T18:23:39.694Z",
"updatedAt": "2026-09-29T18:23:39.694Z",
"tags": [],
"shareUrl": "https://app.postozo.com/p/374f5b42-c4e3-42ab-a357-99e1b5662d9e?t=ea21f38108c04d4d8ed7841e6bde5563",
"channels": [
{
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"channelName": "acme",
"provider": "demo",
"picture": null,
"username": "acme",
"root": {
"id": "d298b9c3-7588-4334-a924-141a451415d6",
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"position": 0,
"content": "This will fail #fail",
"media": [],
"settings": {},
"delay": 0,
"state": "queued",
"releaseId": null,
"releaseUrl": null,
"publishedAt": null,
"error": null,
"held": null,
"heldAt": null,
"attempts": 0
},
"thread": []
}
],
"comments": [],
"activity": [
{
"actor": "API",
"action": "created",
"detail": "now via api",
"created_at": "2026-09-29T18:23:42.807Z"
},
{
"actor": "System",
"action": "failed",
"detail": "acme: Demo network rejected the post (#fail in text).",
"created_at": "2026-09-29T18:23:42.813Z"
},
{
"actor": "API",
"action": "retried",
"detail": null,
"created_at": "2026-09-29T18:23:45.840Z"
}
]
}
The whole post, as in GET /posts/{id}.
GET /posts/{id}/missing
Look for a post on the network. When a channel is "unconfirmed" (the network did not answer, so we do not know if the post went live), this asks the network for a recent post with the same text. Networks that cannot search return an empty list.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Per-channel post id (or group id). |
Example request
curl "https://app.postozo.com/public/v1/posts/2bb35f95-cf65-4316-ae22-3903302a5f36/missing" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
[
{
"id": "demo_22dd2fbd1580",
"url": "https://app.postozo.com/demo/demo_22dd2fbd1580"
}
]
A list with 0 or 1 item: { "id", "url" } of the live post. Link it with PUT /posts/{id}/release-id.
PUT /posts/{id}/release-id
Link a live post you found yourself. Marks 1 channel's post as published with the id (and link) of the live post on the network. Use it after "unconfirmed" when you found the post yourself.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Per-channel post id (or group id). |
Body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
releaseId | string | Yes | Id of the live post on the network. |
releaseUrl | string | No | Link to the live post. |
Example request
curl -X PUT "https://app.postozo.com/public/v1/posts/56142ef7-01a9-45e2-99d8-024fceb76328/release-id" \
-H "Authorization: pz_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"releaseId": "1839267162345",
"releaseUrl": "https://x.com/acme/status/1839267162345"
}'
Example response (200)
{
"id": "389c32ac-8802-415b-8ca2-35bc29e5d881",
"status": "published",
"approvalStatus": null,
"approvedBy": null,
"publishAt": "2026-10-01T09:30:00.000Z",
"repeatDays": null,
"shortLink": false,
"creationMethod": "api",
"createdAt": "2026-09-29T18:23:39.694Z",
"updatedAt": "2026-09-29T18:23:39.694Z",
"tags": [],
"shareUrl": "https://app.postozo.com/p/389c32ac-8802-415b-8ca2-35bc29e5d881?t=0c1f6e0a7d3b4b0e9a4f5c2d8e7b6a19",
"channels": [
{
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"channelName": "acme",
"provider": "demo",
"picture": null,
"username": "acme",
"root": {
"id": "56142ef7-01a9-45e2-99d8-024fceb76328",
"channelId": "de53f029-255d-4104-b090-974efcca2c86",
"position": 0,
"content": "Found it myself",
"media": [],
"settings": {},
"delay": 0,
"state": "published",
"releaseId": "1839267162345",
"releaseUrl": "https://x.com/acme/status/1839267162345",
"publishedAt": "2026-09-29T18:40:12.410Z",
"error": null,
"held": null,
"heldAt": null,
"attempts": 0
},
"thread": []
}
],
"comments": [],
"activity": [
{
"actor": "API",
"action": "created",
"detail": "schedule via api",
"created_at": "2026-09-29T18:35:02.930Z"
},
{
"actor": "API",
"action": "linked live post",
"detail": "1839267162345",
"created_at": "2026-09-29T18:40:12.410Z"
}
]
}
The whole post, as in GET /posts/{id}.
DELETE /posts/{id}
Delete a post. Deletes the whole post from the calendar, on every channel, even when you send a per-channel post id. Queued channels will not publish. Posts already live stay on the network.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Post id or group id. |
Example request
curl -X DELETE "https://app.postozo.com/public/v1/posts/0f3e2d1c-8b7a-4c6d-9e5f-a1b2c3d4e5f6" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
{
"error": false,
"success": true
}
DELETE /posts/group/{group}
Delete a post by group id. The Postiz path for deleting a post. Works the same as DELETE /posts/{id}.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
group | path | string | Yes | Group id (a per-channel post id also works). |
Example request
curl -X DELETE "https://app.postozo.com/public/v1/posts/group/7e6d5c4b-3a29-4817-a6b5-c4d3e2f1a0b9" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
{
"error": false,
"success": true
}
Analytics
Statistics for channels and published posts, where the network shares them.
GET /analytics/{integration}
Statistics for 1 channel. Daily numbers from the network for the last 7, 30 or 90 days. Networks only share the ranges they keep: a longer range than the network keeps falls back to the longest one it has. percentageChange compares with the period before when the network shares it, otherwise the 2nd half of the period with the 1st half.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
integration | path | string | Yes | Channel id. |
date | query | integer | No | Days: 7, 30 or 90. Default 30. |
Example request
curl "https://app.postozo.com/public/v1/analytics/de53f029-255d-4104-b090-974efcca2c86?date=7" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
[
{
"label": "Views",
"help": "How many times your posts were shown. Demo numbers.",
"percentageChange": 2.2,
"data": [
{
"total": 150,
"date": "2026-09-23"
},
{
"total": 132,
"date": "2026-09-24"
},
{
"total": 114,
"date": "2026-09-25"
}
]
}
]
The example is from the Demo network, whose numbers are made up. Real channels return the metrics their network shares.
When the network shares no statistics
{
"missing": true,
"message": "Telegram does not share account statistics with apps."
}
GET /analytics/post/{postId}
Statistics for 1 published post. The numbers the network shares for 1 live post, for example likes and views. Use the per-channel post id.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
postId | path | string | Yes | Per-channel post id (or group id). |
Example request
curl "https://app.postozo.com/public/v1/analytics/post/2bb35f95-cf65-4316-ae22-3903302a5f36" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
[
{
"label": "Likes",
"percentageChange": 0,
"data": [
{
"total": 0,
"date": "2026-09-29"
}
]
},
{
"label": "Views",
"percentageChange": 0,
"data": [
{
"total": 100,
"date": "2026-09-29"
}
]
}
]
Each metric has 1 data point with today's date. percentageChange is always 0 here.
Before the post is live
curl "https://app.postozo.com/public/v1/analytics/post/5001b946-c142-4ab7-b376-188c71272521" \
-H "Authorization: pz_live_YOUR_KEY"
{
"missing": true,
"message": "This post is not published yet."
}
Notifications and networks
Workspace notifications and the rules of every network.
GET /notifications
List notifications. Workspace notifications, newest first, 50 per page: published posts, failed posts, approvals, reconnect requests and billing news.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
page | query | integer | No | Page number, starting at 1. Default 1. |
Example request
curl "https://app.postozo.com/public/v1/notifications?page=1" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
{
"notifications": [
{
"id": "0dcbb820-aebb-444d-beff-21b03d98dae7",
"type": "fail",
"title": "Post failed on Demo network for acme",
"body": "Demo network rejected the post (#fail in text). Remove #fail from the text and retry.",
"link": "/launches?post=374f5b42-c4e3-42ab-a357-99e1b5662d9e",
"created_at": "2026-09-29T18:23:45.850Z"
}
],
"page": 1
}
type is success, fail or info. link is a path in the app.
GET /networks
List every network. Every network this server supports, with its id, limit, whether it takes follow-up comments and whether it needs media. Networks are listed even when the server has no keys for them yet.
Example request
curl "https://app.postozo.com/public/v1/networks" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
[
{
"identifier": "gbp",
"name": "Google Business Profile",
"maxLength": 1500,
"comments": false,
"needs": null
},
{
"identifier": "instagram",
"name": "Instagram",
"maxLength": 2200,
"comments": true,
"needs": "media"
}
]
GET /rules/{identifier}
Rules of 1 network. The rules and settings of a network, without a channel. Useful before you connect one.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
identifier | path | string | Yes | Network id from GET /networks. |
Example request
curl "https://app.postozo.com/public/v1/rules/x" \
-H "Authorization: pz_live_YOUR_KEY"
Example response (200)
{
"rules": "Max 280 characters (links count as 23). Up to 4 images or 1 video. Follow-up comments become a thread.",
"maxLength": 280,
"settings": [
{
"key": "reply",
"label": "Who can reply",
"type": "select",
"options": [
"Everyone",
"Accounts you follow",
"Accounts you mention",
"Verified accounts",
"Subscribers"
]
}
]
}
Unknown network
curl "https://app.postozo.com/public/v1/rules/myspace" \
-H "Authorization: pz_live_YOUR_KEY"
{
"error": "Unknown network",
"msg": "Unknown network"
}
Errors on every endpoint
| Status | When | Body |
|---|---|---|
| 401 | No Authorization header. | {"error":"No API Key found","msg":"No API Key found"} |
| 401 | The key or token is wrong, was rotated or was revoked. | {"error":"Invalid API key","msg":"Invalid API key"} |
| 400 | The body is not valid JSON. | {"error":"The request body is not valid JSON.","msg":"The request body is not valid JSON."} |
| 404 | The post, channel or media does not exist in this workspace. | {"error":"Post not found","msg":"Post not found"} |
| 400 | An id in the path is not in the id format (a UUID). | {"error":"One of the ids is not valid.","msg":"One of the ids is not valid.","fix":"Check the id. Copy it again from the list it came from."} |
| 404 | The path does not exist. | {"error":"Not found","msg":"Not found","fix":"Check the method and the path. The API reference lists every path."} |
| 401 | The workspace has no plan: the trial ended without a plan, or a cancelled plan ended. Every call is refused until a plan is picked. MCP tool calls get the same body. | {"error":"No subscription found","msg":"No subscription found","code":"no_subscription","fix":"The workspace owner should choose a plan on the Billing page of the app. Nothing was deleted."} |
| 402 | Publishing is paused: the 7 day grace after a failed payment is over. Every call is refused until the payment goes through. MCP tool calls get the same body. | {"error":"Publishing is paused because the last payment failed.","msg":"Publishing is paused because the last payment failed.","code":"payment_failed","fix":"The workspace owner should pay the open invoice on the Billing page of the app. Nothing was deleted, and posts go out again once it is paid."} |
| 409 | Something with the same unique value already exists. | {"error":"That already exists.","msg":"That already exists.","fix":"Use a different name or value, or change the one that already exists."} |
| 429 | Over a rate limit. The Retry-After header gives the seconds to wait. | {"error":"Too many requests. Try again in 42 minutes.","msg":"Too many requests. Try again in 42 minutes."} |
| 500 | Something went wrong on our side. | {"error":"Something went wrong on our side. Try again. If it keeps happening, contact support.","msg":"Something went wrong on our side. Try again. If it keeps happening, contact support.","fix":"Try again in a minute. If it keeps happening, email support@postozo.com with the time of the request."} |
More on errors, rate limits and dates in the API guide. To hear when a post goes live instead of asking, use webhooks.
Questions
Is there an OpenAPI file?
Yes, at https://postozo.com/docs/openapi.json (OpenAPI 3.1). It is made from the same data as this page, so the 2 always match. Import it into Postman, Insomnia or a code generator.
Why do some endpoints take a post id and others a group id?
A post you create once is a group with 1 post per channel. Endpoints that change the whole post accept either id. Endpoints about 1 channel (settings, release id, statistics) need the per-channel post id to be exact.
Are the example ids real?
They come from a run on a test server with the Demo network. Replace them with ids from your own workspace, and replace pz_live_YOUR_KEY with your key.
Can I create a channel with the API?
You can get the sign-in link for networks that use one (GET /social/{integration}). The person still signs in to the network in a browser. Networks that connect with a form are connected in the app.
Where are the network settings listed?
GET /integration-settings/{id} for a connected channel, or GET /rules/{identifier} for any network. The channel pages on this site list them too.
Get your API key
Start a 7-day free trial. The API, MCP server, webhooks and CLI are in every plan.
Create your free account