Overview

Media

Upload images, videos and documents and use them in your posts.

Upload a file and attach it to your post, or use a public HTTPS URL that meets the Media URL Requirements.

Upload a File

Create the Media Item

Send the file name, its type and its exact size in bytes. The type is one of the Supported Formats, and the size has to be within its limit.

import { Configuration, MediaApi } from "@onepostly/sdk";

const mediaApi = new MediaApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await mediaApi.createMedia({
  createMediaBody: {
    "filename": "launch.mp4",
    "contentType": "video/mp4",
    "size": 48203776
  },
});
console.log(response);
{
  "id": "med_7f3a9c2e1b4d4e8f9a6b5c4d3e2f1a0b",
  "url": "https://cdn.onepostly.com/media/5b0e7c1a-2f4d-4c8e-9a3b-6d1f0e2c4a7b/7f3a9c2e-1b4d-4e8f-9a6b-5c4d3e2f1a0b.mp4",
  "contentType": "video/mp4",
  "sizeBytes": null,
  "filename": "launch.mp4",
  "status": "pending",
  "createdAt": "2027-03-15T09:00:00.000Z",
  "uploadUrl": "https://5d1e8a3c9f2b4e7a.r2.cloudflarestorage.com/onepostly-media/temp/5b0e7c1a-2f4d-4c8e-9a3b-6d1f0e2c4a7b/7f3a9c2e-1b4d-4e8f-9a6b-5c4d3e2f1a0b.mp4?X-Amz-Expires=3600&X-Amz-Date=20270315T090000Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=EXAMPLEKEY%2F20270315%2Fauto%2Fs3%2Faws4_request&X-Amz-SignedHeaders=content-length%3Bcontent-type%3Bhost&X-Amz-Signature=0f4c2b9e7a1d3f5b8c6e0a2d4f6b8e1c3a5d7f9b2e4c6a8d0f1b3e5c7a9d2f4b",
  "uploadExpiresIn": 3600
}

The response is 201 with the new media item.

FieldDescription
idThe id you use in a post
urlThe file's public URL. It never changes, and serves the file once status is ready
statuspending until the file arrives and is checked, then ready
sizeBytesThe file size, null until status is ready
uploadUrlWhere you send the file
uploadExpiresInSeconds until uploadUrl stops working, 3600

Upload the File

Send the file with a PUT request to uploadUrl. You do not need an API key, because the URL itself allows the upload. The Content-Type header has to match the contentType you sent in step 1, and the file has to be exactly size bytes.

Send the whole file in one request. Your HTTP client sets Content-Length from the file, as in the examples below. Any other length is refused with 403, and nothing is stored. To upload a different file, create a new media item.

import { readFile } from "node:fs/promises";

await fetch(uploadUrl, {
  method: "PUT",
  headers: { "Content-Type": "video/mp4" },
  body: await readFile("launch.mp4"),
});

Each upload is checked against the type and size you declared, usually within seconds. To see the result, call GET /v1/media/{id}, which also checks the file if that has not happened yet. A checked file shows the status ready and its size in sizeBytes, and url serves it from then on.

Use It in a Post

Put the id in media. See Create Posts for the full request.

import { Configuration, PostsApi } from "@onepostly/sdk";

const postsApi = new PostsApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await postsApi.createPost({
  createPostBody: {
    "text": "Launch day",
    "media": [
      {
        "id": "med_7f3a9c2e1b4d4e8f9a6b5c4d3e2f1a0b"
      }
    ],
    "accounts": [
      {
        "accountId": "acc_YOUR_X_ACCOUNT_ID"
      }
    ]
  },
});
console.log(response);
{
  "groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
  "data": [
    {
      "id": "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
      "groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
      "accountId": "acc_YOUR_X_ACCOUNT_ID",
      "platform": "x",
      "source": "onepostly",
      "status": "publishing",
      "text": "Launch day",
      "mediaKind": "video",
      "media": [
        {
          "url": "https://cdn.onepostly.com/media/5b0e7c1a-2f4d-4c8e-9a3b-6d1f0e2c4a7b/7f3a9c2e-1b4d-4e8f-9a6b-5c4d3e2f1a0b.mp4"
        }
      ],
      "thread": [],
      "scheduledFor": null,
      "timezone": null,
      "options": {},
      "platformPostId": null,
      "url": null,
      "error": null,
      "publishedAt": null,
      "metrics": null,
      "createdAt": "2027-03-15T10:00:00.000Z",
      "updatedAt": "2027-03-15T10:00:00.000Z"
    }
  ]
}

In media and thread[].media, each item takes an id or a url, not both. The post shows every file by its url, even one you sent by id.

The files set the post kind. One video makes a video post, and several files make a multi_image post. For a Facebook reel and for stories, set mediaKind yourself. See Media Kinds for the full list.

Expiry

FileKept for
uploadUrl1 hour
A file no post uses7 days
A file in a scheduled postUntil the post publishes
A file in a published postNo expiry

An expired uploadUrl needs a new media item.

Media URL Requirements

Every media URL must meet these rules, uploaded or self-hosted.

  • HTTPS to a public address.
  • At most 2,048 characters.
  • Returns the file with 200, Content-Length and the right Content-Type.
  • At most 5 redirects, each to a public HTTPS address.
  • No stall longer than 30 seconds.

Share links from Google Drive, Dropbox, OneDrive, SharePoint and iCloud fail. They return a web page, not the file. Open the URL in a private window to check it. If you see a page, upload the file to Onepostly.

The first two rules are checked when you create the post and return 400 VALIDATION_ERROR. The rest are checked at publish and fail the post with MEDIA_UNFETCHABLE. Uploaded files meet every rule once their status is ready.

Supported Formats

Onepostly accepts these files, and each platform adds its own limits. See the media requirements on each platform page, such as Instagram.

FormatcontentTypeMax size
JPEGimage/jpeg30 MB
PNGimage/png30 MB
GIFimage/gif30 MB
WebPimage/webp30 MB
MP4video/mp41 GB
MOVvideo/quicktime1 GB
AVIvideo/x-msvideo or video/avi1 GB
WebMvideo/webm1 GB
PDFapplication/pdf100 MB
PPTapplication/vnd.ms-powerpoint100 MB
PPTXapplication/vnd.openxmlformats-officedocument.presentationml.presentation100 MB
DOCapplication/msword100 MB
DOCXapplication/vnd.openxmlformats-officedocument.wordprocessingml.document100 MB

A larger size is refused with 400 MEDIA_TOO_LARGE before the upload starts.

Platform Limits

Each platform takes only some formats and sizes. They are in the Media Requirements on its page, such as Instagram.

  • An uploaded file outside them returns 400 when you create or edit the post, and nothing is created. param names the file, such as media.0 or thread.1.media.0.
  • A file sent by url is checked when the post publishes, and the post fails with the same code.
CodeCause
MEDIA_FORMAT_UNSUPPORTEDThe platform does not take the file's format for this kind of post
MEDIA_TOO_LARGE_FOR_PLATFORMThe file is over the platform's size limit for this kind of post

The Content-Type sets a file's format. When it is missing or application/octet-stream, the URL's extension sets it.

Media per Platform

The mediaKind values and the file count each platform takes.

Platformtextimagemulti_imagevideoreeldocumentstoriesMax files
X (Twitter)NoNoNo4
InstagramNoAs a reelNo10
FacebookNo10
ThreadsNoNoNo20
LinkedInNoNo20
YouTubeNoNoNoNoNoNo1
TikTokNoNoNoNo35
PinterestNoNoNoNoNo1
BlueskyNoNoNo4

Durations, sizes and dimensions are on each platform page, such as X. See Compare Platforms to view all platforms side by side.

List Uploads

GET /v1/media lists the files you uploaded, newest first. Media items still waiting for their upload are not listed.

import { Configuration, MediaApi } from "@onepostly/sdk";

const mediaApi = new MediaApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await mediaApi.listMedia({
  limit: 20,
});
console.log(response);
{
  "data": [
    {
      "id": "med_7f3a9c2e1b4d4e8f9a6b5c4d3e2f1a0b",
      "url": "https://cdn.onepostly.com/media/5b0e7c1a-2f4d-4c8e-9a3b-6d1f0e2c4a7b/7f3a9c2e-1b4d-4e8f-9a6b-5c4d3e2f1a0b.mp4",
      "contentType": "video/mp4",
      "sizeBytes": 48203776,
      "filename": "launch.mp4",
      "status": "ready",
      "createdAt": "2027-03-15T09:00:00.000Z"
    },
    {
      "id": "med_7f3a9c2e1b4d4e8f9a6b5c4d3e2f1a02",
      "url": "https://cdn.onepostly.com/media/5b0e7c1a-2f4d-4c8e-9a3b-6d1f0e2c4a7b/7f3a9c2e-1b4d-4e8f-9a6b-5c4d3e2f1a02.jpg",
      "contentType": "image/jpeg",
      "sizeBytes": 482113,
      "filename": "spring.jpg",
      "status": "ready",
      "createdAt": "2027-03-14T16:30:00.000Z"
    }
  ],
  "nextCursor": null,
  "hasMore": false
}

You get 50 files per page by default and up to 100 with limit. Pass nextCursor as cursor to get the next page, until hasMore is false. Add include=total to get the total count. See Pagination.

Delete an Upload

import { Configuration, MediaApi } from "@onepostly/sdk";

const mediaApi = new MediaApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await mediaApi.deleteMedia({
  id: "med_7f3a9c2e1b4d4e8f9a6b5c4d3e2f1a0b",
});
console.log(response);

This returns 204 and deletes the file for good. Published posts are not affected, because each platform keeps its own copy.

A scheduled post that still uses the file fails when it publishes, so delete files only once all their posts are live.

Errors

These come back from a media request or POST /v1/posts.

CodeHTTPCauseFix
VALIDATION_ERROR400A field is missing or too long, a media item has both or neither of id and url, or a URL breaks the rulesFix the field in param
UNSUPPORTED_MEDIA_TYPE400contentType is not supportedConvert the file
MEDIA_TOO_LARGE400size is over the limit for contentTypeCompress the file
MEDIA_FORMAT_UNSUPPORTED400An account's platform does not take the uploaded file's formatConvert the file. See Platform Limits
MEDIA_TOO_LARGE_FOR_PLATFORM400The uploaded file is over an account's platform limitCompress the file. See Platform Limits
NOT_FOUND404No media item has this idCheck the id with GET /v1/media
MEDIA_NOT_FOUND404A post's media[].id does not existUse an id from GET /v1/media
SERVICE_UNAVAILABLE503Uploads are down for nowTry again later

File problems show up later, in the post's error.code. See Failed Posts.

CodeCauseFix
MEDIA_NOT_UPLOADEDThe file was never uploaded, or the upload expiredUpload the file, then create the post again
MEDIA_TYPE_MISMATCHThe file's Content-Type is not its contentTypeCreate a new media item with the right type
MEDIA_UNFETCHABLEThe URL breaks a URL ruleFix the URL
MEDIA_FORMAT_UNSUPPORTEDThe platform does not take the URL's file formatConvert the file. See Platform Limits
MEDIA_TOO_LARGE_FOR_PLATFORMThe URL's file is over the platform's limitCompress the file. See Platform Limits
INVALID_MEDIAThe file is empty, or the platform rejects itCheck the platform's Media Requirements
URL_OWNERSHIP_UNVERIFIEDTikTok refuses the URL's domainUpload the file and use its id

Other codes are in Errors.