updated 28 October 2026

The Threads API, explained

The Threads API is Meta's official REST API for Threads. It lets your app publish text, images, video, and carousels for a Threads user who authorized it, and read their posts, replies, and insights. Publishing takes two calls, a profile can publish 250 posts a day through it, and you need App Review before users outside your app's roles can grant access.

What the Threads API does

Meta opened the Threads API to all developers on 18 June 2024. Meta describes its purpose narrowly: to let people create and publish content on a person's behalf, and to show those posts in your app to the person who created them (overview). In practice the API covers:

  • publishing text, image, video, and carousel posts, plus replies and quote posts
  • reading a user's own posts, profile, and replies, and hiding or managing replies
  • post and profile insights
  • deleting posts, and webhooks

Requests go to graph.threads.net or graph.threads.com; Meta accepts both. Calls that read a user's data or publish for them need that user's Threads user access token.

Getting access

Create a Meta app and pick the Threads use case. The app gets two IDs and two secrets; use the Threads app ID and Threads app secret (get started). Users grant permissions through Meta's Authorization Window. The ones you will use most:

PermissionWhat it unlocks
threads_basicRequired for every endpoint
threads_content_publishCreating and publishing posts
threads_manage_repliesPOST calls to reply endpoints, such as hiding replies
threads_read_repliesGET calls to reply endpoints
threads_manage_insightsInsights endpoints
threads_deleteDeleting posts

Until your app passes App Review for a permission and is published, only Threads testers you invite under App roles can grant it. For threads_content_publish that means you can post only for those testers (posts). Plan for review time before launch.

Tokens and how long they last

  1. The Authorization Window redirects back with an authorization code, valid for one hour. Exchange it for a short-lived user token, also valid for one hour.
  2. On your server, exchange the short-lived token for a long-lived one with GET /access_token?grant_type=th_exchange_token and your Threads app secret. Long-lived tokens last 60 days.
  3. Refresh a long-lived token with GET /refresh_access_token?grant_type=th_refresh_token once it is at least 24 hours old and before it expires. A token not refreshed within 60 days can't be refreshed again (long-lived tokens).

Permission grants are a separate clock. A grant lasts 90 days. Refreshing the token extends it for users with public profiles; users with private profiles have to grant the permission again (get started). Keep the app secret and every token on the server.

How publishing works

A post is two calls. First create a media container with POST /{threads-user-id}/threads, setting media_type to TEXT, IMAGE, or VIDEO. Images and videos are passed as public URLs (image_url, video_url), because Meta downloads the media itself.

curl -X POST "https://graph.threads.net/v1.0/<THREADS_USER_ID>/threads"   -d "media_type=TEXT"   -d "text=Hello from the Threads API"   -d "access_token=<ACCESS_TOKEN>"
# {"id": "<CONTAINER_ID>"}

Meta's examples pass the token as the access_token parameter, so keep these requests out of URL and proxy logs. Meta recommends waiting about 30 seconds before publishing so it can process the media. Rather than sleeping blindly, read the container's status. It moves from IN_PROGRESS to FINISHED, or to ERROR with an error_message such as FAILED_DOWNLOADING_VIDEO. A container not published within 24 hours becomes EXPIRED. Meta suggests checking once a minute for at most five minutes (troubleshooting).

curl "https://graph.threads.net/v1.0/<CONTAINER_ID>?fields=status,error_message&access_token=<ACCESS_TOKEN>"
# {"status": "FINISHED", "id": "<CONTAINER_ID>"}

Then publish the finished container:

curl -X POST "https://graph.threads.net/v1.0/<THREADS_USER_ID>/threads_publish"   -d "creation_id=<CONTAINER_ID>"   -d "access_token=<ACCESS_TOKEN>"
# {"id": "<THREADS_MEDIA_ID>"}

Carousels add a step: create one container per item with is_carousel_item=true, then a CAROUSEL container listing them in children, then publish that. A carousel holds 2 to 20 images or videos and counts as one post.

One trap: if threads_publish fails or times out, the post may still have gone out. Check the container status before retrying. A status of PUBLISHED means it did, and creating a new container to try again would post twice.

Rate limits and media limits

Publishing limits apply per Threads profile over a moving 24-hour window (overview):

LimitValue
Posts published through the API250 per 24 hours (a carousel counts as one)
Replies1,000 per 24 hours
Deletions100 per 24 hours
Location searches500 per 24 hours
API calls per app and user4,800 × the account's impressions in the last 24 hours, counting at least 10 impressions
Text500 characters, with emojis counted by their UTF-8 bytes
ImagesJPEG or PNG, up to 8 MB; Meta rescales widths outside 320 to 1440 pixels
VideoMOV or MP4, up to 5 minutes and 1 GB, 23 to 60 fps

threads_publish enforces the post limit, so a scheduler that queues more than 250 posts for one profile will see failures. Read the live usage before a batch with threads_publishing_limit; add reply_quota_usage or delete_quota_usage to the fields for the other quotas. Meta can also limit an app by the total CPU time its calls use per day, which scales with impressions the same way.

curl "https://graph.threads.net/v1.0/<THREADS_USER_ID>/threads_publishing_limit?fields=quota_usage,config&access_token=<ACCESS_TOKEN>"
# {"data": [{"quota_usage": 4, "config": {"quota_total": 250, "quota_duration": 86400}}]}

Posting to Threads from TypeScript with Social SDK

Social SDK is an open-source TypeScript library that wraps this flow. Its Threads adapter creates the container, checks its status, and publishes it in one posts.publish call. It doesn't sleep while Meta processes media: you get back published with the post ID, or processing to check again later. If a publish call's result can't be confirmed, the outcome is unknown and the adapter doesn't replay it, which avoids the double post described above.

npm install @opencoredev/social-sdk
import { createSocial, connectedAccountRef } from "@opencoredev/social-sdk";
import { threads } from "@opencoredev/social-sdk/threads";

const userId = process.env["THREADS_USER_ID"];
const accessToken = process.env["THREADS_ACCESS_TOKEN"];
if (!userId || !accessToken) throw new Error("Set THREADS_USER_ID and THREADS_ACCESS_TOKEN");

const social = createSocial({ backend: threads({ auth: { userId, accessToken } }) });
const account = connectedAccountRef({ backend: "default", platform: "threads", accountId: userId });

const result = await social.posts.publish({
  targets: [{ account }],
  content: { text: "Posted to Threads from TypeScript" },
});

for (const outcome of result.outcomes) {
  if (outcome.state === "published") console.log("published", outcome.post.postId);
  else if (outcome.state === "processing") console.log("still processing", outcome.backendState);
  else console.log(outcome.state, outcome);
}

We type-checked this example against @opencoredev/social-sdk 0.5.0 from npm and ran it on 28 October 2026 with a stub fetch standing in for Meta. It made the three calls above in order and printed the new post ID. It runs on Node.js 22.12 or later and Bun. Running the script again is a new publish. To make retries safe, give the client a durable idempotency store and pass an idempotencyKey; without the store, the key doesn't deduplicate anything. The token comes from your own OAuth flow; the authentication guide covers the SDK's threadsOAuth and token refresh helpers.

Images, videos, carousels, reply controls, and the persistent store that lets a processing post resume after a restart are on the Threads platform page. To build without a Meta app at all, start with the mock quickstart. The same client can also post to X, Bluesky, and the other supported platforms in the same call.

Common questions

Is there an official Threads API?
Yes. Meta has run it since June 2024, and it is documented at developers.facebook.com/docs/threads.
Can I schedule Threads posts through the API?
Not directly. threads_publish takes only the container ID and publishes when you call it, and Meta's overview tells apps that schedule posts to enforce the publishing limit themselves. Schedule in your own job runner and publish at the chosen time, keeping the 250-post limit in mind.
Why does my container stay IN_PROGRESS?
Meta is still processing the media. Keep checking the status once a minute for up to five minutes. ERROR carries an error_message that names the problem, such as an invalid frame rate or a URL Meta couldn't download.
Can I post for any Threads user while my app is in development?
No. Only invited Threads testers can grant permissions until the app passes App Review and is published.

Sources

Meta's pages, read on 28 October 2026, with the update date each page showed: