Developer guide ·

Threads API Guide: Access, Publishing & Insights

Everything you need to build on the Threads API: getting access, OAuth tokens, publishing text, images, video and carousels, pulling post and account insights, and staying inside the rate limits - with copy-ready code in cURL, Python and Node.js.

Freeno paid tier - access is governed by rate limits
250API-published posts per profile per 24 hours
60 dayslong-lived access token, refreshable
500characters per text post
Overview

What is the Threads API?

The Threads API is Meta's official, free REST API for Threads. It's part of the Graph API family, served from graph.threads.net (also graph.threads.com) at version v1.0, and secured with OAuth 2.0. Here's what you can build with it.

✍️

Publish

Text, images, videos and carousels of 2–20 items, with topic tags, link attachments, quotes and reply controls.

📊

Insights

Views, likes, replies, reposts, quotes and shares per post; followers, demographics and link clicks per account.

💬

Replies & mentions

Read conversations, reply, hide replies, and track public mentions of a profile.

🔎

Keyword search

Search public posts by keyword or topic tag - the basis for social listening tools.

👤

Profiles & media

Read a user's profile fields and their published posts, with paging.

🔔

Webhooks

Get notified on your server when events happen, instead of polling.

Getting access

How to get access to the Threads API

You can build and test against your own account the same day. Accessing other people's accounts in production needs Meta's App Review.

  1. 1

    Create a Meta app

    At developers.facebook.com, create an app and add the Access the Threads API use case.

  2. 2

    Choose permissions

    Add the scopes you need - at minimum threads_basic, plus e.g. threads_content_publish to post.

  3. 3

    Set redirect URIs and add testers

    Register your OAuth callback URL. Invite your own Threads account as a tester and accept the invite in the Threads app's account settings (Website permissions).

  4. 4

    Build and test

    With testers, every endpoint works on their accounts - no review needed. Use the Graph API Explorer to try calls quickly.

  5. 5

    Submit for App Review

    To let any Threads user connect, request advanced access for each permission with a screencast and a description of how you use it.

PermissionWhat it unlocks
threads_basicRequired for every call - profile and posts
threads_content_publishCreate and publish posts
threads_read_repliesRead replies and conversations
threads_manage_repliesReply, hide replies, set reply controls
threads_manage_insightsPost and account insights
threads_deleteDelete posts
threads_keyword_searchSearch public posts by keyword or tag
threads_manage_mentionsRead posts that mention the profile
threads_location_taggingSearch and tag locations
threads_share_to_instagramCross-share posts to Instagram Stories
Authentication

Threads API authentication with OAuth 2.0

Users authorize your app, you swap the code for a short-lived token (1 hour), exchange that for a long-lived token (60 days), and refresh it before it expires.

👤User authorizes/oauth/authorize 🎟️Code to redirect?code=… ⏱️Short-lived tokenvalid 1 hour 🔑Long-lived tokenvalid 60 days 🔄Refreshafter 24h, before expiry token
1 · Send users to authorize
https://threads.net/oauth/authorize
  ?client_id=YOUR_APP_ID
  &redirect_uri=https://yourapp.com/callback
  &scope=threads_basic,threads_content_publish,threads_manage_insights
  &response_type=code
  &state=RANDOM_STRING
2 · Exchange the code (server-side)
curl -X POST https://graph.threads.net/oauth/access_token \
  -d client_id=YOUR_APP_ID \
  -d client_secret=YOUR_APP_SECRET \
  -d grant_type=authorization_code \
  -d redirect_uri=https://yourapp.com/callback \
  -d code=CODE_FROM_REDIRECT

# → { "access_token": "...", "user_id": 1234567890 }  (valid 1 hour)
3 · Get a 60-day token
curl -G https://graph.threads.net/access_token \
  -d grant_type=th_exchange_token \
  -d client_secret=YOUR_APP_SECRET \
  -d access_token=SHORT_LIVED_TOKEN

# → { "access_token": "...", "token_type": "bearer",
#     "expires_in": 5183944 }  (≈ 60 days)
4 · Refresh before it expires
curl -G https://graph.threads.net/refresh_access_token \
  -d grant_type=th_refresh_token \
  -d access_token=LONG_LIVED_TOKEN

# Works once the token is 24h+ old and not yet expired.
# Returns a new token valid for another 60 days.

Keep your app secret and tokens server-side, in environment variables or a secrets manager - never in browser code or a public repo. A long-lived token can be refreshed once it's at least 24 hours old; an expired token can't be refreshed, so schedule refreshes well before day 60.

Publishing

How to publish posts with the Threads API

Publishing is always two steps: create a media container, then publish it. For images and video, check the container's status before publishing - Meta recommends waiting about 30 seconds on average for processing.

Create containerPOST /{user-id}/threads Check status IN_PROGRESS… FINISHED ✓ PublishPOST /threads_publish Live on Threadsreturns post ID
# 1) Create a media container
curl -X POST "https://graph.threads.net/v1.0/$THREADS_USER_ID/threads" \
  -d media_type=TEXT \
  -d text="Shipping something new today 🚀" \
  -d access_token=$THREADS_TOKEN
# → { "id": "CONTAINER_ID" }

# 2) (Images/video) check the container is ready
curl -G "https://graph.threads.net/v1.0/CONTAINER_ID" \
  -d fields=status,error_message \
  -d access_token=$THREADS_TOKEN
# → { "status": "FINISHED", "id": "CONTAINER_ID" }

# 3) Publish it
curl -X POST "https://graph.threads.net/v1.0/$THREADS_USER_ID/threads_publish" \
  -d creation_id=CONTAINER_ID \
  -d access_token=$THREADS_TOKEN
# → { "id": "POST_ID" }
import os, time, requests

BASE = "https://graph.threads.net/v1.0"
TOKEN = os.environ["THREADS_TOKEN"]
USER_ID = os.environ["THREADS_USER_ID"]

def wait_until_ready(container_id, tries=10):
    for _ in range(tries):
        r = requests.get(f"{BASE}/{container_id}",
                         params={"fields": "status,error_message",
                                 "access_token": TOKEN}).json()
        if r.get("status") == "FINISHED":
            return
        if r.get("status") in ("ERROR", "EXPIRED"):
            raise RuntimeError(r)
        time.sleep(5)
    raise TimeoutError("Container not ready")

def publish(media_type="TEXT", **fields):
    c = requests.post(f"{BASE}/{USER_ID}/threads",
                      data={"media_type": media_type,
                            "access_token": TOKEN, **fields})
    c.raise_for_status()
    container_id = c.json()["id"]
    if media_type != "TEXT":
        wait_until_ready(container_id)
    p = requests.post(f"{BASE}/{USER_ID}/threads_publish",
                      data={"creation_id": container_id,
                            "access_token": TOKEN})
    p.raise_for_status()
    return p.json()["id"]

print(publish(text="Hello from Python 🐍"))
print(publish("IMAGE", image_url="https://example.com/photo.jpg",
              text="New drop"))
// Node.js 18+ (built-in fetch)
const BASE = "https://graph.threads.net/v1.0";
const { THREADS_TOKEN: TOKEN, THREADS_USER_ID: USER_ID } = process.env;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function call(path, params = {}, method = "GET") {
  const body = new URLSearchParams({ ...params, access_token: TOKEN });
  const url = method === "GET" ? `${BASE}/${path}?${body}` : `${BASE}/${path}`;
  const res = await fetch(url, method === "GET" ? {} : { method, body });
  const json = await res.json();
  if (!res.ok) throw new Error(JSON.stringify(json.error));
  return json;
}

async function publish(fields) {
  const { id } = await call(`${USER_ID}/threads`, fields, "POST");
  if (fields.media_type !== "TEXT") {
    for (let i = 0; i < 10; i++) {
      const { status } = await call(id, { fields: "status,error_message" });
      if (status === "FINISHED") break;
      if (status === "ERROR" || status === "EXPIRED") throw new Error(status);
      await sleep(5000);
    }
  }
  const post = await call(`${USER_ID}/threads_publish`, { creation_id: id }, "POST");
  return post.id;
}

publish({ media_type: "TEXT", text: "Hello from Node 👋" }).then(console.log);
Media typeKey parametersLimits
TEXTtextUp to 500 characters
IMAGEimage_url (public URL)JPEG/PNG, up to 8 MB, 320–1440 px wide
VIDEOvideo_url (public URL)MP4/MOV, up to 5 min and 1 GB, 23–60 fps
CAROUSELchildren (container IDs)2–20 image/video items
Optional parameterUse it to
topic_tagAdd one topic tag (1–50 chars, no periods or "&")
link_attachmentAttach a link preview to a text post
reply_controlLimit who can reply (e.g. accounts you follow)
reply_to_idPublish as a reply to another post
quote_post_idPublish as a quote of another post

For a carousel, create each item as a container with is_carousel_item=true, then create a container with media_type=CAROUSEL and children set to the item IDs, then publish that. Media URLs must be publicly reachable - Meta's servers fetch them.

Insights

Threads Insights API: post and account metrics

With the threads_manage_insights permission you can read metrics for individual posts and for the whole profile - the same data that powers analytics dashboards.

LevelMetrics
Post
/{media-id}/insights
views, likes, replies, reposts, quotes, shares
Account
/{user-id}/threads_insights
views (time series), likes, replies, reposts, quotes, clicks, followers_count, follower_demographics
  • Use since / until (Unix timestamps) for date ranges; the default is the last two days.
  • Data starts from April 13, 2024; account insights are only reliable from June 1, 2024.
  • followers_count and demographics don't support date ranges.
  • Demographics need at least 100 followers; use breakdown=country, city, age or gender.

Example: post insights response

GET /{MEDIA_ID}/insights?metric=views,likes,replies,reposts

views12,480 likes642 replies118 reposts57

Bars not to scale - illustrative values.

curl -G "https://graph.threads.net/v1.0/MEDIA_ID/insights" \
  -d metric=views,likes,replies,reposts,quotes,shares \
  -d access_token=$THREADS_TOKEN
# Account totals for a date range (Unix timestamps)
curl -G "https://graph.threads.net/v1.0/$THREADS_USER_ID/threads_insights" \
  -d metric=views,likes,replies,reposts,quotes,clicks \
  -d since=1756684800 -d until=1759276800 \
  -d access_token=$THREADS_TOKEN

# Follower demographics (needs 100+ followers)
curl -G "https://graph.threads.net/v1.0/$THREADS_USER_ID/threads_insights" \
  -d metric=follower_demographics -d breakdown=country \
  -d access_token=$THREADS_TOKEN
{
  "data": [
    {
      "name": "views",
      "period": "lifetime",
      "values": [ { "value": 12480 } ],
      "title": "Views",
      "id": "MEDIA_ID/insights/views/lifetime"
    },
    {
      "name": "likes",
      "period": "lifetime",
      "values": [ { "value": 642 } ],
      "title": "Likes",
      "id": "MEDIA_ID/insights/likes/lifetime"
    }
  ]
}
Beyond posting

Replies, mentions, keyword search and webhooks

💬

Reply management

Read a post's replies or full conversation, publish replies with reply_to_id, and hide or unhide replies - the building blocks of moderation and community tools.

🔎

Keyword search

GET /keyword_search?q=… finds public posts by keyword, or by topic tag with search_mode=TAG. Sort with search_type=TOP or RECENT. Limited to 500 queries per rolling 7 days.

🔔

Mentions & webhooks

Fetch public posts that mention a profile, and subscribe to webhooks so your server hears about new activity in real time instead of polling.

Rate limits

Threads API rate limits

The API is free, but each profile has rolling quotas. Check usage before batch jobs with the publishing-limit endpoint.

Postsper 24 hours
37 / 250
Repliesper 24 hours
212 / 1,000
Deletesper 24 hours
8 / 100
Location searchesper 24 hours
60 / 500
Keyword searchesper 7 days
120 / 500

Example usage for one profile. Quotas reset on a rolling basis.

Check your remaining quota
curl -G "https://graph.threads.net/v1.0/$THREADS_USER_ID/threads_publishing_limit" \
  -d fields=quota_usage,config,reply_quota_usage,reply_config \
  -d access_token=$THREADS_TOKEN

# → { "data": [ {
#       "quota_usage": 37,
#       "config": { "quota_total": 250, "quota_duration": 86400 },
#       "reply_quota_usage": 212,
#       "reply_config": { "quota_total": 1000, "quota_duration": 86400 }
#     } ] }

Best practices

  • Poll container status with backoff before publishing media
  • Check quota before batch publishing
  • Refresh long-lived tokens on a schedule, well before 60 days
  • Use webhooks instead of frequent polling
  • Log the error code and subcode from every failed call

Common mistakes

  • Publishing a video container before it's FINISHED
  • Using media URLs that aren't publicly reachable
  • Exposing the app secret or tokens in client-side code
  • Letting a token expire - it can't be refreshed after that
  • Scraping or automating the app instead of using the API

Hit an error? See our Threads API errors & rate limits guide for codes and fixes.

FAQ

Threads API FAQ

Is the Threads API free?
Yes. Meta doesn't charge for the Threads API. Usage is controlled by rate limits instead - for example, 250 API-published posts and 1,000 replies per profile in a rolling 24 hours.
How do I get a Threads API access token?
Create a Meta app with the Threads use case, send the user through the OAuth authorization window, and exchange the returned code for a short-lived token (valid 1 hour). Exchange that for a long-lived token (valid 60 days) using grant_type=th_exchange_token, and refresh it with grant_type=th_refresh_token.
Can I post images and videos with the Threads API?
Yes. Create a container with media_type=IMAGE and image_url, or media_type=VIDEO and video_url, wait until its status is FINISHED, then publish it. Carousels of 2–20 items are also supported.
What is the Threads API rate limit?
Per profile: 250 posts, 1,000 replies, 100 deletions and 500 location searches in a rolling 24 hours, plus 500 keyword searches per rolling 7 days. Check current usage with the threads_publishing_limit endpoint.
What insights can I get from the Threads API?
Per post: views, likes, replies, reposts, quotes and shares. Per account: views over time, likes, replies, reposts, quotes, link clicks, follower count and follower demographics (with at least 100 followers).
Do I need App Review to use the Threads API?
Not for testing. Your app can call every endpoint for accounts added as testers. To let other Threads users connect in production, request advanced access for each permission through App Review.