# 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](https://social-publish-kappa.vercel.app/dashboard/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).

> [!NOTE]
> 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` \\| `followers_only` | No | `"everyone"` | Who can reply |
| `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

```bash tab="cURL"
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
  }'
```

```ts tab="Node.js"
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();
```

```python tab="Python"
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

```json title="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

| 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. |
