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:
| Permission | What it unlocks |
|---|---|
threads_basic | Required for every endpoint |
threads_content_publish | Creating and publishing posts |
threads_manage_replies | POST calls to reply endpoints, such as hiding replies |
threads_read_replies | GET calls to reply endpoints |
threads_manage_insights | Insights endpoints |
threads_delete | Deleting 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
- 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.
-
On your server, exchange the short-lived token for a long-lived one with
GET /access_token?grant_type=th_exchange_tokenand your Threads app secret. Long-lived tokens last 60 days. -
Refresh a long-lived token with
GET /refresh_access_token?grant_type=th_refresh_tokenonce 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):
| Limit | Value |
|---|---|
| Posts published through the API | 250 per 24 hours (a carousel counts as one) |
| Replies | 1,000 per 24 hours |
| Deletions | 100 per 24 hours |
| Location searches | 500 per 24 hours |
| API calls per app and user | 4,800 × the account's impressions in the last 24 hours, counting at least 10 impressions |
| Text | 500 characters, with emojis counted by their UTF-8 bytes |
| Images | JPEG or PNG, up to 8 MB; Meta rescales widths outside 320 to 1440 pixels |
| Video | MOV 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_publishtakes 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.
ERRORcarries anerror_messagethat 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:
- Threads API overview (updated 22 December 2025): purpose, hosts, rate limits, media specifications
- Threads posts (updated 13 October 2026): permissions, containers, publishing, carousels
- Get started (updated 30 June 2026): app setup, permissions, testers, grants
- Long-lived access tokens (updated 27 March 2025)
- Troubleshooting (updated 14 July 2025): container statuses and quota checks