Skip to content

API Documentation

Authenticate with an API key, list your connected social accounts, and create, schedule and publish posts programmatically.

Overview

The Postfeeds API is a resource-oriented REST API. All requests and responses are JSON.

Base URLhttps://postfeeds.com/api/v1

Authentication

Every request needs an API key sent as a Bearer token. Create one at Dashboard → API Keys — the full key is shown once (only a hash is stored) and can be revoked instantly.

auth
curl https://postfeeds.com/api/v1/accounts \
  -H "Authorization: Bearer pf_live_..."

Keys carry scopes (all granted by default): read for GETs, posts:write to create/cancel posts, profiles:write to create profiles.

Quickstart

  1. Create an API key at Dashboard → API Keys.
  2. Connect a TikTok account at Dashboard → Connections (one-time, in the browser — the OAuth flow needs a signed-in session, so accounts can't be connected via the API).
  3. GET /accounts → copy the account id.
  4. POST /posts with publishNow: true.
  5. Poll GET /posts/{id} for delivery status.
quickstart.sh
# 1. Which accounts can I post to?
curl https://postfeeds.com/api/v1/accounts \
  -H "Authorization: Bearer $PF_API_KEY"

# 2. Publish a video to one of them
curl -X POST https://postfeeds.com/api/v1/posts \
  -H "Authorization: Bearer $PF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "New drop",
    "media": ["https://cdn.example.com/clip.mp4"],
    "platforms": [{ "platform": "tiktok", "accountId": "<account id>" }],
    "publishNow": true
  }'

Core concepts

ConceptWhat it is
AccountA connected social account. Its id is the accountId you post to.
ProfileA named group of accounts — a brand, client or campaign.
PostOne piece of content for one account. A single POST /posts fans out to many accounts and returns one post per account.

Post modes

You sendWhat happens
publishNow: trueDelivered to the platform immediately.
scheduledFor: ISO-8601Stored, then delivered automatically when due.
neitherSaved as a draft — media persisted, nothing sent.

Post lifecycle

draft → scheduled → uploading → processing → failed | canceled. processingmeans the content was handed to the platform — for TikTok it's now in the user's TikTok app inbox (drafts), where they finish publishing. Failed posts carry an error message.

Media rules

Media is passed by URL— the server downloads it. URLs must be public http(s). If your files aren't hosted anywhere, upload them first with POST /media/presign and use the returned publicUrl. Per post, exactly one of:

KindFormatsLimits
Video.mp4 .mov .webmOne per post, max 200 MB
Photos.jpg .png .webp1–35 per post, max 20 MB each

Photos are auto-resized to TikTok's 1080×1920 canvas, re-encoded as JPEG and rehosted on Postfeeds' TikTok-verified storage — your image host does not need TikTok URL verification.

Endpoints

GET/accounts

List connected social accounts. Use an account's id as accountId when posting.

200
{
  "accounts": [
    {
      "id": "adee4cee-399a-480e-ab8d-274fcc31d794",
      "platform": "tiktok",
      "handle": "post.feeds",
      "displayName": "Postfeeds",
      "avatarUrl": "https://...",
      "status": "active",
      "profileId": "98437905-fa96-4f2d-b5bf-60071c52a6e7",
      "connectedAt": "2026-06-06T16:39:34+00:00"
    }
  ]
}
GET/profiles

List profiles (groups of accounts).

200
{
  "profiles": [
    { "id": "...", "name": "Default Profile", "color": "violet",
      "isDefault": true, "createdAt": "2026-06-05T12:41:32+00:00" }
  ]
}
POST/profiles

Create a profile. Body: { "name": "Client — Acme", "color": "violet" } (color optional). Returns 201 with the profile.

POST/posts

Create, schedule or publish a post to one or more accounts. Supports the Idempotency-Key header — retries with the same key return the original response and never duplicate the post.

FieldTypeRequiredNotes
contentstringyesCaption, max 4000 chars — 2200 for video (may be empty)
titlestringnoVideo title
mediastring[]yesPublic media URLs — see Media rules
platformsobject[]yesOne { platform, accountId } per target
scheduledForISO-8601noFuture date — schedule for later
publishNowbooleannoMutually exclusive with scheduledFor
tiktokSettingsobjectno{ draft: false, privacyLevel } directly publishes TikTok photos
request
curl -X POST https://postfeeds.com/api/v1/posts \
  -H "Authorization: Bearer $PF_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 1f6d2c3e-8a4b-4f0e-9d1a-aaaa00000001" \
  -d '{
    "content": "New drop",
    "media": ["https://cdn.example.com/clip.mp4"],
    "platforms": [
      { "platform": "tiktok", "accountId": "adee4cee-..." }
    ],
    "publishNow": true
  }'

For a direct TikTok photo or carousel post, use publishNow: true and include tiktokSettings with draft: false and an allowed privacyLevel. Omitting these settings preserves the TikTok drafts flow.

direct TikTok photos
{
  "content": "Six ideas for a better launch",
  "media": [
    "https://cdn.example.com/slide-1.png",
    "https://cdn.example.com/slide-2.png"
  ],
  "platforms": [
    { "platform": "tiktok", "accountId": "adee4cee-..." }
  ],
  "publishNow": true,
  "tiktokSettings": {
    "draft": false,
    "privacyLevel": "SELF_ONLY"
  }
}
201
{
  "posts": [
    {
      "id": "a4a4ab72-5183-46af-8c77-2785ce2572ec",
      "accountId": "adee4cee-399a-480e-ab8d-274fcc31d794",
      "platform": "tiktok",
      "status": "processing",
      "publishId": "v_inbox_file~v2.123456789"
    }
  ]
}

Per-item status is processing (publishNow), scheduled, draft or failed (with error). One failed item doesn't fail the whole request.

GET/posts

List posts, newest first. Query params: status, platform, limit (default 20, max 100), offset. Returns { posts, limit, offset }.

GET/posts/{id}

A single post and its delivery status.

200
{
  "id": "a4a4ab72-...",
  "accountId": "adee4cee-...",
  "platform": "tiktok",
  "title": "",
  "content": "New drop",
  "status": "processing",
  "scheduledFor": null,
  "publishId": "v_inbox_file~v2.123456789",
  "error": null,
  "createdAt": "2026-06-11T08:29:16+00:00"
}
DELETE/posts/{id}

Cancel a draft or scheduled post (anything else → 400). Stored media is removed. Returns { "id": "...", "status": "canceled" }.

POST/media/presign

Direct upload for files that aren't hosted at a public URL. Returns a presigned PUT URL (valid 15 minutes) and the public URL the file will have. PUT the raw bytes to uploadUrl with the same Content-Type, then use publicUrl in the media array of POST /posts. Accepts contentType (required) and size in bytes (recommended — enforced on upload when provided); same format and size limits as Media rules.

201
{
  "uploadUrl": "https://...r2.cloudflarestorage.com/...&X-Amz-Signature=...",
  "publicUrl": "https://media.postfeeds.com/<userId>/uploads/<uuid>.jpg",
  "key": "<userId>/uploads/<uuid>.jpg",
  "expiresIn": 900
}
GET/usage

Quota and rate-limit picture for the calling key.

200
{
  "postsThisMonth": 8,
  "connections": { "used": 2, "max": 2 },
  "rateLimit": { "perMinute": 60 }
}

Errors

Every error has the same envelope and a meaningful HTTP status:

error
{ "error": { "code": "invalid_request", "message": "media must be an array of URLs" } }
HTTPCodeMeaning
400invalid_requestMalformed body or params
401unauthorizedMissing, invalid or revoked API key
403forbiddenKey lacks the required scope
404not_foundResource doesn't exist or isn't yours
422unsupportede.g. platform not yet supported
429rate_limitedRate limit exceeded
502upstream_errorThe social platform rejected the request
500internal_errorUnexpected server error

Rate limits & idempotency

Default: 60 requests/minute per key. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; exceeding the limit returns 429 with Retry-After: 60.

POST /posts accepts an Idempotency-Key header (any unique string). Replays return the original response with Idempotency-Replayed: true — safe retries, no double-posting.

Platform support

The API currently delivers to TikTok and LinkedIn. Other platforms return 422 unsupported and are coming as their backends ship.

  • TikTok— video posts use the inbox/draft flow: the video lands in the connected user's TikTok app inbox and they tap to finish publishing — the right flow while a TikTok app's Content Posting audit is pending. Photos land as drafts via MEDIA_UPLOAD. Media is always required.
  • LinkedIn— posts publish straight to the member's feed (status published, post URN in publishId). Text-only or 1–35 images; video not yet supported. Tokens last ~60 days without refresh — expired accounts show expired and need a reconnect.
  • Accounts are connected in the dashboard (Dashboard → Connections), not via the API — the OAuth flow requires a signed-in browser session.

Migrating from Zernio

If your client currently targets Zernio's API:

ZernioPostfeeds
zernio.com/api/v1postfeeds.com/api/v1
sk_...pf_live_...
GET /accounts → _idGET /accounts → id
POST /posts → { post: { _id } }{ posts: [{ id }] } (array)
mediaItemsmedia — plain array of URL strings
tiktokSettings / draft: trueOmit publishNow + scheduledFor for a draft; TikTok settings handled server-side
POST /media/presignPOST /media/presign — same flow: presigned PUT, then use publicUrl
GET /connect/tiktokNot available — connect accounts in the dashboard
POST /tools/validate/postNot available — POST /posts returns precise 400s
GET /logsUse GET /posts/{id} (status + error)

Zernio account/profile IDs are meaningless here — re-fetch them from GET /accounts and GET /profiles after connecting your accounts.