POST/v1/media
Upload media
Returns a presigned URL you can PUTa file to, plus the public URL of the resulting object. Use this when you don't want to host the media yourself, or when you need the file to stay reachable for scheduled posts where the window between the API call and the actual publish can span hours or days.
You don't have to use this endpoint. If you already host your media somewhere publicly accessible, you can pass that URL straight into the media array on POST /v1/posts— Synposter will fetch it for you. This endpoint exists for the case where hosting your own media isn't convenient.
How it works
- Call
POST /v1/mediawith the filename and MIME type. We respond with anupload_url(a short-lived presigned URL), apublic_url(where the file will live), and anexpires_attimestamp. PUTthe raw file bytes toupload_url, with the sameContent-Typeyou declared in step 1. No auth header needed — the URL itself is the authorization.- Reference
public_urlin any subsequentPOST /v1/postscall:media: [{ type: "image", url: public_url }].
curl -X POST https://api.synposter.com/v1/media \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filename": "launch-banner.png",
"mime_type": "image/png"
}'Body
| Field | Type | Required | Description |
|---|---|---|---|
| filename | string | Yes | The original filename, used to derive the file extension on the stored object so the public URL is sensibly named. 1 to 255 characters; must not contain path separators (/, \) or null bytes. |
| mime_type | string | Yes | The MIME type of the file you're about to upload, e.g. "image/png" or "video/mp4". Only image/* and video/* are accepted. The same value must be sent as the Content-Type header on the subsequent PUT to upload_url. |
Response (201)
json
{
"id": "9b3a1f2c-...",
"upload_url": "https://<project>.supabase.co/storage/v1/object/upload/sign/media/...",
"public_url": "https://<project>.supabase.co/storage/v1/object/public/media/<prefix>/<uuid>.png",
"path": "<prefix>/<uuid>.png",
"mime_type": "image/png",
"expires_at": "2026-04-29T16:00:00Z"
}| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Synposter's UUID for this upload record. Stored in our audit log so the upload can be retrieved or deleted later by id (those endpoints will ship alongside scheduled posts). |
| upload_url | string | Yes | Short-lived presigned URL. PUT your file bytes here within the expires_at window. Bearer / API-key auth is not required on this PUT — the URL itself is the credential. |
| public_url | string | Yes | Where the file will be readable from once the upload completes. Use this value in the media array on POST /v1/posts. Note: the URL works only after a successful PUT; before that it returns 404. |
| path | string | Yes | The internal storage path for the object — an opaque per-developer prefix followed by a UUID. Useful if you need to reference or delete the object later via Synposter's admin tooling. |
| mime_type | string | Yes | Echoed back from your request. Send the exact same value as the Content-Type header on the PUT. |
| expires_at | string | Yes | ISO 8601 timestamp after which upload_url stops working. Currently 2 hours from the moment the URL is issued. |
Example: full upload + post
bash
# 1. Get a presigned URL.
RESP=$(curl -s -X POST https://api.synposter.com/v1/media \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "filename": "cat.png", "mime_type": "image/png" }')
UPLOAD_URL=$(echo "$RESP" | jq -r .upload_url)
PUBLIC_URL=$(echo "$RESP" | jq -r .public_url)
# 2. PUT the file to the presigned URL.
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: image/png" \
--data-binary @./cat.png
# 3. Reference the public URL in a post.
curl -X POST https://api.synposter.com/v1/posts \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"accounts\": [\"<account_id>\"],
\"text\": \"Hello with image!\",
\"media\": [{ \"type\": \"image\", \"url\": \"$PUBLIC_URL\" }]
}"Limits
- Max file size: 50MB per object. The upload itself is rejected at the storage layer if you exceed this — your
PUTreturns an error and nothing is stored. - Allowed MIME types: anything starting with
image/orvideo/. Other types (PDFs, audio, etc.) are rejected withinvalid_mime_type. - URL TTL:
upload_urlis valid for 2 hours from issuance. Thepublic_urlnever expires (the file stays available as long as the object exists). - Per-platform constraints still apply when you reference the URL in
POST /v1/posts— for example, X enforces a 5MB cap on images and a 15MB cap on GIFs even though the upload itself succeeded at the larger bucket limit.
Status codes
| 201 | The presigned URL was created and is returned in the response body. |
| 400 | Returned with one of invalid_json (the request body wasn't valid JSON or didn't parse to an object), invalid_filename (missing, empty, too long, or contained / \ \0), or invalid_mime_type (missing or not image/* or video/*). |
| 401 | The API key was missing or invalid. |
| 500 | Something failed on Synposter's side while signing the upload URL. Safe to retry. |