API docs
Base URL https://postcache.dev · OpenAPI 3.1: /openapi.json · for agents: /llms.txt
Authentication
Every request sends Authorization: Bearer pc_live_…. Keys are issued for early access: write to hello@postcache.dev.
Upload
Send the file as the raw body and name the platform it is for. The answer is a public URL to hand to the platform API (image_url / video_url).
curl -X PUT https://postcache.dev/v1/media?ttl=86400 \
-H "Authorization: Bearer $POSTCACHE_KEY" \
-H "x-target: instagram.feed" \
-H "x-filename: spring-sale.jpg" \
--data-binary @spring-sale.jpg
x-target: one of the targets.ttl: seconds the URL stays valid, 3600–604800, default 86400 (24 h).- Max file size 1 GiB.
Upload by URL
For files that already live somewhere public. Only public http(s) addresses are fetched; private and internal addresses are refused, also after redirects.
curl -X POST https://postcache.dev/v1/media/from-url \
-H "Authorization: Bearer $POSTCACHE_KEY" -H "content-type: application/json" \
-d '{"source_url": "https://example.com/reel.mp4", "target": "instagram.reels"}'
Fixing files
Add convert=crop or convert=pad (query parameter, or "convert" in the JSON body). A file that already passes is stored at once (201). A file that fails only on things conversion can fix is converted in the background:
- Images: format, aspect ratio, colour space, file size. Output is an sRGB JPEG;
cropcuts to the nearest allowed ratio,padadds white borders. - Video: container, codecs, pixel format, interlacing, frame rate, width, aspect ratio, bitrate, moov position, edit lists, file size. Output is an H.264/AAC MP4;
padadds black bars. - Not fixable: duration (we never trim), unreadable files, a file type the target doesn't take. These get 422 at once.
The answer is 202 with "status": "converting". Poll GET /v1/media/{id} until status is ready (the url is set) or failed (checks says why). original_checks keeps the problems the upload had.
Dry run
dry_run=1 checks the file and stores nothing. With convert, the answer also says whether conversion could fix it (convertible).
Responses
201 { "id": "x8Kq…", "status": "ready", "url": "https://cdn.postcache.dev/m/x8Kq…/spring-sale.jpg",
"target": "instagram.feed", "content_type": "image/jpeg", "size": 412339,
"expires_at": "2026-10-08T12:00:00Z", "checks": { "pass": true, "problems": [], "notes": [] } }
202 { "id": "Zp3v…", "status": "converting", "target": "instagram.feed",
"original_checks": { "pass": false, "problems": [{ "rule": "aspect", … }] },
"status_url": "/v1/media/Zp3v…" }
422 { "error": "checks_failed", "checks": { "pass": false, "problems": [
{ "rule": "duration", "message": "Duration is 2.0 s, allowed 3–900 s.", "fix": "Trim the video." } ] } }
Other errors: 400 (bad target, ttl, convert or source URL), 401 (key), 402 quota_exceeded (the plan's uploads are used up; upgrade_url says where to upgrade), 413 (over 1 GiB), 429 budget_exceeded (the key's daily cap is reached; Retry-After counts to 00:00 UTC), 503 with Retry-After (busy).
Pay per use (no account)
Agents can pay per request instead of using a key: $0.01 per upload in USDC, through x402 (Base) or MPP (Tempo). Send the upload without Authorization; the answer is 402 with the payment challenge in WWW-Authenticate (MPP) and Payment-Required (x402). Pay and send the same request again. Payments settle through Stripe.
- The paid answer adds
paymentand a one-timedelete_token. Send it asx-delete-tokento read the status (GET /v1/media/{id}) or delete the file (DELETE /v1/media/{id}). - You pay when the payment verifies, before the checks. A file that fails the checks (422) is not refunded, so check first with
dry_run=1: it is free and needs no payment. - A payment can be used once (409
payment_reused). At most 1,000 paid uploads per payer per day. - Credit pack:
POST /v1/creditssells 500 uploads for $5, by card (MPP) or USDC. Without a key the answer has a new API key (shown once); withAuthorization: Bearerthe credits go to your account and are used when your plan's uploads run out.
Plans and keys
Free has 100 uploads a month, Builder 2,000 and Studio 20,000. Dry runs are free. Manage keys, daily caps and billing at postcache.dev/dashboard.
Status and delete
GET /v1/media/{id} returns status, URL, expiry and how often the URL was fetched. DELETE /v1/media/{id} removes the file at once (204); call it after the post is published. Expired or deleted URLs answer 410; after a delete the status is deleted.
MCP
postcache is also an MCP server: https://postcache.dev/mcp (Streamable HTTP). Add it to your agent:
{
"mcpServers": {
"postcache": {
"type": "http",
"url": "https://postcache.dev/mcp",
"headers": { "Authorization": "Bearer pc_live_…" }
}
}
}
upload_media:targetand eithersource_urlordata_base64(max 10 MB); optionalfilename,ttl,convert.check_media: same input, stores nothing.get_mediaanddelete_media: byid.
Targets
| Target | Images | Video |
|---|---|---|
| instagram.feed | JPEG, ≤ 8 MB, 4:5–1.91:1, sRGB | none (use instagram.reels) |
| instagram.carousel | as feed | as reels |
| instagram.reels | none | MP4/MOV, H.264/HEVC, 4:2:0, AAC ≤ 48 kHz stereo, 23–60 fps, ≤ 1920 px wide, 0.01–10:1, ≤ 25 Mbps, 3 s–15 min, ≤ 300 MB, moov first, no edit list |
| instagram.story | JPEG, ≤ 8 MB, sRGB | as reels, 0.1–10:1, 3–60 s, ≤ 100 MB |
| threads | JPEG/PNG, ≤ 8 MB, up to 10:1 | as reels, ≤ 100 Mbps, up to 5 min, ≤ 1 GB |
| facebook.page | JPEG/PNG, ≤ 10 MB | MP4/MOV, H.264/HEVC, ≤ 1 GB |
Limits come from Meta's developer documentation.