POST /api/v1/media
Uploads an image into the media library of the organization. The body is multipart/form-data,
not JSON — this is the only family in the reference that receives a file rather than a document.
Authorization
| Role in the organization | This endpoint |
|---|---|
OWNER | Allowed |
ADMIN | Allowed |
MEMBER | Refused — 403 |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | The image. Accepted types: image/png, image/jpeg, image/webp, image/gif |
folder | string | No | A sub-folder of the media library. Lowercase letters, digits and dashes only |
compression | string | No | none (default), light, balanced or strong — see Notes |
Example
bash
curl -X POST -H "x-api-key: $AGENTMAIL_API_KEY" \
-F "file=@./logo.png" \
-F "folder=newsletter" \
-F "compression=balanced" \
"https://www.agentsmail.io/api/v1/media"Response
201 Created— the media.400 Bad Request— thefilefield is missing or empty,compressionis not one of the four accepted values,folderis not lowercase letters, digits and dashes, the file's type is unsupported, the file is too large, or the organization's plan has reached its media storage limit.401 Unauthorized— missing or invalid key, or its owner left the organization.403 Forbidden— the caller is a legitimate member, but their role is too low.
Response fields
| Field | Type | Description |
|---|---|---|
success | boolean | Indicates if the operation was successful |
data.path | string | The media's identifier — pass it to Delete a media to remove this file |
data.url | string | Public URL — an email client is never authenticated |
data.name | string | File name, after compression if it changed the extension |
data.size | number | File size in bytes, after compression |
data.type | string | MIME type, after compression |
data.createdAt | string | ISO creation date |
data.folder | string | Sub-folder, absent at the root of the media library |
Example response
json
{
"success": true,
"data": {
"path": "organizations/7c2e…/media/newsletter/logo-1703123456789.jpg",
"url": "https://…/logo-1703123456789.jpg",
"name": "logo-1703123456789.jpg",
"size": 48213,
"type": "image/jpeg",
"createdAt": "2026-08-29T10:00:00.000Z",
"folder": "newsletter"
}
}Notes
- The limit that stops uploads is the organization's plan, not a rate limit. Every key is
already capped at 120 requests a minute like the rest of the API; on top of that, a plan has a
maximum number of media files, and reaching it answers
400, not403— it is a state you can resolve by deleting a media, not a permission you are missing. - Compression runs on the server, with the same four levels the media screen offers
(
light,balanced,strong, and the defaultnone). It targets the same definition and quality as the screen, but the engine that re-encodes the file is not the same one — expect the same order of magnitude in the resulting size, not a byte-identical file. - An animated GIF is never compressed, whatever
compressionis set to — it comes back untouched. - Compression never makes a file bigger: if re-encoding does not shrink it, the original file is stored instead, silently.
- An opaque PNG comes back as JPEG when compressed — that gives the biggest size reduction, since JPEG has no transparency to preserve. A PNG with real transparency keeps its format and is still quantized to shrink it (palette-based re-encoding), just usually not as far as JPEG would.