API reference
Get a presigned URL to upload an image or video, and the public URL to use in posts.
https://api.betterpost.dev/v1/media/uploadThen PUT the raw file to uploadUrl with the same Content-Type. See Upload media.
| Field | Type | Required | Description |
|---|---|---|---|
filename | string | Yes | Original file name. Sanitised to letters, digits, ., _ and - (max 100 characters). |
mimeType | string | Yes | image/* or video/*, for example image/png or video/mp4. |
size | integer | Yes* | File size in bytes. Checked against your storage limit; the upload must be exactly this size. |
sizeBytes | integer | Yes* | Alias of size. If both are sent they must match. |
* Send size or sizeBytes. PostPeer treats the size as optional; BetterPost requires it so every presigned
upload is capped. No other fields are accepted.
curl -s -X POST https://api.betterpost.dev/v1/media/upload \
-H "x-access-key: $BETTERPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "filename": "banner.png", "mimeType": "image/png", "size": 482133 }'Then upload the file:
curl -s -X PUT "$UPLOAD_URL" -H "Content-Type: image/png" --data-binary @banner.png200 OK
{
"success": true,
"data": {
"uploadUrl": "https://example-account.r2.cloudflarestorage.com/betterpost/proj_123/2026/10/med_7h2k9d4s1m6q3w8e0r5t2y7u-banner.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=900&X-Amz-Signature=...",
"publicUrl": "https://assets.betterpost.dev/proj_123/2026/10/med_7h2k9d4s1m6q3w8e0r5t2y7u-banner.png"
}
}| Field | Description |
|---|---|
uploadUrl | Presigned PUT URL, valid for 15 minutes. |
publicUrl | Where the file will be served. Use it as mediaItems[].url. |
| Status | Code | When |
|---|---|---|
200 | The upload URL was created. | |
400 | unsupported_mime_type | mimeType is not image/* or video/*. |
400 | size_required | Neither size nor sizeBytes was sent. |
400 | conflicting_size | size and sizeBytes differ. |
400 | validation_error | A required field is missing, size is not a positive integer, or an unknown field was sent. |
401 | unauthorized | Missing or unknown API key. |
413 | storage_limit_exceeded | The file would exceed your storage limit (Free 100 MB, paid plans 5 GB). |
429 | rate_limited | More than 120 requests per minute. |