PostAtom docs

API reference

Version 0.1.0. Generated from the schemas the routes parse, so this page and the API cannot disagree — a change to one that is not reflected in the other fails the build.

Authentication

Send your workspace key as a bearer token. A key beginning pn_test_ resolves to the sandbox environment, where posts travel the whole pipeline and reach no timeline.

Authorization: Bearer pn_live_…

GET /v1/posts

Posts, newest first

Paged by creation instant rather than by offset, so publishing during paging cannot show a reader the same post twice while hiding another.

Parameters

NameInTypeNotes
statusquerystring
platformquerystring
fromquerystring
toquerystring
beforequerystringThe `nextCursor` of the previous page.
limitqueryinteger

Responses

200A page of posts.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
500An internal error occurred.

POST /v1/posts

Publish or schedule a post

Parameters

NameInTypeNotes
Idempotency-KeyheaderstringReplaying this key with the same body returns the original post.

Request body

application/json. The schema is in the specification, which is the same document the SDKs are generated from.

Responses

202Accepted, with per-destination status.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
409The request conflicts with what is already recorded.
413The request body is larger than this route accepts.
500An internal error occurred.

POST /v1/media

Announce an upload and get a URL to put the bytes at

Three steps: announce, upload straight to storage with the URL returned here, then confirm. The URL is signed for exactly this size and type and expires in minutes.

Request body

application/json. The schema is in the specification, which is the same document the SDKs are generated from.

Responses

201The asset, and where to put the bytes.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
413The request body is larger than this route accepts.
500An internal error occurred.

POST /v1/media/{assetId}/confirmation

Confirm an upload

Reads the file’s own header back and records what it actually is. An asset is not publishable until this succeeds, and a file that is not what it was declared to be is refused here rather than by a platform hours later.

Parameters

NameInTypeNotes
assetId*pathstring

Responses

200The asset, now publishable.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
409Nothing was uploaded, or the asset was already settled.
500An internal error occurred.

GET /v1/media/{assetId}

Read an asset back

Parameters

NameInTypeNotes
assetId*pathstring

Responses

200The asset and what was found in it.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
500An internal error occurred.

GET /v1/accounts

Where this workspace can post, and what each will accept

Call this before composing. The limits travel with the account because on a federated network two accounts on the same platform genuinely differ. An account that needs reconnecting is still listed, marked unavailable with the reason.

Responses

200Connected accounts with their live capabilities.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
500An internal error occurred.

GET /v1/accounts/{accountId}

One connected account

Parameters

NameInTypeNotes
accountId*pathstring

Responses

200The account and its capabilities.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
500An internal error occurred.

GET /v1/accounts/{accountId}/boards

The boards a Pinterest account may Pin to

Every Pin names a board, so this is what a caller asks before naming one. Refused with not_supported_on_platform for an account whose platform has no such list. An alias of PostPeer’s GET /v1/pinterest/boards.

Parameters

NameInTypeNotes
accountId*pathstring

Responses

200The boards this account may Pin to.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
422This account’s platform does not offer this list.
500An internal error occurred.

GET /v1/accounts/{accountId}/creator-info

What a TikTok creator’s application audit currently permits

Privacy levels on offer, duet/stitch/comment state and the duration ceiling, read from the account rather than assumed. Refused with not_supported_on_platform for an account whose platform has no such audit. An alias of PostPeer’s GET /v1/tiktok/creator-info.

Parameters

NameInTypeNotes
accountId*pathstring

Responses

200What this account may currently post.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
422This account’s platform does not offer this list.
500An internal error occurred.

GET /v1/subjects/{externalUserRef}

What PostNode holds about one of your end users

Row counts per table for one `external_user_ref`. Erasure is irreversible, so seeing what will go — and that it is the right subject — is worth one extra call.

Parameters

NameInTypeNotes
externalUserRef*pathstring

Responses

200What erasure would remove.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
500An internal error occurred.

DELETE /v1/subjects/{externalUserRef}

Erase everything PostNode holds about one end user

Removes the connected account and its tokens, the inbox conversations and messages, the media files and their rows, the request logs and webhook deliveries, and any post whose only destination was theirs. Files go before the rows that name them, and every table is re-counted afterwards: the receipt reports zero residual rows or the call fails. Irreversible, and safe to repeat.

Parameters

NameInTypeNotes
externalUserRef*pathstring

Responses

200What was removed.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
409This deployment has no object storage and the subject owns files, so erasing the rows would leave the files behind.
500An internal error occurred.

GET /v1/posts/{postId}

One post, with each destination’s own status

Parameters

NameInTypeNotes
postId*pathstring

Responses

200The post and every destination.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
500An internal error occurred.

POST /v1/posts/{postId}/retry

Send a dead-lettered post again

Parameters

NameInTypeNotes
postId*pathstring

Responses

200Per destination: reopened, or the reason it was left alone.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
409The request conflicts with what is already recorded.
500An internal error occurred.

POST /v1/posts/{postId}/schedule

Move a scheduled post

Parameters

NameInTypeNotes
postId*pathstring

Request body

application/json. The schema is in the specification, which is the same document the SDKs are generated from.

Responses

200Per destination: moved, or why it was not.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
500An internal error occurred.

DELETE /v1/posts/{postId}/schedule

Cancel a scheduled post

Parameters

NameInTypeNotes
postId*pathstring

Responses

200Per destination: cancelled, or why it was not.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
500An internal error occurred.

GET /v1/analytics

Normalized metrics across platforms

Posts are dated by when they were published, and every post in the window is counted. Totals are built from each post’s latest reading, and every total states how many platforms reported it. A figure a platform does not measure is null, never zero.

Parameters

NameInTypeNotes
fromquerystringDefaults to thirty days before `to`.
toquerystringDefaults to now.
platformquerystring
accountIdquerystring
granularityquerystring day · week · month

Responses

200Totals and a series, both null-aware.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
500An internal error occurred.

GET /v1/analytics/posts

Posts with their numbers, sortable by any of them

One row per post with its destinations beneath it. `source` is `postnode` for a post published here and `platform` for one found on the account, which is how a post made in the platform’s own app appears at all. Every figure is a stored reading: sorting never becomes a platform call. A figure no platform reported is null, never zero — so an unmeasured post ranks last rather than alongside one nobody engaged with.

Parameters

NameInTypeNotes
sortByquerystringDefaults to `date`. `engagement` is likes + comments + shares + saves. date · likes · comments · impressions · views · shares · saves · clicks · engagement
orderquerystring asc · desc
platformquerystring
accountIdquerystring
fromDatequerystringOn the post’s own date, not the reading’s.
toDatequerystring
sourcequerystring postnode · platform
limitqueryinteger
pagequeryintegerExclusive with `cursor`; sending both is refused.
cursorquerystringThe `nextCursor` of the previous page. Preferred over `page`: a reading arriving mid-paging shifts every offset.

Responses

200A page of posts, ranked.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
500An internal error occurred.

GET /v1/analytics/instagram/account-insights

An Instagram account’s own daily figures

Reach, profile views and website clicks, one point per day, from the readings the daily account job already takes. An account with nothing captured yet falls back to one live call, at most once an hour. Answered for any account whose adapter publishes account insights — the route dispatches on that, not on the platform’s name; an account whose platform does not answers `409`.

Parameters

NameInTypeNotes
accountId*querystring
fromquerystring
toquerystring

Responses

200A day series, and when it was captured.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
409The request conflicts with what is already recorded.
500An internal error occurred.

GET /v1/analytics/instagram/demographics

Who follows an Instagram account

Age, gender, country and city, as the platform cuts them. A cut the platform withholds — it does so below a hundred followers — comes back empty rather than failing the others.

Parameters

NameInTypeNotes
accountId*querystring

Responses

200Four breakdowns, and when they were captured.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
409The request conflicts with what is already recorded.
500An internal error occurred.

GET /v1/analytics/instagram/follower-stats

Follower count, and how it moved

The count answers “how many now” and never “how did that change”, so the daily deltas come with it, built from the readings the account job has been taking all along.

Parameters

NameInTypeNotes
accountId*querystring
fromquerystring
toquerystring

Responses

200The latest count and the daily series.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
409The request conflicts with what is already recorded.
500An internal error occurred.

GET /v1/analytics/deliveries/{deliveryId}

One post’s readings over time

Every capture is kept, so growth is visible rather than a single figure being overwritten.

Parameters

NameInTypeNotes
deliveryId*pathstring

Responses

200The capture history, oldest first.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
500An internal error occurred.

GET /v1/conversations

Comment threads, most recent activity first

Comment threads only. Direct messages are their own surface under `/v1/messages` and their own scopes: a key that may moderate comments has not been told it may read somebody private conversation. An id of the wrong kind answers 404 rather than 403, so it cannot be used to discover that a private thread exists.

Parameters

NameInTypeNotes
platformquerystring
accountIdquerystring
beforequerystringThe `nextCursor` of the previous page.
limitqueryinteger

Responses

200A page of threads.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
500An internal error occurred.

PATCH /v1/conversations/{id}

Archive or reopen a thread

Parameters

NameInTypeNotes
id*pathstring

Request body

application/json. The schema is in the specification, which is the same document the SDKs are generated from.

Responses

200The thread, as it now stands.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
500An internal error occurred.

GET /v1/conversations/{id}/messages

One thread, oldest message first

Parameters

NameInTypeNotes
id*pathstring
afterquerystring
beforequerystringReach back past what PostNode keeps. Messages are partitioned by month and retained for a bounded window; a `before` earlier than that window is answered from the platform instead of the database and nothing is stored. The response `source` says which it was — a live read costs a platform call, is subject to that platform’s rate limits, and can disagree with what was stored if a message has since been deleted.
cursorquerystringThe platform’s own cursor, for paging a live read.
limitqueryinteger

Responses

200A page of messages, with `source` naming where they came from.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
500An internal error occurred.

POST /v1/conversations/{id}/reply

Reply in a thread

A platform that does not let an application reply is refused with not_supported_on_platform at the moment of asking, rather than accepting a reply that could never be sent.

Parameters

NameInTypeNotes
id*pathstring

Request body

application/json. The schema is in the specification, which is the same document the SDKs are generated from.

Responses

201The reply was recorded and sent.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
413The request body is larger than this route accepts.
422This platform does not accept replies from an application.
500An internal error occurred.

POST /v1/connect/bluesky/auth

Connect a Bluesky account with an app password

OAuth is the default and is what GET /v1/connect/bluesky starts; this exists because many Bluesky accounts live on servers whose operators have never registered this deployment's OAuth client. The password is used once and never stored: what is stored is the session it buys, which the person can revoke from their own settings without changing their password. An app password cannot be scoped and grants everything the account can do.

Request body

application/json. The schema is in the specification, which is the same document the SDKs are generated from.

Responses

201The account is connected.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No Bluesky account answers to that handle.
413The request body is larger than this route accepts.
500An internal error occurred.
502The server holding that account did not answer.

GET /v1/comments

One post's comments, oldest first

Flat rather than nested: each comment carries its parent id, so a caller that wants the tree can build it and one that wants to read through in order is not made to walk it.

Parameters

NameInTypeNotes
accountId*querystring
postId*querystringThe post as the platform identifies it.
afterquerystring
limitqueryinteger

Responses

200The post's comments.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
500An internal error occurred.

POST /v1/comments

Comment on a post

Starts a thread under a post, which is not the same as replying inside one — a reply goes to /v1/conversations/{id}/reply, which has the thread the comment belongs to. A platform with no comment endpoint an application may use is refused at the moment of asking.

Request body

application/json. The schema is in the specification, which is the same document the SDKs are generated from.

Responses

201The comment was created.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
409This platform has no comment endpoint PostNode can use.
413The request body is larger than this route accepts.
500An internal error occurred.

DELETE /v1/comments/{commentId}

Delete a comment by its own id

The same action as the thread-shaped route, for a caller that holds a comment id and nothing else. A direct message is never found here.

Parameters

NameInTypeNotes
commentId*pathstring

Responses

204Deleted.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
409This platform does not let an application delete comments.
500An internal error occurred.

POST /v1/comments/{commentId}/hide

Hide a comment by its own id

Parameters

NameInTypeNotes
commentId*pathstring

Responses

204Hidden.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
409This platform does not let an application hide comments.
500An internal error occurred.

GET /v1/messages

Direct threads, most recent first

Behind `messages:read`, which is not `inbox:read`: a comment is public and a message is not. Instagram only, through a connected Page.

Parameters

NameInTypeNotes
accountIdquerystring
platformquerystring
beforequerystring
limitqueryinteger

Responses

200A page of threads.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
500An internal error occurred.

POST /v1/messages

Send a direct message

The platform allows a reply within 24 hours of the last message received. A thread whose stored last inbound message has aged past that is refused here with the instant named; silence is never read as a refusal, and the platform own answer is the authority in every other case.

Request body

application/json. The schema is in the specification, which is the same document the SDKs are generated from.

Responses

201The message was sent.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
409The reply window has closed, or this platform has no messaging PostNode can use.
413The request body is larger than this route accepts.
500An internal error occurred.

GET /v1/messages/{conversationId}

One direct thread, oldest message first

Parameters

NameInTypeNotes
conversationId*pathstring
afterquerystring
limitqueryinteger

Responses

200A page of messages.
400The request was not valid.
401The bearer key was missing, unknown or malformed.
403The key lacks the scope this route requires.
404No such post, or none in this workspace.
500An internal error occurred.

GET /health

Liveness

Responses

200The service is running.

GET /ready

Readiness, including the database

Responses

200The service and its database are reachable.
503A dependency is unavailable.

Every page here is generated from the code that runs. If a number on this site disagrees with the API, the build fails before it ships.