Home / Docs / API guide / Reference
API reference

API reference

All 28 endpoints of the public API at https://app.postozo.com/public/v1. Each example was sent to a real server and the answer checked.

Developer docs: Overview, API guide, API reference, MCP server, Webhooks, CLI, OAuth for apps. Machine readable: openapi.json.

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.

ParameterInTypeRequiredDescription
groupquerystringNoOnly 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.

ParameterInTypeRequiredDescription
idpathstringYesChannel 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.

ParameterInTypeRequiredDescription
idpathstringYesChannel id.

Body (JSON):

FieldTypeRequiredDescription
methodNamestringYesThe tool name (methodName from the settings answer).
dataobjectNoInput 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.

ParameterInTypeRequiredDescription
integrationpathstringYesNetwork id, for example x or linkedin-page (see GET /networks).
customerquerystringNoPut the new channel under this customer id.
refreshquerystringNoChannel 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"

Answer (400)

{
  "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).

ParameterInTypeRequiredDescription
idpathstringYesChannel 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):

FieldTypeRequiredDescription
filefileYesThe 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):

FieldTypeRequiredDescription
urlstringYesPublic 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.

ParameterInTypeRequiredDescription
pagequeryintegerNoPage number, starting at 1. Default 1.
searchquerystringNoOnly 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.

ParameterInTypeRequiredDescription
tzOffsetMinutesqueryintegerNoYour 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.

ParameterInTypeRequiredDescription
idpathstringYesChannel id.
tzOffsetMinutesqueryintegerNoMinutes 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"

Answer (400)

{
  "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.

ParameterInTypeRequiredDescription
startDatequerystringNoISO 8601 date. Default: 30 days ago.
endDatequerystringNoISO 8601 date. Default: 30 days from now.
customerquerystringNoOnly 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.

ParameterInTypeRequiredDescription
Idempotency-KeyheaderstringNoOptional. 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):

FieldTypeRequiredDescription
typestring
draft, schedule, now, review
Nodraft (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.
datestringNoWhen 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[]arrayYes1 entry per channel.
posts[].integrationobjectYesThe channel: { "id": "CHANNEL_ID" }. channelId at the entry level also works.
posts[].integration.idstringYesChannel id.
posts[].value[]arrayYesThe post, then its follow-up comments, in order.
posts[].value[].contentstringYesThe text. HTML is turned into plain text.
posts[].value[].imagearrayNoMedia from POST /upload: [{ "id": "MEDIA_ID" }]. path and alt are also read. media works too.
posts[].value[].delaynumberNoMinutes to wait after the item before. Ignored on the 1st item.
posts[].settingsobjectNoNetwork settings for this channel, keys from GET /integration-settings/{id}. __type is ignored.
posts[].groupstringNoId of an existing post to edit.
tagsarrayNoTags. 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.
shortLinkbooleanNoReplace links with short links that count clicks. Default false.
interintegerNoRepeat the post every N days (1 to 365). repeatDays also works.
groupIdstringNoId of an existing post to edit.
creationMethodstringNoShown 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
    }
  ]
}'

Answer (200)

[
  {
    "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":{}}]}'

Answer (400)

{
  "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": {}
    }
  ]
}'

Answer (200)

[
  {
    "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": {}
    }
  ]
}'

Answer (409)

{
  "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.

ParameterInTypeRequiredDescription
idpathstringYesPost 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.

ParameterInTypeRequiredDescription
idpathstringYesPost id or group id.

Body (JSON):

FieldTypeRequiredDescription
datestringYesNew 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).

ParameterInTypeRequiredDescription
idpathstringYesPost id or group id.

Body (JSON):

FieldTypeRequiredDescription
statusstring
draft, schedule
Yesdraft 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.

ParameterInTypeRequiredDescription
idpathstringYesPer-channel post id (or group id).

Body (JSON):

FieldTypeRequiredDescription
settingsobjectYesKeys 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.

ParameterInTypeRequiredDescription
idpathstringYesPost 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.

ParameterInTypeRequiredDescription
idpathstringYesPer-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.

ParameterInTypeRequiredDescription
idpathstringYesPer-channel post id (or group id).

Body (JSON):

FieldTypeRequiredDescription
releaseIdstringYesId of the live post on the network.
releaseUrlstringNoLink 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.

ParameterInTypeRequiredDescription
idpathstringYesPost 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}.

ParameterInTypeRequiredDescription
grouppathstringYesGroup 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.

ParameterInTypeRequiredDescription
integrationpathstringYesChannel id.
datequeryintegerNoDays: 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

Answer (200)

{
  "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.

ParameterInTypeRequiredDescription
postIdpathstringYesPer-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"

Answer (200)

{
  "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.

ParameterInTypeRequiredDescription
pagequeryintegerNoPage 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.

ParameterInTypeRequiredDescription
identifierpathstringYesNetwork 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"

Answer (404)

{
  "error": "Unknown network",
  "msg": "Unknown network"
}

Errors on every endpoint

StatusWhenBody
401No Authorization header.{"error":"No API Key found","msg":"No API Key found"}
401The key or token is wrong, was rotated or was revoked.{"error":"Invalid API key","msg":"Invalid API key"}
400The body is not valid JSON.{"error":"The request body is not valid JSON.","msg":"The request body is not valid JSON."}
404The post, channel or media does not exist in this workspace.{"error":"Post not found","msg":"Post not found"}
400An 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."}
404The path does not exist.{"error":"Not found","msg":"Not found","fix":"Check the method and the path. The API reference lists every path."}
401The 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."}
402Publishing 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."}
409Something 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."}
429Over 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."}
500Something 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.

FAQ

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