# Threads

Publish text, image, video and carousel posts and multi-post threads to Threads, set topic tags, manage replies and read post metrics.

Publish text posts, images, videos, carousels and chains of connected posts to Threads, with a topic tag and a first reply. The same account reads post metrics and manages the replies people leave. The platform id is `threads`.

## What you can do

| Capability | Supported | Where |
| --- | --- | --- |
| Publish | Text, one image, one video, carousels of up to 10 images, thread sequences | [Create a post](https://pozy.ai/docs/publishing) |
| Schedule | Yes | [Scheduling](https://pozy.ai/docs/publishing/scheduling) |
| Delete after publishing | Yes, with [Unpublish](https://pozy.ai/docs/api/unpublishPost) | [Edit and unpublish](https://pozy.ai/docs/publishing/editing) |
| Analytics | Views, likes, replies, reposts and quotes | [Platform insights](https://pozy.ai/docs/analytics/platform-insights) |
| Replies | List, reply, hide, unhide, delete | [Comments](https://pozy.ai/docs/inbox/comments) |
| DMs | No. Threads has no messaging API. | |
| Automations | Public reply to keyword replies | [Comment to DM](https://pozy.ai/docs/automations/comment-to-dm) |

## Connect

Threads profiles belong to Instagram accounts, so the user signs in with the Instagram **Business or Creator** account that owns the Threads profile. Start with [`GET /v1/connect/threads`](https://pozy.ai/docs/api/getConnectUrl) and a `profileId`, then send the user to `authUrl`.

Because Threads depends on Instagram, an Instagram account that is restricted or loses access also stops publishing to Threads. If a Threads post fails with "Instagram account is restricted", fix the Instagram account first.

## Post types

Threads accepts text with no media, which makes it one of the easiest places to cross-post short updates.

```bash cURL
curl -X POST https://api.pozy.ai/v1/posts \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Small teams ship faster when the default answer to a meeting is no.",
    "platforms": [{
      "platform": "threads",
      "accountId": "6720c1a94f3b2e0018d4a7f7",
      "platformSpecificData": { "topic_tag": "Startups" }
    }],
    "publishNow": true
  }'
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/posts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    content: "Small teams ship faster when the default answer to a meeting is no.",
    platforms: [{
      platform: "threads",
      accountId: "6720c1a94f3b2e0018d4a7f7",
      platformSpecificData: { topic_tag: "Startups" },
    }],
    publishNow: true,
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);

console.log(data.post.platforms[0].platformPostUrl);
```
```python Python
import os, requests

resp = requests.post(
    "https://api.pozy.ai/v1/posts",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    json={
        "content": "Small teams ship faster when the default answer to a meeting is no.",
        "platforms": [{
            "platform": "threads",
            "accountId": "6720c1a94f3b2e0018d4a7f7",
            "platformSpecificData": {"topic_tag": "Startups"},
        }],
        "publishNow": True,
    },
)
print(resp.json()["post"]["platforms"][0]["platformPostUrl"])
```

| Post type | How to send it |
| --- | --- |
| Text | `content`, up to 500 characters |
| Image | One image in `mediaItems` |
| Video | One video, up to 5 minutes |
| Carousel | 2 to 10 images |
| Thread sequence | `platformSpecificData.threadItems` |

> **500 characters:**
> 
> Threads posts stop at 500 characters. A caption cross-posted from LinkedIn, Facebook or Instagram usually runs longer and fails with "Param text must be at most 500 characters long". Give the Threads entry its own `customContent`.

### Thread sequence

`threadItems` publishes a chain: item 0 is the first post and each later item replies to the one before. Every item can carry text, media or both (text is optional when there is media). When `threadItems` is set, the top-level `content` is stored in Pozy for display and search but not published.

```json
{
  "platform": "threads",
  "accountId": "6720c1a94f3b2e0018d4a7f7",
  "platformSpecificData": {
    "threadItems": [
      { "content": "What we learned shipping a mobile app with two engineers.", "mediaItems": [{ "type": "image", "url": "https://cdn.example.com/app-cover.jpg" }] },
      { "content": "1. One codebase. We never regretted it." },
      { "content": "2. A weekly release train, even when the train was half empty." },
      { "content": "3. Support tickets in the team channel, not a separate tool." }
    ]
  }
}
```

### Platform fields

Schema: `ThreadsPlatformData`.

| Field | Notes |
| --- | --- |
| `topic_tag` | 1 to 50 characters, no periods or ampersands. Overrides the topic Threads would take from hashtags in the text. |
| `threadItems` | The whole sequence, first post first. Each item: `content`, `mediaItems`. |
| `firstComment` | A reply to your own post right after publishing (to the first post when using `threadItems`). Up to 500 characters. It is a post in its own right, so it counts toward the daily limit. |

## Media requirements

Threads fetches media the same way Instagram does. Media URLs must return the file itself (cloud-drive sharing links fail), and WebP images can fail, so send JPEG or PNG. Images over 8 MB are compressed automatically.

| | Images | Video |
| --- | --- | --- |
| Formats | JPEG, PNG | MP4 (H.264, AAC) |
| Max size | 8 MB each | 1 GB |
| Count | 1, or up to 10 in a carousel | 1 |
| Duration | | Up to 5 minutes |
| Aspect ratio | 4:5 (1080 x 1350) recommended; 1:1 and 16:9 work | 9:16, 16:9 or 1:1 |
| Other | | 1080p, 30 fps, AAC 128 kbps recommended |

## Limits

| Limit | Value |
| --- | --- |
| Post text | 500 characters |
| First reply | 500 characters |
| Topic tag | 50 characters |
| Posts per account | 250 per 24 hours, including first replies and each post in a sequence |
| Carousel | 10 images |

The API cannot create polls or quote posts, search GIFs, edit a published post, show who liked or reposted, or like replies.

## Analytics

[`GET /v1/analytics?platform=threads`](https://pozy.ai/docs/api/getAnalytics) returns `views`, `likes`, `comments` (replies) and `shares` per post. Threads has no impressions metric, so use `views` as the reach figure. `shares` adds reposts and quotes together.

## Inbox

Threads has no DMs. Replies to your posts show up as comments. A comment on Threads is itself a reply to the post, so calling [Reply to comment](https://pozy.ai/docs/api/replyToInboxPost) without a `commentId` replies to the post itself, and with one it answers that reply. You can delete replies, and [hide](https://pozy.ai/docs/api/hideInboxComment) or unhide them. Liking replies is not supported.

New replies are picked up by polling, so automated replies can lag by a few minutes.

## Errors you may see

| Message | Cause | Fix |
| --- | --- | --- |
| Param text must be at most 500 characters long | Text over the limit | Shorten it or set `customContent` for Threads. |
| Media download has failed. The media URI doesn't meet our requirements. | Threads could not fetch the file | Use a direct file URL; switch WebP to JPEG or PNG. |
| Instagram account is restricted | The linked Instagram account has a restriction | Resolve it in Instagram, then retry. |
| Publishing failed due to max retries reached | Temporary failure on Threads' side | Wait a few minutes and retry. |

A rejected `publishNow: true` post returns `207` with `post.status: "failed"` and the reason in `platforms[].errorMessage`.

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`POST /v1/posts`](https://pozy.ai/docs/api/createPost) | Create post |
| [`POST /v1/posts/{postId}/unpublish`](https://pozy.ai/docs/api/unpublishPost) | Unpublish post |
| [`GET /v1/inbox/comments/{postId}`](https://pozy.ai/docs/api/getInboxPostComments) | Get post comments |
| [`POST /v1/inbox/comments/{postId}`](https://pozy.ai/docs/api/replyToInboxPost) | Reply to comment |
| [`POST /v1/inbox/comments/{postId}/{commentId}/hide`](https://pozy.ai/docs/api/hideInboxComment) | Hide comment |
| [`DELETE /v1/inbox/comments/{postId}/{commentId}/hide`](https://pozy.ai/docs/api/unhideInboxComment) | Unhide comment |
| [`DELETE /v1/inbox/comments/{postId}`](https://pozy.ai/docs/api/deleteInboxComment) | Delete comment |
| [`GET /v1/analytics`](https://pozy.ai/docs/api/getAnalytics) | Get post analytics |

## Webhook events

| Event | When it fires |
| --- | --- |
| [`post.published`](https://pozy.ai/docs/webhooks/events#post-published) | Post published. |
| [`post.failed`](https://pozy.ai/docs/webhooks/events#post-failed) | Post failed. |
| [`post.platform.published`](https://pozy.ai/docs/webhooks/events#post-platform-published) | Post platform published. |
| [`post.platform.failed`](https://pozy.ai/docs/webhooks/events#post-platform-failed) | Post platform failed. |
| [`post.platform.deleted`](https://pozy.ai/docs/webhooks/events#post-platform-deleted) | Post platform deleted. |
| [`account.disconnected`](https://pozy.ai/docs/webhooks/events#account-disconnected) | Account disconnected. |
| [`comment.received`](https://pozy.ai/docs/webhooks/events#comment-received) | Comment received. |
