API Reference
The Postally REST API lets you upload media and schedule or publish posts to every connected platform — X, Instagram, LinkedIn, Facebook, Threads, TikTok, YouTube, and Bluesky — from your own code. All endpoints are authenticated with an API key and return JSON.
Base URL
https://app.postally.io/api/public/v1Every path below is relative to this base URL, e.g. POST https://app.postally.io/api/public/v1/posts.
Authentication
Every request must include two headers: your API key and the brand you're acting on.
Authorization: Bearer pst_live_xxxxxxxxxxxxxxxxxxxx x-brand-id: <your_brand_id> Content-Type: application/json
Getting an API key
Generate a key in your dashboard under Settings → API Keys. Choose the scopes it needs, then copy the token — it starts with pst_live_ (or pst_test_) and is shown only once.
The x-brand-id header
A Postally account can hold several brands. The x-brand-id header selects which brand every request acts on. You can find your Brand ID in the dashboard, or by calling GET /me.
Keep your key secret
Scopes
Each API key is granted one or more scopes. A request to an endpoint without its required scope returns 403 INSUFFICIENT_SCOPE.
| Scope | Grants |
|---|---|
posts:read | List and read posts |
posts:write | Create, schedule, reschedule, and delete posts |
media:write | Upload media (files and remote URLs) |
analytics:read | Read profile and post analytics |
brands:read | List brands and read account context |
Account context
/mebrands:readReturns the organization, brand, and user resolved from your API key and x-brand-id header. Handy for confirming a key and brand are wired up correctly.
Response
{
"organization": { "id": "org_...", "name": "Acme Inc" },
"brand": { "id": "brand_...", "name": "Acme" },
"user": { "id": "usr_...", "email": "you@acme.com", "name": "You" }
}/brandsbrands:readList every brand in the API key's organization. Use GET /brands/:id to fetch a single brand.
Upload media
Upload an image or video first, then attach the returned id and path to a post. Both endpoints return the same media object.
/media/uploadmedia:writeUpload a file as multipart/form-data in a field named file. Supports common image and video formats.
Response
{
"id": "media_...",
"name": "launch.png",
"path": "https://cdn.postally.io/media/launch.png",
"thumbnail": "https://cdn.postally.io/media/launch_thumb.png",
"alt": null
}/media/upload-from-urlmedia:writeHand Postally a public URL and it downloads and stores the file for you. Returns the same media object as above.
Request body
{
"url": "https://example.com/image.jpg"
}Publish & schedule posts
/postsposts:writeCreate a draft, schedule a post for later, or publish immediately. A single call can target multiple platforms — add one entry to posts[] per connected account.
Top-level fields
type— one ofdraft,schedule, ornow. Draft saves without publishing; schedule publishes atdate; now publishes immediately.date— ISO 8601 datetime (required). Must be in the future forschedule. Ignored fornow.shortLink— boolean (required). Auto-shorten URLs in the content.posts[]— one entry per target account (required unlesstypeisdraft).
Each posts[] entry
integration.id— the connected account to post to. This is what selects the platform (see “Finding integration IDs” below).value[]— the content blocks. One block is a normal post; multiple blocks form a thread / multi-part post. Each block hascontent(text) andimage[](media from the upload step).settings.__type— the platform identifier; must match the integration's platform. Some platforms require extra fields here (see the settings reference below).commentDelays[]— optional per-block delay in minutes (0–1440) for pacing threads.
Request body
{
"type": "schedule",
"date": "2026-09-01T14:30:00Z",
"shortLink": false,
"posts": [
{
"integration": { "id": "integration_x_123" },
"value": [
{
"content": "Launching something new today \ud83d\ude80",
"image": [
{
"id": "media_...",
"path": "https://cdn.postally.io/media/launch.png"
}
]
}
],
"settings": { "__type": "x" }
}
]
}Response
[
{
"postId": "post_...",
"integration": "integration_x_123",
"group": "group_..."
}
]Platform settings (settings.__type)
Set __type to the target platform. Most platforms need only { "__type": "..." }; a few require extra fields, listed here.
| __type | Platform | Required extra fields |
|---|---|---|
x | X (Twitter) | — |
instagram | post_type: "post" | "story" | |
linkedin | — | |
linkedin-page | LinkedIn Page | — |
facebook | — | |
threads | Threads | — |
tiktok | TikTok | privacy_level, duet, stitch, comment, autoAddMusic, brand_content_toggle, brand_organic_toggle, content_posting_method |
youtube | YouTube | title (2–100), type: "public" | "private" | "unlisted" |
bluesky | Bluesky | — |
Finding integration IDs
integration.id from your dashboard, or read it from the integration.id field returned by GET /posts. Posting with an unknown ID returns a 400.Manage posts
/posts?startDate&endDateposts:readList posts (scheduled and published) between two ISO 8601 dates. Each item includes its integration, state, and publishDate.
/posts/:idposts:readFetch a single post, including its content blocks, attached media, platform settings, and group.
/posts/:id/dateposts:writeReschedule a post to a new time.
Request body
{ "date": "2026-09-05T09:00:00Z" }/posts/:groupposts:writeDelete a post group (identified by the group returned when the post was created). Removes all platform copies in that group.
Analytics
/analytics/insights/profile?integrationId=analytics:readReturn profile-level metrics plus a list of recent posts with per-post metrics for one connected account. Pass the integrationId as a query parameter.
Errors
Errors return a JSON body with a machine-readable code and a human-readable message:
{
"statusCode": 403,
"code": "INSUFFICIENT_SCOPE",
"message": "This API key is missing the required scope."
}| Status | code | Meaning |
|---|---|---|
401 | API_KEY_REQUIRED | Missing or malformed Authorization header |
401 | INVALID_API_KEY | Key is invalid or revoked |
400 | BRAND_ID_REQUIRED | Missing x-brand-id header |
403 | BRAND_NOT_ALLOWED | Key is not permitted to act on this brand |
403 | INSUFFICIENT_SCOPE | Key is missing the required scope |
402 | — | Storage limit exceeded (media upload) |
400 | Platform validation failed | Post content/settings invalid — see issues[] |
429 | — | Rate limit exceeded |
Rate limits
429 Too Many Requests — back off and retry.Interactive reference
A live OpenAPI (Swagger) reference for these endpoints is available at https://app.postally.io/api/public/v1/docs.