Docs
Media
POST
/media

Two ways to add a file, then pass its id in mediaIds when creating or updating a post.

  • Import from a URL: send url (public https, up to 100 MB). FeedHorizon downloads it and answers when it is stored. Easiest for agents and scripts.
  • Upload: send filename, mimeType and sizeBytes (up to 5 GB). The answer carries upload.url; PUT the raw bytes there with the same Content-Type within two hours. No API key is needed for that request.

Supported types: image/jpeg, image/png, image/gif, image/webp, video/mp4, video/quicktime, video/webm, application/pdf. Which platform takes which type is listed under GET /platforms.

Requires posts:write.

Authorization

bearerAuth
AuthorizationBearer <token>

API key starting with fh_

In: header

Header Parameters

Idempotency-Key?string

Any unique string up to 255 characters. A retry with the same key within 24 hours returns the first response instead of running again.

Lengthlength <= 255

Request Body

application/json

url?string

Import this public https URL

Formaturi
Lengthlength <= 2048
filename?string

File name, required for uploads

Length1 <= length <= 255
mimeType?string

Required for uploads

Value in"image/jpeg" | "image/png" | "image/gif" | "image/webp" | "video/mp4" | "video/quicktime" | "video/webm" | "application/pdf"
sizeBytes?integer

Required for uploads

Range1 <= value <= 5368709120

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://feedhorizon.com/api/v1/media" \  -H "Content-Type: application/json" \  -d '{}'
{
  "data": {
    "id": "med_0195f0a27b3c7d4e8f5a1b2c3d4e5f60",
    "url": "string",
    "filename": "string",
    "mimeType": "string",
    "sizeBytes": 0,
    "createdAt": "2026-11-10T08:00:00.000Z",
    "upload": {
      "url": "string",
      "method": "PUT",
      "headers": {
        "property1": "string",
        "property2": "string"
      },
      "expiresAt": "2026-11-10T08:00:00.000Z"
    }
  }
}
{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_json",
    "message": "The body is not valid JSON",
    "docUrl": "https://feedhorizon.com/docs/api/errors#invalid_request_error",
    "requestId": "req_m2x81a0kfd3h9s"
  }
}
{
  "error": {
    "type": "authentication_error",
    "code": "missing_api_key",
    "message": "No Authorization: Bearer header",
    "docUrl": "https://feedhorizon.com/docs/api/errors#authentication_error",
    "requestId": "req_m2x81a0kfd3h9s"
  }
}
{
  "error": {
    "type": "permission_error",
    "code": "insufficient_scope",
    "message": "The key lacks the scope for this endpoint",
    "docUrl": "https://feedhorizon.com/docs/api/errors#permission_error",
    "requestId": "req_m2x81a0kfd3h9s"
  }
}
{
  "error": {
    "type": "idempotency_error",
    "code": "idempotency_request_in_progress",
    "message": "The first request with this key is still running. Retry in a few seconds",
    "docUrl": "https://feedhorizon.com/docs/api/errors#idempotency_error",
    "requestId": "req_m2x81a0kfd3h9s"
  }
}
{
  "error": {
    "type": "idempotency_error",
    "code": "idempotency_key_reused",
    "message": "The key was used for a different request. Use a new key",
    "docUrl": "https://feedhorizon.com/docs/api/errors#idempotency_error",
    "requestId": "req_m2x81a0kfd3h9s"
  }
}
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limited",
    "message": "Too many requests. Wait the seconds in Retry-After",
    "docUrl": "https://feedhorizon.com/docs/api/errors#rate_limit_error",
    "requestId": "req_m2x81a0kfd3h9s"
  }
}
{
  "error": {
    "type": "api_error",
    "code": "internal_error",
    "message": "Something failed on our side. Retry with backoff, using the same Idempotency-Key for writes",
    "docUrl": "https://feedhorizon.com/docs/api/errors#api_error",
    "requestId": "req_m2x81a0kfd3h9s"
  }
}