PostraDocsOpen Postra

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:

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