What the Threads API can and cannot do
Meta opened the Threads API to all developers in June 2024 (launch post) and has added features since. As of September 2026 the official documentation covers:
- Publishing: text, image, video and carousel posts, with link attachments, topic tags, polls, quote posts, GIFs from GIPHY, alt text, location tags and spoilers.
- Replies: posting replies (which is how you build a multi-post thread), reading replies, hiding them, controlling who can reply and approving replies before they show.
- Insights: views, likes, replies, reposts, quotes and shares per post, plus account-level metrics such as followers and demographics.
- Keyword search: public posts by keyword, once the permission is approved.
What it does not do: schedule posts for later, accept file uploads (media must be at a public URL Meta can fetch), or give general read access to anyone’s timeline.
Official Threads API vs unofficial libraries
Search GitHub for “threads api” and the top result is still Danie1/threads-api, which describes itself as an “Unofficial, Reverse-Engineered Python client” and has been archived since 1 October 2023. Libraries like it were written before Meta had a public API. They log in as the user and call the private endpoints the Threads app uses.
That approach has three problems. You hand over the account password instead of an OAuth token the user can revoke. The private endpoints change without notice, so the code breaks when the app updates, which is why most of these projects stopped. And automated logins from a server look exactly like the activity Meta’s integrity systems are built to stop, so the account is the one at risk, not the library author. With the official API, the user grants specific permissions, Meta tells you the limits, and a revoked token is the worst case. Use it.
How to set up the Threads API
The steps from Meta’s get started guide:
- Create a Meta app with the Threads use case (“Access the Threads API”) in the App Dashboard. The app gets two sets of credentials; for Threads, use the Threads app ID and its app secret.
- Add your redirect URI to the valid OAuth redirect URIs in the Threads settings of the use case.
- Pick permissions.
threads_basicis required for every endpoint. Addthreads_content_publishto post,threads_read_repliesandthreads_manage_repliesfor replies,threads_manage_insightsfor metrics. Deleting needsthreads_deleteand keyword search needsthreads_keyword_search. - Add testers. Under App roles, invite a Threads profile as a Threads tester and accept the invitation from that account. Testers can use the app before review.
- Go through App Review. For users without a role on your app to grant these permissions, each permission must be approved through App Review and the app must be published.
Access tokens: 1 hour, then 60 days
Send the user to the authorization window:
https://threads.com/oauth/authorize
?client_id=THREADS_APP_ID
&redirect_uri=https://yourapp.com/callback
&scope=threads_basic,threads_content_publish
&response_type=code
&state=RANDOM_STRING
Meta’s current docs use threads.com for this window; many older tutorials still show threads.net. The code that comes back is valid for one hour and can be used once, and the redirect appends #_ to it, which you strip before exchanging it (source). Then turn it into tokens:
# 1. Code -> short-lived token (valid 1 hour)
curl -X POST https://graph.threads.com/oauth/access_token \
-F client_id=THREADS_APP_ID \
-F client_secret=THREADS_APP_SECRET \
-F grant_type=authorization_code \
-F redirect_uri=https://yourapp.com/callback \
-F code=AUTH_CODE
# 2. Short-lived -> long-lived token (valid 60 days)
curl -G https://graph.threads.net/access_token \
-d grant_type=th_exchange_token \
-d client_secret=THREADS_APP_SECRET \
-d access_token=SHORT_LIVED_TOKEN
# 3. Refresh before day 60 (token must be at least 24 hours old)
curl -G https://graph.threads.net/refresh_access_token \
-d grant_type=th_refresh_token \
-d access_token=LONG_LIVED_TOKENAccording to the token docs, a short-lived token lasts one hour and cannot be exchanged once it expires. A long-lived token lasts 60 days and can be refreshed once it is at least 24 hours old and still valid; each refresh gives another 60 days. A token not refreshed within 60 days expires for good and the user has to log in again. Put the refresh in a daily job; it is the most common reason Threads integrations stop posting.
How to post to Threads via the API
Every post is a media container created with POST /{threads-user-id}/threads and then published with POST /{threads-user-id}/threads_publish. Meta recommends waiting about 30 seconds between the two so the server can process the media (source).
Text post (with a link)
# Step 1: create a TEXT container
curl -X POST "https://graph.threads.net/v1.0/THREADS_USER_ID/threads" \
-d media_type=TEXT \
-d "text=Shipping notes for this week are up." \
-d "link_attachment=https://example.com/changelog" \
-d access_token=LONG_LIVED_TOKEN
# -> {"id": "CONTAINER_ID"}
# Step 2: publish it
curl -X POST "https://graph.threads.net/v1.0/THREADS_USER_ID/threads_publish" \
-d creation_id=CONTAINER_ID \
-d access_token=LONG_LIVED_TOKEN
# -> {"id": "THREADS_MEDIA_ID"} link_attachment works on text posts and adds a link preview. Since 22 December 2025, posts with more than five links fail with THREADS_API__LINK_LIMIT_EXCEEDED. For text-only posts you can also send auto_publish_text=true and skip the second call.
Image post
curl -X POST "https://graph.threads.net/v1.0/THREADS_USER_ID/threads" \
-d media_type=IMAGE \
-d "image_url=https://cdn.example.com/launch.jpg" \
-d "text=The new dashboard, in one picture." \
-d "alt_text=Screenshot of the analytics dashboard" \
-d access_token=LONG_LIVED_TOKEN
# then threads_publish with creation_id, as aboveVideo post and the status check
Video needs processing time, so check the container before you publish:
# Create the VIDEO container
curl -X POST "https://graph.threads.net/v1.0/THREADS_USER_ID/threads" \
-d media_type=VIDEO \
-d "video_url=https://cdn.example.com/demo.mp4" \
-d "text=60-second product demo" \
-d access_token=LONG_LIVED_TOKEN
# Check processing before publishing
curl -G "https://graph.threads.net/v1.0/CONTAINER_ID" \
-d fields=status,error_message \
-d access_token=LONG_LIVED_TOKEN
# -> {"status": "FINISHED", "id": "CONTAINER_ID"}
The status field is one of IN_PROGRESS, FINISHED, PUBLISHED, ERROR or EXPIRED. Meta’s troubleshooting page suggests checking once per minute for no more than five minutes. Publish only when it says FINISHED.
Carousel
A carousel takes three steps: one container per item, a carousel container that lists them, then publish. It needs 2 to 20 items, can mix images and videos, and counts as a single post against your limit.
# 1. One container per item, flagged as carousel items
curl -X POST "https://graph.threads.net/v1.0/THREADS_USER_ID/threads" \
-d media_type=IMAGE -d is_carousel_item=true \
-d "image_url=https://cdn.example.com/1.jpg" \
-d access_token=LONG_LIVED_TOKEN # -> ITEM_1
curl -X POST "https://graph.threads.net/v1.0/THREADS_USER_ID/threads" \
-d media_type=VIDEO -d is_carousel_item=true \
-d "video_url=https://cdn.example.com/2.mp4" \
-d access_token=LONG_LIVED_TOKEN # -> ITEM_2
# 2. The carousel container
curl -X POST "https://graph.threads.net/v1.0/THREADS_USER_ID/threads" \
-d media_type=CAROUSEL \
-d children=ITEM_1,ITEM_2 \
-d "text=Before and after" \
-d access_token=LONG_LIVED_TOKEN # -> CAROUSEL_ID
# 3. Publish CAROUSEL_ID with threads_publishA multi-post thread (reply chain)
There is no “thread” object. A chain is a post plus replies to it, each created with reply_to_id set to the post above it and published separately:
# Post 2 of a chain: reply to the post you just published
curl -X POST "https://graph.threads.net/v1.0/THREADS_USER_ID/threads" \
-d media_type=TEXT \
-d "text=2/ Here is what changed under the hood..." \
-d reply_to_id=THREADS_MEDIA_ID_OF_POST_1 \
-d access_token=LONG_LIVED_TOKEN
# then threads_publish; repeat with the new ID for post 3
On any post you can also set reply_control to everyone, accounts_you_follow, mentioned_only, parent_post_author_only or followers_only, add one topic_tag (1 to 50 characters, no periods or ampersands), or quote another post with quote_post_id.
Threads API limits
| Limit | Value |
|---|---|
| Published posts | 250 per profile per 24 hours (moving window) |
| Replies | 1,000 per profile per 24 hours |
| Deletions | 100 per profile per 24 hours |
| API calls | 4,800 × the profile’s impressions per 24 hours (impressions counted as at least 10) |
| Text | 500 characters per post |
| Links | 5 per post |
| Carousel | 2 to 20 images or videos, counts as one post |
| Images | JPEG or PNG, up to 8 MB, 320 to 1440 px wide, sRGB, aspect ratio up to 10:1 |
| Video | MOV or MP4 (H264 or HEVC), up to 5 minutes and 1 GB, 23 to 60 fps, up to 100 Mbps; 9:16 recommended |
| Alt text | 1,000 characters |
| Unpublished container | Expires after 24 hours |
| Keyword search | 2,200 queries per user per 24 hours |
Sources: overview, posts, publishing reference, troubleshooting, keyword search. Read 28 September 2026.
The character count is in UTF-8, so emoji use up the 500 faster than letters. To see how much of the daily quota is left before a batch:
curl -G "https://graph.threads.net/v1.0/THREADS_USER_ID/threads_publishing_limit" \
-d access_token=LONG_LIVED_TOKENThreads API pricing
Meta lists no fee and no paid tier for the Threads API. That is different from the X API, which charges per post. The real costs are the ones above: App Review, the 60-day token refresh, the 250-post cap, and hosting your media somewhere public for Meta to fetch.
Replies and insights
To moderate, POST /{reply-id}/manage_reply with hide=true hides a reply and the replies nested under it. If you create a post with enable_reply_approvals=true, new replies wait in /pending_replies until you approve or ignore them (reply management).
Insights need threads_manage_insights. GET /{media-id}/insights returns views, likes, replies, reposts, quotes and shares for a post; nested replies are not included. GET /{user-id}/threads_insights adds account metrics such as followers_count, link clicks and follower_demographics, which needs at least 100 followers. The since and until parameters do not work before 13 April 2024 (insights).
Threads API in Python
Meta does not ship a Threads SDK, and you do not need one: the API is plain HTTPS. This script publishes a video, waits for it to process, then adds a reply to make a two-post thread. It only needs requests.
import time
import requests
BASE = "https://graph.threads.net/v1.0"
TOKEN = "LONG_LIVED_TOKEN"
USER_ID = "THREADS_USER_ID"
def create_container(**params):
r = requests.post(f"{BASE}/{USER_ID}/threads",
data={**params, "access_token": TOKEN})
r.raise_for_status()
return r.json()["id"]
def wait_until_ready(container_id, tries=5):
# Meta suggests checking once per minute, for no more than 5 minutes
for _ in range(tries):
r = requests.get(f"{BASE}/{container_id}",
params={"fields": "status,error_message",
"access_token": TOKEN})
data = r.json()
if data["status"] == "FINISHED":
return
if data["status"] in ("ERROR", "EXPIRED"):
raise RuntimeError(data.get("error_message", data["status"]))
time.sleep(60)
raise TimeoutError("container not ready")
def publish(container_id):
r = requests.post(f"{BASE}/{USER_ID}/threads_publish",
data={"creation_id": container_id,
"access_token": TOKEN})
r.raise_for_status()
return r.json()["id"]
# A two-post chain: a video, then a text reply
first = create_container(media_type="VIDEO",
video_url="https://cdn.example.com/demo.mp4",
text="New feature, 45 seconds")
time.sleep(30)
wait_until_ready(first)
post_id = publish(first)
reply = create_container(media_type="TEXT",
text="Docs and changelog are linked in our bio.",
reply_to_id=post_id)
time.sleep(30)
publish(reply)For a real integration, add the daily token refresh and store the container ID with each job so a crash between the two calls does not create a duplicate.
How to schedule Threads posts with the API
The publish endpoint has no publish-time parameter, and a container expires if it is not published within 24 hours. So “Threads API automatic post” means building a scheduler yourself: store the post and its time in a database, and have a worker that, at the right moment, refreshes the token if needed, creates the container, waits for FINISHED and publishes. Create the container at publish time, not in advance. You also need retries for failures and a check against the daily quota.
That is a small project for one account and a real one for many. The alternative is to let a service run it.
The shortcut: Threads, Instagram and X in one scheduled call
Upload-Post’s Threads API publishes through Meta’s official Threads API and handles the parts above: OAuth and token refresh, containers, status checks, retries and scheduling. It sends the same post to 20+ other platforms in the same request. You upload the file, so there is no need to host media at a public URL.
curl -X POST https://api.upload-post.com/api/upload \
-H "Authorization: Apikey YOUR_KEY" \
-F "[email protected]" \
-F "user=my-brand" \
-F "platform[]=threads" \
-F "platform[]=instagram" \
-F "platform[]=x" \
-F "title=New feature, 45 seconds" \
-F "threads_topic_tag=Product Design" \
-F "first_comment=Docs: https://example.com/docs" \
-F "scheduled_date=2026-10-02T15:00:00" \
-F "timezone=Europe/London"
That schedules the video to Threads, Instagram and X for the same moment, with a topic tag on Threads and a first comment (on Threads and X it is posted as a reply). Other Threads parameters in the docs include threads_title, threads_reply_control, threads_reply_to_id, threads_quote_post_id, threads_link_attachment and threads_alt_text. Text longer than 500 characters is split into a reply chain for you:
curl -X POST https://api.upload-post.com/api/upload_text \
-H "Authorization: Apikey YOUR_KEY" \
-F "user=my-brand" \
-F "platform[]=threads" \
-F "title=A 1,200-character post goes here..." \
-F "threads_poll_options[]=Ship it" \
-F "threads_poll_options[]=Wait a week"
# Over 500 characters: split into a reply chain automatically.
# Add threads_long_text_as_post=true for one post (up to 10,000 characters).Scope, honestly:
- Scheduling:
scheduled_datewith atimezone, up to 365 days ahead, oradd_to_queue=trueto take the next free queue slot. - Carousels: up to 20 images and videos per post; send more and they are split across several posts.
- Quota: a publishing limit endpoint shows Meta’s remaining quota for the account.
- Comments: list, reply, hide, unhide, approve and ignore Threads replies.
- Analytics: Threads views are included in the analytics endpoints alongside the other platforms.
The Free plan is free forever with no card: 10 uploads a month and 2 profiles, with Threads, scheduling and API access included. Paid plans with unlimited uploads start at $24 a month ($16 billed annually). If you also post to Meta’s other apps, see the Instagram API guide and the Facebook Graph API guide, or compare options in the social media API overview.
Common Threads API errors
- Container in
ERROR: readerror_message. Video values includeFAILED_DOWNLOADING_VIDEO(Meta could not fetch your URL),INVALID_DURATION,INVALID_FRAME_RATE,INVALID_BIT_RATEandINVALID_ASPEC_RATIO(spelled that way by Meta). Check the file against the specs in the table. - Container
EXPIRED: it was not published within 24 hours. Create a new one. - Publishing too early: calling
threads_publishbefore the media has finished processing is what Meta’s 30-second wait exists to prevent. Poll the status and publish onFINISHED. THREADS_API__LINK_LIMIT_EXCEEDED: the post has more than five links.- Token exchange fails: the code was used twice, is over an hour old, or still has the
#_suffix. For refresh failures, the long-lived token is under 24 hours old or already expired. - A user cannot grant permissions: they have no role on the app and the permission has not passed App Review.
Frequently asked questions
Is the Threads API free?
Yes. Meta does not list any fee or paid tier for the Threads API. What it costs you is time and limits: a Meta app, App Review before the public can connect, token refreshes every 60 days, and a cap of 250 published posts and 1,000 replies per profile in any 24 hours.
Is there an official Threads API?
Yes. Meta launched the official Threads API in June 2024 and documents it on developers.facebook.com. It uses its own OAuth flow and its own host (graph.threads.net), separate from the Instagram Graph API. Older GitHub libraries that predate it are unofficial and reverse-engineered, and the best known one was archived in October 2023.
How many posts per day can I publish with the Threads API?
250 API-published posts per Threads profile in a 24-hour moving window. Replies have a separate cap of 1,000 and deletions 100. A carousel counts as one post. You can check what is left with GET /{threads-user-id}/threads_publishing_limit.
What is the Threads API character limit?
A post’s text is limited to 500 characters. Emoji take more than one byte in UTF-8, so a post heavy on emoji hits the limit sooner. For longer text you either split it into a reply chain or use a text attachment. Upload-Post does the split for you and can also publish up to 10,000 characters as a single post.
Can I schedule Threads posts via the API?
Not natively. The publish endpoint has no publish-time parameter, and a media container expires if it is not published within 24 hours, so you cannot pre-create posts days ahead. You need your own scheduler (a cron job and a database) or a service such as Upload-Post, which takes a scheduled_date up to 365 days ahead.
How do I use the Threads API with Python?
There is no official Meta SDK for Threads, but you do not need one. Two POST requests with the requests library publish a post: one to /{user-id}/threads to create the container, and one to /{user-id}/threads_publish with the returned creation_id. The Python example on this page shows the full flow, including the status check for videos.
Do I need App Review to use the Threads API?
Only to let other people connect. While testing, you can use the API with Threads accounts you invite as testers on your app. Before users without a role on your app can grant permissions, each permission must pass App Review and the app must be published.
Can I read other users’ posts or search Threads with the API?
Partly. The keyword search endpoint returns public posts, but only after the threads_keyword_search permission is approved; before that it only searches the authenticated user’s own posts. It is limited to 2,200 queries per user in 24 hours. Most other endpoints work on the connected user’s own posts and replies.
Can I post to Threads and Instagram with the same API?
Not with the same Meta API: Threads has its own app use case, tokens and endpoints, and Instagram uses the Instagram Graph API. Upload-Post puts both behind one request, so platform[]=threads and platform[]=instagram publish the same content to both.
Can I delete a Threads post through the API?
Meta’s API supports deletion with the threads_delete permission, up to 100 deletions per profile in 24 hours.