API Status Get API Key

Hosting Media

When you attach an image or video to a post, the Buffer API doesn't accept a file upload - there's no upload endpoint. Instead, you host the file yourself and pass a publicly accessible URL in the assets array.

Why media must be hosted

The url field on each asset (image or video) must point to a file that is reachable over the public internet without authentication.

This means the URL must work for anyone, not just you. Links that require a viewer to be signed in - for example a Google Drive or Dropbox "share" link - will not work.

The URL must stay reachable until the post publishes, not just when you create it. Buffer fetches the media when the post goes out, which for scheduled or queued posts can be hours or days later. Avoid expiring or signed URLs (such as S3 pre-signed links or Cloudinary signed-delivery URLs) - they often work the moment you call createPost but expire before the post publishes, causing it to fail silently. Use a stable, permanent URL.

Where to host your media

You can use any host that serves files at a direct, public URL. If you don't already have one, these options have a free tier and work well:

  • Cloudinary - A media management platform with a generous free tier. After you upload a file you get a direct, publicly accessible URL you can use right away. It also offers optional on-the-fly image and video transformations. Use the delivery URL (e.g. https://res.cloudinary.com/...), not the link to the dashboard or media-library page.
  • Cloudflare R2 - Object storage with a free tier. A good fit if you already use Cloudflare or are comfortable with a slightly more technical setup. Upload your file and enable public access on the bucket (or attach a public custom domain) so the file is reachable without credentials.

Verifying that a URL works

Before using a URL with the API, open it in a private/incognito browser window:

  • If the file loads directly without asking you to log in, it will work with the API.
  • If you see a login prompt, a preview page, or an error, the URL won't work - host the file somewhere that serves it directly.

A good media URL is:

  • Public - loads without authentication
  • Direct - points straight at the file (not a redirect or preview page)
  • HTTPS - served over https://
  • Stable - won't expire before the post publishes

Using the media URL

Once your file is hosted, pass its URL in the assets array on createPost or editPost. assets is an ordered list where each entry specifies exactly one asset.

Image:

assets: [
  {
    image: {
      url: "https://your-host.example.com/photo.jpg"
    }
  }
]

Video - you can optionally provide a thumbnailUrl (also a publicly hosted URL) for the video's poster image:

assets: [
  {
    video: {
      url: "https://your-host.example.com/clip.mp4"
      thumbnailUrl: "https://your-host.example.com/clip-thumb.jpg"
    }
  }
]

A full mutation looks like this:

mutation CreatePost {
  createPost(
    input: {
      text: "Check out our latest update!"
      channelId: "some_channel_id"
      schedulingType: automatic
      mode: addToQueue
      assets: [
        {
          image: {
            url: "https://your-host.example.com/photo.jpg"
          }
        }
      ]
    }
  ) {
    ... on PostActionSuccess {
      post { id }
    }
    ... on MutationError {
      message
    }
  }
}

For complete walkthroughs, see the Create an image post and Create a video post examples.

Troubleshooting

If a media URL can't be fetched, the mutation returns a MutationError with a message:

{
  "data": {
    "createPost": {
      "message": "Failed to create post: Failed to fetch image dimensions: Not Found"
    }
  }
}

If you hit this, check the URL against this list:

  1. Public - open it in an incognito window; it should load with no login.
  2. Direct - it points at the file itself, not a share, preview, or redirect page.
  3. HTTPS - it's served over https://.
  4. Still live - it isn't a signed/expiring URL that may lapse before a scheduled post publishes.