Every one of these looks like it should be one POST with a file and a caption. None of them is.
Instagram makes you create a container, check whether it finished processing, then publish it by id. TikTok makes you ask about the creator before you are allowed to post on their behalf, and only then hands you an upload URL with its own chunking rules. YouTube opens a resumable session and takes the bytes in a second request. Three platforms, three shapes — and three separate review gates standing between a working integration and a post the public can actually see.
So this is a reference page, not an essay. One section per publishing surface, each answering the same four questions: which endpoint, which scopes, which media constraints, which rate limits. Every figure below is from the platform's own documentation, linked where it is used.
Every posting surface on one table
Surface | Endpoint | Scope | Media ceiling | Publish limit |
|---|---|---|---|---|
Instagram feed photo |
|
| JPEG, 8 MB, max width 1440 px | 100 posts / 24 h per account |
Instagram carousel | same two calls, |
| 2–10 children | counts as one post |
Instagram Reels | same two calls, |
| MOV/MP4, 3 s – 15 min, 300 MB | shares the same 100 / 24 h |
Instagram Stories | same two calls, |
| one item; JPEG 8 MB, or video 3–60 s | shares the same 100 / 24 h |
TikTok upload to inbox |
|
| MP4/MOV/WEBM, 4 GB, ≤ 10 min | 6 init requests / min per user |
TikTok Direct Post |
|
| same, duration per creator | 6 / min, plus the creator cap |
YouTube Shorts |
|
|
| 100 uploads / day per project |
Facebook Reels |
|
| MP4, 3–90 s, 9:16 | per-Page, not published as a number |
Reels, Stories and Facebook Reels already have a page of their own: Instagram Reels API and Stories API has the container polling loop, the five status_code values, the three-phase Facebook upload and the failure modes for all three. The three sections below are the surfaces that page does not cover.
Instagram feed posts: two calls and a wait
There is no single Instagram posting API endpoint that takes a photo and a caption. Meta's Content Publishing API is a container model: you create a media container, then you publish it by id.
1# 1. Create the container
2curl -X POST "https://graph.instagram.com/v24.0/<IG_ID>/media" \
3 -d "image_url=https://cdn.example.com/photo.jpg" \
4 -d "caption=Three things we shipped this week" \
5 -d "access_token=<TOKEN>"
6# => {"id":"17998123456789012"}
7
8# 2. Publish it
9curl -X POST "https://graph.instagram.com/v24.0/<IG_ID>/media_publish" \
10 -d "creation_id=17998123456789012" \
11 -d "access_token=<TOKEN>"
12# => {"id":"17912345678901234"}A photo container is normally ready the moment it is created, so those two calls back to back will work. A video container is not — Instagram transcodes it first, and publishing early fails. That polling loop is on the Reels and Stories page.
Scopes
Which permissions you ask for depends on which login you built against:
- Instagram API with Instagram Login —
instagram_business_basicandinstagram_business_content_publish. - Instagram API with Facebook Login —
instagram_basic,instagram_content_publishandpages_read_engagement, plusads_managementandads_readif your user holds their Page role through Business Manager.
What Instagram accepts on a feed photo
Requirement | Specification |
|---|---|
Format | JPEG only |
Maximum file size | 8 MB |
Width | 320 px minimum, 1440 px maximum |
Aspect ratio | Between 4:5 and 1.91:1 |
Colour space | sRGB; anything else is converted |
"JPEG is the only image format supported," per Meta's IG User media reference — PNG and HEIC are rejected, not converted. Captions run to 2,200 characters with at most 30 hashtags and 20 @ mentions.
Carousels are containers of containers
A carousel is between 2 and 10 items. Each child is its own container created with is_carousel_item=true, and the parent collects their ids:
1# 1. One container per child
2curl -X POST "https://graph.instagram.com/v24.0/<IG_ID>/media" \
3 -d "image_url=https://cdn.example.com/1.jpg" \
4 -d "is_carousel_item=true" \
5 -d "access_token=<TOKEN>"
6# => {"id":"17998000000000001"} (repeat for each item)
7
8# 2. One parent container holding the children
9curl -X POST "https://graph.instagram.com/v24.0/<IG_ID>/media" \
10 -d "media_type=CAROUSEL" \
11 -d "children=17998000000000001,17998000000000002" \
12 -d "caption=Three things we shipped this week" \
13 -d "access_token=<TOKEN>"
14# => {"id":"17998123456789099"}
15
16# 3. Publish the parent
17curl -X POST "https://graph.instagram.com/v24.0/<IG_ID>/media_publish" \
18 -d "creation_id=17998123456789099" \
19 -d "access_token=<TOKEN>"Every child is cropped to the first image's aspect ratio, defaulting to 1:1. Decide the shape of item one deliberately, because it decides the shape of the other nine.
The rate limit
One hundred API-published posts per Instagram account per rolling 24 hours, enforced on media_publish. A carousel counts as one. Reels and Stories draw on the same hundred. You do not have to track it yourself — GET /{ig-user-id}/content_publishing_limit returns your current usage.
If you are reading published media rather than writing it, the shape is different and getting past posts from the Instagram API covers it.
TikTok upload: ask about the creator first
TikTok's upload API is four steps, and the first one is not optional. Which endpoint you hit — and which scope you need — is decided by where you want the video to land.
Upload to inbox | Direct Post | |
|---|---|---|
Endpoint |
|
|
Scope |
|
|
Where it lands | the creator's TikTok inbox, as a draft | the creator's profile |
Who writes the caption | the creator, in the app | your request |
| not used | required, and must come from the creator's own options |
Audit required | no | yes, for anything but |
Step 1 — query creator info
POST /v2/post/publish/creator_info/query/ returns creator_username, creator_nickname, creator_avatar_url, privacy_level_options, comment_disabled, duet_disabled, stitch_disabled and max_video_post_duration_sec. Limit: 20 requests per minute per user access token.
Two of those fields are the reason this step exists. privacy_level_options is the only legal source for the privacy_level you send on a Direct Post — a creator with a private account will not offer PUBLIC_TO_EVERYONE, and sending a value they did not offer is an error. And max_video_post_duration_sec is per creator. There is no constant to hardcode.
Step 2 — initialise the upload
1curl -X POST "https://open.tiktokapis.com/v2/post/publish/video/init/" \
2 -H "Authorization: Bearer <USER_ACCESS_TOKEN>" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "post_info": {
6 "title": "Three things we shipped this week",
7 "privacy_level": "SELF_ONLY",
8 "disable_comment": false,
9 "video_cover_timestamp_ms": 1000
10 },
11 "source_info": {
12 "source": "FILE_UPLOAD",
13 "video_size": 52428800,
14 "chunk_size": 10485760,
15 "total_chunk_count": 5
16 }
17 }'You get back a publish_id and an upload_url, and the upload URL is good for one hour.
Step 3 — transfer the bytes
Two transfer modes, and the constraints are real:
FILE_UPLOAD— PUT the bytes atupload_url. Each chunk must be at least 5 MB and no greater than 64 MB, except the final chunk, which may run up to 128 MB to carry the trailing bytes. Maximum 1,000 chunks, maximum 4 GB. Every chunk carriesContent-Range: bytes {FIRST_BYTE}-{LAST_BYTE}/{TOTAL_BYTE_LENGTH}.PULL_FROM_URL— you hand TikTok a URL and it fetches the file. This one has a prerequisite that bites late: you must verify ownership of the URL prefix or domain in the developer portal first. The URL has to be HTTPS and must not redirect. TikTok's download task times out one hour after it starts.
The longest video the Upload Video endpoint will accept is 10 minutes — but check max_video_post_duration_sec anyway, because the creator's own ceiling is usually lower.
Accepted containers are video/mp4, video/quicktime and video/webm. Photo posts are a different endpoint entirely, /v2/post/publish/content/init/, taking 1 to 35 WebP or JPEG images as public URLs.
Step 4 — poll for status
POST /v2/post/publish/status/fetch/ with the publish_id. Terminal and intermediate values: PROCESSING_UPLOAD, PROCESSING_DOWNLOAD, SEND_TO_USER_INBOX, PUBLISH_COMPLETE, FAILED.
Build this before you build anything else. A 200 on step 2 means TikTok accepted the job, not that a video exists.
The rate limits
Per user access token, on a one-minute sliding window: 6 requests per minute on the post-init endpoints, 30 per minute on status and fetch. Exceeding either returns HTTP 429 with a rate_limit_exceeded error.
The limit that actually shapes your roadmap is not any of those. An unaudited API client can only publish in SELF_ONLY viewership and only for five distinct creators in a rolling 24 hours. Why your first TikTok post is private is the whole audit story — what it checks, and how to sequence the build so passing it is a config change rather than a rewrite.
YouTube Shorts: there is no Shorts endpoint
This is the part that surprises people, so it is worth stating flatly: the YouTube Data API has no Shorts method. There is no shorts.insert, no isShort parameter, and no way to declare one. Shorts go up through videos.insert like every other video.
1curl -X POST \
2 "https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&part=snippet,status" \
3 -H "Authorization: Bearer $GOOGLE_ACCESS_TOKEN" \
4 -H "Content-Type: application/json" \
5 -H "X-Upload-Content-Type: video/mp4" \
6 -H "X-Upload-Content-Length: 18874368" \
7 -D - \
8 -d '{
9 "snippet": {
10 "title": "Three things we shipped this week",
11 "description": "60 seconds on what changed. #shorts",
12 "categoryId": "28"
13 },
14 "status": {
15 "privacyStatus": "public",
16 "selfDeclaredMadeForKids": false
17 }
18 }'
19# Then PUT the bytes at the URL in the Location response header.What makes it a Short is the file, not the request. Google's own help page describes Shorts as "short-form videos that are up to 3 minutes long", uploaded as vertical video, at a maximum resolution of 1080p. None of that is an API parameter, which has a consequence worth planning around: the API will not tell you whether your upload became a Short. You find out from the video.
The #shorts hashtag in the description is a widespread convention rather than a documented switch. Treat it as a hint you give YouTube, not a flag you set.
Scopes and quota
Any one of youtube.upload, youtube, youtube.force-ssl or youtubepartner. Maximum file size is 256 GB; accepted MIME types are video/* and application/octet-stream.
The quota is the thing to design around. A videos.insert call costs 1 unit — but in its own Video Uploads bucket, and a default project is allocated 100 of those calls per day. It is a hard ceiling of 100 uploads, not a slice of the 10,000-unit pool everything else shares. YouTube API pricing and quota has the audit path past it.
And until your Google Cloud project is verified, every upload is forced to private no matter what privacyStatus you sent. One render, every network covers that gate and the resumable session in full.
The three gates, side by side
The plumbing above is a few days of work. The gates are weeks, they are per platform, and none of them is a code problem — which is exactly why they are the thing to start early.
Platform | Gate | What is capped until you pass |
|---|---|---|
Advanced Access, via Business Verification and App Review | Standard Access permissions can only be requested from users who hold a role on your app | |
TikTok | API client audit |
|
YouTube | Google Cloud project verification | every upload forced to private |
Meta is explicit about the first one: "Permissions with Standard Access can only be requested from app users who have a role on the requesting app," while Advanced Access "can be requested from any app user" and requires Business Verification. In practice that means your Instagram integration works perfectly for your own test accounts and publishes for nobody else until you clear review.
The same three posts through one request
If all three surfaces are on your roadmap, the per-platform work above multiplies faster than the value of owning it. That is the case for a social media api that normalises the shapes: media once, then one post.
Media is three requests — POST /v1/media/upload for a presigned URL, a PUT of the bytes, then POST /v1/media/{id}/confirm, which returns the public url you reference in a post. Keep the url, not the id; the Create a post reference is blunt that media is not referenced by id.
Then one call covers all three networks, with a per-network block for the things only that network has:
1curl -X POST "https://api.outstand.so/v1/posts" \
2 -H "Authorization: Bearer $OUTSTAND_API_KEY" \
3 -H "Content-Type: application/json" \
4 -H "Idempotency-Key: b1f6c3f4-8f0e-4a5b-9c2d-7e1a0b3c4d5e" \
5 -d '{
6 "accounts": ["Kx7vQ", "Tm2bN", "Rd8pL"],
7 "containers": [
8 {
9 "content": "Three things we shipped this week",
10 "media": [{ "url": "https://media.outstand.so/.../render.mp4", "filename": "render.mp4" }]
11 }
12 ],
13 "processMedia": true,
14 "youtube": { "isShort": true, "privacyStatus": "public", "categoryId": "28" },
15 "tiktok": { "postMode": "DIRECT_POST", "privacyLevel": "SELF_ONLY" },
16 "instagram": { "reelCoverUrl": "https://media.outstand.so/.../cover.jpg" }
17 }'Three details in there are worth knowing before you copy it:
youtube.isShortis a real switch rather than detection. Outstand does not inspect duration or aspect ratio to guess; set it totrueand#shortsis appended to the description for you.tiktok.postModedefaults toMEDIA_UPLOAD, which is the inbox draft, not a publish.DIRECT_POSTpublishes to the profile and draws on the creator cap — and an unaudited client cannot Direct Post publicly at all, which is why the example sendsSELF_ONLY.processMedia: trueinspects the video and re-encodes it per network before publishing, which is what stops one render from being a legal YouTube upload and an illegal Instagram feed video at the same time.
Every field is in the Instagram, TikTok and YouTube configuration references, and the commercial pages for each are Instagram publishing, TikTok publishing and YouTube publishing. If your client is an LLM rather than your own backend, the same surfaces are exposed as tools over MCP: Instagram, TikTok and YouTube.
One honest limit: Managed Keys cover the Instagram, TikTok and YouTube connections, so you can publish without registering anything. They do not transfer platform standing. TikTok's restrictions attach to the API client, so shared credentials carry the shared restrictions — SELF_ONLY and five creators per 24 hours. If you need public Direct Post at scale, you register your own TikTok app and pass your own audit. Nobody can hand you an exemption.
Knowing it worked
Publishing is asynchronous on all three platforms, so no 2xx anywhere above means a post exists. GET /v1/posts/{id} carries a per-account status of pending, published or failed, with the platform's own error message on a failure. The post.published and post.error webhooks fire within seconds if you would rather not poll — but read their definitions carefully, because post.published fires when at least one account succeeded. Iterate the socialAccounts array rather than branching on the event name.
FAQ
What is the Instagram posting API?
It is the Content Publishing API on the Instagram Graph API: POST /{ig-user-id}/media to create a container and POST /{ig-user-id}/media_publish to publish it by creation_id. One endpoint pair covers photos, carousels, Reels and Stories, switched by media_type.
What is the TikTok upload API endpoint?
Two, depending on where the video should land. /v2/post/publish/inbox/video/init/ with the video.upload scope delivers a draft to the creator's inbox; /v2/post/publish/video/init/ with video.publish is Direct Post, which publishes to the profile. Both are preceded by /v2/post/publish/creator_info/query/.
Is there a YouTube Shorts API?
No. The Data API has no Shorts method and no Shorts parameter — you upload through videos.insert and YouTube classifies the result from the video itself: vertical, up to 3 minutes, up to 1080p.
Do I need app review to publish to Instagram?
To publish for accounts other than ones with a role on your own app, yes. Standard Access only reaches users who hold a role on the requesting app; reaching anyone else means Advanced Access, which requires Business Verification and may require App Review per permission.
Can one request post to Instagram, TikTok and YouTube at once?
Yes — one POST /v1/posts with the account ids and a per-network override block each. Set processMedia: true and the file is re-encoded to each network's requirements first, so you are not maintaining three sets of encoding rules.
Last verified: October 2026, against Meta's Instagram Content Publishing and IG User media references, Meta's Reels Publishing guide and Graph API access levels guide, TikTok's Content Posting API references for Direct Post, Upload Video, Query Creator Info and Get Post Status plus the Media Transfer Guide, and Google's videos.insert reference. All three platforms revise these periodically — check the figures against the date on this post before you plan a launch around them.