Get a media upload URL
Uploading an image or video is a three-step process. This is step one.
How it works:
- Call this endpoint with the file name and content type. The server gives you back an
uploadUrl(where to send the bytes), apublicUrl(where the file will live once uploaded), and the exactheadersto use on the upload request. - Send the file’s bytes directly to
uploadUrlusing aPUTrequest with the headers from step 1. Don’t include anAuthorizationheader on this request — the URL itself is already signed, and adding one will break the upload. - Attach the file to a post in one of two ways: include
publicUrlinmediaItems: [{ url: publicUrl, type: "image" }], or pass themediaAsset.idyou got back inmediaIds: ["med_…"]. Both work identically — pick whichever feels cleaner in your code.
A few things to know:
- The
uploadUrlexpires after 15 minutes. If you miss the window, just call this endpoint again for a fresh one. - Uploaded media is shared across all your workspaces — one upload can be attached to posts on any account you can reach.
- If you’re using the Node SDK,
postbreeze.media.upload(...)handles all three steps for you.
Authorizations
Your Postbreeze API key
Body
Original file name. Persisted on the MediaAsset row and used to derive the R2 storage key. Sanitized server-side, so unsafe characters (/, \, control chars) are stripped before storage.
1 - 255"hero-image.jpg"
MIME type of the bytes you're about to PUT. Supported — Images: image/jpeg, image/png, image/gif, image/webp, image/heic, image/heif. Videos: video/mp4, video/quicktime, video/x-msvideo, video/webm. Documents: application/pdf (LinkedIn document carousels only). The same value MUST be set as the Content-Type header on the PUT — R2 binds the signature to it.
100"image/jpeg"
File size in bytes. When provided, the server enforces the per-kind size cap (images, video, PDF) and your account-wide storage quota before signing the URL — failures return 400 (limit) or 403 STORAGE_LIMIT (quota). When omitted, both limits are reconciled lazily on the PUT itself; R2 still rejects oversize uploads.
x >= 1Internal — dashboard media-library folder id. Public API integrations can omit this; assets land in the library root.
64Response
Upload URL is ready. Send the bytes to uploadUrl within 15 minutes, then attach the result to a post using publicUrl or mediaAsset.id.
Presigned R2 PUT URL valid for 15 minutes. PUT the raw bytes to this URL with the headers from headers. Do NOT send an Authorization header on the PUT — the signature is embedded in the URL's query string, and adding Authorization breaks the signature.
Long-lived public URL of the uploaded object. Stable across the asset's lifetime — store it and reuse it across workspaces. Pass it in mediaItems: [{ url: publicUrl, type: 'image' }] when creating posts via the flat URL-based attach path.
When uploadUrl expires (ISO 8601). After this instant the presigned PUT will be rejected by R2; if you haven't uploaded by then, request a fresh presign.
HTTP headers you MUST include on the PUT to satisfy the signature. Always contains Content-Type matching the contentType you sent in the request body. Send these verbatim — mismatching even one byte invalidates the signature.
The pending MediaAsset row the server created up-front. Use mediaAsset.id to attach the upload via mediaIds: [...] on a later post-create call (id-based attach), or ignore it and use publicUrl instead (URL-based attach — both produce identical results).