Instagram API
Schedule and automate Instagram posts with RelayAPI — feed posts, carousels, stories, reels, collaborators, and user tags.
Quick Reference
| Property | Value |
|---|---|
| Platform key | instagram |
| Auth method | OAuth 2.0 (via Facebook Business) |
| Character limit | 2,200 (caption) |
| Images per post | 1 (feed), 10 (carousel) |
| Videos per post | 1 |
| Image formats | JPEG, PNG |
| Image max size | 8 MiB |
| Video formats | MP4, MOV |
| Video max size | 100 MiB |
| Video max duration | 90 sec (reels), 60 min (feed), 60 sec (story) |
| Post types | Feed, Carousel, Story, Reel |
| Scheduling | Yes |
| Analytics | Yes (impressions, reach, likes, comments, shares, saves, views) |
Client options — TypeScript · Python REST/OpenAPI · Go · Java · REST API
Before You Start
Instagram requires a Business or Creator account — personal accounts cannot post via the API. Media is required for all Instagram posts; there are no text-only posts. Google Drive, Dropbox, and OneDrive URLs do not work as media sources because they return HTML pages, not raw media bytes. Always use direct CDN URLs. Instagram enforces a hard limit of 100 posts per 24-hour rolling window across all content types.
Quick Start
Post a photo to your Instagram feed:
import Relay from '@relayapi/sdk';
const client = new Relay();
const post = await client.posts.create({
content: 'Beautiful sunset today #photography',
targets: ['instagram'],
media: [
{ url: 'https://cdn.example.com/sunset.jpg', type: 'image' },
],
scheduled_at: 'now',
});
console.log(post.id); // post_abc123Content Types
Feed Post (Single Image)
A single image post on the Instagram feed. Only the first 125 characters are visible before the "more" fold.
const post = await client.posts.create({
content: 'Beautiful sunset today #photography',
targets: ['instagram'],
media: [
{ url: 'https://cdn.example.com/sunset.jpg', type: 'image' },
],
scheduled_at: 'now',
});Carousel (Up to 10 Images/Videos)
Mix images and videos in a swipeable carousel. The first item determines the aspect ratio for all items.
const post = await client.posts.create({
content: 'Trip highlights from last weekend',
targets: ['instagram'],
media: [
{ url: 'https://cdn.example.com/photo1.jpg', type: 'image' },
{ url: 'https://cdn.example.com/photo2.jpg', type: 'image' },
{ url: 'https://cdn.example.com/clip.mp4', type: 'video' },
{ url: 'https://cdn.example.com/photo3.jpg', type: 'image' },
],
scheduled_at: 'now',
});Story
Stories are ephemeral (24 hours). Text captions are not displayed on stories. Link stickers are not available via the API.
const post = await client.posts.create({
targets: ['instagram'],
media: [
{ url: 'https://cdn.example.com/story.jpg', type: 'image' },
],
scheduled_at: 'now',
target_options: {
instagram: {
content_type: 'story',
},
},
});Reel
Vertical video (9:16), max 90 seconds. Reels appear on the Reels tab and optionally on your profile feed.
const post = await client.posts.create({
content: 'New tutorial!',
targets: ['instagram'],
media: [
{ url: 'https://cdn.example.com/reel.mp4', type: 'video' },
],
scheduled_at: 'now',
target_options: {
instagram: {
content_type: 'reels',
share_to_feed: true,
first_comment: 'Link in bio!',
},
},
});Trial Reel with a custom cover
Trial Reels and cover selectors apply only when content_type is reels.
trial_params.graduation_strategy is either MANUAL or SS_PERFORMANCE.
Choose exactly one cover source: a public cover_url, a ready Relay
cover_media_id, a ready generated cover_variant_id, or thumb_offset.
const post = await client.posts.create({
content: 'Testing this Reel with non-followers first',
targets: ['instagram'],
scheduled_at: 'now',
media: [{ url: reelURL, type: 'video' }],
target_options: {
instagram: {
content_type: 'reels',
share_to_feed: false,
cover_variant_id: 'mder_ready_cover',
trial_params: { graduation_strategy: 'SS_PERFORMANCE' },
},
},
});Relay IDs remain stable in a scheduled post. At publish time, RelayAPI verifies that the media/variant belongs to the same organization and authorized workspace, is ready and unexpired, and then generates the short-lived provider URL in memory. See Media Uploads for creating a cover variant from a video.
Post with Collaborators and User Tags
Tag collaborators (up to 3) and users in images. Collaborators receive an invite to co-author the post.
const post = await client.posts.create({
content: 'Collab post with our partners!',
targets: ['instagram'],
media: [
{ url: 'https://cdn.example.com/photo.jpg', type: 'image' },
],
scheduled_at: 'now',
target_options: {
instagram: {
collaborators: ['brandpartner', 'creator123'],
user_tags: [
{ username: 'friend', x: 0.5, y: 0.5 },
],
},
},
});Post with First Comment
Automatically post a first comment after publishing. Useful for hashtag blocks or calls to action.
const post = await client.posts.create({
content: 'New product launch!',
targets: ['instagram'],
media: [
{ url: 'https://cdn.example.com/product.jpg', type: 'image' },
],
scheduled_at: 'now',
target_options: {
instagram: {
first_comment: '#newproduct #launch #startup #tech #innovation',
},
},
});First comments work on feed posts and carousels only. They are not supported on stories or reels.
Media Requirements
Images
| Property | Feed | Story | Carousel |
|---|---|---|---|
| Max images | 1 | 1 | 10 |
| Formats | JPEG, PNG | JPEG, PNG | JPEG, PNG |
| Max file size | 8 MiB | 8 MiB | 8 MiB each |
| Recommended | 1080 x 1350 px | 1080 x 1920 px | 1080 x 1080 px |
Aspect Ratios
| Orientation | Ratio | Dimensions | Notes |
|---|---|---|---|
| Portrait | 4:5 | 1080 x 1350 px | Best engagement for feed posts |
| Square | 1:1 | 1080 x 1080 px | Standard feed and carousel |
| Landscape | 1.91:1 | 1080 x 566 px | Widest allowed for feed |
| Vertical | 9:16 | 1080 x 1920 px | Stories and Reels only |
Feed posts accept aspect ratios between 4:5 (0.8) and 1.91:1. Outside that range, the content must be a Story or Reel.
Videos
| Property | Feed | Reel | Story |
|---|---|---|---|
| Formats | MP4, MOV | MP4, MOV | MP4, MOV |
| Max size | 100 MiB | 100 MiB | 100 MiB |
| Max duration | 60 min | 90 sec | 60 sec |
| Min duration | 3 sec | 3 sec | 3 sec |
| Aspect ratio | 4:5 to 1.91:1 | 9:16 | 9:16 |
| Recommended | 1080px wide | 1080 x 1920 px | 1080 x 1920 px |
| Codec | H.264 | H.264 | H.264 |
target_options Fields
All fields go inside target_options.instagram on your post request.
| Field | Type | Description |
|---|---|---|
content | string | Override caption for Instagram specifically |
media | object[] | Override media for Instagram specifically |
content_type | string | "story" or "reels" (default: feed post) |
share_to_feed | boolean | Reels: also show on profile feed grid (default: true) |
collaborators | string[] | Up to 3 usernames to invite as collaborators (feed and reels only) |
user_tags | object[] | {username, x, y, media_index?} — tag users in images. Coordinates are 0-1 range. |
first_comment | string | Auto-posted first comment (feed and carousels only) |
thumb_offset | number | Millisecond offset for a Reel cover; mutually exclusive with the three other cover selectors |
cover_url | URL | Public image URL used as the Reel cover |
cover_media_id | med_... | Ready Relay media resolved to a fresh cover URL at publish time |
cover_variant_id | mder_... | Ready Relay-generated cover variant resolved at publish time |
trial_params | object | Trial Reel graduation strategy: MANUAL or SS_PERFORMANCE |
Common Errors
| Error | Cause | Fix |
|---|---|---|
| Cannot process video from URL | Cloud storage sharing link used (Drive, Dropbox) | Use a direct CDN URL that returns raw media bytes, not an HTML page |
| 100 posts per day limit | Instagram hard 24-hour rolling limit reached | Reduce posting volume. This limit includes all content types. |
| Instagram blocked request | Automation detection triggered | Reduce frequency and vary content between posts |
| Duplicate content | Identical content posted recently | Modify caption or swap media files |
| Media fetch failed | Media URL is inaccessible or returns HTML | Verify URL returns actual media bytes with correct Content-Type header |
| Token expired | OAuth token expired or revoked | Reconnect the account via the dashboard or Connect API |
Known Quirks
- Media is required for all posts — Instagram does not support text-only posts.
- Business or Creator account required — personal accounts cannot post via the API.
- First 125 characters visible before the "more" fold. Front-load your most important message.
- Google Drive, Dropbox, OneDrive URLs do not work — they return HTML download pages, not media bytes. Always use direct CDN URLs.
- Stories do not display text captions — text is ignored. Link stickers are not available via the API.
- Carousel first item determines aspect ratio for all subsequent items in the carousel.
- Images over 8 MiB are rejected by RelayAPI preflight — resize them before publishing.
- 100 posts per 24-hour rolling limit includes feed posts, carousels, stories, and reels combined.
- User tags only work on images — not videos or stories. For carousels, use
media_indexto target specific slides.
Social actions
RelayAPI can hide/unhide Instagram comments, send a provider read receipt for
a persisted inbound Instagram Messaging message, and list normalized story/post
mentions already received through signed webhooks. It does not edit a published
Instagram media object or comment. Reauthorize an older connection if it lacks
instagram_manage_comments or instagram_manage_messages. See Published
Edits and Social Actions.
Automations
The automations engine supports Instagram as a Tier 1 channel for inbound conversational events, message flows, live relationship checks, and Meta profile bindings.
Entrypoints
| Kind | Fires on | Notes |
|---|---|---|
dm_received | Ordinary inbound DM | Optional keywords, match mode, case sensitivity, and first-message-only filter |
comment_created | New post or reel comment | Optional post IDs, keywords, and reply inclusion |
story_reply | Reply to a story | Optional story IDs and keywords |
story_mention | Account mentioned in a story | Can also resume a wait_event node |
live_comment | Comment during a Live | Time-sensitive |
ad_click | Click-to-message ad event | Optional ad IDs |
ref_link_click | Instagram referral link | Match selected referral IDs |
share_to_dm | Content shared into DM | Instagram-only |
Instagram automations also support channel-independent schedule, field_changed, tag_applied, tag_removed, conversion_event, and webhook_inbound entrypoints. Button, quick-reply, ice-breaker, and menu postbacks resume the exact waiting run or route through the configured binding; they are not separate generic trigger kinds.
Instagram does not expose a public follower webhook. The follow_to_dm preset therefore starts on a contact's first inbound DM and performs a live is_user_follow_business check before sending the welcome message. It never invents a follow event or sends an unsolicited follower DM.
Messages and actions
Outbound content uses the channel-neutral message node. Its ordered blocks can contain text, images, videos, cards, galleries, and short in-message delays. Instagram supports branch buttons and up to 13 quick replies; audio and file blocks are rejected by validation or skipped with an explicit delivery result. See the Instagram Messaging API.
| Surface | Instagram behavior |
|---|---|
message node | Sends rendered blocks through the exact triggering account |
| Comment private reply | Used by comment_to_dm and follower_growth; provider shape is one button-free text block |
reply_to_comment action | Posts a public reply to the triggering comment |
social_profile_check node | Live Instagram-only follow relationship branch |
| Merge tags | contact.name/email/phone, context.* (or state.*), and bare contact-field shorthand |
Profile bindings
Instagram exposes two provider-synchronized automation bindings:
main_menu— one or more postback or URL items.ice_breaker— up to four starter questions with postback payloads.
Creating or updating either binding records desired state and queues provider synchronization. The dashboard reports pending, synced, or failed; deleting waits for Meta's delete acknowledgement before removing the local row. Runtime-only default_reply and welcome_message bindings are also available and do not modify the Instagram profile.
Quick example: comment → DM
await client.automations.create({
name: 'Spring launch comment → DM',
channel: 'instagram',
template: {
kind: 'comment_to_dm',
config: {
social_account_id: 'acc_instagram_xyz',
post_ids: ['17895...'],
keyword_filter: ['LINK', 'INFO'],
dm_message: {
blocks: [{ id: 'reply', type: 'text', text: 'Hey {{contact.name}}, here is the link you asked for.' }],
},
public_reply: 'Check your DMs! 📨',
once_per_user: true,
daily_cap: 500,
},
},
});Rate limits + constraints
- Messaging admission: reactive sends require explicit automation consent or a bounded inbound customer-service conversation. A suppression or opt-out veto always wins.
- Daily caps: quick-start presets accept
daily_cap; admission is enforced atomically per entrypoint and UTC day. - Account scope: a preset's
social_account_idmust be active and match the automation's organization, workspace, and Instagram channel. - Comment replies: Meta controls private/public reply eligibility and volume. Wire the message node's
errorport when you need a fallback. - Provider sync: menus and ice breakers are asynchronous. Treat local
pendingstate as not yet live on Instagram.
Found something wrong? Help us improve this page.