REST API
Programmatically create posts, manage accounts, and schedule content via the posterly API.
The posterly API lets you schedule posts, manage accounts, and upload media programmatically.
Public signup for agents
Agents and AI tools can start a paid API signup without an existing posterly session:
curl -X POST https://www.poster.ly/api/v1/signup \
-H "Content-Type: application/json" \
-d '{
"email": "owner@example.com",
"name": "Example Owner",
"tier": "starter",
"api_addon": true,
"source": "chatgpt"
}'
The response returns a posterly checkout_redirect_url, raw Stripe checkout_url, signup poll_url, dashboard/login URLs, and an optional OAuth authorize URL when the request includes a registered client_id, redirect_uri, and PKCE challenge. Agents should send checkout_redirect_url to the user because raw Stripe URLs can contain fragments that are easy to truncate in chat. Poll the signup session while the user pays and sets their posterly password so the AI conversation can keep reporting progress.
The public, crawlable signup handoff page is /agents/signup. Use it when an agent needs a human-readable explanation of the same paid signup and account-connection flow.
If an AI client supports MCP but not generic HTTP requests, install posterly-mcp-server@latest without POSTERLY_API_KEY and use the public MCP tools start_signup and get_signup_session for the same pre-auth signup handshake.
API signup is not a free-token path: no API key is returned from signup. After payment, the verified user lands on the API page and posterly auto-creates the first API key if none exists, showing it once. OAuth clients receive their access token after the paid user completes consent, then should ask which social account to connect and create a connect session.
Authentication
All API requests require an API key. Generate one in Dashboard → API & MCP.
Pass your key in the Authorization header:
Authorization: Bearer YOUR_API_KEY
Base URL
https://www.poster.ly/api/v1
Quickstart
The fastest end-to-end flow is: confirm who you are, list your connected accounts, upload media, validate the complete payload with dry_run: true, show the preview, obtain explicit confirmation, then schedule the live post. The examples below run against production with a key from Dashboard → API & MCP.
Node.js
No SDK needed; the built-in fetch in Node 18+ covers everything:
const BASE_URL = "https://www.poster.ly/api/v1";
const headers = {
Authorization: "Bearer pst_live_YOUR_API_KEY",
"Content-Type": "application/json",
};
async function main() {
// 1. Confirm the authenticated user and scopes
const whoami = await fetch(`${BASE_URL}/whoami`, { headers }).then((r) => r.json());
console.log("Authenticated as", whoami.user.email, "scopes:", whoami.api_key.scopes);
// 2. List connected social accounts and pick one
const { accounts } = await fetch(`${BASE_URL}/accounts`, { headers }).then((r) => r.json());
const account = accounts[0];
console.log("Posting to", account.platform, account.username);
// 3. Copy a public image into posterly storage
const media = await fetch(`${BASE_URL}/media/upload-from-url`, {
method: "POST",
headers,
body: JSON.stringify({
url: "https://example.com/launch-photo.jpg",
filename: "launch-photo.jpg",
}),
}).then((r) => r.json());
// 4. Schedule the post
const res = await fetch(`${BASE_URL}/posts`, {
method: "POST",
headers,
body: JSON.stringify({
account_id: account.id,
caption: "Launch notes are live.",
media_urls: [media.url],
scheduled_at: "2026-08-01T14:00:00Z",
}),
});
const { post } = await res.json();
console.log("Post", post.id, "is", post.status, "for", post.scheduled_at);
}
main().catch(console.error);
Python
The same flow with requests:
import requests
BASE_URL = "https://www.poster.ly/api/v1"
headers = {"Authorization": "Bearer pst_live_YOUR_API_KEY"}
# 1. Confirm the authenticated user and scopes
whoami = requests.get(f"{BASE_URL}/whoami", headers=headers).json()
print("Authenticated as", whoami["user"]["email"], "scopes:", whoami["api_key"]["scopes"])
# 2. List connected social accounts and pick one
accounts = requests.get(f"{BASE_URL}/accounts", headers=headers).json()["accounts"]
account = accounts[0]
print("Posting to", account["platform"], account["username"])
# 3. Copy a public image into posterly storage
media = requests.post(
f"{BASE_URL}/media/upload-from-url",
headers=headers,
json={"url": "https://example.com/launch-photo.jpg", "filename": "launch-photo.jpg"},
).json()
# 4. Schedule the post
post = requests.post(
f"{BASE_URL}/posts",
headers=headers,
json={
"account_id": account["id"],
"caption": "Launch notes are live.",
"media_urls": [media["url"]],
"scheduled_at": "2026-08-01T14:00:00Z",
},
).json()["post"]
print("Post", post["id"], "is", post["status"], "for", post["scheduled_at"])
Zapier
posterly does not have a native Zapier app yet, but both directions work today with Zapier's built-in tools:
Actions (Zap creates a posterly post): use the Webhooks by Zapier action with these settings:
- Event:
Custom Request - Method:
POST - URL:
https://www.poster.ly/api/v1/posts - Headers:
Authorization: Bearer pst_live_YOUR_API_KEYandContent-Type: application/json - Data:
{
"account_id": "123",
"caption": "New blog post is live: {{title}}",
"media_urls": ["{{image_url}}"],
"scheduled_at": "2026-08-01T14:00:00Z"
}
Get the account_id values once by calling GET /api/v1/accounts (curl or the Node/Python snippets above).
Triggers (posterly events start a Zap): use the Webhooks by Zapier trigger (Catch Hook), copy the hook URL Zapier gives you, then register it as a posterly outbound webhook:
curl -X POST https://www.poster.ly/api/v1/webhooks \
-H "Authorization: Bearer pst_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.zapier.com/hooks/catch/xxx/yyy/",
"events": ["post.published", "post.failed"]
}'
posterly will now call your Zap whenever a post publishes or fails. Supported events also include post.created, post.updated, post.deleted, post.publishing, account.disconnected, and analytics.synced. Use POST /api/v1/webhooks/{id}/test to send a test delivery while building the Zap.
n8n
Create posts with the HTTP Request node:
- Method:
POST - URL:
https://www.poster.ly/api/v1/posts - Authentication:
Generic Credential Type→Header Auth, nameAuthorization, valueBearer pst_live_YOUR_API_KEY - Body Content Type:
JSON - Body:
{
"account_id": "123",
"caption": "{{ $json.caption }}",
"media_urls": ["{{ $json.image_url }}"],
"scheduled_at": "{{ $json.publish_time }}"
}
React to posterly events with the Webhook node: create a workflow starting with a Webhook node (method POST), copy its production URL, and register it via POST /api/v1/webhooks exactly as in the Zapier example. Each delivery is signed with an X-posterly-Signature header (t=<unix>,v1=<hmac_sha256>) you can verify in a Code node using the webhook secret returned on create.
Make
Create posts with the HTTP → Make a request module:
- URL:
https://www.poster.ly/api/v1/posts - Method:
POST - Headers:
Authorization: Bearer pst_live_YOUR_API_KEY - Body type:
Raw, Content type:JSON (application/json) - Request content:
{
"account_id": "123",
"caption": "Launch notes are live.",
"media_urls": ["https://example.com/launch-photo.jpg"],
"scheduled_at": "2026-08-01T14:00:00Z"
}
React to posterly events with the Webhooks → Custom webhook module: add the module, copy the webhook address Make generates, and register it via POST /api/v1/webhooks with the events you care about (for example post.published and post.failed), the same call shown in the Zapier section.
Scopes
Every API key is granted a set of scopes. The default set on new keys covers everything the public surface offers today:
accounts:read, list connected social accountsaccounts:write, disconnect connected social accountsposts:read/posts:write, read and schedule postsmedia:write, upload images and videoanalytics:read, read per-account and per-post analyticsbilling:read, read your posterly subscription statusbilling:write, cancel, pause, resume, or downgrade your posterly subscription
Keys issued before analytics:read existed have been backfilled automatically, no regeneration needed. Keys created from the dashboard include the billing scopes so a connected AI tool can manage your own subscription; only share those keys with tools you trust.
Workspace scoping
API keys can be scoped to a single workspace. Connection handoff and webhook endpoints use that workspace automatically when workspace_id is omitted. If those requests include a different workspace_id, posterly returns 403 API key is scoped to a different workspace.
Unscoped keys can still pass workspace_id on workspace-aware endpoints. posterly verifies the API-key user belongs to that workspace before returning data or creating resources.
Endpoints
Identity and platform discovery
GET /api/v1/whoami
GET /api/v1/platforms
GET /api/v1/accounts/{id}/schema
POST /api/v1/platforms/trigger
Use whoami to confirm the authenticated user, scopes, default workspace, and available workspaces. Use platform discovery before creating posts with settings; it returns supported post types, media limits, content limits, first-class settings fields, analytics support, and helper tools.
Available helper tools currently include pinterest.boards, youtube.playlists, tiktok.creator_info, linkedin.recent_mentions, and x.quota. Pass include_planned=true to GET /api/v1/platforms to see the integration backlog and possibilities from the provider registry.
The human-readable registry page is at /api-platforms.
List accounts
GET /api/v1/accounts
Returns all connected social accounts in your workspace.
Disconnect an account
DELETE /api/v1/accounts/{id}
Disconnects a connected social account owned by the caller. This requires accounts:write and should be treated as irreversible: posterly deletes the account connection, preserves reconnect metadata on posts, archives Instagram analytics where available, and emits account.disconnected webhooks.
Agents should call GET /api/v1/accounts first, show the exact id, platform, username, and workspace_id, and only call delete after explicit user confirmation.
Account connection handoff
GET /api/v1/connect
GET /api/v1/connect/{platform}
POST /api/v1/connect/{platform}/sessions
GET /api/v1/connect/sessions/{session_id}
Use these endpoints when an agent or external workflow needs to help a user connect another channel. They return the dashboard connection_url, connection method (dashboard_oauth, manual_credentials, or planned), provider scopes, missing environment configuration, and matching connected accounts.
Direct public OAuth URLs are intentionally not returned yet. The current provider callbacks depend on short-lived browser state, provider-mode cookies, and PKCE verifiers, so agents should open connection_url in a logged-in browser session.
For a smoother agent experience, create a connect session instead of only fetching the static handoff link:
- Call
POST /api/v1/connect/{platform}/sessions. - Show or open the returned
connect_session.connect_urlfor the user. - Poll
connect_session.poll_url. - Narrate the returned
status_messageuntil the status becomesconnected,failed,cancelled, orexpired.
The dashboard updates the session as the user moves through the flow: created -> opened -> awaiting_provider or awaiting_credentials -> connected. This lets agents say things like "waiting for Instagram approval" or "connection completed" instead of leaving the user guessing.
Examples:
curl -H "Authorization: Bearer pst_live_xxx" \
https://www.poster.ly/api/v1/connect/instagram
{
"connect": {
"platform": "instagram",
"label": "Instagram",
"method": "dashboard_oauth",
"connection_url": "https://www.poster.ly/dashboard/connect?platform=instagram",
"oauth_url": null,
"requires_browser_session": true,
"configured": true,
"connected_count": 1
}
}
Create a live connection session:
curl -X POST \
-H "Authorization: Bearer pst_live_xxx" \
-H "Content-Type: application/json" \
-d '{"workspace_id":"2d0a6d4c-9eef-4ee2-9026-2ca0dc29c8d6","auto_start":true}' \
https://www.poster.ly/api/v1/connect/instagram/sessions
{
"connect_session": {
"id": "8ec5ae8c-fd6b-4e0b-83d7-0f88c3f5e9e1",
"platform": "instagram",
"method": "dashboard_oauth",
"status": "created",
"status_message": "Connection link created. Waiting for the user to open posterly.",
"connect_url": "https://www.poster.ly/dashboard/connect?platform=instagram&connect_session=8ec5ae8c-fd6b-4e0b-83d7-0f88c3f5e9e1&auto_start=1",
"poll_url": "https://www.poster.ly/api/v1/connect/sessions/8ec5ae8c-fd6b-4e0b-83d7-0f88c3f5e9e1",
"connected_count": 0
}
}
OAuth developer apps
GET /api/v1/oauth/clients
POST /api/v1/oauth/clients
PATCH /api/v1/oauth/clients/{clientId}
DELETE /api/v1/oauth/clients/{clientId}
Use these endpoints to create public OAuth + PKCE clients for third-party tools that need to act on behalf of a posterly user. Developer clients are owned by the API-key user, use exact redirect URI matching, and never expose client secrets or wildcard redirects.
Direct provider connection URLs are still handled by the dashboard connection handoff endpoints above. OAuth developer apps are for apps authenticating into posterly's API surface.
Example:
curl -X POST https://www.poster.ly/api/v1/oauth/clients \
-H "Authorization: Bearer pst_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"client_name": "Agency approval app",
"allowed_redirect_uris": ["https://agency.example.com/oauth/callback"],
"default_scopes": ["accounts:read", "posts:read", "posts:write"]
}'
Brand and client context
GET /api/v1/brands
GET /api/v1/brands/{id}
GET /api/v1/brands/{id}/accounts
GET /api/v1/brands/{id}/profile
Use brand endpoints when an external workflow needs the same client context posterly uses in the dashboard. They return workspace-scoped brands/clients, assigned social accounts, and saved brand profile guidance such as voice, tone, audience, and positioning. These endpoints require accounts:read.
Create a post
POST /api/v1/posts
Create and schedule a new post. See the API Reference for the full request body schema. media_url and media_urls can be posterly media URLs or third-party HTTP(S) URLs. Third-party assets are copied into posterly storage before the post is saved, so short-lived signed URLs must still be live when the request runs.
Platform-specific settings, pass settings to use the same first-class scheduling controls available in the posterly composer. The API maps these into the right platform metadata and publisher fields for you, so you do not need to rely on raw metadata for common platform options:
{
"account_id": "169",
"caption": "Launch notes are live.",
"media_urls": ["https://assets.poster.ly/users/123/reel.mp4"],
"scheduled_at": "2026-05-01T14:00:00Z",
"settings": {
"post_type": "reel",
"first_comment": "Full launch notes: www.poster.ly/updates",
"collaborators": ["partnerbrand"],
"reel_cover_url": "https://example.com/reel-cover.jpg",
"is_trial_reel": true,
"graduation_strategy": "MANUAL"
}
}
Supported settings include:
- Instagram, feed, story, reel, carousel, collaborators, user tags, first comment, alt text, trial Reels, Reel covers, and optional companion Story posting
- Facebook, post, story, reel, cover photo intent, colored text backgrounds, and Reel covers
- YouTube, title, thumbnail, privacy status, made-for-kids, tags, category, playlist,
notify_subscribers, andadd_shorts_hashtag - LinkedIn, document title and filename, organization mentions, polls (question, 2-4 options, 1 day/3 days/1 week/2 weeks), video thumbnail, and alt text. Pasting a URL in the caption auto-generates a link-preview card.
- TikTok, video posts and photo slideshows, direct-post privacy, comment/duet/stitch toggles, title, auto-music, commercial disclosure,
video_cover_timestamp_ms(pick the video frame used as the cover on direct-post videos; TikTok does not accept custom uploaded covers), andai_generated(AI-content label) - Pinterest,
post_type(image/video/carousel), board (required via helper), title, destination link,cover_image_url(for video Pins), andai_generated - Google Business Profile, standard, event, and offer posts with event schedule, offer details, CTA, and video
- X, reply settings (
everyone/following/mentionedUsers/subscribers/verified), polls,ai_generated(made-with-AI label), and media alt text - Threads, reply controls (incl.
followers_only/parent_post_author_only), polls, topic tags, text attachments, media alt text, andshare_to_instagram: trueto also share the published post to the Instagram account linked to the Threads profile as a Story (requires Instagram sharing enabled for the Threads profile in the Threads app) - Telegram, native polls (
{ question, options }), inline CTA buttons ([{ text, url }]), and parse mode - Bluesky, languages, content warnings (
content_label), hidden discovery tags (tags), quote posts (quote_url),disable_quotes, and media alt text. Pasting a URL auto-generates a link-preview card. - Discord, webhook channel posts with text (2,000 chars), up to 10 attachments (10 MB each), and media alt text (
media_alt_texts). Mention parsing is disabled so captions never ping @everyone or roles. - Mastodon, statuses on any instance with
visibility(public/unlisted/private), content warnings (spoiler_text),language, and media alt text. Publishes are idempotent. - Dev.to, markdown articles with
title(falls back to the first caption line),tags(up to 4), andcanonical_url. First image becomes the cover. - Hashnode, markdown articles to your publication with
title,tags(up to 4), and optionalpublication_idoverride. First image becomes the cover. - WordPress, self-hosted posts with
title(falls back to the first caption line). First image is uploaded as the featured image. - Lemmy, community posts with
community(required, e.g.technology@lemmy.world),title, optional linkurl, andnsfw. First image becomes the link embed.
ai_generated: true (alias ai_disclosure) labels a post as AI-generated where the platform supports it (TikTok, X, YouTube, Pinterest).
TikTok photo slideshows, TikTok supports either a single video or a photo slideshow of 1 to 35 images, and never multiple videos. You do not need to set post_type or media_type for images: posterly auto-detects image media and posts it as a slideshow, sending every image you provide. Provide one image for a single-image slideshow or 2 or more images for a multi-image slideshow. To be explicit you can still set post_type to "carousel" (2 or more images) or "image" (single image), or pass settings.media_type: "PHOTO". A post with one video publishes as a standard TikTok video.
Instagram Trial Reels use graduation_strategy: "MANUAL" by default. Set "SS_PERFORMANCE" to let Meta auto-share the Reel with followers if it performs well. Meta controls Trial Reel availability per Instagram account; if the Trial toggle does not appear for that account in the Instagram app, Meta may reject API publishing. Collaborators are not sent for Trial Reels. See Instagram Trial Reels (posterly dashboard login required) for the composer workflow and eligibility check.
X / Threads thread chains, to schedule a multi-post reply chain on X (Twitter) or Threads (Meta), set post_type and pass the matching array in metadata:
{
"account_id": "169",
"scheduled_at": "2026-05-01T14:00:00Z",
"post_type": "x_thread",
"metadata": {
"x_thread_tweets": [
"1/ What's new on posterly. Here's everything we shipped in April.",
"2/ Tag users in Instagram Stories...",
"3/ Threads long-form posts up to 10,000 characters..."
]
}
}
For Threads, use post_type: "threads_thread" and metadata.threads_thread_posts. Each entry is validated against per-platform limits (X: 280 chars, Threads: 500 chars) before insert. URLs are still blocked in X thread segments.
Media rules checked at create time
posterly runs the same platform rules the composer uses, so a post that could never publish is rejected here with a 400 and validation_errors rather than failing later on the platform. Three of these are worth calling out because they are enforced at create time:
| Rule | Code | Applies to |
|---|---|---|
PDFs can only be published to LinkedIn. A .pdf in media_url/media_urls, or post_type: "document", is rejected for every other platform. | pdf_requires_linkedin | all platforms except LinkedIn |
Instagram cannot publish .avi or .mkv video. Export as MP4 (H.264/AAC). | instagram_video_unsupported_container | |
| The Instagram account must have its business account id. Reconnect the account in posterly if you see this. | missing_instagram_business_account_id |
Each of these previously reached the publish step and failed there. They now fail fast at creation with an actionable message.
.mov, .webm and .m4v are not rejected for Instagram. posterly converts those containers to MP4 for you before publishing, so keep sending them as-is. Only .avi and .mkv, which posterly does not convert, are refused.
Create multiple posts
POST /api/v1/posts/batch
Create up to 25 posts in one API request. Each item uses the same body shape as POST /api/v1/posts.
{
"posts": [
{
"account_id": "169",
"caption": "Monday launch notes.",
"scheduled_at": "2026-05-18T14:00:00Z"
},
{
"account_id": "170",
"caption": "Behind the scenes from launch week.",
"media_urls": ["https://assets.poster.ly/users/123/photo.jpg"],
"scheduled_at": "2026-05-19T14:00:00Z"
}
]
}
Batch requests can partially succeed. Successful items are returned in posts with their original index; failed items are returned in errors with index, status, and a sanitized error. Send an Idempotency-Key header to make retries safe; posterly derives a stable idempotency key per item.
Update or delete a post
PATCH /api/v1/posts/{id}
DELETE /api/v1/posts/{id}
Update scheduled or draft content, or delete a draft/scheduled post. Updates can pass the same settings object used when creating posts, which makes API updates line up with the composer controls for every supported platform.
Change post status
PUT /api/v1/posts/{id}/status
Pause a scheduled post, resume/schedule a paused, draft, or failed post, or move a scheduled/failed/paused post back to draft.
{
"status": "paused"
}
Use status: "scheduled" with scheduled_at when scheduling a draft or when a paused post's original time is already in the past. Published, publishing, and processing posts cannot be changed through this endpoint.
Repair and group cleanup
GET /api/v1/posts/{id}/missing
PUT /api/v1/posts/{id}/release-id
DELETE /api/v1/posts/group/{group}
Use /missing to inspect whether a post is missing required content, media, account context, or platform settings before it publishes. release-id stores external release/group metadata for agent workflows. Group delete removes only draft, scheduled, failed, or paused posts and requires confirm=true.
Upload media
POST /api/v1/media/upload
Upload an image or video to attach to a post. This endpoint accepts base64 data and is best for small files up to 5MB decoded:
{
"filename": "launch-photo.jpg",
"content_type": "image/jpeg",
"data": "<base64 file data>"
}
For larger files, first request a signed upload URL:
POST /api/v1/media/signed-upload
PUT /api/v1/media/signed-upload/upload?token=...
{
"filename": "launch-video.mp4",
"content_type": "video/mp4",
"size": 28400000
}
Upload the raw file bytes to the returned upload_url with PUT. The upload request must use the same content type and stay within the signed size limit. Signed upload tokens expire after 15 minutes. After upload, pass the returned public_url as media_url or inside media_urls when creating posts. Supported media: JPEG, PNG, GIF, WebP, MP4, MOV, and WebM. Images can be up to 10MB; videos can be up to 50MB.
You can also fetch an existing public asset into posterly storage:
POST /api/v1/media/upload-from-url
{
"url": "https://example.com/launch-photo.jpg",
"filename": "launch-photo.jpg"
}
The fetcher blocks localhost/private IP targets and redirect chains that resolve to private networks.
Use the returned url as media_url or inside media_urls when creating or updating posts.
Automatic media analysis
Uploads are automatically registered and analyzed in the background (dimensions, duration, codecs). Upload responses from /api/v1/media/upload and the signed upload PUT include an asset_id field referencing the analyzed asset; it can be null if analysis registration was skipped. You do not need to do anything with it.
Once a video has been analyzed, post creation validates that the file can actually reach the selected platform. Combinations that can never publish are rejected with a structured 400 error instead of failing at publish time:
| Code | Meaning | Example message |
|---|---|---|
video_too_long | The video exceeds the platform's maximum duration | This video is 4m 20s but X allows up to 2m 20s. Trim it to include X. |
video_exceeds_platform_limit | The video is too large for the platform's file size limit | This video is too large for Telegram (20 MB limit). Export it under 20 MB to include Telegram. |
Media that has not been analyzed yet (or was uploaded before this feature) is accepted as before, and anything posterly can adapt automatically is handled without any error.
Find next available slot
GET /api/v1/slots/next
Find the next available posting slot for a given account.
X posting quota
GET /api/v1/x-posting/quota
Returns the current workspace's managed X posting allowance, used and remaining posts, current quota period, URL-block status, active add-on details, and available X quota plans. This requires posts:read.
Account analytics
GET /api/v1/analytics/accounts?account_id={id}&from=YYYY-MM-DD&to=YYYY-MM-DD
Daily snapshots plus a period summary. Social platforms include follower delta, reach, views, and engagement rate; platforms with native dashboards include display_metrics so clients can render exact labels such as Google Business Profile's Profile Views, Search Views, Maps Views, Customer Actions, and Posts. Defaults to the last 30 days. Supported platforms: Instagram, Facebook Pages, LinkedIn, Google Business Profile, Pinterest, YouTube, and Threads. Requires analytics:read.
Post analytics
GET /api/v1/analytics/posts?account_id={id}&from=YYYY-MM-DD&to=YYYY-MM-DD&limit=50&offset=0
Per-post engagement rows (likes, comments, reach, impressions, saves, shares, plays) most-recent-first. limit defaults to 50, max 200. Same platform support and scope requirement as above.
Performance feedback loop
These three read endpoints and one write endpoint expose the performance feedback loop (post to analytics to per-post insight to performance-aware suggestion). All require analytics:read and a Pro plan or higher; lower tiers get 403 with code: "feature_not_available". Data is scoped to the API key's workspace.
Account performance profile
GET /api/v1/accounts/{id}/performance-profile
Returns the read-only performance profile posterly derives for the account from the last 90 days of per-post analytics: stats (top formats, timing, caption-length bands), an engagement-rate trend, coaching bullets, a narrative summary, and derived_at. When the account is connected but has no profile, performance_profile is null with a reason:
insufficient_data, not enough tracked posts yet.platform_not_measurable, the platform has no per-post analytics (for example Google Business Profile).
{
"performance_profile": {
"social_account_id": 42,
"platform": "instagram",
"username": "brandhandle",
"sample_size": 38,
"window_days": 90,
"engagement_rate_avg": 0.041,
"engagement_rate_trend": "up",
"bullets": ["reel posts average 5.2% engagement (12 posts sampled)"],
"narrative_summary": "Reels posted midweek tend to outperform static images.",
"stats": { "windowDays": 90, "sampleSize": 38, "platform": "instagram" },
"derived_at": "2026-07-03T02:00:00.000Z"
},
"reason": null
}
Post feedback insights
GET /api/v1/analytics/insights?account_id={id}&post_id={id}&checkpoint=24h&limit=20
Per-post feedback insights for recently published posts, most-recent-first. Each item carries the performance tier (great / good / mixed / poor), a diagnosis, a next_action, impact_score, confidence, metrics, and the baseline it was compared against. Optional filters: account_id, post_id, and checkpoint (one of 1h, 6h, 24h, 72h, 7d). limit defaults to 20, max 100.
{
"insights": [
{
"id": "a1b2c3d4-0000-0000-0000-000000000000",
"post_id": 9182,
"platform": "instagram",
"checkpoint": "24h",
"performance_tier": "great",
"title": "Strong 24 hour signal",
"diagnosis": "This reel is ahead of your usual Instagram baseline on reach.",
"next_action": "Turn the hook into a follow-up while the topic is still fresh.",
"impact_score": 90,
"confidence": 0.72,
"metrics": { "primaryViews": 5200, "engagements": 410 },
"baseline": { "basis": "same_media_90d", "primaryViewsPercentile": 0.91 },
"created_at": "2026-07-03T14:05:00.000Z"
}
]
}
Post suggestions
GET /api/v1/suggestions?account_id={id}&status=pending&limit=20
POST /api/v1/suggestions/{id}/dismiss
GET lists proactive post suggestions (evidence-based weekly drafts written in each account's learned voice). Each item includes the caption, platform, account, a rationale tying the draft back to what has performed well, period_key, and status. Filter by account_id and status (pending default, or scheduled / dismissed). limit defaults to 20, max 100.
POST /api/v1/suggestions/{id}/dismiss dismisses a suggestion so it stops appearing. It never overwrites a suggestion that was already turned into a scheduled post (returns 409). This write requires posts:write (the closest existing write scope; suggestions are draft posts).
{
"suggestions": [
{
"id": "f9e8d7c6-0000-0000-0000-000000000000",
"social_account_id": 42,
"platform": "instagram",
"account": { "id": 42, "username": "brandhandle", "platform": "instagram" },
"caption": "Behind the scenes of our midweek reel workflow...",
"rationale": "Reels posted midweek are your top performers over the last 90 days.",
"period_key": "2026-W27",
"status": "pending",
"created_at": "2026-07-01T09:00:00.000Z"
}
]
}
Google Business Profile workflows
GET /api/v1/google-business/reviews
POST /api/v1/google-business/reviews/reply
DELETE /api/v1/google-business/reviews/reply
POST /api/v1/google-business/reviews/suggest-reply
GET /api/v1/google-business/review-link
GET /api/v1/google-business/audit
GET /api/v1/google-business/media
POST /api/v1/google-business/media
DELETE /api/v1/google-business/media
Local-business tools expose connected GBP reviews, review request links, live profile audits, review reply workflows, and profile photo/video management over the same API/MCP/CLI surface. Read endpoints require analytics:read or accounts:read; reply and media writes require posts:write plus publisher workspace access. AI reply suggestions use the AI Caption Assist allowance.
Profile media manages the standing photo and video gallery shown on a location's Maps/Search profile (distinct from media attached to a post). GET /api/v1/google-business/media lists items for one location (account_id or location_id) or across all GBP locations. To add one, first upload the file via the /media endpoints to get a public source_url, then POST with source_url, a category (one of COVER, PROFILE, LOGO, EXTERIOR, INTERIOR, PRODUCT, AT_WORK, FOOD_AND_DRINK, MENU, COMMON_AREA, ROOMS, TEAMS, ADDITIONAL), and optional media_format (PHOTO default, or VIDEO). DELETE takes the media_name returned by GET. COVER and PROFILE are single-slot. Photos are limited to 25MB; videos to ~30s and 75MB and pass Google's review before appearing.
AI generation
POST /api/v1/ai/generate-captions
GET /api/v1/ai/video-options
POST /api/v1/ai/video-function
POST /api/v1/ai/generate-video
GET /api/v1/ai/video-jobs
GET /api/v1/ai/video-jobs/{id}
POST /api/v1/ai/generate-image
/ai/generate-captions generates or adapts brand-aware caption suggestions for one or more platforms. It requires posts:write, uses the AI Caption Assist allowance, and returns suggestions only; it does not create a post.
/ai/video-options is a read-only Veo discovery endpoint for models, input modes, durations, resolutions, aspect ratios, and credit-cost estimates. It does not generate video or spend credits.
/ai/video-function runs read-only Veo helper functions such as estimate_cost and validate_request before generation. It does not generate video or spend credits.
/ai/generate-video queues a cost-guarded Veo job and returns 202 Accepted with a job_id. It can spend included or purchased Veo credits, so show the user the prompt, model, duration, resolution, and cost before calling it from an agent. Poll /ai/video-jobs/{id} until the job is completed and video_url is available.
{
"prompt": "A clean vertical product teaser for a social scheduling app.",
"aspect_ratio": "9:16",
"duration_seconds": 8,
"resolution": "720p",
"model": "fast",
"generate_audio": true
}
/ai/generate-image creates Nano Banana images and returns media URLs you can pass to media_url or media_urls when creating posts.
Subscription management
GET /api/v1/subscription
POST /api/v1/subscription/cancel
POST /api/v1/subscription/pause
POST /api/v1/subscription/resume
POST /api/v1/subscription/downgrade
These endpoints let you read and self-manage your own posterly subscription. GET requires billing:read; the four mutations require billing:write. They act only on the API key owner's subscription.
Unlike the rest of the API, subscription endpoints do not require the paid API add-on. Managing your own subscription is not an API-product feature, and gating it would trap paused or cancel-pending users. They still require a valid, non-revoked key with the right billing scope. If the user has no subscription, these return 404 with code: "no_subscription".
cancel requires a reason from a fixed catalog, matching the dashboard flow: too_expensive, not_using, missing_features, technical_issues, switching_service, temporary_break, other. An invalid or missing reason returns 400 with code: "invalid_reason". Optional feedback (free text) and immediate (defaults to false, cancel at period end) are also accepted.
pause pauses for 30 days and is limited to one pause per 90-day cooldown (429 pause_cooldown otherwise). resume clears a pause. downgrade moves the plan down one tier at the next renewal with no proration, or to an explicit lower tier (starter, pro, power_user, agency) if provided.
curl -X POST https://www.poster.ly/api/v1/subscription/cancel \
-H "Authorization: Bearer pst_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"reason":"too_expensive","immediate":false}'
To reactivate a pending cancellation, restart a lapsed plan, or accept a retention offer, use the posterly dashboard; those flows stay app-only.
Activity and webhooks
GET /api/v1/activity
GET /api/v1/notifications
GET /api/v1/webhooks
POST /api/v1/webhooks
PATCH /api/v1/webhooks/{id}
DELETE /api/v1/webhooks/{id}
POST /api/v1/webhooks/{id}/test
/activity returns recent post activity and publish logs so agents can audit what happened without scraping the dashboard. /notifications is an alias for agent and Postiz-style notification consumers.
Webhooks support webhook.test, post.created, post.updated, post.deleted, post.publishing, post.published, post.failed, account.disconnected, and analytics.synced. Deliveries are signed with X-posterly-Signature using t=<unix>,v1=<hmac_sha256>.
Updates feed
GET /api/v1/updates
Returns the posterly updates feed (the same product news shown at /updates), newest first. Requires accounts:read and an active posterly subscription; without one it returns 403 with code: "subscription_required", exactly like the web page.
Query parameters:
limit, integer 1 to 50, defaults to 10.since, ISO date. Only return updates published on or after this date.include_content, boolean, defaults tofalse. Whentrue, each update includes its full markdown body incontent.
curl "https://www.poster.ly/api/v1/updates?limit=5&since=2026-07-01" \
-H "Authorization: Bearer pst_live_your_key_here"
Each update includes slug, title, date, version, summary, type (feature, improvement, fix, or null), areas (product areas such as composer or api), a canonical url on poster.ly, and optional image/video links.
CLI
The public posterly CLI is the terminal interface over the same /api/v1 surface used by the REST API and MCP. Run it without installing anything globally:
npx @posterly/cli@latest auth:login
npx @posterly/cli@latest doctor --pretty
npx @posterly/cli@latest whoami --pretty
npx downloads and runs the CLI for that command. It does not leave a permanent posterly binary on your shell PATH, so keep using npx @posterly/cli@latest for each command unless you install globally with npm i -g @posterly/cli@latest.
If global install fails with an EACCES error under /usr/local/lib/node_modules, use npx or switch to a user-owned Node install such as nvm. A local npm i @posterly/cli install only exposes ./node_modules/.bin/posterly inside that folder.
After a global install, the same commands can be run as posterly:
posterly auth:login
posterly auth:key
posterly doctor --pretty
posterly whoami --pretty
posterly connect:link instagram --pretty
posterly oauth:create-client --client-name "Agency approval app" --redirect-uri https://agency.example.com/oauth/callback --scopes accounts:read,posts:read,posts:write --pretty
posterly platforms:schema --platform instagram --pretty
posterly accounts:disconnect 123 --confirm --pretty
posterly posts:create --account-id 123 --caption "Launching soon"
posterly posts:status 123 --status paused --confirm --pretty
posterly posts:missing 123 --pretty
posterly posts:release-id 123 --release-id launch-2026-05-16 --group-id campaign-42 --pretty
posterly posts:delete-group campaign-42 --confirm --pretty
posterly gbp:reviews --account-id 123 --unanswered --pretty
posterly gbp:audit 123 --pretty
posterly gbp:media --account-id 123 --pretty
posterly gbp:add-media --account-id 123 --source-url https://cdn.example.com/front.jpg --category COVER --confirm --pretty
posterly gbp:delete-media --account-id 123 --media-name accounts/123/locations/456/media/abc --confirm --pretty
posterly gbp:suggest-reply --account-id 123 --star-rating 5 --review-text "Great service" --pretty
auth:login opens a browser and uses posterly's OAuth + PKCE flow to store a local token in ~/.posterly/config.json. auth:key remains available for CI, servers, and users who prefer to paste a Personal Access Token. Use posterly doctor --pretty to validate Node, the configured API origin, the installed package version, and the API key against /api/v1/whoami. The CLI outputs JSON by default for jq-friendly scripting; use --pretty for formatted output.
For non-production deployments, set --url or POSTERLY_URL. The CLI refuses to send API keys to non-local http:// URLs by default; use https://, http://localhost for local development, or --allow-insecure-url only for a trusted private development endpoint.
Rate limits
API limits are per API key and route-aware:
POST /api/v1/postsandPOST /api/v1/posts/batch: 100 requests per hour- post creation: 1,000 created post items per hour per user across all API keys; a batch of 25 counts as 25 items
POST /api/v1/media/*: 120 requests per hour- read endpoints, including accounts, posts, slots, analytics, and subscription status: 300 requests per hour
POST /api/v1/subscription/*(cancel, pause, resume, downgrade): 30 requests per hour- authenticated API traffic: 2,000 requests per hour per user across all API keys
Each API response includes X-Request-Id, X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. A 429 response also includes Retry-After, retry_after, limit, remaining, and reset. Pre-authenticated requests are also IP-limited before API key lookup to protect the API from invalid-token floods.
Full reference
See the interactive API Reference for complete endpoint documentation with request/response examples.