Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.letmepost.dev/llms.txt

Use this file to discover all available pages before exploring further.

A post body’s media array accepts three sources per item. Pick exactly one.
fieldwhat it iswhen to use
mediaIdreference to a POST /v1/media uploadproduction. Preferred — bytes already on our CDN.
urlpassthrough; we fetch from your CDNyou host your own assets and want zero copies.
bytesBase64inline base64tiny images, scripts, tests. Don’t use for video.

The mediaId path

The two-step flow: upload bytes once, then reference them on every post that uses them. Latency at publish time stays low because the platform fetches from our CDN (or we already have the bytes).
upload.sh
# Step 1 — upload.
curl -X POST https://api.letmepost.dev/v1/media \\
  -H "Authorization: Bearer $LMP_KEY" \\
  -F file=@./hero.jpg

# Response: { "id": "med_…", "url": "https://media.letmepost.dev/...", ... }
post-with-media.json
{
  "account": { "platform": "bluesky", "id": "..." },
  "text": "shipped",
  "media": [
    { "kind": "image", "mediaId": "med_a1b2c3d4e5f6g7h8i9j0k1", "altText": "Status page screenshot" }
  ]
}
mediaId matches the regex ^med_[0-9A-Za-z]{22}$ — anything else fails request validation.

The url path

Send a public URL we can GET anonymously:
url.json
{
  "media": [
    { "kind": "image", "url": "https://cdn.example.com/hero.jpg", "altText": "..." }
  ]
}
The URL must be publicly reachable. Google Drive share links, Dropbox preview URLs, and S3 buckets with object ACLs locked down will all fail — see instagram.media.reachable for the canonical version of that failure (Instagram’s OAuthException 2207052). When in doubt, upload via POST /v1/media and use mediaId.

The bytesBase64 path

Inline base64. Convenient for tests and tiny assets:
inline.json
{
  "media": [
    { "kind": "image", "bytesBase64": "iVBORw0KGgoAAAANSUhEUgAAA...", "altText": "..." }
  ]
}
A few platforms refuse inline bytes outright (URL-only ingest paths). When that happens the response is media.bytes_inline_unsupported — upload via POST /v1/media and switch to mediaId.

Per-platform constraints

Each platform has its own ceilings. The full set is enforced in preflight; here’s the headline shape:
platformimage maxvideo maxmime allowedcarousel size
Bluesky976 KB100 MBjpeg, png, webp, gif / mp4up to 4 imgs / 1 vid
Threads8 MB1 GBjpeg, png, webp / mp4, mov2–20 mixed
Instagram8 MB1 GBjpeg only / mp4, mov2–10 mixed
Facebook4 MB4 GBjpeg, png / mp4, movup to 10
Pinterest20 MBjpeg, png / —single only
Twitter / X5 MB512 MBjpeg, png, webp / mp4 (gif: 15 MB)single (v1)
LinkedIn(uploads handled per-asset by LinkedIn)
The exact constants live in packages/schemas/src/post.ts. Each ceiling has a preflight rule with its own page; e.g. instagram.media.image_size_max.

Notes per platform

  • Instagram is JPEG-only on the photo path. PNG, WebP, HEIC, and GIF all surface as opaque IG code 100 errors upstream — preflight catches them with instagram.media.mime_allowed and points you at the remediation.
  • Bluesky enforces image-vs-video exclusivity. A single post can have images or a video, not both. See bluesky.media.image_video_exclusive.
  • Pinterest is single-image only in v1. Multi-pin and video pins are deferred.
  • Threads is the most permissive carousel. 2–20 children, mixed images and videos.

Alt text

Always provide altText. Most platforms cap it (Bluesky 2000, Instagram 2200, Threads 1000); see the per-rule pages for the exact ceiling.