Media

Upload photos and videos up to 1 GB and get a URL to publish with.

View as MarkdownOpenAPIBase URL https://postwire.io

You can pass any public https link as video_url, photo_url or media[]; Google Drive, Dropbox and OneDrive share links are turned into direct file links. Or upload the file to PostWire: the bytes go straight from your client to storage, never through the API. Uploaded media is deleted after 30 days.

  • POST /api/media/upload-url: Get a signed URL to upload a photo or video (up to 1 GB). Files over 50 MB: send "multipart": true, PUT bytes [start, end) to each returned part's upload_url, then GET /api/media/finalize?path= returns one media_url for the whole file. Media passed by URL is fetched by us: up to 1GB for TikTok and YouTube (streamed), 80MB elsewhere.
  • GET /api/media/finalize: Confirm the upload landed and get the media_url to publish with
  • POST /api/media/upload-link: A link to upload a file from another device (a phone), valid 24 hours

Get a signed URL to upload a photo or video (up to 1 GB). Files over 50 MB: send "multipart": true, PUT bytes [start, end) to each returned part's upload_url, then GET /api/media/finalize?path= returns one media_url for the whole file. Media passed by URL is fetched by us: up to 1GB for TikTok and YouTube (streamed), 80MB elsewhere.

POST/api/media/upload-url

Upload your own file instead of hosting it yourself. The bytes go straight from your client to storage — they never pass through this API. 1) POST here. 2) PUT the raw file bytes to upload_url. 3) GET /api/media/finalize?path=<path> to receive media_url. 4) pass media_url as video_url or photo_url to /api/post. Media is deleted after 30 days.

Needs an API key: Authorization: Bearer pw_live_….

Body (JSON)

FieldTypeDescription
content_type requiredstringOne of: image/jpeg, image/png, image/webp, image/gif, video/mp4, video/quicktime, video/webm.
size_bytesinteger
multipartbooleantrue for files over 50 MB: the answer lists parts, each with its own upload_url and byte range [start, end).

Responses

StatusMeans
200upload_url + path
413code use_multipart (send multipart: true) or too_large (over 1 GB)
curl
curl -X POST "https://postwire.io/api/media/upload-url" \
  -H "Authorization: Bearer $POSTWIRE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "content_type": "video/mp4",
  "size_bytes": 48213504
}'
Node
const res = await fetch("https://postwire.io/api/media/upload-url", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.POSTWIRE_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "content_type": "video/mp4",
    "size_bytes": 48213504
  }),
});
console.log(res.status, await res.json());
Python
import os, requests

r = requests.post(
    "https://postwire.io/api/media/upload-url",
    headers={"Authorization": f"Bearer {os.environ['POSTWIRE_API_KEY']}"},
    json={
        "content_type": "video/mp4",
        "size_bytes": 48213504,
    },
)
print(r.status_code, r.json())

Example response

JSON
{
  "path": "<account_id>/2f9c….mp4",
  "upload_url": "https://…supabase.co/storage/v1/object/upload/sign/pw-media/…",
  "token": "…",
  "content_type": "video/mp4",
  "max_bytes": 52428800,
  "retention_days": 30,
  "how": "1) PUT the raw file bytes to upload_url. 2) GET /api/media/finalize?path=<path> …"
}

Then PUT the raw bytes to upload_url with the same Content-Type, and call GET /api/media/finalize?path= with the returned path.

Confirm the upload landed and get the media_url to publish with

GET/api/media/finalize

Needs an API key: Authorization: Bearer pw_live_….

Parameters

FieldTypeDescription
path requiredquery, string

Responses

StatusMeans
200media_url + bytes
404not uploaded yet
curl
curl "https://postwire.io/api/media/finalize?path=%3Caccount_id%3E%2F2f9c%E2%80%A6.mp4" \
  -H "Authorization: Bearer $POSTWIRE_API_KEY"
Node
const res = await fetch("https://postwire.io/api/media/finalize?path=%3Caccount_id%3E%2F2f9c%E2%80%A6.mp4", {
  method: "GET",
  headers: { Authorization: `Bearer ${process.env.POSTWIRE_API_KEY}` },
});
console.log(res.status, await res.json());
Python
import os, requests

r = requests.get(
    "https://postwire.io/api/media/finalize?path=%3Caccount_id%3E%2F2f9c%E2%80%A6.mp4",
    headers={"Authorization": f"Bearer {os.environ['POSTWIRE_API_KEY']}"},
)
print(r.status_code, r.json())

Example response

JSON
{
  "ok": true,
  "path": "<account_id>/2f9c….mp4",
  "media_url": "https://…/pw-media/<account_id>/2f9c….mp4?token=…",
  "bytes": 48213504,
  "content_type": "video/mp4",
  "expires_at": "2026-10-04T12:00:00.000Z"
}

A link to upload a file from another device (a phone), valid 24 hours

POST/api/media/upload-link

For an assistant or a flow that cannot send the file itself: open the url on the device that has the video, upload, and GET /api/media/uploads/{upload_id} (or the dashboard) returns its media_url. 30 links an hour.

Needs an API key: Authorization: Bearer pw_live_….

Responses

StatusMeans
200{ url, upload_id, expires_in: 86400, max_bytes, accepts[] }
curl
curl -X POST "https://postwire.io/api/media/upload-link" \
  -H "Authorization: Bearer $POSTWIRE_API_KEY"
Node
const res = await fetch("https://postwire.io/api/media/upload-link", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.POSTWIRE_API_KEY}` },
});
console.log(res.status, await res.json());
Python
import os, requests

r = requests.post(
    "https://postwire.io/api/media/upload-link",
    headers={"Authorization": f"Bearer {os.environ['POSTWIRE_API_KEY']}"},
)
print(r.status_code, r.json())