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
mediaKind | Content | Media count | Notes |
|---|---|---|---|
image | Single photo | Exactly 1 URL | Optional caption |
multi-image | Carousel | 2 to 10 URLs | Images and videos can be mixed |
video | Reel | Exactly 1 URL | Also appears in feed |
stories | Story | Exactly 1 URL | Image or video. Caption is ignored |
text | No | No | Not 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"
}
}Carousel
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
| Property | Feed | Story | Carousel |
|---|---|---|---|
| Max images | 1 | 1 | 10 |
| Formats | JPEG, PNG | JPEG, PNG | JPEG, PNG |
| Max file size | 8 MB | 8 MB | 8 MB each |
| Recommended | 1080 × 1350 px | 1080 × 1920 px | 1080 × 1080 px |
Aspect ratios
| Orientation | Ratio | Dimensions | Notes |
|---|---|---|---|
| Portrait | 4:5 | 1080 × 1350 px | Best engagement for feed posts |
| Square | 1:1 | 1080 × 1080 px | Standard feed and carousel |
| Landscape | 1.91:1 | 1080 × 566 px | Widest allowed for feed |
| Vertical | 9:16 | 1080 × 1920 px | Stories and Reels |
Feed posts accept aspect ratios between 0.5625 (9:16) and 1.91 (1.91:1).
Videos
| Property | Reel | Story |
|---|---|---|
| Formats | MP4, MOV | MP4, MOV |
| Max file size | 300 MB | 100 MB |
| Max duration | 90 sec | 60 sec |
| Min duration | 3 sec | 3 sec |
| Aspect ratio | 9:16 | 9:16 |
| Resolution | 1080 × 1920 px | 1080 × 1920 px |
| Codec | H.264 | H.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.
timezonedefaults toUTC. Use an IANA name likeEurope/Istanbul- Do not append
Zor an offset toscheduledFor. It is local wall time - Scheduled posts return
201 Createdand publish automatically at the scheduled moment - Max 1 year ahead
- Cancel with
posts.cancelPost({ id })/posts.cancel_post(id=…), orDELETE /v1/posts/:id, any time before then
Publish Errors
| Code | Cause | Fix |
|---|---|---|
MEDIA_REQUIRED | No media URL passed, or mediaKind missing | Set an explicit mediaKind and add at least one entry to mediaUrls |
INVALID_SPONSOR | A branded content sponsor could not be resolved, or sponsors were sent with stories | Use a public professional account (or its numeric id); drop sponsors for stories |
INVALID_TRIAL_REEL | trialReel without a Reel, or combined with sponsors | Use mediaKind video/reel without sponsors |
INVALID_MULTI_IMAGE | Fewer than 2 or more than 10 URLs | Adjust the count |
INVALID_AUDIO | audioId off a single-video Reel, or on Instagram Login | Use mediaKind video/reel on a Facebook Login connection |
INVALID_AI_LABEL | isAiGenerated on stories | Drop the flag for stories |
CAPTION_TOO_LONG | Caption over 2,200 characters | Trim the text |
INVALID_MEDIA | Unsupported file type or format | Check Media limits |
MEDIA_UNFETCHABLE | Instagram could not download the URL | Use a public HTTPS URL |
CONTAINER_FAILED | Instagram could not process the media | Retry. Check format, size, and that the URL opens correctly |
TOKEN_INVALID | Connection token expired or revoked | Reconnect the account |
PERMISSION_DENIED | Connection lacks publish permission | Reconnect the account |
RATE_LIMITED | Instagram throttled the request | Retry later |