PostQueueHD
Log in

API v1

API reference

Schedule and manage posts from your own code, a script or an AI agent.

https://postqueuehd.com/api/v1

Authentication

Every request needs a bearer token, created under Settings → API.

Authorization: Bearer pq_live_xxxxxxxx

The token is shown once, at creation, and stored only as a hash. If it is lost, revoke it and make another.

The API is included in paid plans. A valid token on the free plan answers 403, not 401 — the token is fine, the plan is not.

Errors

One shape for everything, so a client parses one thing:

{
  "message": "Pick at least one channel.",
  "errors": { "channels": ["Pick at least one channel."] }
}
Status Meaning
401 No token, or the token is unknown, revoked or expired
403 The token is valid but the plan does not include the API
404 No such post, or it belongs to another team
422 The payload or a plan limit rejected the request
429 Rate limited — wait for the seconds in Retry-After

Rate limits

Per token: 60 reads a minute and 20 writes a minute. Over the limit answers 429 with a Retry-After header.

GET /api/v1/me

What this token can do

The team the token belongs to, its plan, and how much of each allowance is left. Call this first: it answers what you are allowed to do without having to fail a write to find out.

curl https://postqueuehd.com/api/v1/me \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

GET /api/v1/channels

The channels you can publish to

Every connected channel with its provider and status. The `id` values here are what `POST /posts` expects in `channels`. A channel whose `needs_attention` is true needs reconnecting: it still accepts posts, but they only publish after it is reconnected — a target whose time comes first waits as `pending` and goes out once the channel is back. `has_queue` says whether the channel has weekly posting times (set on the web), and `next_queue_slot_at` is the UTC moment `POST /queue` would use next, or null when the channel has no times or none is free in the next 60 days. TikTok channels also carry `options.privacy_level`: the privacy levels TikTok offers that creator right now, one of which `channel_options.{id}.privacy_level` has to be. YouTube channels carry `options.categories` — the `{id, name}` pairs `channel_options.{id}.category_id` accepts, named in the request's language — and `options.can_add_to_playlist`: true when playlists are on and the channel granted the permission, false for a connection that must be reconnected first.

curl https://postqueuehd.com/api/v1/channels \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

GET /api/v1/channels/{id}/playlists

A YouTube channel's playlists

The playlists of one of your YouTube channels, for choosing `channel_options.{id}.playlist_id`: up to 200, each with `id`, `title`, `privacy_status` and `item_count`. Cached for ten minutes, so asking again is free. `can_add` says whether a video can be added to a playlist on this channel; `needs_reconnect` is true, with an empty list, when the channel was connected before playlists could be read and has to be connected again. Another team's channel, a channel of another network and an id that does not exist all answer the same 404. 503 `playlists_unavailable` means YouTube could not be read right now — save the post without a playlist, or try again in a moment.

curl https://postqueuehd.com/api/v1/channels/12/playlists \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

GET /api/v1/schedule

Your posting schedule

The recurring weekly slots of every publish-capable channel, grouped by channel. `weekday` is ISO-8601 — Monday is 1, Sunday is 7 — and `time` is a wall clock in the team `timezone`, which is the only zone this product shows. `free_this_week` is how many of that channel's slots in the next 7 days have nothing in them yet. `paused: true` means new posts are not being queued into slots; posts already scheduled still go out. Slots are read-only over the API: they are edited on the Posting schedule screen.

curl https://postqueuehd.com/api/v1/schedule \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

GET /api/v1/schedule/next-slot

Where the next post would land

Resolves the next slot free on *every* channel you name, and renders `local` — "Sun 5 Oct, 19:00" — on the server in the request's language, so you never re-implement date formatting. `days_ahead` is counted in team-local calendar days, so a slot tomorrow morning is 1 however few hours away it is. Always 200, even when there is nothing to resolve: `at` is null and `reason` is `no_slots`, `paused` or `queue_full`, so you render the right empty copy without parsing an error body. Previewing is not reserving — a client that previews and then saves can be beaten to the slot, so read the created post's `scheduled_for` rather than echoing the preview.

Field Type Required Notes
channels integer[] yes Channel ids from GET /channels. An id belonging to another team answers 422.
curl "https://postqueuehd.com/api/v1/schedule/next-slot?channels[]=1&channels[]=8" \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

GET /api/v1/posts

The queue

Posts in the order they go out, cursor-paginated. Follow `links.next` (or `meta.next_cursor`) to page; `links.next` carries the filters along, and a client that pages with `cursor` sends them again. `scope=queue` keeps what is still to go out, soonest first with drafts last; `scope=history` keeps what has finished, newest first. Every filter is optional and they combine with AND. A channel of another team, an unknown network, or a `to` before `from` answers 422.

Field Type Required Notes
scope string no queue or history. Combines with status.
status string no draft, scheduled, publishing, published, partially_failed, failed or cancelled
social_account_id integer no Only posts sent to this channel, by id from GET /channels.
social_account_ids[] integer[] no Only posts sent to at least one of these channels, e.g. social_account_ids[]=1&social_account_ids[]=3. Joined with social_account_id when both are sent. A channel of another team is a 422.
provider string no Only posts sent to at least one channel of this network, e.g. youtube.
q string no Up to 100 characters, found in the caption, a per-channel caption or a YouTube title.
from date no First day, Y-m-d in the team's timezone, inclusive. Posts are dated by their scheduled time; with scope=history, by when they went out.
to date no Last day, Y-m-d, inclusive. Not before from.
per_page integer no Up to 100. Defaults to 25.
curl "https://postqueuehd.com/api/v1/posts?scope=queue&provider=youtube&q=trailer&from=2026-08-01&to=2026-08-31" \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

GET /api/v1/posts/{id}

One post

A single post with one target per channel, each carrying its own status, `channel_name` and `handle` (null once the channel is disconnected), and for a failed target the `error` code plus `error_message`, the explanation in the request's language. `timezone` is the zone the post was planned in. A YouTube target also carries `thumbnail_url` (null without a custom thumbnail) and `warnings[]`: one `{code, message}` for each thing that did not work on a video that did publish — the thumbnail refused, the playlist missing — in the request's language, and the target stays `published`. `options.playlist_id` is the playlist chosen. An id belonging to another team answers 404, exactly like an id that does not exist.

curl https://postqueuehd.com/api/v1/posts/42 \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

POST /api/v1/posts

Schedule a post

Creates a post and one target per channel. Send `scheduled_for` to cue it, `next_free_slot: true` to let your posting schedule choose the time, or `publish_now: true` to publish as soon as possible; with none of the three it is saved as a draft. Send the same `idempotency_key` twice and you get the same post back, which is what makes retrying after a timeout safe — and a repeated key consumes one slot, not two. Each target carries `slot_at`, the slot it occupies, so you can tell a claim from a coincidence. A channel that needs reconnecting (`needs_attention` on `GET /channels`) is accepted; its target only publishes after the channel is reconnected.

Field Type Required Notes
caption string no Up to 5000 characters. Each platform truncates to its own limit.
channels integer[] yes Channel ids from GET /channels.
scheduled_for ISO 8601 no Must be in the future.
next_free_slot boolean no Take the next slot free on every channel named, from GET /schedule. Cannot be combined with scheduled_for or publish_now. Read the real time off the 201 rather than off an earlier preview. A schedule with no slots, a paused one, or one with nothing free inside 90 days answers 422 on scheduled_for.
publish_now boolean no Publish as soon as possible. Cannot be combined with scheduled_for.
media_ids integer[] no Media already uploaded to this team.
idempotency_key string no Up to 64 characters. Reuse it to make a retry safe.
channel_captions object no Per-channel caption override, keyed by channel id.
channel_options object no Per-channel platform options, keyed by channel id — e.g. `{"8": {"privacy_level": "PUBLIC_TO_EVERYONE"}}`. Required for TikTok and YouTube; see Channel options for every field.
curl -X POST https://postqueuehd.com/api/v1/posts \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "caption": "Summer drop is live",
    "channels": [1, 8],
    "media_ids": [91],
    "scheduled_for": "2026-08-01T18:30:00Z",
    "idempotency_key": "summer-drop-1",
    "channel_options": {
      "8": { "privacy_level": "PUBLIC_TO_EVERYONE" }
    }
  }'

POST /api/v1/posts/{id}

Change a cued post

Edits a draft or scheduled post. Send only what changes: every field left out keeps its value. `channels`, when sent, is the complete new list — a channel left out is removed — and each kept channel keeps its own caption and options unless new ones are sent for it. The whole edit is refused (422) if any channel would reject it, and nothing is changed. A public YouTube video uploaded early, private, waiting for its time can still be edited: the change is sent to YouTube after the save, and `warnings[]` on its target says if it could not be; swapping its video, removing its channel or clearing the time answers 422 with a link to YouTube Studio. A POST, not a PATCH: the server in front of the API refuses PATCH.

Field Type Required Notes
caption string no Up to 5000 characters; null clears it.
channels integer[] no The complete list of channel ids after the edit.
scheduled_for ISO 8601 no Must be in the future.
next_free_slot boolean no Re-resolve from your posting schedule. The post's own slot is not counted as taken, so a re-save keeps the slot it already holds instead of moving one forward. Cannot be combined with scheduled_for.
timezone string no The zone the time was chosen in.
media_ids integer[] no The complete, ordered media list after the edit.
channel_captions object no Per-channel caption override, keyed by channel id.
channel_options object no Per-channel platform options, keyed by channel id. Required for a TikTok or YouTube channel being added.
expected_updated_at ISO 8601 no The updated_at you last read; the edit is refused if someone changed the post since.
curl -X POST https://postqueuehd.com/api/v1/posts/42 \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channels": [1, 8],
    "channel_options": {
      "8": { "privacy_level": "SELF_ONLY" }
    }
  }'

POST /api/v1/posts/{id}/cancel

Cancel a cued post

Cancels every channel that has not gone out yet. Anything already published is history and stays as it is; if every channel is already publishing, this answers 422. A YouTube video that was uploaded early and is waiting private for its time cannot be cancelled from here: the 422 says when it goes public and links to YouTube Studio, where it can be changed or cancelled. A POST, not a DELETE: the server in front of the API refuses DELETE.

curl -X POST https://postqueuehd.com/api/v1/posts/42/cancel \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

GET /api/v1/posts/{id}/targets/{target}/translations

A video's translations

The title and description of one target in other languages, and `default_language`, the language the post itself is in (null until chosen: the upload then uses the language of the person who created the post). Only YouTube takes translations: any other target answers 422 with `code: translations_unsupported`. They are sent with the upload. Once the video is on YouTube, a write answers 409 with `code: translations_locked` — or, where sending changes to published videos is switched on, is saved and sent to the video (202) when the channel allows it, else 409 with `code: translations_reconnect_required`. `sync_status` is then `pending`, `synced` or `failed` (null before any), with `sync_error` `{code, message}` when failed. Each target in `GET /posts/{id}` also carries `default_language` and `translations`, and each channel says `supports_translations`.

curl https://postqueuehd.com/api/v1/posts/42/targets/7/translations \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

POST /api/v1/posts/{id}/targets/{target}/translations/{locale}

Add or change a translation

Creates the translation for `locale` (201) or replaces the one there is (200), and answers with it. On a published video it answers 202 with `sync_status: pending`. `locale` is a BCP-47 tag — `en`, `pt-PT`, `es-419` — and cannot be the post's own `default_language`. At most 50 languages per target.

Field Type Required Notes
title string yes Up to 100 characters, without < or >.
description string no Up to 5000 bytes.
curl -X POST https://postqueuehd.com/api/v1/posts/42/targets/7/translations/es-419 \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "Lanzamiento de verano", "description": "Ya disponible"}'

POST /api/v1/posts/{id}/targets/{target}/translations/{locale}/delete

Remove a translation

Removes the translation for `locale` and answers 204, also when there was none, so a retry is safe. Removing one from a published video answers 202 with `sync_status: pending`. A POST, not a DELETE: the server in front of the API refuses DELETE.

curl -X POST https://postqueuehd.com/api/v1/posts/42/targets/7/translations/es-419/delete \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

POST /api/v1/posts/{id}/targets/{target}/translations/default-language

Set the post's own language

Says which language the post's own title and description are in; YouTube needs it to accept translations. null goes back to the creator's language. A language that already has a translation answers 422. Answers like the GET. 202 when it changed the language of a published video.

Field Type Required Notes
default_language string|null yes BCP-47, like pt-PT.
curl -X POST https://postqueuehd.com/api/v1/posts/42/targets/7/translations/default-language \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"default_language": "pt-PT"}'

GET /api/v1/comments

Comments on your published posts

Top-level comments under the team's published posts, newest first, cursor-paginated as `{data, next_cursor}`. Each carries its `replies` (oldest first, without replies of their own). A comment is `{id, provider, channel_id, post_id, target_id, parent_id, remote_id, permalink, author: {name, handle, avatar_url, is_own}, text, like_count, published_at, replied_at, can_reply, replies}`. `like_count` is null when the platform does not say; `can_reply` is true when the network is on, the channel's grant covers comments, the comment is still on the platform and you may answer. Comments are read back from the platforms every so often, and the platform deleted ones are left out. `status=unanswered` keeps the comments by others the channel has not replied to.

Field Type Required Notes
channel_id integer no One channel's comments, an id from GET /channels.
post_id integer no One post's comments.
status string no `all` (default) or `unanswered`.
per_page integer no Up to 50. Defaults to 25.
cursor string no The `next_cursor` of the previous page.
curl "https://postqueuehd.com/api/v1/comments?status=unanswered" \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

GET /api/v1/posts/{id}/comments

Comments on one post

GET /comments for one post: the same comments, filters and pages. Another team's post is a 404.

Field Type Required Notes
status string no `all` (default) or `unanswered`.
per_page integer no Up to 50.
cursor string no The `next_cursor` of the previous page.
curl https://postqueuehd.com/api/v1/posts/42/comments \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

POST /api/v1/comments/{id}/reply

Reply to a comment

Posts the reply on the platform, as the channel, and answers 201 with it (a comment with `parent_id` set and `author.is_own: true`). It is public at once; the same call twice posts two replies. Refusals carry a `code`: 422 `comments_unsupported` (the network or this channel's grant does not cover comments), 409 `reconnect_required` (connect the channel again), 429 `quota_exhausted` (the platform's budget for today is spent, with `Retry-After` when known), 404 `comment_not_found` (gone from the platform, or not this team's). An empty text or one longer than the platform takes is a 422 on `text`.

Field Type Required Notes
text string yes The reply. Each platform has its own length limit.
curl -X POST https://postqueuehd.com/api/v1/comments/311/reply \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"text": "Thank you!"}'

POST /api/v1/comments/sync

Read comments now

Asks for the comments to be read from the platforms now instead of at the next scheduled read, and answers 202 with `queued`, the number of posts it will read; list again shortly after. Covers the team's posts published in the last 14 days, or one post. At most 3 calls a minute.

Field Type Required Notes
post_id integer no Read only this post's comments.
curl -X POST https://postqueuehd.com/api/v1/comments/sync \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

POST /api/v1/queue

Add a post to the queue

Puts a post into the next free time of each channel's weekly schedule, so no date is sent. It takes the `POST /posts` content and creates one post per channel, each at that channel's own next free time — the response is a list. A time is free when no scheduled post of that channel sits on it and it is more than two minutes away; cancelled or failed posts free their time. All channels are queued or none: if one has no posting times the answer is 422 with the error key `targets.no_queue_slots` (set times for it on the web first), and if its next 60 days are full, `targets.queue_full`. `idempotency_key` is required and must be a UUID: send the same one twice and you get the same posts back with 200 instead of 201, and no second time is used. `scheduled_for` and `publish_now` are refused — use `POST /posts` to choose a time. A channel that needs reconnecting is queued like any other; its post only publishes after the channel is reconnected.

Field Type Required Notes
idempotency_key uuid yes Reuse it to make a retry safe: the posts already made come back and no further time is taken.
targets object[] yes One entry per channel: `social_account_id` (an id from GET /channels), and optionally `caption_override` and `options` — the same per-channel options as `channel_options` on POST /posts.
caption string no Up to 5000 characters. Each platform truncates to its own limit.
media_ids integer[] no Media already uploaded to this team.
curl -X POST https://postqueuehd.com/api/v1/queue \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "3f0c6c1e-8a52-4f3e-9a55-0d1a4f1d2b77",
    "caption": "Clip from tonight",
    "media_ids": [91],
    "targets": [
      { "social_account_id": 1 },
      { "social_account_id": 8, "options": { "privacy_level": "PUBLIC_TO_EVERYONE" } }
    ]
  }'

GET /api/v1/media

The media you have uploaded

Everything this team has uploaded, newest first, cursor-paginated. Transcoded variants are left out: they are built per platform and are not something you refer to. Use this to reuse a file you already sent rather than uploading it twice.

Field Type Required Notes
per_page integer no Up to 100. Defaults to 25.
curl https://postqueuehd.com/api/v1/media \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

POST /api/v1/media

Start an upload

Reserves a media row and answers with somewhere to send the file: `upload.method` and `upload.url`, plus any `upload.headers` to send with it. The bytes go to the URL that comes back, not to this endpoint, so a large video is never held in a JSON request. Your plan limits are checked here, before you send anything — a file over the limit is refused now rather than after the upload finishes. Send a sha256 `checksum` and a file already uploaded answers 200 with `already_uploaded: true` and no upload target, because there is nothing left to send. Send the original file as it is: any video format is accepted (MP4, MOV, MKV, WebM, AVI; H.264, HEVC, ProRes, VP9; HDR or 10-bit; any resolution or frame rate). Each channel then gets a copy it takes, made automatically when the post is scheduled — YouTube keeps the original resolution (a 4K upload stays 4K); Instagram, Facebook and TikTok get MP4 H.264/AAC, 8-bit SDR, at most 1080p on the short side (1080×1920, 1920×1080, 1080×1080). A file over a channel's size limit is compressed for it when a lower bitrate fits; only a video over a channel's maximum duration is refused, because trimming it is your call.

Field Type Required Notes
filename string yes The original name. Its extension decides the stored one.
mime_type string yes Anything starting image/ is an image; everything else is treated as video.
size_bytes integer yes Checked against the plan's video size and storage allowance before you upload.
checksum string no sha256 of the file, 64 hex characters. A match reuses the existing media instead of uploading again.
# 1. reserve the row and read the upload target
curl -X POST https://postqueuehd.com/api/v1/media \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "summer.mp4",
    "mime_type": "video/mp4",
    "size_bytes": 8412345
  }'

# 2. send the bytes where it told you to
curl -X "$UPLOAD_METHOD" "$UPLOAD_URL" \
  -H "Content-Type: video/mp4" \
  --data-binary @summer.mp4

POST /api/v1/media/{id}/complete

Finish an upload

Confirms the bytes arrived and starts the probe that reads the dimensions, duration and codecs. Answers 422 if nothing was uploaded. The probe runs in the background, so this does not mean the file is usable yet — poll `GET /media/{id}` until the status is `ready`.

curl -X POST https://postqueuehd.com/api/v1/media/91/complete \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

GET /api/v1/media/{id}

One piece of media

The status, and what the probe found. `youtube_short` is whether YouTube will treat this video as a Short — `{qualifies, reason, label}`, with `reason` one of `horizontal`, `too_long` or `horizontal_and_too_long` and `label` the sentence to show, in the request's language — and null for an image or a file that has not been probed yet. Status walks `uploaded` → `probing` → `ready`, or `failed` with the reason in `error`. Only `ready` media is worth scheduling: a failed file will not publish, and the `error` says why in words you can pass on to whoever supplied it.

curl https://postqueuehd.com/api/v1/media/91 \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

POST /api/v1/media/{id}/delete

Delete media

Deletes the file and everything transcoded from it, freeing the storage against your plan. Media still attached to a post that has not gone out answers 422 — cancel the post first. Media whose posts have all published can go; it would be deleted by the retention window anyway. A POST, not a DELETE: the server in front of the API refuses DELETE.

curl -X POST https://postqueuehd.com/api/v1/media/91/delete \
  -H "Authorization: Bearer $POSTQUEUE_TOKEN"

POST /api/v1/posts → channel_options

Channel options

Platform settings, keyed by channel id. A required option that is missing answers 422 under targets, and nothing is scheduled.

"channel_options": {
  "8": { "privacy_level": "PUBLIC_TO_EVERYONE", "disable_duet": true },
  "12": { "title": "Summer drop", "made_for_kids": false }
}

TikTok

Field Type Required Notes
privacy_level string yes Who can see the post: `PUBLIC_TO_EVERYONE`, `MUTUAL_FOLLOW_FRIENDS`, `FOLLOWER_OF_CREATOR` or `SELF_ONLY`, and only one listed in the channel's `options.privacy_level` from GET /channels. Leaving it out is what answers "Choose who can see this post on TikTok." There is no default: TikTok forbids choosing it on the creator's behalf.
disable_comment boolean no Turn comments off. Defaults to false.
disable_duet boolean no Turn duets off. Defaults to false.
disable_stitch boolean no Turn stitches off. Defaults to false.
cover_offset_ms integer no Which moment of the video to use as the cover, in milliseconds.
disclose boolean no The post promotes a brand, product or service. When true, set at least one of the two below.
brand_organic_toggle boolean no Promotes your own business. Only counts while `disclose` is true.
brand_content_toggle boolean no Paid partnership with a third party. Only counts while `disclose` is true, and cannot be combined with `SELF_ONLY`.

YouTube

Field Type Required Notes
title string yes Up to 100 characters. The caption becomes the description.
made_for_kids boolean yes Required by YouTube, true or false — leaving it out is refused.
privacy_status string no `public`, `unlisted` or `private`. Defaults to `private`. A `public` video with a future `scheduled_for` is uploaded up to two hours early as private and made public by YouTube at that time; until then it cannot be edited or cancelled from here.
category_id string no One of `options.categories` from GET /channels. Defaults to `22` (People & Blogs); anything else answers `Invalid YouTube category.`
tags string[] no Video tags: each up to 100 characters, no `<` or `>`, 500 characters in total.
thumbnail_media_id integer no The id of a `ready` image of yours (JPEG or PNG, up to 2 MiB — 2,097,152 bytes — read from the file itself, not its mime type), uploaded through POST /media. 1280×720 is recommended; other sizes are accepted. A thumbnail that is not yours answers exactly like one that does not exist. If YouTube refuses it after the video is up, the post still publishes and `warnings[]` says why.
playlist_id string no One of the ids from GET /channels/{id}/playlists. Answers 422 `Connect YouTube again to choose a playlist.` when `options.can_add_to_playlist` is false.

Instagram

Field Type Required Notes
cover_offset_ms integer no Which moment of the Reel to use as the cover, in milliseconds.
share_to_feed boolean no Also show the Reel in the profile feed.

Facebook

Field Type Required Notes
place_id string no A Facebook Page id to tag as the location.

POST /mcp

MCP server

The same API as an MCP server, so an agent can schedule a post without anybody writing the glue first.

https://postqueuehd.com/mcp

The same token from Settings → API, in the same Authorization header, and the same plan gate — a free-plan token answers 403 here too.

Add it with the Claude CLI:

claude mcp add --transport http postqueue https://postqueuehd.com/mcp \
  --header "Authorization: Bearer pq_live_xxxxxxxx"

Or, for any host that takes a URL and headers:

{
  "mcpServers": {
    "postqueue": {
      "url": "https://postqueuehd.com/mcp",
      "headers": { "Authorization": "Bearer pq_live_xxxxxxxx" }
    }
  }
}

The tools it exposes:

Tool What it does
whoami The team this token belongs to, the plan it is on, the platforms that plan includes, and how much of each allowance is left as limit/used/remaining..
list_channels Every connected channel, with the `id` values `create_post` expects in `channels`..
list_posts The team's posts, cursor-paginated..
get_post One post, with one entry in `targets` per channel..
create_post Create a post and one target per channel..
update_post Edit a draft or a scheduled post.
cancel_post Cancel every channel on a post that has not gone out yet..
upload_media Step 1 of 3.
confirm_media Step 3 of 3.
get_media The status of an upload, and what the probe found: `width`, `height`, `duration_seconds`..
list_translations The title and description of one channel's copy of a post in other languages, and the language the post itself is written in (`default_language`)..
set_translation Add the title and description of one channel's copy of a post in another language, or replace the one there is for that language.
delete_translation Remove the title and description of one channel's copy of a post in one language..
list_comments The comments under the team's published posts, newest first, cursor-paginated..
reply_to_comment Answer a comment on its platform, as the channel, and get the reply back..

60 reads and 20 writes a minute per token, the same as the REST API.

Platform rules

Platform rules — what each channel accepts. A post is refused at `create_post` if any
of its channels would reject it, and nothing is scheduled.

| Platform | Caption | Media | Max video | Max size | Required channel_options |
| --- | --- | --- | --- | --- | --- |
| Instagram | 2200 characters | required: photo or video | 15 minutes | 1000 MB | none |
| Facebook | 63206 characters | optional: photo, video, or text only | 4 hours | 10000 MB | none |
| TikTok | 2200 characters | required: video | 10 minutes | 4000 MB | `privacy_level` |
| YouTube | 5000 characters | required: video | 12 hours | 2000 MB | `title`, `made_for_kids` |

Any video format is accepted on upload (MP4, MOV, MKV, WebM, AVI; H.264, HEVC, ProRes, VP9;
HDR or 10-bit; any resolution or frame rate). Each channel gets a copy it accepts, made
automatically: Instagram gets up to 1080p H.264/AAC SDR, Facebook gets up to 1080p H.264/AAC SDR, TikTok gets up to 1080p H.264/AAC SDR, YouTube keeps the original resolution. Converting can delay a post by a few
minutes. Only the duration limit and photos are not fixed for you.

Instagram carousels take up to 10 items, all photos or all videos.

Two things here are **per account and cannot be listed in advance** — call `list_channels`
first and read them off the channel:

- TikTok: the `privacy_level` values TikTok offers that creator right now, in
  `options.privacy_level`, and that creator's own maximum video duration. One of those
  values has to go in `channel_options.{id}.privacy_level`; there is no default,
  because TikTok forbids choosing it on the creator's behalf.
- Any channel whose `needs_attention` is true accepts posts but only publishes them
  once it is reconnected — an Instagram account that is PERSONAL rather than a
  Business or Creator account shows up this way.

For AI agents

Point your agent at one of these instead of at this page — both are plain text and read in a single fetch.

This works with Claude, Gemini, ChatGPT or anything else that can fetch a URL — there is nothing to install.

Read https://postqueuehd.com/docs/api.md and schedule a post for tomorrow at 18:30 on every connected channel.