Engineering

Instagram, TikTok and YouTube Shorts: the posting-endpoint reference

Six publishing surfaces, four questions each: which endpoint, which scopes, which media limits, which rate limits. None of them is a single POST, and all three platforms gate public publishing behind a review you cannot code around.

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

POST /{ig-user-id}/media then /media_publish

instagram_business_content_publish

JPEG, 8 MB, max width 1440 px

100 posts / 24 h per account

Instagram carousel

same two calls, media_type=CAROUSEL

instagram_business_content_publish

2–10 children

counts as one post

Instagram Reels

same two calls, media_type=REELS

instagram_business_content_publish

MOV/MP4, 3 s – 15 min, 300 MB

shares the same 100 / 24 h

Instagram Stories

same two calls, media_type=STORIES

instagram_business_content_publish

one item; JPEG 8 MB, or video 3–60 s

shares the same 100 / 24 h

TikTok upload to inbox

POST /v2/post/publish/inbox/video/init/

video.upload

MP4/MOV/WEBM, 4 GB, ≤ 10 min

6 init requests / min per user

TikTok Direct Post

POST /v2/post/publish/video/init/

video.publish

same, duration per creator

6 / min, plus the creator cap

YouTube Shorts

POST /upload/youtube/v3/videos

youtube.upload

video/*, 256 GB; vertical, ≤ 3 min

100 uploads / day per project

Facebook Reels

POST /{page-id}/video_reels, three phases

pages_manage_posts

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.

bash
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_basic and instagram_business_content_publish.
  • Instagram API with Facebook Login — instagram_basic, instagram_content_publish and pages_read_engagement, plus ads_management and ads_read if 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:

bash
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

/v2/post/publish/inbox/video/init/

/v2/post/publish/video/init/

Scope

video.upload

video.publish

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

privacy_level

not used

required, and must come from the creator's own options

Audit required

no

yes, for anything but SELF_ONLY

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

bash
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 at upload_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 carries Content-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.

bash
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

Instagram

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

SELF_ONLY viewership, and 5 distinct creators per 24 h

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:

bash
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.isShort is a real switch rather than detection. Outstand does not inspect duration or aspect ratio to guess; set it to true and #shorts is appended to the description for you.
  • tiktok.postMode defaults to MEDIA_UPLOAD, which is the inbox draft, not a publish. DIRECT_POST publishes to the profile and draws on the creator cap — and an unaudited client cannot Direct Post publicly at all, which is why the example sends SELF_ONLY.
  • processMedia: true inspects 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.