Skip to main content
POST
/
media
/
presign
Get a media upload URL
curl --request POST \
  --url http://localhost:4100/api/v1/media/presign \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "filename": "hero-image.jpg",
  "contentType": "image/jpeg",
  "bytes": 2,
  "folderId": "<string>"
}
'
{
  "uploadUrl": "<string>",
  "publicUrl": "<string>",
  "expiresAt": "2023-11-07T05:31:56Z",
  "headers": {},
  "mediaAsset": {
    "id": "<string>",
    "ownerUserId": "<string>",
    "folderId": "<string>",
    "mimeType": "<string>",
    "bytes": 123,
    "width": 123,
    "height": 123,
    "durationMs": 123,
    "storageKey": "<string>",
    "thumbnailKey": "<string>",
    "previewKey": "<string>",
    "altText": "<string>",
    "publicUrl": "<string>",
    "previewUrl": "<string>",
    "thumbnailUrl": "<string>",
    "createdAt": "2023-11-07T05:31:56Z",
    "updatedAt": "2023-11-07T05:31:56Z"
  }
}

Authorizations

Authorization
string
header
required

Your Postbreeze API key

Body

application/json
filename
string
required

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.

Required string length: 1 - 255
Example:

"hero-image.jpg"

contentType
string
required

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.

Maximum string length: 100
Example:

"image/jpeg"

bytes
integer

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.

Required range: x >= 1
folderId
string | null

Internal — dashboard media-library folder id. Public API integrations can omit this; assets land in the library root.

Maximum string length: 64

Response

Upload URL is ready. Send the bytes to uploadUrl within 15 minutes, then attach the result to a post using publicUrl or mediaAsset.id.

uploadUrl
string

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.

publicUrl
string

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.

expiresAt
string<date-time>

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.

headers
object

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.

mediaAsset
object

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).