Media API
The Media API handles image and file uploads. Every upload gets indexed in the media table and (for images) auto-converted into AVIF and WebP variants plus multiple sizes so your front-end can serve <picture> elements without extra tooling. Endpoints live under /api/v1/media and require a Bearer token with the relevant media:* scope.
List media
GET /api/v1/media
Required scope: media:read.
Query parameters:
type—image,video,document,audiosearch— keyword search across filename, alt text, and captionuploaded_by— user IDpage— 1-indexed (default 1)per_page— max 100 (default 50)order_by—uploaded_at,filename,size(defaultuploaded_at)
curl -H "Authorization: Bearer YOUR_TOKEN" \
"https://yoursite.com/api/v1/media?type=image&per_page=20"
Response:
{
"data": [
{
"id": 45,
"filename": "cover.jpg",
"url": "https://yoursite.com/storage/uploads/2026/04/cover.jpg",
"mime": "image/jpeg",
"size_bytes": 248301,
"width": 1600,
"height": 900,
"alt_text": "Plate of scrambled eggs",
"caption": null,
"variants": {
"avif": "https://yoursite.com/storage/uploads/2026/04/cover.avif",
"webp": "https://yoursite.com/storage/uploads/2026/04/cover.webp",
"thumb_400": "https://yoursite.com/storage/uploads/2026/04/cover_400.jpg",
"thumb_800": "https://yoursite.com/storage/uploads/2026/04/cover_800.jpg"
},
"uploaded_by": 1,
"uploaded_at": "2026-04-10T08:55:00Z"
}
],
"pagination": { "page": 1, "per_page": 20, "total": 412, "total_pages": 21 }
}
Upload a file (multipart)
POST /api/v1/media
Content-Type: multipart/form-data
Required scope: media:write.
Form fields:
file(required) — the binary filealt_text(optional) — accessibility text for imagescaption(optional)folder(optional) — virtual folder path, e.g.,blog/2026/april
Example with curl:
curl -X POST https://yoursite.com/api/v1/media \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@/path/to/cover.jpg" \
-F "alt_text=Plate of scrambled eggs" \
-F "folder=blog/breakfast"
Response: 201 Created with the full media object. For images, the AVIF and WebP variants plus resized thumbnails are generated asynchronously — the variants object is populated within a few seconds. Poll GET /api/v1/media/{id} if you need to wait for them before publishing.
Upload a remote image via URL
Convenience endpoint for pulling in external images (stock libraries, AI-generated URLs) without downloading them locally first:
POST /api/v1/media/fetch
Content-Type: application/json
Required scope: media:write.
{
"url": "https://images.example.com/food/eggs-4k.jpg",
"alt_text": "Plate of scrambled eggs",
"folder": "blog/breakfast"
}
The server fetches the URL, validates the mime type against the allowlist, stores it locally, and generates the same variants as a normal upload. Response shape identical to the multipart endpoint.
Fails with 422 if the URL isn't reachable, returns a non-image mime type, or exceeds the max upload size.
Delete a media item
DELETE /api/v1/media/{id}
Required scope: media:write.
Removes the DB row, the original file, and all generated variants. If the media is currently used as a featured image or referenced in any post body, the delete is rejected with 409 Conflict and a list of referencing post IDs. Pass ?force=true to override (featured image references become null; body references become broken <img> tags).
Response: 204 No Content on success.
Limits
Max upload size
Hard limit: 50 MB per file. This is bounded by both PHP's upload_max_filesize / post_max_size and jekcms's own check. On shared hosting the practical limit is usually lower (check your host's PHP settings).
For video or archive uploads larger than 50 MB, use an S3-compatible external storage integration — the admin UI at Settings → Storage supports direct-to-bucket uploads that bypass the PHP process entirely.
Allowed mime types
By default the allowlist covers common web-safe types:
- Images:
image/jpeg,image/png,image/gif,image/webp,image/avif,image/svg+xml(SVG is sanitized on upload) - Video:
video/mp4,video/webm,video/ogg - Audio:
audio/mpeg,audio/mp4,audio/ogg,audio/wav - Documents:
application/pdf
Non-allowlisted types are rejected with 422 unsupported_media_type. Admins can extend the list under Settings → Media → Allowed types — never add executable or archive types without serving them with Content-Disposition: attachment to prevent browser inline execution.
Variant generation
On image upload, jekcms queues background jobs that produce:
- AVIF at 80% quality (best compression, modern browsers)
- WebP at 85% quality (fallback)
- Resized JPEGs at 400px and 800px wide (for thumbnails and cards)
All variants preserve EXIF orientation but strip GPS metadata (privacy). The originals are kept untouched. If the original is already AVIF or WebP, only the resized thumbnails are generated.
Variant generation runs on the media.variant_worker scheduled task — if it's not firing, check that cron is alive.
Error codes
400— missingfilefield or invalid form data401— missing / invalid Bearer token403— token lacksmedia:writescope404— media ID not found (on DELETE / GET single)409— cannot delete; media referenced elsewhere (passforce=trueto override)413— file exceeds 50 MB limit422— unsupported mime type or invalid remote URL429— rate limited