Guides

Media & images

Upload media once to get an id, then attach it to a post. Each network has its own limits. A media set that isn't valid for one target fails just that target.

Upload

Two ways, whichever suits your client. POST the raw bytes with the correct Content-Type:

curl -X POST https://api.postlake.dev/v1/media \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: image/jpeg" \
  --data-binary @photo.jpg

Or send it as a form file, which is what most HTTP libraries do by default:

curl -X POST https://api.postlake.dev/v1/media \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@photo.jpg"

Either way you get back a MediaAsset. For images it includes the pixel dimensions:

{ "id": "med_abc", "url": "https://…", "contentType": "image/jpeg", "size": 208134, "width": 1080, "height": 1920 }

From a local MCP agent

Call upload_media without url, passing the file's contentType and sizeBytes. It returns a five-minute signed PUT target and a preallocated med_… id. The agent PUTs the local bytes with the returned headers, then uses that id in create_post. For several slides, call upload_media_batch. The file does not need public hosting and its bytes do not pass through MCP JSON or base64. See the MCP local-file workflow.

POST /v1/media/batch takes either JSON { "items": [{ "contentType": "image/jpeg", "sizeBytes": 123 }] } (signed five-minute PUT targets) or multipart with multiple file parts. Max 12 items. Use this for a carousel instead of one request per slide.

Reuse and retention

Successfully uploaded PostLake assets have a minimum 90-day retention period from upload while the account remains open. This applies to free, PAYG and subscription accounts; it does not depend on a paid plan, credit balance or recent activity. Assets referenced by scheduled posts remain protected until the post completes, fails, is rejected or is cancelled, plus 30 days. Every reused reference must be respected, and the later of that protection and the original 90-day minimum applies. There is currently no automatic expiry, so the minimum date is not a deletion date. Account deletion, a verified erasure request, and necessary legal or security removal are exceptions. Cancelling, rejecting or deleting a post does not delete its media.

Future cleanup must preserve both the minimum period and scheduled-post protection. Storage deployment checks reject automatic object-deletion rules that could bypass this policy. Posts can carry campaign and assetMetadata; those describe the post, not ownership of the uploaded asset.

Public source URLs: anyone with an uploaded file or preview URL can access it. A private YouTube publication does not make the source upload private. These URLs are not suitable for confidential documents or secrets. External URLs have no PostLake retention protection.

Storage purpose and erasure: PostLake is a publishing service, not a backup archive or unrelated CDN. No current automatic expiry does not mean unlimited permanent storage. Use Uploads in the dashboard or the guarded API below to erase individual files; support can help with verified erasure requests. Account erasure removes original files and previews. Channel disconnection and subscription cancellation do not erase media. Future routine expiry changes require notice and must preserve protection already granted. See retention terms, privacy and acceptable use.

Choose the right removal action

ActionWhat changesWhat stays
Discard a personal draftRemoves the saved draft. Nothing is sent.Uploaded files and other posts.
Reject an approval requestRecords rejection and prevents that publication.The request, decision and uploads remain on record.
Cancel a scheduleStops a future publication that has not started.Uploaded files, other schedules and previously published posts.
Remove a published postRequests network removal where supported. Unsupported or failed destinations stay online.Uploads; spent publishing credits are not refunded.
Delete an uploadErases PostLake's original file and stored previews.Published social posts and their PostLake records. Their local previews will disappear.
Disconnect a channelRemoves PostLake's access to that channel.Social posts, uploads and saved schedules. Schedules may need reconnection.

Delete an uploaded file safely

  1. Open Uploads and choose Review deletion. The review lists affected posts before you confirm.
  2. Replace or remove the file from drafts and pending publications first, or cancel schedules and reject approval requests that no longer need it. An upload shared by another pending post stays protected.
  3. Confirm Permanently delete upload. Published social copies stay online. Copies downloaded by other people cannot be recalled.

API clients first call GET /v1/media/{id}/deletion, then DELETE /v1/media/{id} with { "snapshot": "the preview snapshot" }. A stale review, pending reference or concurrent publication returns 409, with no successful erasure claimed. Review again before retrying. Storage failures retain a deletion-in-progress manifest so cleanup can be retried; completed requests return a repeatable receipt. A deleted ID cannot be uploaded to again.

GET /v1/media?limit=50 lists account-owned files with items and next. Pass before=next for the next page. The maximum page size is 100; ordering is descending media ID, not upload time. Completed deletions are excluded. At most 200 references are shown per erasure review; files used by more saved posts require support-assisted erasure. This limit bounds database work, not the number of posts you may schedule.

MCP agents use manage_media with action=list, then action=preview_delete and an upload id. Only after the owner confirms erasure should the agent use action=delete with that snapshot. SDK source provides media.list(), media.deletionPreview(id) and media.delete(id, snapshot). Check your installed SDK version; REST and MCP do not depend on an SDK update.

Remove a post from social networks

In Post details choose Review network removal. The preview identifies each destination and links to posts that must be removed manually. Instagram and TikTok posts must be removed in their apps; connecting Instagram through Facebook does not enable published-post deletion. Facebook image Stories are also not removable through this API. Other destinations depend on the connected account, permissions and the network accepting removal.

GET /v1/posts/{id}/deletion previews support and consequences. Apply DELETE /v1/posts/{id} with its snapshot. 200 means the local action completed or all network removals were accepted. 207 reports destinations that remain online; inspect every target's deleted, verified and reason. Acceptance is not the same as confirmed absence. Accepted destinations are remembered on partial results and are not removed again on retry. Do not infer success from the post disappearing from one network. For cancellation-only integrations, use POST /v1/posts/{id}/cancel, which cannot retract published content.

Supported: JPEG, PNG, WebP and GIF images; MP4, MOV and WebM video. Video-only networks (YouTube, TikTok) require a video asset.

Pixel limits

Some networks reject an image for its dimensions, and several only do so after accepting the upload. TikTok is the worst case: it takes the post, processes it in the background, and fails a minute later with picture_size_check_failed, which names neither the limit nor the offending image.

So we check before publishing. An image outside a network's range fails validation immediately, with the limit and the actual size in the message:

{ "error": { "type": "invalid_input",
  "message": "TikTok images must be at most 1080x1920. This one is 2160x3840" } }

Current limits are on the platforms page, and in get_platform_capabilities as maxPx. TikTok photos must be between 540x960 and 1080x1920. Where we cannot read an image's header we do not guess, and the check is skipped rather than failing the post.

Attach. Globally or per platform

Put ids in media to attach to every account, or use mediaOverrides to give each platform its own set (a square image for Instagram, a video for TikTok). The url from the upload response works anywhere an id does, so you can pass back whichever field you kept.

{
  "text": "Launch day",
  "media": ["med_abc"],
  "mediaOverrides": { "tiktok": ["med_video"] },
  "accounts": ["acc_ig", "acc_tt"]
}

Per-platform media limits

PlatformMedia
BlueskyUp to 4 images, or 1 video
ThreadsImage carousel, or video
InstagramJPEG or PNG images, or video (Reels and Stories). PostLake converts PNG to JPEG for Instagram at create/validate, with a warning. The original PNG stays available for other networks.
TikTokJPEG/WebP photos (max 1080×1920) or video. PNG is converted to JPEG at create/validate, with a warning.
YouTubeVideo only
X, FacebookImages and video
LinkedInImages. Video is not accepted on a personal member profile.
PinterestImage or video (needs a boardId)

These are the practical rules today; call GET /v1/platforms/{platform} for the live, authoritative limits (max count, size, aspect) so you, or an agent, can validate before posting.

Alt text

Provide accessibility alt text per media item through platformOptions / media alt overrides. If a media set is invalid for a target, that target fails with a clear error and the others still publish, see Errors.