Platforms
Threads
Publish text, image, video and carousel posts to Threads, with reply controls, topic tags, link cards and quotes.
Overview
The threads platform publishes with Meta's Threads API. You can post:
- Text posts, optionally with a link preview card
- A single image or video
- Carousels of up to 20 images and/or videos
- Quote posts
- One topic tag per post, and reply controls
Connect the account
In Integrations, choose Threads and approve access.
App setup (self-hosted)
- In the Meta App Dashboard, create an app with the Access the Threads API use case.
- Add the permissions
threads_basic,threads_content_publishandthreads_manage_insights. - Under Settings, set the Redirect Callback URL to
https://social-publish-kappa.vercel.app/api/integrations/threads/callback(override withTHREADS_REDIRECT_URI), and fill in the Uninstall and Delete callback URLs. - Set
THREADS_APP_IDandTHREADS_APP_SECRET(the Threads app ID and secret).
In development mode, only Threads testers can connect. A tester accepts the invitation in the Threads app under Settings → Account → Website permissions. Going live needs App Review for threads_content_publish.
Tokens are long-lived (60 days) and refreshed automatically before they expire.
Post types & media rules
| Rule | Limit |
|---|---|
| Text | 500 characters; each emoji counts as its UTF-8 byte length (a plain emoji counts as 4) |
| Text or media | A post needs text or media |
| Media types | Images and videos only (no documents) |
| Carousel | Up to 20 items, images and videos can be mixed |
| Alt text | media[].altText is sent for images and videos |
| Link preview | linkAttachment only on text-only posts |
| Topic tag | One tag, 1–50 characters, no periods (.) or ampersands (&); a leading # is removed |
| Quote | quotePostId must be the numeric Threads media ID |
Media is fetched by Threads from its URL, so it must be publicly reachable. File size and duration limits are Threads' own.
Options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
replyControl | everyone \ | accounts_you_follow \ | mentioned_only \ | parent_post_author_only \ |
topicTag | string | No | Topic tag. One topic, up to 50 characters, without # and without periods or ampersands. | |
linkAttachment | string (URL) | No | Link preview. Shows a link card. Only for text-only posts (no media). | |
quotePostId | string | No | Quote post ID. Numeric Threads media ID of the post to quote (from the API, not the post URL). | |
Threads post URLs (threads.com/@user/post/<shortcode>) can't be turned into a media ID through the API, so quotePostId only accepts the numeric ID. The externalPostId of a target published by Relaycast is that ID. |
Examples
curl -X POST https://social-publish-api.onrender.com/v1/posts \
-H "x-access-key: $RELAYCAST_API_KEY" \
-H "content-type: application/json" \
-d '{
"content": "New on the blog: how we cut our build times in half.",
"targets": [{
"accountId": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"options": {
"replyControl": "accounts_you_follow",
"topicTag": "engineering",
"linkAttachment": "https://example.com/blog/faster-builds"
}
}],
"publishNow": true
}'const res = await fetch("https://social-publish-api.onrender.com/v1/posts", {
method: "POST",
headers: {
"x-access-key": process.env.RELAYCAST_API_KEY!,
"content-type": "application/json",
},
body: JSON.stringify({
content: "New on the blog: how we cut our build times in half.",
targets: [
{
accountId: "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
options: {
replyControl: "accounts_you_follow",
topicTag: "engineering",
linkAttachment: "https://example.com/blog/faster-builds",
},
},
],
publishNow: true,
}),
});
const { data } = await res.json();import os, requests
res = requests.post(
"https://social-publish-api.onrender.com/v1/posts",
headers={"x-access-key": os.environ["RELAYCAST_API_KEY"]},
json={
"content": "New on the blog: how we cut our build times in half.",
"targets": [{
"accountId": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"options": {
"replyControl": "accounts_you_follow",
"topicTag": "engineering",
"linkAttachment": "https://example.com/blog/faster-builds",
},
}],
"publishNow": True,
},
)
data = res.json()["data"]Carousel quoting another post
{
"content": "Our take on this, in pictures",
"media": [
{ "url": "https://cdn.example.com/slide-1.jpg", "type": "image", "altText": "Chart of build times" },
{ "url": "https://cdn.example.com/slide-2.jpg", "type": "image" },
{ "url": "https://cdn.example.com/clip.mp4", "type": "video" }
],
"targets": [{
"accountId": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"options": { "quotePostId": "18012345678901234" }
}],
"scheduledFor": "2026-11-08T10:00:00Z"
}Analytics & comments
Analytics show views, likes, replies (comments), and reposts + quotes + shares (shares). Insights need the threads_manage_insights permission; accounts connected before it was added must reconnect.
Replies and messaging are not supported for Threads.
Limitations & common errors
| Error | Cause |
|---|---|
Threads posts need text or media | Empty post. |
Threads allows 500 characters (emoji count as several) … | Text is too long once emoji are counted by byte length. |
Threads carousels allow at most 20 items | Too many media items. |
Threads supports images and videos only | A document was attached. |
Threads link previews are only allowed on text-only posts | linkAttachment with media. |
Topic tags cannot contain periods (.) or ampersands (&) | Invalid topicTag. |
Quote post ID must be a numeric Threads media ID | A URL was sent as quotePostId. |
| Connect fails in development mode | The account isn't an accepted Threads tester of the app. |
This page is also available as Markdown.