Media Uploads
Add images, videos and PDFs to posts, by URL import or direct upload.
Media goes in two steps: add the file with POST /media, then pass its id in mediaIds when you create or update a post.
Import from a URL
The simplest way, and the right one for agents and scripts that already have a link. FeedHorizon downloads the file and answers once it is stored.
curl -X POST https://feedhorizon.com/api/v1/media \
-H "Authorization: Bearer fh_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/launch.jpg" }'{
"data": {
"id": "med_0193f…",
"url": "https://…/media/0193f….jpg",
"filename": "launch.jpg",
"mimeType": "image/jpeg",
"sizeBytes": 245760,
"createdAt": "2026-11-04T14:00:00.000Z",
"upload": null
}
}The URL must be public https and the file at most 100 MB. The type comes from the Content-Type the server sends.
Upload directly
For files on your machine and anything over 100 MB (up to 5 GB). Announce the file, then PUT the bytes to the returned link. The link is valid for two hours and needs no API key.
curl -X POST https://feedhorizon.com/api/v1/media \
-H "Authorization: Bearer fh_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "filename": "teaser.mp4", "mimeType": "video/mp4", "sizeBytes": 48211337 }'
# then, with upload.url from the answer:
curl -X PUT "UPLOAD_URL" -H "Content-Type: video/mp4" --data-binary @teaser.mp4Use media in a post
{
"profileId": "YOUR_PROFILE_ID",
"content": "The teaser is out",
"platforms": ["instagram", "linkedin"],
"mediaIds": ["0193f…"],
"addToQueue": true
}Files appear in the post in the order of mediaIds. On update, mediaIds replaces the media of the post, and [] removes it.
Before the post is stored, each platform's rules are checked: supported types and how many files it takes (see GET /platforms). Instagram needs at least one image or video. A file that was announced but never uploaded answers 400 media_not_uploaded. Use POST /posts/validate to see all problems before you create the post.
Supported types
POST /media stores any of these files:
| Type | MIME types |
|---|---|
| Images | image/jpeg, image/png, image/gif, image/webp |
| Video | video/mp4, video/quicktime, video/webm |
| Documents | application/pdf |
Which of them a post may use depends on the platform. A file a platform does not take fails the post with media_type_unsupported:
| Platform | Images | Video | Other |
|---|---|---|---|
| X | JPEG, PNG, GIF | MP4 | |
| JPEG, PNG, GIF | MP4 | ||
| JPEG, PNG | MP4 | ||
| Bluesky | JPEG, PNG, WebP, GIF (1 MB each) | ||
| JPEG, PNG, WebP (20 MB each) | |||
| TikTok | MP4, MOV, WebM |
Counts and text limits are on Platform Support and in GET /platforms.
Errors
| Code | Cause |
|---|---|
invalid_url | Not a valid https URL, a URL with credentials, or a URL that points to a private network |
media_fetch_failed | The URL could not be resolved or fetched, or answered with an error status |
media_too_large | An import over 100 MB, or an upload over 5 GB |
media_not_found (on posts) | A mediaIds entry does not exist in your organization |
duplicate_media (on posts) | The same id appears twice in mediaIds |
media_not_uploaded (on posts) | The file was announced but its bytes never arrived |
media_in_use (409) | DELETE /media/{id} on a file a post still uses |
Manage files
GET /medialists your files, newest first.GET /media/{id}returns one file.DELETE /media/{id}deletes a file that no post uses. Files still in a post answer409 media_in_use.
Media needs the posts:write scope to add and delete, posts:read to list.