# Media

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

Source: https://postwire.io/docs/media/ · Base URL: https://postwire.io · OpenAPI: https://postwire.io/openapi.json

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`](#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`](#get-api-media-finalize): Confirm the upload landed and get the media_url to publish with
- [`POST /api/media/upload-link`](#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)**

| Field | Type | Description |
|---|---|---|
| `content_type` (required) | string | One of: image/jpeg, image/png, image/webp, image/gif, video/mp4, video/quicktime, video/webm. |
| `size_bytes` | integer |  |
| `multipart` | boolean | true for files over 50 MB: the answer lists parts, each with its own upload_url and byte range [start, end). |

**Responses**

| Status | Means |
|---|---|
| `200` | upload_url + path |
| `413` | code use_multipart (send multipart: true) or too_large (over 1 GB) |

```bash
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
}'
```

```js
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**

| Field | Type | Description |
|---|---|---|
| `path` (required) | query, string |  |

**Responses**

| Status | Means |
|---|---|
| `200` | media_url + bytes |
| `404` | not uploaded yet |

```bash
curl "https://postwire.io/api/media/finalize?path=%3Caccount_id%3E%2F2f9c%E2%80%A6.mp4" \
  -H "Authorization: Bearer $POSTWIRE_API_KEY"
```

```js
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**

| Status | Means |
|---|---|
| `200` | { url, upload_id, expires_in: 86400, max_bytes, accepts[] } |

```bash
curl -X POST "https://postwire.io/api/media/upload-link" \
  -H "Authorization: Bearer $POSTWIRE_API_KEY"
```

```js
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())
```
