Posts

Publish image, multi-image, Reels, and Stories on Instagram.

Publish to Instagram through the shared posts surface. Feed photos, carousels, Reels, and Stories all go through the same endpoint.

Quick Start

Post an image to Instagram in under 60 seconds:

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

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

const response = await postsApi.createPost({
  createPostBody: {
    "text": "Ship day",
    "mediaUrls": [
      "https://cdn.onepostly.com/media/product.jpg"
    ],
    "mediaKind": "image",
    "destinations": [
      {
        "accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5"
      }
    ]
  },
});
console.log(response);
{
  "post": {
    "id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
    "text": "Ship day",
    "mediaUrls": [
      "https://cdn.onepostly.com/media/product.jpg"
    ],
    "mediaKind": "image",
    "status": "queued",
    "scheduledFor": null,
    "timezone": null,
    "destinations": [
      {
        "id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
        "accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
        "platform": "instagram",
        "status": "draft",
        "externalPostId": "17895695668004550",
        "externalUrl": "https://www.instagram.com/p/17895695668004550/",
        "errorCode": null,
        "errorMessage": null,
        "publishedAt": "2026-08-25T10:00:00.000Z",
        "metrics": null
      }
    ],
    "createdAt": "2026-08-25T10:00:00.000Z",
    "updatedAt": "2026-08-25T10:00:00.000Z"
  }
}

The response is 202 Accepted with status: "queued". Poll GET /v1/posts/:id until the destination is published or failed, or receive webhooks instead of polling.

Content Types

mediaKindContentMedia countNotes
imageSingle photoExactly 1 URLOptional caption
multi-imageCarousel2 to 10 URLsImages and videos can be mixed
videoReelExactly 1 URLAlso appears in feed
storiesStoryExactly 1 URLImage or video. Caption is ignored
textNoNoNot supported on Instagram

Feed Post

A single image or video in the main feed. Best aspect ratio is 4:5 (portrait), but everything from 9:16 (portrait) through 1:1 (square) to 1.91:1 (landscape) is supported:

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

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

const response = await postsApi.createPost({
  createPostBody: {
    "text": "Ship day",
    "mediaUrls": [
      "https://cdn.onepostly.com/media/product.jpg"
    ],
    "mediaKind": "image",
    "destinations": [
      {
        "accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5"
      }
    ]
  },
});
console.log(response);
{
  "post": {
    "id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
    "text": "Ship day",
    "mediaUrls": [
      "https://cdn.onepostly.com/media/product.jpg"
    ],
    "mediaKind": "image",
    "status": "queued",
    "scheduledFor": null,
    "timezone": null,
    "destinations": [
      {
        "id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
        "accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
        "platform": "instagram",
        "status": "draft",
        "externalPostId": "17895695668004550",
        "externalUrl": "https://www.instagram.com/p/17895695668004550/",
        "errorCode": null,
        "errorMessage": null,
        "publishedAt": "2026-08-25T10:00:00.000Z",
        "metrics": null
      }
    ],
    "createdAt": "2026-08-25T10:00:00.000Z",
    "updatedAt": "2026-08-25T10:00:00.000Z"
  }
}

Up to 10 mixed image/video items. All items should share the same aspect ratio. The first item determines the ratio for the entire carousel:

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

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

const response = await postsApi.createPost({
  createPostBody: {
    "text": "Trip highlights from last weekend",
    "mediaUrls": [
      "https://cdn.example.com/photo1.jpg",
      "https://cdn.example.com/photo2.jpg",
      "https://cdn.example.com/clip.mp4"
    ],
    "mediaKind": "multi-image",
    "destinations": [
      {
        "accountId": "YOUR_INSTAGRAM_ACCOUNT_ID"
      }
    ]
  },
});
console.log(response);
{
  "post": {
    "id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
    "text": "Trip highlights from last weekend",
    "mediaUrls": [
      "https://cdn.example.com/photo1.jpg",
      "https://cdn.example.com/photo2.jpg",
      "https://cdn.example.com/clip.mp4"
    ],
    "mediaKind": "multi-image",
    "status": "queued",
    "scheduledFor": null,
    "timezone": null,
    "destinations": [
      {
        "id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
        "accountId": "YOUR_INSTAGRAM_ACCOUNT_ID",
        "platform": "instagram",
        "status": "draft",
        "externalPostId": "17895695668004550",
        "externalUrl": "https://www.instagram.com/p/17895695668004550/",
        "errorCode": null,
        "errorMessage": null,
        "publishedAt": "2026-08-25T10:00:00.000Z",
        "metrics": null
      }
    ],
    "createdAt": "2026-08-25T10:00:00.000Z",
    "updatedAt": "2026-08-25T10:00:00.000Z"
  }
}

Reel

Any single video publishes as a Reel automatically. No content type flag is needed. Reels should be vertical (9:16) and no longer than 90 seconds:

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

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

const response = await postsApi.createPost({
  createPostBody: {
    "text": "New tutorial is up!",
    "mediaUrls": [
      "https://cdn.example.com/reel.mp4"
    ],
    "mediaKind": "video",
    "destinations": [
      {
        "accountId": "YOUR_INSTAGRAM_ACCOUNT_ID"
      }
    ]
  },
});
console.log(response);
{
  "post": {
    "id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
    "text": "New tutorial is up!",
    "mediaUrls": [
      "https://cdn.example.com/reel.mp4"
    ],
    "mediaKind": "video",
    "status": "queued",
    "scheduledFor": null,
    "timezone": null,
    "destinations": [
      {
        "id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
        "accountId": "YOUR_INSTAGRAM_ACCOUNT_ID",
        "platform": "instagram",
        "status": "draft",
        "externalPostId": "17895695668004550",
        "externalUrl": "https://www.instagram.com/p/17895695668004550/",
        "errorCode": null,
        "errorMessage": null,
        "publishedAt": "2026-08-25T10:00:00.000Z",
        "metrics": null
      }
    ],
    "createdAt": "2026-08-25T10:00:00.000Z",
    "updatedAt": "2026-08-25T10:00:00.000Z"
  }
}

Published Reels (and all other media) resolve their permalink automatically. The destination carries it as externalUrl once published.

Trial Reel

Set trialReel: true on a Reel to show it to non-followers first. trialGraduationStrategy controls the graduation, default MANUAL. Trial reels do not support branded content sponsors:

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

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

const response = await postsApi.createPost({
  createPostBody: {
    "text": "Testing a new format",
    "mediaUrls": [
      "https://cdn.example.com/reel.mp4"
    ],
    "mediaKind": "video",
    "destinations": [
      {
        "accountId": "YOUR_INSTAGRAM_ACCOUNT_ID",
        "trialReel": true,
        "trialGraduationStrategy": "MANUAL"
      }
    ]
  },
});
console.log(response);
{
  "post": {
    "id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
    "text": "Testing a new format",
    "mediaUrls": [
      "https://cdn.example.com/reel.mp4"
    ],
    "mediaKind": "video",
    "status": "queued",
    "scheduledFor": null,
    "timezone": null,
    "destinations": [
      {
        "id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
        "accountId": "YOUR_INSTAGRAM_ACCOUNT_ID",
        "platform": "instagram",
        "status": "draft",
        "externalPostId": "17895695668004550",
        "externalUrl": "https://www.instagram.com/p/17895695668004550/",
        "errorCode": null,
        "errorMessage": null,
        "publishedAt": "2026-08-25T10:00:00.000Z",
        "metrics": null
      }
    ],
    "createdAt": "2026-08-25T10:00:00.000Z",
    "updatedAt": "2026-08-25T10:00:00.000Z"
  }
}

Catalog Audio

Attach catalog audio to a single-video Reel with audioId (from creator media search, kind=audio). Facebook Login connections only; Instagram Login rejects it with INVALID_AUDIO. Volumes default to 100:

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

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

const response = await postsApi.createPost({
  createPostBody: {
    "text": "Trending sound",
    "mediaUrls": [
      "https://cdn.example.com/reel.mp4"
    ],
    "mediaKind": "video",
    "destinations": [
      {
        "accountId": "YOUR_INSTAGRAM_ACCOUNT_ID",
        "audioId": "587784541076604",
        "audioVolume": 80,
        "videoVolume": 50
      }
    ]
  },
});
console.log(response);
{
  "post": {
    "id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
    "text": "Trending sound",
    "mediaUrls": [
      "https://cdn.example.com/reel.mp4"
    ],
    "mediaKind": "video",
    "status": "queued",
    "scheduledFor": null,
    "timezone": null,
    "destinations": [
      {
        "id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
        "accountId": "YOUR_INSTAGRAM_ACCOUNT_ID",
        "platform": "instagram",
        "status": "draft",
        "externalPostId": "17895695668004550",
        "externalUrl": "https://www.instagram.com/p/17895695668004550/",
        "errorCode": null,
        "errorMessage": null,
        "publishedAt": "2026-08-25T10:00:00.000Z",
        "metrics": null
      }
    ],
    "createdAt": "2026-08-25T10:00:00.000Z",
    "updatedAt": "2026-08-25T10:00:00.000Z"
  }
}

AI Label

Self-disclose AI usage with isAiGenerated: true. Applies to images, Reels, and carousels (parent only). Not supported on stories:

Story

Set mediaKind: "stories" to publish to Stories. Stories disappear after 24 hours, captions are not displayed, and link stickers are not available via the API:

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

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

const response = await postsApi.createPost({
  createPostBody: {
    "text": "",
    "mediaUrls": [
      "https://cdn.example.com/story.jpg"
    ],
    "mediaKind": "stories",
    "destinations": [
      {
        "accountId": "YOUR_INSTAGRAM_ACCOUNT_ID"
      }
    ]
  },
});
console.log(response);
{
  "post": {
    "id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
    "text": "",
    "mediaUrls": [
      "https://cdn.example.com/story.jpg"
    ],
    "mediaKind": "stories",
    "status": "queued",
    "scheduledFor": null,
    "timezone": null,
    "destinations": [
      {
        "id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
        "accountId": "YOUR_INSTAGRAM_ACCOUNT_ID",
        "platform": "instagram",
        "status": "draft",
        "externalPostId": "17895695668004550",
        "externalUrl": "https://www.instagram.com/p/17895695668004550/",
        "errorCode": null,
        "errorMessage": null,
        "publishedAt": "2026-08-25T10:00:00.000Z",
        "metrics": null
      }
    ],
    "createdAt": "2026-08-25T10:00:00.000Z",
    "updatedAt": "2026-08-25T10:00:00.000Z"
  }
}

Destination Parameters

Every Instagram destination accepts the following parameters. Content-type restrictions are noted per field.

Prop

Type

Tag Format

userTags is an array of objects. The @ prefix in username is optional and stripped automatically:

{
  "accountId": "YOUR_IG_ACCOUNT_ID",
  "userTags": [
    { "username": "friend_username", "x": 0.5, "y": 0.5 },
    { "username": "second_user" }
  ]
}

Media Requirements

Images

PropertyFeedStoryCarousel
Max images1110
FormatsJPEG, PNGJPEG, PNGJPEG, PNG
Max file size8 MB8 MB8 MB each
Recommended1080 × 1350 px1080 × 1920 px1080 × 1080 px

Aspect ratios

OrientationRatioDimensionsNotes
Portrait4:51080 × 1350 pxBest engagement for feed posts
Square1:11080 × 1080 pxStandard feed and carousel
Landscape1.91:11080 × 566 pxWidest allowed for feed
Vertical9:161080 × 1920 pxStories and Reels

Feed posts accept aspect ratios between 0.5625 (9:16) and 1.91 (1.91:1).

Videos

PropertyReelStory
FormatsMP4, MOVMP4, MOV
Max file size300 MB100 MB
Max duration90 sec60 sec
Min duration3 sec3 sec
Aspect ratio9:169:16
Resolution1080 × 1920 px1080 × 1920 px
CodecH.264H.264

Media URL Requirements

These do not work as media URLs:

  • Google Drive returns an HTML download page, not the file
  • Dropbox returns an HTML preview page
  • OneDrive / SharePoint returns HTML
  • iCloud returns HTML

Test your URL in an incognito browser window. If you see a webpage instead of the raw image or video, the URL will not work.

Media URLs must be publicly accessible, return actual media bytes with the correct Content-Type, and use HTTPS.

Videos take time

Instagram processes video containers before publishing. Large videos can take a few minutes. The destination stays queued until the media is ready. If Instagram is still processing after several minutes the publish fails with MEDIA_PROCESSING_TIMEOUT.

Schedule

Add scheduledFor (local wall time) and timezone to the same call. The request shape is otherwise identical to an immediate post.

  • timezone defaults to UTC. Use an IANA name like Europe/Istanbul
  • Do not append Z or an offset to scheduledFor. It is local wall time
  • Scheduled posts return 201 Created and publish automatically at the scheduled moment
  • Max 1 year ahead
  • Cancel with posts.cancelPost({ id }) / posts.cancel_post(id=…), or DELETE /v1/posts/:id, any time before then

Publish Errors

CodeCauseFix
MEDIA_REQUIREDNo media URL passed, or mediaKind missingSet an explicit mediaKind and add at least one entry to mediaUrls
INVALID_SPONSORA branded content sponsor could not be resolved, or sponsors were sent with storiesUse a public professional account (or its numeric id); drop sponsors for stories
INVALID_TRIAL_REELtrialReel without a Reel, or combined with sponsorsUse mediaKind video/reel without sponsors
INVALID_MULTI_IMAGEFewer than 2 or more than 10 URLsAdjust the count
INVALID_AUDIOaudioId off a single-video Reel, or on Instagram LoginUse mediaKind video/reel on a Facebook Login connection
INVALID_AI_LABELisAiGenerated on storiesDrop the flag for stories
CAPTION_TOO_LONGCaption over 2,200 charactersTrim the text
INVALID_MEDIAUnsupported file type or formatCheck Media limits
MEDIA_UNFETCHABLEInstagram could not download the URLUse a public HTTPS URL
CONTAINER_FAILEDInstagram could not process the mediaRetry. Check format, size, and that the URL opens correctly
TOKEN_INVALIDConnection token expired or revokedReconnect the account
PERMISSION_DENIEDConnection lacks publish permissionReconnect the account
RATE_LIMITEDInstagram throttled the requestRetry later

See Also