Endpoints
All paths start from the base URL https://app.postra.co.uk/api/public/v1. Every request needs your key in the Authorization header. See Get started.
Two kinds of id appear below:
- A channel id is the
idfromGET /integrations. - A post id is a
postIdfromPOST /posts, or anidfromGET /posts.
| Group | Routes |
|---|---|
| Channels | GET /is-connected, GET /integrations, GET /integration-settings/:id, POST /integration-trigger/:id, GET /social/:platform, DELETE /integrations/:id |
| Posts | GET /posts, POST /posts, PUT /posts/:id/status, DELETE /posts/:id, DELETE /posts/group/:group, GET /posts/:id/missing, PUT /posts/:id/release-id |
| Slots | GET /find-slot/:id |
| Analytics | GET /analytics/:id, GET /analytics/post/:postId |
| Notifications | GET /notifications |
| Media | POST /upload, POST /upload-from-url, GET /media |
Channels
GET /is-connected
Checks that your key works.
curl https://app.postra.co.uk/api/public/v1/is-connected \
-H "Authorization: YOUR_API_KEY"
{ "connected": true }
GET /integrations
Lists the channels connected to your organisation.
| Field | Meaning |
|---|---|
id |
Channel id |
name |
Channel name |
identifier |
Platform, for example instagram |
picture |
Profile picture |
disabled |
true when the channel is switched off |
profile |
Username on the platform |
customer |
{"id", "name"} of the client the channel belongs to. Missing when it has none. |
curl https://app.postra.co.uk/api/public/v1/integrations \
-H "Authorization: YOUR_API_KEY"
[
{
"id": "8d3f1c2a-5b6e-4f7a-9c0d-1e2f3a4b5c6d",
"name": "Northside Bakery",
"identifier": "instagram",
"picture": "https://cdn-dev.postra.pl/2026/10/01/avatar.jpg",
"disabled": false,
"profile": "northsidebakery",
"customer": { "id": "c41e7a90-2b3d-4e5f-8a6b-7c8d9e0f1a2b", "name": "Northside" }
}
]
GET /integration-settings/:id
Tells you what a channel accepts:
| Field | Meaning |
|---|---|
rules |
The platform’s media rules, in words |
maxLength |
Most characters in one post |
settings |
A JSON schema of the settings object a post needs, or "No additional settings required" |
tools |
Extra actions you can run with POST /integration-trigger/:id |
curl https://app.postra.co.uk/api/public/v1/integration-settings/8d3f1c2a-5b6e-4f7a-9c0d-1e2f3a4b5c6d \
-H "Authorization: YOUR_API_KEY"
{
"output": {
"rules": "Instagram should have at least one attachment, if it's a story, it can have only one picture",
"maxLength": 2200,
"settings": { "properties": { "post_type": { "enum": ["post", "story"] } } },
"tools": []
}
}
The settings schema above is shortened. A channel that does not exist answers 404.
POST /integration-trigger/:id
Runs one of the channel’s tools. For example, a Discord channel has the tool channels, which lists the Discord channels you can post to.
| Body field | Required | Meaning |
|---|---|---|
methodName |
yes | The methodName of a tool from GET /integration-settings/:id |
data |
no | Input for the tool, as the tool’s schema describes |
curl https://app.postra.co.uk/api/public/v1/integration-trigger/3b9e2d1f-6a7c-4b8d-9e0f-1a2b3c4d5e6f \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"methodName": "channels", "data": {}}'
{
"output": [
{ "id": "1234567890123456789", "name": "announcements" }
]
}
An unknown channel or tool answers 404. If the platform’s sign-in has expired and cannot be renewed, the answer is 401 and you need to reconnect the channel in Postra.
GET /social/:platform
Starts connecting a new channel. :platform is a platform identifier, such as linkedin. You get back a sign-in address. Open it in a browser where a member of your organisation is signed in to Postra, and finish there.
curl https://app.postra.co.uk/api/public/v1/social/linkedin \
-H "Authorization: YOUR_API_KEY"
{ "url": "https://www.linkedin.com/oauth/v2/authorization?..." }
| Status | When |
|---|---|
400 |
The identifier is unknown |
402 |
The platform is not in your plan, or all your channel slots are taken |
403 |
The platform does not take new channels yet |
Bluesky and Telegram do not use a sign-in page. Connect them in the Postra app. See Platforms and settings.
DELETE /integrations/:id
Removes a channel. This also deletes all posts of that channel.
curl -X DELETE https://app.postra.co.uk/api/public/v1/integrations/8d3f1c2a-5b6e-4f7a-9c0d-1e2f3a4b5c6d \
-H "Authorization: YOUR_API_KEY"
{ "deleted": true }
A channel that does not exist answers 404.
Posts
GET /posts
Lists your posts in a date range.
| Query field | Required | Meaning |
|---|---|---|
startDate |
yes | Start of the range, ISO 8601 |
endDate |
yes | End of the range, ISO 8601. The range is cut to 370 days at most. |
customer |
no | A customer id from GET /integrations, to see only that client’s posts |
curl "https://app.postra.co.uk/api/public/v1/posts?startDate=2026-11-01T00:00:00Z&endDate=2026-11-30T23:59:59Z" \
-H "Authorization: YOUR_API_KEY"
{
"posts": [
{
"id": "f2a4c6e8-1b3d-4f5a-8c7e-9d0b1a2c3e4f",
"content": "Fresh sourdough every Saturday.",
"publishDate": "2026-11-02T09:30:00.000Z",
"releaseURL": null,
"releaseId": null,
"state": "QUEUE",
"error": null,
"intervalInDays": null,
"group": "5e7d9c1b-3a2f-4e6d-8b0a-c1d2e3f4a5b6",
"creationMethod": "API",
"tags": [],
"integration": {
"id": "8d3f1c2a-5b6e-4f7a-9c0d-1e2f3a4b5c6d",
"providerIdentifier": "instagram",
"name": "Northside Bakery",
"picture": "https://cdn-dev.postra.pl/2026/10/01/avatar.jpg"
}
}
]
}
state is one of QUEUE (scheduled), PUBLISHED, ERROR or DRAFT. When a post failed, error says why. releaseURL is the link to the published post. A repeating post appears once for each time it repeats in the range.
POST /posts
Creates a post on one or more channels. It can be a draft, a scheduled post or a post that goes out now.
| Field | Required | Meaning |
|---|---|---|
type |
yes | draft, schedule or now |
date |
yes | When to publish, ISO 8601. Send it with now too. |
shortLink |
yes | true shortens the links in the text |
tags |
yes | A list of {"value", "label"}. Can be empty. A tag is added when label matches the name of a tag you already have in Postra. |
posts |
yes | One item per channel, 1 to 50 |
posts[].integration.id |
yes | Channel id |
posts[].value |
yes | 1 to 100 parts. The first part is the post. The others are follow-up parts: replies in a thread or comments, depending on the platform. |
posts[].value[].content |
yes | The text. Plain text works. These HTML tags are kept: p, br, strong, u, a, ul, li, h1, h2, h3, span. Others are removed. |
posts[].value[].image |
yes | Up to 20 media items, each {"id", "path"} from an upload. Can be empty. |
posts[].settings |
depends | Platform options. Some platforms require them. See Platforms and settings. |
inter |
no | Repeat the post every N days. N is 1 or more. |
Each part needs text, media or both.
curl https://app.postra.co.uk/api/public/v1/posts \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "schedule",
"date": "2026-11-02T09:30:00.000Z",
"shortLink": false,
"tags": [],
"posts": [
{
"integration": { "id": "8d3f1c2a-5b6e-4f7a-9c0d-1e2f3a4b5c6d" },
"value": [
{
"content": "Fresh sourdough every Saturday.",
"image": [
{
"id": "0c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"path": "https://cdn-dev.postra.pl/2026/10/08/k3j9x2m1qa.jpg"
}
]
}
],
"settings": { "post_type": "post" }
}
]
}'
The answer is 201, with one item per channel:
[
{
"postId": "f2a4c6e8-1b3d-4f5a-8c7e-9d0b1a2c3e4f",
"integration": "8d3f1c2a-5b6e-4f7a-9c0d-1e2f3a4b5c6d"
}
]
Postra checks the post with the same rules as its own editor: the length, the media and the platform settings. A post that breaks a rule gets a 400 naming the channel. See Errors.
A draft skips the length and media checks, but its platform settings must still be valid.
PUT /posts/:id/status
Turns a post into a draft, or puts it back in the queue.
| Body field | Required | Meaning |
|---|---|---|
status |
yes | draft or schedule |
republish |
no | true to put a post that was already published back in the queue. Without it, that request answers 400. |
curl -X PUT https://app.postra.co.uk/api/public/v1/posts/f2a4c6e8-1b3d-4f5a-8c7e-9d0b1a2c3e4f/status \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "draft"}'
{ "id": "f2a4c6e8-1b3d-4f5a-8c7e-9d0b1a2c3e4f", "state": "DRAFT" }
Moving a post to schedule counts against your monthly posts. Moving it to draft does not. A post that does not exist answers 404.
DELETE /posts/:id
Deletes a post and its follow-up parts. Other channels of the same request keep their posts.
curl -X DELETE https://app.postra.co.uk/api/public/v1/posts/f2a4c6e8-1b3d-4f5a-8c7e-9d0b1a2c3e4f \
-H "Authorization: YOUR_API_KEY"
{ "deleted": true, "id": "f2a4c6e8-1b3d-4f5a-8c7e-9d0b1a2c3e4f" }
A post that does not exist answers 404.
DELETE /posts/group/:group
The same as above, using the group value of a post from GET /posts.
curl -X DELETE https://app.postra.co.uk/api/public/v1/posts/group/5e7d9c1b-3a2f-4e6d-8b0a-c1d2e3f4a5b6 \
-H "Authorization: YOUR_API_KEY"
{ "deleted": true, "id": "f2a4c6e8-1b3d-4f5a-8c7e-9d0b1a2c3e4f" }
When nothing matches, the answer is {"deleted": false, "id": null}.
GET /posts/:id/missing
Sometimes a platform publishes a post but does not tell Postra its id. Postra then cannot link to it or read its analytics. This route lists the channel’s recent items on the platform, so you can pick the right one.
curl https://app.postra.co.uk/api/public/v1/posts/f2a4c6e8-1b3d-4f5a-8c7e-9d0b1a2c3e4f/missing \
-H "Authorization: YOUR_API_KEY"
[
{ "id": "7301234567890123456", "url": "https://example.com/preview.jpg" }
]
The list is empty when the post is not waiting for an id.
PUT /posts/:id/release-id
Links a post from the route above to its item on the platform.
| Body field | Required | Meaning |
|---|---|---|
releaseId |
yes | An id from GET /posts/:id/missing |
curl -X PUT https://app.postra.co.uk/api/public/v1/posts/f2a4c6e8-1b3d-4f5a-8c7e-9d0b1a2c3e4f/release-id \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"releaseId": "7301234567890123456"}'
{ "id": "f2a4c6e8-1b3d-4f5a-8c7e-9d0b1a2c3e4f", "releaseId": "7301234567890123456" }
A post that is not waiting for an id answers 404.
Slots
GET /find-slot/:id
Finds the next free time on a channel. Postra looks at the posting times set for the channel and returns the first one in the future that has no post yet. The time is in UTC.
curl https://app.postra.co.uk/api/public/v1/find-slot/8d3f1c2a-5b6e-4f7a-9c0d-1e2f3a4b5c6d \
-H "Authorization: YOUR_API_KEY"
{ "date": "2026-11-03T09:00:00" }
Use the value as date in POST /posts. A channel without posting times, or one that does not exist, answers 404.
Analytics
GET /analytics/:id
Analytics of a channel.
| Query field | Required | Meaning |
|---|---|---|
date |
no | Number of days to cover. Default 7. |
curl "https://app.postra.co.uk/api/public/v1/analytics/8d3f1c2a-5b6e-4f7a-9c0d-1e2f3a4b5c6d?date=7" \
-H "Authorization: YOUR_API_KEY"
[
{
"label": "Page Impressions",
"data": [
{ "total": 120, "date": "2026-11-01" },
{ "total": 164, "date": "2026-11-02" }
],
"percentageChange": 36.7
}
]
The labels depend on the platform. The list is empty when the platform gives no analytics. A channel that does not exist answers 404. Results are kept for up to an hour, so a repeat call can show the same numbers.
GET /analytics/post/:postId
Analytics of one published post.
| Query field | Required | Meaning |
|---|---|---|
date |
no | Number of days to cover. Default 7. |
curl "https://app.postra.co.uk/api/public/v1/analytics/post/f2a4c6e8-1b3d-4f5a-8c7e-9d0b1a2c3e4f?date=7" \
-H "Authorization: YOUR_API_KEY"
The answer has the same shape as channel analytics. It is an empty list when the post is not published yet or the platform gives no post analytics. It is {"missing": true} when the platform did not return the post’s id: fix that with GET /posts/:id/missing.
Notifications
GET /notifications
Your organisation’s notifications, newest first, 100 per page.
| Query field | Required | Meaning |
|---|---|---|
page |
no | Page number, starting at 0 |
curl "https://app.postra.co.uk/api/public/v1/notifications?page=0" \
-H "Authorization: YOUR_API_KEY"
{
"notifications": [
{
"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"content": "Your post has been published on Instagram at https://www.instagram.com/p/example/",
"link": null,
"createdAt": "2026-11-02T09:30:12.000Z"
}
],
"total": 1,
"page": 0,
"limit": 100,
"hasMore": false
}
Media
Media in a post must be stored in Postra first. Full details, limits and examples are on Uploading media.
| Route | What it does |
|---|---|
POST /upload |
Uploads a file as multipart/form-data, in the field file |
POST /upload-from-url |
Postra downloads a file from a public HTTPS address: {"url": "https://..."} |
GET /media |
Lists your media library, newest first, 18 per page. Query: page (from 1), search |