Connect an Instagram professional account and publish or schedule feed photos, carousels, reels, and stories with PostZen.
PostZen publishes and schedules feed photos, carousels, reels, and stories for Instagram professional accounts. Every Instagram post needs at least one media item, and the selected postType determines how many items it takes and whether each must be an image or a video. Carousels take 2–10 items and can mix images and videos.
Quick reference
| Item | Instagram through PostZen |
|---|---|
| Platform value | instagram |
| Account requirement | Business or Creator account |
| Caption limit | 2,200 characters |
| Media required | Yes — text-only posts fail validation |
| Media per published post | One item for feed and story; exactly one video for reel; 2–10 items for carousel |
| Feed | One image |
| Carousel | 2–10 items (images and videos); ordered by mediaItems |
| Reel | Exactly one video |
| Story | One image or one video |
| Image size | 8 MB or less; larger images produce a warning and Instagram may reject them |
| Video size | 1 GB or less |
| Image upload formats | JPEG/JPG, PNG, WebP, GIF |
| Video upload formats | MP4, MPEG, QuickTime, AVI, WebM, M4V |
| Mixed image and video | Carousels only; feed, reel, and story take a single media type |
| Multi-item behavior | A carousel renders as a swipeable set in the order of mediaItems |
| Scheduling | Supported |
Before you start
- Connect an Instagram Business or Creator account. Personal accounts cannot connect.
- You do not need to link a Facebook Page to the Instagram account.
- The free tier allows two connected accounts before you need a payment method. A connection beyond that limit returns a
402. - Prepare one image for a feed post, one video for a reel, one image or video for a story, or 2–10 items for a carousel. Only carousels accept multiple media items or a mix of images and videos; feed, reel, and story posts each take a single media item.
Cloud-storage share links from Google Drive, Dropbox, OneDrive, and iCloud return HTML pages instead of the media file. Use a publicly accessible direct URL, or upload the file through POST /v1/media/presign and pass the returned publicUrl in mediaItems.
PostZen downloads and re-hosts media from external URLs up to 100 MB. Presigned uploads accept files up to 5 GB, but Instagram's publishing limits still apply: 8 MB for an image and 1 GB for a video.
Connect your account
Request an Instagram connect URL for the profile that will own the account. Send the user to the returned authUrl to complete Instagram Login. PostZen manages the OAuth scopes, and the returned state expires after 10 minutes.
import PostZen from '@postzen/node';
const postzen = new PostZen({
apiKey: process.env.POSTZEN_API_KEY,
});
const { data } = await postzen.connect.createConnectUrl({
path: { platform: 'instagram' },
query: {
profileId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
redirectUrl: 'https://yourapp.com/connected',
},
});
console.log(data.authUrl); // redirect the user here
console.log(data.state); // expires after 10 minutesfrom postzen import PostZen
client = PostZen() # reads POSTZEN_API_KEY
response = client.connect.create_connect_url(
"instagram",
profile_id="jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
redirect_url="https://yourapp.com/connected",
)
print(response.authUrl) # redirect the user here
print(response.state) # expires after 10 minutescurl "https://api.postzen.dev/v1/connect/instagram?profileId=jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e&redirectUrl=https://yourapp.com/connected" \
-H "Authorization: Bearer $POSTZEN_API_KEY"After the user completes OAuth, use GET /v1/accounts with profileId and platform=instagram to find the connected account. Use that account's ID as accountId in each Instagram target.
Quick start
This request publishes a feed photo immediately. A feed post is the default, but setting postType explicitly makes the target's behavior clear.
const { data } = await postzen.posts.createPost({
body: {
content: 'A new view from the studio.',
mediaItems: [
{ url: 'https://cdn.example.com/instagram/studio.jpg' },
],
publishNow: true,
platforms: [
{
platform: 'instagram',
accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
settings: { postType: 'feed' },
},
],
},
});
console.log(data.post._id);response = client.posts.create_post(
content="A new view from the studio.",
media_items=[
{"url": "https://cdn.example.com/instagram/studio.jpg"},
],
publish_now=True,
platforms=[
{
"platform": "instagram",
"account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
"settings": {"post_type": "feed"},
},
],
)
print(response.post.field_id)curl -X POST https://api.postzen.dev/v1/posts \
-H "Authorization: Bearer $POSTZEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "A new view from the studio.",
"mediaItems": [
{ "url": "https://cdn.example.com/instagram/studio.jpg" }
],
"publishNow": true,
"platforms": [
{
"platform": "instagram",
"accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
"settings": { "postType": "feed" }
}
]
}'Set exactly one creation mode on a post: publishNow, scheduledFor, or isDraft. For a scheduled post, replace publishNow with an ISO-8601 scheduledFor value at least 60 seconds in the future. You can also send an x-request-id header to make the create request idempotent.
Content types
Set settings.postType inside the Instagram target. Its default value is feed.
Feed
A feed post accepts one image. It also supports up to three collaborator usernames and photo tags with relative x and y coordinates from 0 to 1.
const { data } = await postzen.posts.createPost({
body: {
content: 'Meet the team behind today\'s shoot.',
mediaItems: [
{ url: 'https://cdn.example.com/instagram/team.jpg' },
],
publishNow: true,
platforms: [
{
platform: 'instagram',
accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
settings: {
postType: 'feed',
collaborators: ['creativepartner'],
userTags: [
{ username: 'photographer', x: 0.35, y: 0.6 },
],
},
},
],
},
});
console.log(data.post._id);response = client.posts.create_post(
content="Meet the team behind today's shoot.",
media_items=[
{"url": "https://cdn.example.com/instagram/team.jpg"},
],
publish_now=True,
platforms=[
{
"platform": "instagram",
"account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
"settings": {
"post_type": "feed",
"collaborators": ["creativepartner"],
"user_tags": [
{"username": "photographer", "x": 0.35, "y": 0.6},
],
},
},
],
)
print(response.post.field_id)curl -X POST https://api.postzen.dev/v1/posts \
-H "Authorization: Bearer $POSTZEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Meet the team behind today\u0027s shoot.",
"mediaItems": [
{ "url": "https://cdn.example.com/instagram/team.jpg" }
],
"publishNow": true,
"platforms": [
{
"platform": "instagram",
"accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
"settings": {
"postType": "feed",
"collaborators": ["creativepartner"],
"userTags": [
{ "username": "photographer", "x": 0.35, "y": 0.6 }
]
}
}
]
}'Use a 1080×1350 image for a 4:5 portrait feed post. A 1:1 image also works well. Feed videos return Choose Reel for video, and attaching multiple media items to a feed post returns a validation error directing you to carousel.
Carousel
A carousel accepts 2–10 media items and can mix images and videos. The items publish in the order they appear in mediaItems. Carousels support up to three collaborator usernames and a firstComment, but userTags are not supported — user tags are feed-only. PostZen creates a child media container for each item, then a parent carousel container, and publishes once processing finishes, using the same resumable container handling as reels.
const { data } = await postzen.posts.createPost({
body: {
content: 'Three angles from today\'s shoot.',
mediaItems: [
{ url: 'https://cdn.example.com/instagram/carousel-1.jpg' },
{ url: 'https://cdn.example.com/instagram/carousel-2.mp4' },
{ url: 'https://cdn.example.com/instagram/carousel-3.jpg' },
],
publishNow: true,
platforms: [
{
platform: 'instagram',
accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
settings: {
postType: 'carousel',
collaborators: ['creativepartner'],
firstComment: 'Swipe through for the full set.',
},
},
],
},
});
console.log(data.post._id);response = client.posts.create_post(
content="Three angles from today's shoot.",
media_items=[
{"url": "https://cdn.example.com/instagram/carousel-1.jpg"},
{"url": "https://cdn.example.com/instagram/carousel-2.mp4"},
{"url": "https://cdn.example.com/instagram/carousel-3.jpg"},
],
publish_now=True,
platforms=[
{
"platform": "instagram",
"account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
"settings": {
"post_type": "carousel",
"collaborators": ["creativepartner"],
"first_comment": "Swipe through for the full set.",
},
},
],
)
print(response.post.field_id)curl -X POST https://api.postzen.dev/v1/posts \
-H "Authorization: Bearer $POSTZEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Three angles from today\u0027s shoot.",
"mediaItems": [
{ "url": "https://cdn.example.com/instagram/carousel-1.jpg" },
{ "url": "https://cdn.example.com/instagram/carousel-2.mp4" },
{ "url": "https://cdn.example.com/instagram/carousel-3.jpg" }
],
"publishNow": true,
"platforms": [
{
"platform": "instagram",
"accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
"settings": {
"postType": "carousel",
"collaborators": ["creativepartner"],
"firstComment": "Swipe through for the full set."
}
}
]
}'Attach at least 2 and no more than 10 items. Each image follows the feed image limits and each video follows the reel video limits.
Reel
A reel requires exactly one video. shareToFeed defaults to true; set it to false when you do not want the reel to appear on the feed.
const { data } = await postzen.posts.createPost({
body: {
content: 'A quick look behind the scenes.',
mediaItems: [
{ url: 'https://cdn.example.com/instagram/behind-the-scenes.mp4' },
],
publishNow: true,
platforms: [
{
platform: 'instagram',
accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
settings: {
postType: 'reel',
shareToFeed: false,
firstComment: 'Shot in the studio this morning.',
},
},
],
},
});
console.log(data.post._id);response = client.posts.create_post(
content="A quick look behind the scenes.",
media_items=[
{"url": "https://cdn.example.com/instagram/behind-the-scenes.mp4"},
],
publish_now=True,
platforms=[
{
"platform": "instagram",
"account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
"settings": {
"post_type": "reel",
"share_to_feed": False,
"first_comment": "Shot in the studio this morning.",
},
},
],
)
print(response.post.field_id)curl -X POST https://api.postzen.dev/v1/posts \
-H "Authorization: Bearer $POSTZEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "A quick look behind the scenes.",
"mediaItems": [
{ "url": "https://cdn.example.com/instagram/behind-the-scenes.mp4" }
],
"publishNow": true,
"platforms": [
{
"platform": "instagram",
"accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
"settings": {
"postType": "reel",
"shareToFeed": false,
"firstComment": "Shot in the studio this morning."
}
}
]
}'Use a 1080×1920, 9:16 vertical video for a reel.
Story
A story accepts one image or one video. Instagram does not display captions on stories, and PostZen produces a warning if you set one. The examples use an empty content value.
const { data } = await postzen.posts.createPost({
body: {
content: '',
mediaItems: [
{ url: 'https://cdn.example.com/instagram/story.jpg' },
],
publishNow: true,
platforms: [
{
platform: 'instagram',
accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
settings: { postType: 'story' },
},
],
},
});
console.log(data.post._id);response = client.posts.create_post(
content="",
media_items=[
{"url": "https://cdn.example.com/instagram/story.jpg"},
],
publish_now=True,
platforms=[
{
"platform": "instagram",
"account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
"settings": {"post_type": "story"},
},
],
)
print(response.post.field_id)curl -X POST https://api.postzen.dev/v1/posts \
-H "Authorization: Bearer $POSTZEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "",
"mediaItems": [
{ "url": "https://cdn.example.com/instagram/story.jpg" }
],
"publishNow": true,
"platforms": [
{
"platform": "instagram",
"accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e",
"settings": { "postType": "story" }
}
]
}'Use 1080×1920, 9:16 vertical media for a story.
Media requirements
Instagram cannot publish a text-only post. Attach media in mediaItems, using either a direct public URL or the publicUrl returned by the media presign endpoint. Feed, reel, and story posts take a single item; carousels take 2–10 items.
Images
| Requirement | Feed | Carousel | Story |
|---|---|---|---|
| Media count | One image | 2–10 items, mixed with videos allowed | One image, if the story uses an image |
| Maximum size | 8 MB; larger images produce a warning and Instagram may reject them | 8 MB per image; larger images produce a warning and Instagram may reject them | 8 MB; larger images produce a warning and Instagram may reject them |
| Recommended dimensions | 1080×1350 (4:5 portrait); 1:1 also works well | 1080×1350 (4:5 portrait); 1:1 also works well | 1080×1920 (9:16 vertical) |
| Presigned upload formats | JPEG/JPG, PNG, WebP, GIF | JPEG/JPG, PNG, WebP, GIF | JPEG/JPG, PNG, WebP, GIF |
Reels do not accept images. You can only combine images and videos within a carousel — feed, reel, and story posts each take a single media type.
Videos
| Requirement | Reel | Carousel | Story |
|---|---|---|---|
| Media count | Exactly one video | Any of the 2–10 items may be a video | One video, if the story uses a video |
| Maximum size | 1 GB | 1 GB per video | 1 GB |
| Recommended dimensions | 1080×1920 (9:16 vertical) | 1080×1350 (4:5 portrait) | 1080×1920 (9:16 vertical) |
| Presigned upload formats | MP4, MPEG, QuickTime, AVI, WebM, M4V | MP4, MPEG, QuickTime, AVI, WebM, M4V | MP4, MPEG, QuickTime, AVI, WebM, M4V |
Feed posts do not accept video. Set postType to reel for a single video, or carousel to include videos alongside images.
Platform settings
Put Instagram settings inside the matching target in platforms:
{
platform: 'instagram',
accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e',
settings: {
postType: 'reel',
shareToFeed: true,
firstComment: 'More details in the first comment.',
},
}| Setting | Type | Notes |
|---|---|---|
postType | "feed" | "story" | "reel" | "carousel" | Defaults to feed. feed and story take one media item, reel takes one video, and carousel takes 2–10 items (images and videos may be mixed). |
collaborators | string[] | Invites up to three usernames as collaborators. Feed, reel, and carousel posts. |
userTags | { username, x, y }[] | Feed photos only — not supported on carousels. x and y are relative coordinates from 0 to 1. |
firstComment | string | Posts a first comment after publishing. Maximum 2,200 characters. |
shareToFeed | boolean | Reels only. Defaults to true. |
PostZen analytics is live for Instagram Business and Creator accounts, including post metrics, follower counts, and best-time-to-post suggestions. Story insights expire 24 hours after posting, so stories are not included.
What you can't do
- Tag users in a carousel. User tags are feed-only: sending
userTagswithpostType: "carousel"returns a validation error, and reel and story posts ignore them. - Connect a personal Instagram account. Instagram Login requires a Business or Creator account.
- Edit a published post through PostZen.
- Add music, stickers, location tags, or product tags.
- Go live.
- Read or manage DMs and comments. PostZen does not yet support inboxes. The
firstCommentsetting publishes a first comment without adding comment management. - Manage ads or receive engagement webhooks through PostZen.
Common errors
| Error | Meaning | Fix |
|---|---|---|
Choose Reel for video | A feed post contains a video. | Set settings.postType to reel and attach exactly one video. |
Carousel posts require 2–10 media items. | A carousel target has fewer than 2 or more than 10 items in mediaItems. | Attach between 2 and 10 images or videos. |
| Multiple media on a feed post | A feed target attaches more than one media item. | Use postType: "carousel" for multiple items, or attach a single image for a feed post. |
| Media required | The request has no mediaItems. | Attach the media allowed by the selected post type. |
| Caption over 2,200 characters | content exceeds Instagram's caption limit. | Shorten the caption to 2,200 characters or fewer. |
| Image and video mixed | A feed, reel, or story post combines image and video media. | Send a single media type for these post types, or use postType: "carousel" to mix images and videos. |
402 while connecting | The profile has reached the free-tier allowance of two connected accounts without a payment method. | Add a payment method before connecting another account. |
| OAuth state expired | More than 10 minutes passed after PostZen created the connect URL. | Request a fresh connect URL and restart the OAuth flow. |