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)

  1. In the Meta App Dashboard, create an app with the Access the Threads API use case.
  2. Add the permissions threads_basic, threads_content_publish and threads_manage_insights.
  3. Under Settings, set the Redirect Callback URL to https://social-publish-kappa.vercel.app/api/integrations/threads/callback (override with THREADS_REDIRECT_URI), and fill in the Uninstall and Delete callback URLs.
  4. Set THREADS_APP_ID and THREADS_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

RuleLimit
Text500 characters; each emoji counts as its UTF-8 byte length (a plain emoji counts as 4)
Text or mediaA post needs text or media
Media typesImages and videos only (no documents)
CarouselUp to 20 items, images and videos can be mixed
Alt textmedia[].altText is sent for images and videos
Link previewlinkAttachment only on text-only posts
Topic tagOne tag, 1–50 characters, no periods (.) or ampersands (&); a leading # is removed
QuotequotePostId 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

OptionTypeRequiredDefaultDescription
replyControleveryone \accounts_you_follow \mentioned_only \parent_post_author_only \
topicTagstringNoTopic tag. One topic, up to 50 characters, without # and without periods or ampersands.
linkAttachmentstring (URL)NoLink preview. Shows a link card. Only for text-only posts (no media).
quotePostIdstringNoQuote 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

Request body
{
  "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

ErrorCause
Threads posts need text or mediaEmpty 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 itemsToo many media items.
Threads supports images and videos onlyA document was attached.
Threads link previews are only allowed on text-only postslinkAttachment with media.
Topic tags cannot contain periods (.) or ampersands (&)Invalid topicTag.
Quote post ID must be a numeric Threads media IDA URL was sent as quotePostId.
Connect fails in development modeThe account isn't an accepted Threads tester of the app.

This page is also available as Markdown.