# letmepost.dev > Open-source social media publishing API. Preflight validation, transparent errors, idempotency, stable platform versions. - [Quickstart](https://docs.letmepost.dev/quickstart.md): Publish your first tweet via curl in 90 seconds. - [Authentication](https://docs.letmepost.dev/authentication.md): API keys, scoping, and rotation. Bearer tokens, scoped per organization. - [Idempotency](https://docs.letmepost.dev/idempotency.md): Every write accepts an Idempotency-Key. Retries are safe. No double-posting. - [Errors](https://docs.letmepost.dev/errors/index.md): The thirteen error codes, the response shape, and what to do about each one. - [Preflight](https://docs.letmepost.dev/preflight/index.md): Every documented platform constraint is checked locally before we touch the upstream API. Here are all the rules. - [MCP server](https://docs.letmepost.dev/agents/mcp.md): Use letmepost.dev from Claude Code, Cursor, Claude Desktop, or any MCP-compatible client. 21 tools, autogenerated from the OpenAPI spec. - [CLI](https://docs.letmepost.dev/agents/cli.md): A command-line client for letmepost.dev. Publish from your terminal or from any agentic tool that can shell out. - [Accounts](https://docs.letmepost.dev/guides/connect-account.md): Connect, refresh, fan-out. The OAuth lifecycle for each platform. - [Publish a post](https://docs.letmepost.dev/guides/publish-post.md): From key to published — the synchronous path, the scheduled path, and what to subscribe to in between. - [Scheduled posts](https://docs.letmepost.dev/guides/schedule-post.md): Pass scheduledAt to queue a batch for the future. 202 instead of 200, queued status per target, post.queued webhook per target. - [Media](https://docs.letmepost.dev/guides/upload-media.md): Three ways to attach media. Why mediaId is the production answer. - [Quick start](https://docs.letmepost.dev/self-host/quick-start.md): Get the API, worker, and dashboard running locally against a Postgres + Redis you control. - [Environment variables](https://docs.letmepost.dev/self-host/environment.md): Every variable the API reads, what defaults to what, and which ones are required to boot. - [Platform credentials](https://docs.letmepost.dev/self-host/platform-credentials.md): Register the developer apps that let your self-hosted instance talk to each social network. - [Deploying](https://docs.letmepost.dev/self-host/deploying.md): Two-service split, managed Postgres + Redis, custom domain, zero-downtime migrations. - [Troubleshooting](https://docs.letmepost.dev/self-host/troubleshooting.md): Common boot failures, what they mean, and how to fix them. - [Migrate from Postiz](https://docs.letmepost.dev/guides/migrate-from-postiz.md): Map your existing Postiz publishing flow to letmepost.dev. - [Migrate from Ayrshare](https://docs.letmepost.dev/guides/migrate-from-ayrshare.md): Map your existing Ayrshare publishing flow to letmepost.dev. - [Migrate from Buffer](https://docs.letmepost.dev/guides/migrate-from-buffer.md): Map your existing Buffer publishing flow to letmepost.dev. - [Pricing](https://docs.letmepost.dev/pricing.md): Two tiers. Free for evaluation, Pro at $79/mo for real workloads. Self-host always free under Apache 2.0. - [Changelog](https://docs.letmepost.dev/changelog.md): Reverse-chronological release notes covering API surface, schema, breaking changes, and new platforms. - [Platforms overview](https://docs.letmepost.dev/platforms-overview.md): Seven networks, one API. Status, features, and connect flow per platform. - [Twitter / X](https://docs.letmepost.dev/platforms/twitter.md): Twitter / X v2 API. OAuth 2.0 PKCE. Up to 4 images, video via chunked upload, reply chains, quote tweets. - [Instagram](https://docs.letmepost.dev/platforms/instagram.md): Instagram Business / Creator via the Content Publishing API. Standalone Instagram Login — no Facebook required. - [LinkedIn](https://docs.letmepost.dev/platforms/linkedin.md): Personal posting via LinkedIn's Versioned REST API. Org/Company posting ships with MDP approval. - [Facebook Pages](https://docs.letmepost.dev/platforms/facebook.md): Facebook Pages via the Meta Graph API. Page Access Tokens, three endpoints (/feed, /photos, /videos), no IG fan-out. - [Threads](https://docs.letmepost.dev/platforms/threads.md): Meta's Threads via the standalone Threads Graph API. Two-step container publish. - [Pinterest](https://docs.letmepost.dev/platforms/pinterest.md): Pinterest v5 API. Image pins via direct URL, video pins via register-media + S3 multipart + status poll. - [TikTok](https://docs.letmepost.dev/platforms/tiktok.md): TikTok Content Posting API. OAuth 2.0 PKCE + push_by_file upload to the creator's inbox, confirmed in the TikTok app. - [Bluesky](https://docs.letmepost.dev/platforms/bluesky.md): AT Proto. Live end-to-end. App-password connect, no OAuth. - [Webhooks](https://docs.letmepost.dev/webhooks/index.md): HMAC-signed deliveries for post lifecycle, token, version, subscription, quota, and billing events. - [post.queued](https://docs.letmepost.dev/webhooks/post-queued.md): A scheduled post was accepted and a delayed publish job was enqueued. - [post.validated](https://docs.letmepost.dev/webhooks/post-validated.md): Preflight passed. Today fired together with post.queued; reserved for an explicit validate step in a future slice. - [post.published](https://docs.letmepost.dev/webhooks/post-published.md): The post was successfully published upstream. - [post.rejected](https://docs.letmepost.dev/webhooks/post-rejected.md): Preflight failed, the platform rejected the post, or auth failed. Not retried. - [post.failed](https://docs.letmepost.dev/webhooks/post-failed.md): Transient failure — internal error or upstream 5xx. The worker may retry. - [post.canceled](https://docs.letmepost.dev/webhooks/post-canceled.md): A queued scheduled post was canceled via DELETE /v1/posts/:id before it fired. - [post.rescheduled](https://docs.letmepost.dev/webhooks/post-rescheduled.md): An operator moved a queued post to a new scheduledAt via PATCH /v1/posts/:id. - [token.expiring](https://docs.letmepost.dev/webhooks/token-expiring.md): A connected platform token is approaching expiry and cannot be auto-refreshed. The user must reconnect. - [token.revoked](https://docs.letmepost.dev/webhooks/token-revoked.md): A connected platform token was rejected by the upstream — the user revoked, expired, or rotated it. - [version.deprecated](https://docs.letmepost.dev/webhooks/version-deprecated.md): An upstream platform announced an API version sunset. We're migrating internally; this is informational. - [List posts (post log)](https://docs.letmepost.dev/api-reference/posts/list-posts-post-log.md): Cursor-paginated post log scoped to the caller's organization (and profile, when the key is profile-scoped). Filters compose. Array filters (`platform`, `status`, `errorCode`) accept either repeated query params or comma-separated single values. - [Publish or schedule a multi-target post](https://docs.letmepost.dev/api-reference/posts/publish-or-schedule-a-multi-target-post.md): Publish a single body to up to 25 connected accounts in one call, or schedule the whole batch for a future time. - [Fetch a single post with its publish attempts](https://docs.letmepost.dev/api-reference/posts/fetch-a-single-post-with-its-publish-attempts.md): Returns the post row plus every publish attempt (start/finish, success flag, upstream response, error code). Use the `postId` returned in `CreatePostResponse.results[]` as `{id}`. - [Cancel a queued scheduled post](https://docs.letmepost.dev/api-reference/posts/cancel-a-queued-scheduled-post.md): Cancel a scheduled post before it fires. Only allowed when `status=queued` AND `scheduledAt` is still in the future — same window as `PATCH`. Removes the BullMQ job and transitions the row to `status=canceled`. A `post.canceled` webhook fires on success. - [Reschedule a queued post](https://docs.letmepost.dev/api-reference/posts/reschedule-a-queued-post.md): Move a scheduled post to a new firing time. Only allowed when `status=queued` AND `scheduledAt` is still in the future — once a post enters `publishing` or any terminal state, the window closes and the request returns `409`. - [List uploaded media](https://docs.letmepost.dev/api-reference/media/list-uploaded-media.md): Cursor-paginated. Scoped to the caller's organization, and (when the key is profile-scoped) to that profile. Org-wide keys may narrow with `?profileId=`. - [Upload media (multipart)](https://docs.letmepost.dev/api-reference/media/upload-media-multipart.md): Stream a single `file` part to S3 and get back a `med_…` id to reference from post bodies as `media: [{ kind, mediaId }]`. - [List connected accounts](https://docs.letmepost.dev/api-reference/accounts/list-connected-accounts.md): Lists every connected platform account in the caller's organization. No secrets ever leak — display name, platform identifiers, and lifecycle timestamps only. - [Fetch a connected account](https://docs.letmepost.dev/api-reference/accounts/fetch-a-connected-account.md) - [Disconnect (hard delete)](https://docs.letmepost.dev/api-reference/accounts/disconnect-hard-delete.md): Dashboard session only — programmatic keys cannot disconnect accounts. Hard delete: the platform_accounts row is removed and any persisted token is destroyed. - [Start a connect flow](https://docs.letmepost.dev/api-reference/accounts/start-a-connect-flow.md): Dashboard session only. Returns a `ConnectDescriptor` describing how to finish the connect — an OAuth authorization URL for OAuth platforms, or a credentials form schema for Bluesky. Programmatic API keys cannot mint OAuth state. - [Finish a connect flow](https://docs.letmepost.dev/api-reference/accounts/finish-a-connect-flow.md): Dashboard session only. Completes the handshake started by `POST /v1/accounts/connect/{platform}` — for OAuth platforms this is invoked from the redirect callback flow; for credentials platforms (Bluesky) it carries the credentials in the body. Upserts the platform_accounts row (re-connecting the sa… - [List Pinterest boards on a connected account](https://docs.letmepost.dev/api-reference/accounts/list-pinterest-boards-on-a-connected-account.md): Proxy to Pinterest's `/v5/boards` using the stored token. Used by the dashboard's default-board picker so a user can see their actual boards. - [Create a Pinterest board](https://docs.letmepost.dev/api-reference/accounts/create-a-pinterest-board.md): Creates a board on the user's Pinterest account without sending them off-site. Optional `setAsDefault` lifts the new board id into the account's default in one round-trip. Optional `upsert: true` returns the existing board (with `existing: true`) on duplicate-name instead of 409-ing. - [Set the default Pinterest board for publishes](https://docs.letmepost.dev/api-reference/accounts/set-the-default-pinterest-board-for-publishes.md): Persists the supplied `boardId` as the account's default — used when a `POST /v1/posts` body omits `options.boardId` for a Pinterest target. The board is validated against the account's own boards list before persisting. - [List API keys](https://docs.letmepost.dev/api-reference/api-keys/list-api-keys.md): Dashboard session only. Plaintext is never returned again — only `last4` and metadata. - [Mint an API key](https://docs.letmepost.dev/api-reference/api-keys/mint-an-api-key.md): Dashboard session only. The plaintext `key` is returned **once** in this response and never again — store it securely. Optional `profileId` pins the key to a single profile; cross-profile reads/writes 404 under that key. - [Revoke an API key](https://docs.letmepost.dev/api-reference/api-keys/revoke-an-api-key.md): Soft-revoke. The key is invalidated immediately; the row stays for audit. Already-revoked keys 404. - [List webhook endpoints](https://docs.letmepost.dev/api-reference/webhooks/list-webhook-endpoints.md) - [Register a webhook endpoint](https://docs.letmepost.dev/api-reference/webhooks/register-a-webhook-endpoint.md): Authenticated with an API key or dashboard session. The `signingSecret` is returned **once** at creation and is used to verify the HMAC-SHA256 `X-Letmepost-Signature` header on every delivery. An empty `events` array subscribes to every event type. - [Fetch a webhook endpoint](https://docs.letmepost.dev/api-reference/webhooks/fetch-a-webhook-endpoint.md) - [Delete a webhook endpoint](https://docs.letmepost.dev/api-reference/webhooks/delete-a-webhook-endpoint.md): Hard delete — the row is removed. Use `PATCH … { active: false }` instead if you want to pause delivery while preserving history. - [Update a webhook endpoint](https://docs.letmepost.dev/api-reference/webhooks/update-a-webhook-endpoint.md): Partial update. Setting `active: false` pauses delivery without losing the endpoint; flipping back to `true` re-enables it. - [Send a synthetic test delivery](https://docs.letmepost.dev/api-reference/webhooks/send-a-synthetic-test-delivery.md): Fires a synthetic event at the endpoint URL synchronously and returns what the consumer responded — bypasses the BullMQ delivery queue on purpose so operators get immediate feedback. The real signing secret is used; the consumer's HMAC verification path is identical to production. - [validation_failed](https://docs.letmepost.dev/errors/validation_failed.md): Request body or query failed schema or batch-level validation. The rule field carries the specific check that failed. - [preflight_failed](https://docs.letmepost.dev/errors/preflight_failed.md): A documented platform constraint failed before we touched the upstream API. The rule field carries the canonical rule id. - [platform_auth_failed](https://docs.letmepost.dev/errors/platform_auth_failed.md): The connected account's platform token is missing, expired, or revoked. The user must reconnect. - [platform_rejected](https://docs.letmepost.dev/errors/platform_rejected.md): Preflight passed but the upstream platform still rejected the call. Carries the raw platform response. - [platform_unavailable](https://docs.letmepost.dev/errors/platform_unavailable.md): Upstream is down, throttled, or returning 5xx. Transient — retry later. - [platform_not_enabled](https://docs.letmepost.dev/errors/platform_not_enabled.md): The platform exists in the API enum but isn't switched on for your org yet. - [internal_error](https://docs.letmepost.dev/errors/internal_error.md): Unexpected server-side issue. Includes a requestId you can file with us. - [unauthenticated](https://docs.letmepost.dev/errors/unauthenticated.md): The Authorization header is missing, malformed, or the API key is revoked. - [unauthorized](https://docs.letmepost.dev/errors/unauthorized.md): The API key is authentic but not allowed to perform this action. - [not_found](https://docs.letmepost.dev/errors/not_found.md): Resource doesn't exist or is out of scope. We 404 instead of 403 to avoid leaking existence. - [idempotency_conflict](https://docs.letmepost.dev/errors/idempotency_conflict.md): Same Idempotency-Key reused with a different request body. We surface this rather than silently publish twice. - [rate_limited](https://docs.letmepost.dev/errors/rate_limited.md): Per-key rate limit exhausted. Honor the Retry-After header. - [quota_exceeded](https://docs.letmepost.dev/errors/quota_exceeded.md): Your organization hit its monthly post quota. Upgrade the plan or wait for the reset. - [bluesky.text.non_empty](https://docs.letmepost.dev/preflight/bluesky-text-non_empty.md): Bluesky posts cannot be empty. Whitespace-only text is rejected. - [bluesky.text.max_graphemes](https://docs.letmepost.dev/preflight/bluesky-text-max_graphemes.md): Bluesky caps post text at 300 graphemes. Emoji and ZWJ sequences count as 1. - [bluesky.media.image_video_exclusive](https://docs.letmepost.dev/preflight/bluesky-media-image_video_exclusive.md): A single Bluesky post is either images-only or video-only — not mixed. - [bluesky.media.count_max](https://docs.letmepost.dev/preflight/bluesky-media-count_max.md): Up to 4 images per post, or 1 video. No exceptions. - [bluesky.media.alt_text_max_graphemes](https://docs.letmepost.dev/preflight/bluesky-media-alt_text_max_graphemes.md): Alt text caps at 2000 graphemes per media item. - [bluesky.media.mime_allowed](https://docs.letmepost.dev/preflight/bluesky-media-mime_allowed.md): Bluesky accepts jpeg/png/webp/gif images and mp4 video. Other formats are rejected. - [bluesky.media.image_size_max](https://docs.letmepost.dev/preflight/bluesky-media-image_size_max.md): Each image must be ≤ 976 KB (1,000,000 bytes). Bluesky's blob limit. - [bluesky.media.video_size_max](https://docs.letmepost.dev/preflight/bluesky-media-video_size_max.md): Each video must be ≤ 100 MB. - [bluesky.video.quota_exhausted](https://docs.letmepost.dev/preflight/bluesky-video-quota_exhausted.md): The Bluesky account is out of daily video upload quota. - [bluesky.video.job_failed](https://docs.letmepost.dev/preflight/bluesky-video-job_failed.md): Bluesky's video pipeline rejected the file during transcode or scanning. - [bluesky.video.transcode_timeout](https://docs.letmepost.dev/preflight/bluesky-video-transcode_timeout.md): Bluesky's transcoder didn't finish within our 6-minute deadline. - [bluesky.first_comment.non_empty](https://docs.letmepost.dev/preflight/bluesky-first_comment-non_empty.md): When firstComment is set, its text must be non-empty. - [bluesky.first_comment.max_graphemes](https://docs.letmepost.dev/preflight/bluesky-first_comment-max_graphemes.md): First-comment text is also capped at 300 graphemes — same as the parent post. - [threads.text.required](https://docs.letmepost.dev/preflight/threads-text-required.md): Threads text-only posts require non-empty text. Media-less + empty-text combinations are rejected. - [threads.text.max_graphemes](https://docs.letmepost.dev/preflight/threads-text-max_graphemes.md): Threads caps post text at 500 graphemes, including image captions. - [threads.media.count_max](https://docs.letmepost.dev/preflight/threads-media-count_max.md): Threads carousels accept 2–20 mixed media items. Beyond 20 is rejected. - [threads.media.alt_text_max_graphemes](https://docs.letmepost.dev/preflight/threads-media-alt_text_max_graphemes.md): Threads alt text caps at 1000 graphemes per media item. - [threads.media.mime_allowed](https://docs.letmepost.dev/preflight/threads-media-mime_allowed.md): Threads accepts jpeg/png/webp images and mp4/mov video. - [threads.media.image_size_max](https://docs.letmepost.dev/preflight/threads-media-image_size_max.md): Each image on Threads must be ≤ 8 MB. - [threads.media.video_size_max](https://docs.letmepost.dev/preflight/threads-media-video_size_max.md): Each video on Threads must be ≤ 1 GB. - [threads.container.expired](https://docs.letmepost.dev/preflight/threads-container-expired.md): A media container exceeded its 24-hour TTL before publish. Re-create the container. - [threads.container.error](https://docs.letmepost.dev/preflight/threads-container-error.md): Threads's media container transitioned to ERROR during preparation. Carries the upstream error. - [instagram.media.required](https://docs.letmepost.dev/preflight/instagram-media-required.md): Instagram has no text-only post surface. Every IG post needs at least one media item. - [instagram.text.max_graphemes](https://docs.letmepost.dev/preflight/instagram-text-max_graphemes.md): Instagram captions cap at 2200 graphemes. Same counter as Bluesky and Threads. - [instagram.media.count_max](https://docs.letmepost.dev/preflight/instagram-media-count_max.md): Instagram carousels cap at 10 mixed media items. - [instagram.media.alt_text_max_graphemes](https://docs.letmepost.dev/preflight/instagram-media-alt_text_max_graphemes.md): Instagram alt text caps at 2200 graphemes — same as the caption. - [instagram.media.mime_allowed](https://docs.letmepost.dev/preflight/instagram-media-mime_allowed.md): Instagram is JPEG-only for photos. PNG, WebP, HEIC, and GIF will all fail with opaque IG errors. - [instagram.media.image_size_max](https://docs.letmepost.dev/preflight/instagram-media-image_size_max.md): Each Instagram image must be ≤ 8 MB. - [instagram.media.video_size_max](https://docs.letmepost.dev/preflight/instagram-media-video_size_max.md): Instagram videos and Reels must be ≤ 1 GB. - [instagram.media.reachable](https://docs.letmepost.dev/preflight/instagram-media-reachable.md): Instagram fetches media URLs from its own servers. Drive/Dropbox/auth-gated URLs fail as OAuthException 2207052. - [instagram.media.aspect_ratio](https://docs.letmepost.dev/preflight/instagram-media-aspect_ratio.md): Instagram rejects media that's outside its supported aspect-ratio range. Caught at container-finalize time. - [instagram.container.expired](https://docs.letmepost.dev/preflight/instagram-container-expired.md): An IG media container aged past Instagram's 24-hour window before publish. - [instagram.account_type.requires_professional](https://docs.letmepost.dev/preflight/instagram-account_type-requires_professional.md): Connect attempted with a PERSONAL Instagram account. Instagram's Graph API requires Business or Creator. - [facebook.text.required](https://docs.letmepost.dev/preflight/facebook-text-required.md): Facebook posts without media require non-empty text. - [facebook.text.max_graphemes](https://docs.letmepost.dev/preflight/facebook-text-max_graphemes.md): Facebook Page posts cap at 63,206 graphemes. Hard server-side limit. - [facebook.media.image_video_exclusive](https://docs.letmepost.dev/preflight/facebook-media-image_video_exclusive.md): A Facebook Page post is either images-only or video-only. No mixing. - [facebook.media.count_max](https://docs.letmepost.dev/preflight/facebook-media-count_max.md): Facebook Page photo posts accept up to 10 images. - [facebook.media.mime_allowed](https://docs.letmepost.dev/preflight/facebook-media-mime_allowed.md): Facebook Pages accept jpeg/png images and mp4/mov video. - [facebook.media.image_size_max](https://docs.letmepost.dev/preflight/facebook-media-image_size_max.md): Each Facebook Page image must be ≤ 4 MB. - [facebook.media.video_size_max](https://docs.letmepost.dev/preflight/facebook-media-video_size_max.md): Each Facebook Page video must be ≤ 4 GB. - [facebook.pages.none](https://docs.letmepost.dev/preflight/facebook-pages-none.md): The connecting Facebook user has no Pages they can manage. Connection cannot complete. - [linkedin.text.non_empty](https://docs.letmepost.dev/preflight/linkedin-text-non_empty.md): LinkedIn posts cannot be empty. Whitespace-only text is rejected. - [linkedin.text.max_graphemes](https://docs.letmepost.dev/preflight/linkedin-text-max_graphemes.md): LinkedIn caps post commentary at 3000 graphemes. Emoji-aware. - [linkedin.author.org_not_supported](https://docs.letmepost.dev/preflight/linkedin-author-org_not_supported.md): Org/Company posting requires LinkedIn's Marketing Developer Platform (MDP). Not in v1. - [linkedin.author.urn_format](https://docs.letmepost.dev/preflight/linkedin-author-urn_format.md): The author URN must match urn:li:person:{id}. We surface the format check before LinkedIn's opaque 422. - [linkedin.author.unresolved](https://docs.letmepost.dev/preflight/linkedin-author-unresolved.md): We don't have a usable LinkedIn author URN for this account. Re-auth needed. - [linkedin.visibility.enum](https://docs.letmepost.dev/preflight/linkedin-visibility-enum.md): visibility must be PUBLIC or CONNECTIONS. Other values are rejected. - [pinterest.board.required](https://docs.letmepost.dev/preflight/pinterest-board-required.md): A Pinterest pin needs a board id — either explicit or set as the account default. - [pinterest.media.required](https://docs.letmepost.dev/preflight/pinterest-media-required.md): Every pin needs media. Pinterest is media-first. - [pinterest.media.single_only](https://docs.letmepost.dev/preflight/pinterest-media-single_only.md): v1 Pinterest support is single-image only. Multi-image pins are deferred. - [pinterest.image.mime_allowed](https://docs.letmepost.dev/preflight/pinterest-image-mime_allowed.md): Pinterest accepts jpeg/png images. - [pinterest.image.size_max](https://docs.letmepost.dev/preflight/pinterest-image-size_max.md): Pinterest images cap at 20 MB. - [pinterest.image_url.reachable](https://docs.letmepost.dev/preflight/pinterest-image_url-reachable.md): Pinterest fetches the pin image from the URL you provide. It must respond 200 anonymously. - [pinterest.destination_url.reachable](https://docs.letmepost.dev/preflight/pinterest-destination_url-reachable.md): The pin's click-through URL must be reachable. - [pinterest.url.reachable](https://docs.letmepost.dev/preflight/pinterest-url-reachable.md): Generic URL-reachable failure on Pinterest pin creation. - [pinterest.video.cover_required](https://docs.letmepost.dev/preflight/pinterest-video-cover_required.md): Pinterest video pins must include a publicly reachable cover image URL. - [pinterest.video.mime_allowed](https://docs.letmepost.dev/preflight/pinterest-video-mime_allowed.md): Video bytes must be mp4, mov, or m4v. - [pinterest.video.size_max](https://docs.letmepost.dev/preflight/pinterest-video-size_max.md): Pinterest video pins are capped at ~2 GB per upload. - [pinterest.cover_image_url.reachable](https://docs.letmepost.dev/preflight/pinterest-cover_image_url-reachable.md): The cover image URL on a video pin must return 2xx and a supported image mime. - [pinterest.cover_image.mime_allowed](https://docs.letmepost.dev/preflight/pinterest-cover_image-mime_allowed.md): Cover image content-type must be jpeg, png, or webp. - [pinterest.media.register_failed](https://docs.letmepost.dev/preflight/pinterest-media-register_failed.md): Pinterest refused to register a video upload slot. - [pinterest.video.upload_failed](https://docs.letmepost.dev/preflight/pinterest-video-upload_failed.md): The S3 multipart upload Pinterest handed us back rejected the bytes. - [pinterest.video.transcode_failed](https://docs.letmepost.dev/preflight/pinterest-video-transcode_failed.md): Pinterest's video pipeline rejected the file during transcoding. - [pinterest.video.transcode_timeout](https://docs.letmepost.dev/preflight/pinterest-video-transcode_timeout.md): Pinterest didn't finish transcoding within our 5-minute deadline. - [twitter.text.non_empty](https://docs.letmepost.dev/preflight/twitter-text-non_empty.md): Tweets cannot be empty. Whitespace-only text is rejected. - [twitter.text.max_graphemes](https://docs.letmepost.dev/preflight/twitter-text-max_graphemes.md): Tweets cap at 280 graphemes — with t.co URL wrapping accounted for. - [twitter.media.count_max](https://docs.letmepost.dev/preflight/twitter-media-count_max.md): Tweets accept up to 4 images, OR 1 video, OR 1 GIF. - [twitter.media.image_video_exclusive](https://docs.letmepost.dev/preflight/twitter-media-image_video_exclusive.md): A tweet can carry images OR a video, not both. - [twitter.media.alt_text_max_graphemes](https://docs.letmepost.dev/preflight/twitter-media-alt_text_max_graphemes.md): Alt text on a tweet's media is capped at 1000 graphemes. - [twitter.media.mime_allowed](https://docs.letmepost.dev/preflight/twitter-media-mime_allowed.md): Twitter accepts jpeg/png/webp images, gif animations, and mp4 video. - [twitter.media.image_size_max](https://docs.letmepost.dev/preflight/twitter-media-image_size_max.md): Static images cap at 5 MB on Twitter / X. - [twitter.media.gif_size_max](https://docs.letmepost.dev/preflight/twitter-media-gif_size_max.md): Animated GIFs cap at 15 MB on Twitter / X. - [twitter.media.video_size_max](https://docs.letmepost.dev/preflight/twitter-media-video_size_max.md): Videos cap at 512 MB on Twitter / X. - [twitter.media.video_processing_failed](https://docs.letmepost.dev/preflight/twitter-media-video_processing_failed.md): X's chunked upload finalize step or status poll reported a failed transcode. - [twitter.media.processing_timeout](https://docs.letmepost.dev/preflight/twitter-media-processing_timeout.md): X's STATUS endpoint didn't reach succeeded/failed within our 5-minute deadline. - [twitter.launch_cap.per_account](https://docs.letmepost.dev/preflight/twitter-launch_cap-per_account.md): The per-account X launch cap of 50 billable posts per rolling 30 days is exhausted. - [media.unknown](https://docs.letmepost.dev/preflight/media-unknown.md): A mediaId on the request body doesn't resolve to a media record we can see. - [media.bytes_inline_unsupported](https://docs.letmepost.dev/preflight/media-bytes_inline_unsupported.md): Inline base64 isn't accepted on this platform's URL-only ingest path. Upload via POST /v1/media first. - [meta.container.expired](https://docs.letmepost.dev/preflight/meta-container-expired.md): Instagram or Threads media container exceeded its 24-hour TTL. Re-create. ## OpenAPI Specs - [openapi](/api-reference/openapi.json) This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.