# letmepost.dev > Open-source social media publishing API. Preflight validation, transparent errors, idempotency, stable platform versions. ## Docs - [Complete a connect flow](https://docs.letmepost.dev/api-reference/accounts/complete-a-connect-flow.md): TODO: full schema. See https://letmepost.dev/docs/ for the surrounding context. - [Disconnect](https://docs.letmepost.dev/api-reference/accounts/disconnect.md): TODO: full schema. See https://letmepost.dev/docs/ for the surrounding context. - [Fetch a connected account](https://docs.letmepost.dev/api-reference/accounts/fetch-a-connected-account.md): TODO: full schema. See https://letmepost.dev/docs/ for the surrounding context. - [List connected accounts](https://docs.letmepost.dev/api-reference/accounts/list-connected-accounts.md): TODO: full schema. See https://letmepost.dev/docs/ for the surrounding context. - [Start a connect flow](https://docs.letmepost.dev/api-reference/accounts/start-a-connect-flow.md): TODO: full schema. See https://letmepost.dev/docs/ for the surrounding context. - [List API keys](https://docs.letmepost.dev/api-reference/api-keys/list-api-keys.md): TODO: full schema. See https://letmepost.dev/docs/ for the surrounding context. - [Mint an API key](https://docs.letmepost.dev/api-reference/api-keys/mint-an-api-key.md): TODO: full schema. See https://letmepost.dev/docs/ for the surrounding context. - [Revoke an API key](https://docs.letmepost.dev/api-reference/api-keys/revoke-an-api-key.md): TODO: full schema. See https://letmepost.dev/docs/ for the surrounding context. - [List media uploads](https://docs.letmepost.dev/api-reference/media/list-media-uploads.md): TODO: full schema. See https://letmepost.dev/docs/ for the surrounding context. - [Upload media (multipart)](https://docs.letmepost.dev/api-reference/media/upload-media-multipart.md): Upload a single file part named `file`. Returns a `med_…` id you reference from post bodies. - [Public version tracker](https://docs.letmepost.dev/api-reference/platform-versions/public-version-tracker.md): TODO: full schema. See https://letmepost.dev/docs/ for the surrounding context. - [Fetch a single post with attempts](https://docs.letmepost.dev/api-reference/posts/fetch-a-single-post-with-attempts.md): Returns the full post record including every publish attempt (timing, upstream response, error code). - [List posts](https://docs.letmepost.dev/api-reference/posts/list-posts.md): Cursor-paginated. Filter by `profileId`, `platform`, `status`, `errorCode`, `after`, `before`. Cursor pagination via `nextCursor` on responses. - [Publish or schedule a post](https://docs.letmepost.dev/api-reference/posts/publish-or-schedule-a-post.md): Publish to a connected account, or schedule for a future time when `scheduledAt` is set. - [Create a profile](https://docs.letmepost.dev/api-reference/profiles/create-a-profile.md): TODO: full schema. See https://letmepost.dev/docs/ for the surrounding context. - [List profiles](https://docs.letmepost.dev/api-reference/profiles/list-profiles.md): TODO: full schema. See https://letmepost.dev/docs/ for the surrounding context. - [Delete a webhook endpoint](https://docs.letmepost.dev/api-reference/webhooks/delete-a-webhook-endpoint.md): TODO: full schema. See https://letmepost.dev/docs/ for the surrounding context. - [List webhook endpoints](https://docs.letmepost.dev/api-reference/webhooks/list-webhook-endpoints.md): TODO: full schema. See https://letmepost.dev/docs/ for the surrounding context. - [Register a webhook URL](https://docs.letmepost.dev/api-reference/webhooks/register-a-webhook-url.md): TODO: full schema. See https://letmepost.dev/docs/ for the surrounding context. - [Authentication](https://docs.letmepost.dev/authentication.md): API keys, scoping, and rotation. Bearer tokens, prefixed by environment. - [Changelog](https://docs.letmepost.dev/changelog.md): Reverse-chronological release notes. API surface, schema, breaking changes. - [CLI](https://docs.letmepost.dev/cli.md): A command-line client for letmepost.dev. Coming soon. - [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. - [Errors](https://docs.letmepost.dev/errors/index.md): The eleven error codes, the response shape, and what to do about each one. - [internal_error](https://docs.letmepost.dev/errors/internal_error.md): Unexpected server-side issue. Includes a requestId you can file with us. - [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. - [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. - [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. - [rate_limited](https://docs.letmepost.dev/errors/rate_limited.md): Per-key rate limit exhausted. Honor the Retry-After header. - [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. - [validation_failed](https://docs.letmepost.dev/errors/validation_failed.md): Request body or query string failed schema validation. The rule field carries the path of the offending field. - [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 post for the future. 202 instead of 201, queued status, post.queued webhook. - [Self-host](https://docs.letmepost.dev/guides/self-host.md): Docker Compose, BYO Postgres + Redis + S3, BYO platform credentials. Same code as hosted. - [Media](https://docs.letmepost.dev/guides/upload-media.md): Three ways to attach media. Why mediaId is the production answer. - [Idempotency](https://docs.letmepost.dev/idempotency.md): Every write accepts an Idempotency-Key. Retries are safe. No double-posting. - [MCP server](https://docs.letmepost.dev/mcp.md): Use letmepost.dev from Claude, Cursor, or any MCP-compatible client. - [Bluesky](https://docs.letmepost.dev/platforms/bluesky.md): AT Proto. Live end-to-end. App-password connect, no OAuth. - [Facebook Pages](https://docs.letmepost.dev/platforms/facebook.md): Facebook Pages via the Meta Graph API. One OAuth connects Pages and any linked Instagram Business accounts. - [Instagram Business](https://docs.letmepost.dev/platforms/instagram.md): Instagram Business via the Meta Graph API. Connected together with Facebook Pages — one OAuth, two platforms. - [LinkedIn](https://docs.letmepost.dev/platforms/linkedin.md): Personal posting via LinkedIn's Versioned REST API. Org/Company posting ships with MDP approval. - [Pinterest](https://docs.letmepost.dev/platforms/pinterest.md): Pinterest v5 API. Single-image pins in v1; carousels and video pins ship later. - [Threads](https://docs.letmepost.dev/platforms/threads.md): Meta's Threads via the standalone Threads Graph API. Two-step container publish. - [Twitter / X](https://docs.letmepost.dev/platforms/twitter.md): Twitter / X v2 API. OAuth 2.0 with PKCE. Single-media tweets in v1. - [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. - [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.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.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.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.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.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.video_size_max](https://docs.letmepost.dev/preflight/bluesky-media-video_size_max.md): Each video must be ≤ 100 MB. - [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.text.non_empty](https://docs.letmepost.dev/preflight/bluesky-text-non_empty.md): Bluesky posts cannot be empty. Whitespace-only text is rejected. - [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.image_size_max](https://docs.letmepost.dev/preflight/facebook-media-image_size_max.md): Each Facebook Page image must be ≤ 4 MB. - [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.mime_allowed](https://docs.letmepost.dev/preflight/facebook-media-mime_allowed.md): Facebook Pages accept jpeg/png images and mp4/mov video. - [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. - [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.text.required](https://docs.letmepost.dev/preflight/facebook-text-required.md): Facebook posts without media require non-empty text. - [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. - [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.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.media.count_max](https://docs.letmepost.dev/preflight/instagram-media-count_max.md): Instagram carousels cap at 10 mixed media items. - [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.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.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.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.media.video_size_max](https://docs.letmepost.dev/preflight/instagram-media-video_size_max.md): Instagram videos and Reels must be ≤ 1 GB. - [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. - [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.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.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.text.max_graphemes](https://docs.letmepost.dev/preflight/linkedin-text-max_graphemes.md): LinkedIn caps post commentary at 3000 graphemes. Emoji-aware. - [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.visibility.enum](https://docs.letmepost.dev/preflight/linkedin-visibility-enum.md): visibility must be PUBLIC or CONNECTIONS. Other values are rejected. - [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. - [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. - [meta.container.expired](https://docs.letmepost.dev/preflight/meta-container-expired.md): Instagram or Threads media container exceeded its 24-hour TTL. Re-create. - [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.destination_url.reachable](https://docs.letmepost.dev/preflight/pinterest-destination_url-reachable.md): The pin's click-through URL must be reachable. - [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.media.image_only](https://docs.letmepost.dev/preflight/pinterest-media-image_only.md): v1 supports image pins only. Video pins are deferred. - [pinterest.media.required](https://docs.letmepost.dev/preflight/pinterest-media-required.md): Every pin needs an image. 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.url.reachable](https://docs.letmepost.dev/preflight/pinterest-url-reachable.md): Generic URL-reachable failure on Pinterest pin creation. - [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. - [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.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.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.image_size_max](https://docs.letmepost.dev/preflight/threads-media-image_size_max.md): Each image on Threads must be ≤ 8 MB. - [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.video_size_max](https://docs.letmepost.dev/preflight/threads-media-video_size_max.md): Each video on Threads must be ≤ 1 GB. - [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.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. - [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.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.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.single_only](https://docs.letmepost.dev/preflight/twitter-media-single_only.md): v1 supports single-image and single-video tweets only. Multi-image (4-up) ships in a follow-up slice. - [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.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.text.non_empty](https://docs.letmepost.dev/preflight/twitter-text-non_empty.md): Tweets cannot be empty. Whitespace-only text is rejected. - [Pricing](https://docs.letmepost.dev/pricing.md): Free during alpha. Flat rate after, with a real free tier and no per-profile tax. - [Quickstart](https://docs.letmepost.dev/quickstart.md): Send your first post to Bluesky in 90 seconds. Curl-only, no SDK. - [Go SDK](https://docs.letmepost.dev/sdks/go.md): Post-launch follow-up. Use net/http in the meantime. - [Python SDK](https://docs.letmepost.dev/sdks/python.md): Post-launch follow-up. Use requests/httpx in the meantime. - [TypeScript SDK](https://docs.letmepost.dev/sdks/typescript.md): An official TypeScript SDK is in flight. Use raw fetch in the meantime. - [Webhooks](https://docs.letmepost.dev/webhooks/index.md): HMAC-signed deliveries for queued, validated, published, rejected, failed, token, and version events. - [post.failed](https://docs.letmepost.dev/webhooks/post-failed.md): Transient failure — internal error or upstream 5xx. The worker may retry. - [post.published](https://docs.letmepost.dev/webhooks/post-published.md): The post was successfully published upstream. - [post.queued](https://docs.letmepost.dev/webhooks/post-queued.md): A scheduled post was accepted and a delayed publish job was enqueued. - [post.rejected](https://docs.letmepost.dev/webhooks/post-rejected.md): Preflight failed, the platform rejected the post, or auth failed. Not retried. - [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. - [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. ## OpenAPI Specs - [openapi](https://docs.letmepost.dev/api-reference/openapi.json)