# Upload media

> Upload an image or video and get a URL to attach to posts.

**Source:** https://postprism.app/docs/api/upload-media  
**Updated:** 2026-10-10

---

`POST https://studio.postprism.app/api/v1/media`

Upload a file, then pass the returned `url` in `media` when you create or update a post. Every attachment has to come through this endpoint; links to files hosted elsewhere are rejected.

Send the raw file as the request body with its MIME type as `Content-Type` (best for scripts and large files), or send JSON with the file base64-encoded (best for agents that can only send JSON).

Accepted: JPEG, PNG, GIF and WebP images (up to 10 MB) and MP4 and MOV videos (up to 100 MB). Base64 uploads are limited to 10 MB. The file contents must match the declared type. Images are converted to WebP when stored; platforms that need JPEG receive JPEG automatically.

## Parameters

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `X-Filename` | header | string | Raw uploads only: the original file name (up to 300 characters). |

## Request body

Content-Type: `image/jpeg, image/png, image/gif, image/webp, video/mp4, video/quicktime`

The request body is the file itself.

## Or send JSON

Content-Type: `application/json`

| Field | Type | Description |
| --- | --- | --- |
| `filename` | string, required | The original file name, for example `launch-photo.png`. |
| `mime_type` | string, required | The file’s MIME type. It must match the file contents. One of: `image/jpeg`, `image/png`, `image/gif`, `image/webp`, `video/mp4`, `video/quicktime`. |
| `data` | string, required | The file, base64-encoded. A `data:<type>;base64,` prefix is allowed. |

## Example

```bash
curl -X POST "https://studio.postprism.app/api/v1/media" \
  -H "Authorization: Bearer pp_live_your_api_key" \
  -H "Content-Type: image/png" \
  -H "X-Filename: launch-photo.png" \
  --data-binary @launch-photo.png
```

## Response

Status `201`.

`data` is a `UploadedMedia`: The file you uploaded, ready to attach to a post.

| Field | Type | Description |
| --- | --- | --- |
| `url` | string | Pass this unchanged as `media[].url` when you create or update a post. Treat it as opaque. |
| `type` | string | Detected from the file contents. One of: `image`, `video`. |
| `file_name` | string | The stored file name. Images are converted to WebP, so their name ends in `.webp`. |
| `file_size` | integer | Size of the stored file in bytes. |
| `mime_type` | string | MIME type of the stored file (`image/webp` for every image). |
| `storage_path` | string | An opaque reference to the file. You do not need it. |

```json
{
  "data": {
    "url": "https://…/launch-photo.webp",
    "type": "image",
    "storage_path": "…",
    "file_name": "launch-photo.webp",
    "file_size": 184320,
    "mime_type": "image/webp"
  },
  "error": null
}
```

## Errors

| Status | Message | When |
| --- | --- | --- |
| 400 | `Request body was empty.` | No file was sent. |
| 400 | `data is not valid base64.` | JSON uploads only: `data` could not be decoded. `filename`, `mime_type` and `data` are all required. |
| 413 | `File too large. Maximum is 10MB for images.` | The file is over 10 MB (images or base64) or 100 MB (raw video). |
| 415 | `Unsupported mime_type "image/svg+xml". Allowed: JPEG, PNG, GIF, WebP images; MP4, MOV videos.` | The file type is not accepted. |
| 422 | `File content is image/jpeg but was declared as image/png.` | The bytes do not match the declared type, or the image could not be read. |
| 401 | `Invalid or revoked API key` | The `Authorization` header is missing, malformed, or the key was revoked. |
| 429 | `Too many requests` | Uploads have their own, lower hourly limit. See Rate limits. |
