# Threads API: Posting Reference

Threads is supported via Meta's Threads API. Only accounts linked to a Threads profile can be connected. The account must be a public profile.

**Channel ID:** `threads`

## Supported content types

| Type | Supported |
|------|-----------|
| Feed post | ✅ |
| Story | - |
| Reel | - |

## Minimal example

```bash
curl -X POST https://api.omnisocials.com/v1/posts/create \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": { "default": "Thought of the day 💭" },
    "accounts": ["your-threads-account-id"]
  }'
```

Text-only posts are supported. Media is optional.

## Platform-specific options

| Field | Type | Description |
|-------|------|-------------|
| `threads.thread_parts` | array | Publish as a chained thread instead of a single post. See [Posting threads](#posting-threads). |
| `threads.location_id` | string | Tag a location on the post. See [Location tagging](#location-tagging). |
| `threads.location` | object | Alternative to `location_id`: the full search result `{ id, name, address, city, country }`, so your app can show the place name without another lookup. Only `id` is required; `location_id` wins when both are given. |

Threads has no required option fields. The default create-post body is sufficient for a single post.

## Posting threads

To publish a thread (a chain of connected posts), pass `threads.thread_parts`. Each part becomes its own post, published as a reply to the previous one from the same account.

:::caution
**Reconnect once.** Chaining posts needs the Threads reply permission (`threads_manage_replies`). Threads accounts connected before this permission was added need a one-time reconnect under Settings -> Organisation -> Workspaces; until then `thread_parts` is rejected with a `400` that names the reconnect.
:::

```bash
curl -X POST https://api.omnisocials.com/v1/posts/create \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accounts": ["your-threads-account-id"],
    "content": { "default": "" },
    "threads": {
      "thread_parts": [
        { "text": "1/3: a thread on Threads 🧵" },
        { "text": "2/3: every part is its own post, replying to the one before it" },
        { "text": "3/3: and the whole chain is scheduled in one call" }
      ]
    }
  }'
```

**Rules:**

- 2 to 25 parts. For a single post, omit `thread_parts` and use `content.threads` (or `content.default`) instead.
- Each `text` is required, non-empty, and ≤ 500 characters. Parts that exceed the limit are rejected with a `400`.
- `media_urls` (or `media_ids`) is optional per part, up to 10 items per part. A part with several items publishes as a carousel, and images and videos can be mixed in the same part, exactly like a single Threads post. Any entry can be an object with alt text:

  ```json
  {
    "threads": {
      "thread_parts": [
        {
          "text": "Photo dump from the trip 📷",
          "media_urls": [
            { "url": "https://example.com/1.jpg", "alt": "Sunrise over the harbour" },
            "https://example.com/2.jpg"
          ]
        },
        { "text": "More to come tomorrow." }
      ]
    }
  }
  ```

- When `content.threads` is provided alongside `thread_parts`, `thread_parts` wins: the first part is the post's caption.
- The first part's URL becomes the post's Threads URL. If a later part fails, `POST /posts/:id/retry` resumes from the first unpublished part instead of re-posting the ones that already landed.

To convert a thread back into a single post on update, send `threads.thread_parts: null` to `PATCH /posts/:id`.

## Location tagging

Tag a physical place on a Threads post by passing `threads.location_id`. The tag shows on the post like a location tag in the Threads app.

:::caution
**Rolling out.** Location tagging needs the Threads location permission (`threads_location_tagging`), which Meta has not approved for OmniSocials yet. Until that review passes, `threads.location_id` is rejected with a `400` that says location tagging is not available yet, and `GET /locations/search?platform=threads` answers `error.code = not_available`. Once enabled, Threads accounts connected before that date need a one-time reconnect under Settings -> Organisation -> Workspaces.
:::

Find an id with the locations search, using `platform=threads`. You can search by name or by coordinates (`latitude` + `longitude`):

```bash
curl "https://api.omnisocials.com/v1/locations/search?platform=threads&q=Griffith%20Observatory" \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY"
# -> { "locations": [ { "id": "17841400000000000", "name": "Griffith Observatory",
#      "address": "2800 E Observatory Rd", "city": "Los Angeles", "country": "United States",
#      "latitude": 34.1184, "longitude": -118.3004 } ] }
```

Then create the post with the id:

```bash
curl -X POST https://api.omnisocials.com/v1/posts/create \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": { "default": "Best view in the city 🔭" },
    "accounts": ["your-threads-account-id"],
    "threads": { "location_id": "17841400000000000" }
  }'
```

**Rules:**

- Threads location ids are NOT Facebook Place IDs. Never reuse an Instagram `location_id` here; always search with `platform=threads`.
- On a multi-post thread (`thread_parts`), the tag goes on the first post.
- On `PATCH /posts/:id`, `threads.location_id: null` (or `threads.location: null`) removes the tag.
- The connection must have the `threads_location_tagging` permission; without it, create/update/publish return a `400` naming the reconnect, and the search answers `error.code = threads_reauth_required`.
- Threads allows 500 location searches per account per rolling 24 hours.

The post object echoes the tag back as `threads.location` (`{ id, name, address, city, country }`, with the display fields as you stored them).

## Social Inbox

Replies people leave on your Threads posts, and posts that mention you, land in the [Social Inbox](/inbox#threads-replies-and-mentions). You can reply (published as a native Threads reply) and hide or unhide replies on your own posts via `POST /inbox/messages/{id}/hide`.

:::caution
**Reconnect once.** The Threads inbox needs the Threads reply permissions. Threads accounts connected before these permissions were added need a one-time reconnect under Settings -> Organisation -> Workspaces; until then Threads conversations do not appear and Threads replies and hides answer `401 reauth_required`.
:::

- Conversation types: `comment` (replies on your posts, grouped per root post) and `mention`. Threads has no DM API.
- Replies need the Threads reply permission on the connection; otherwise the reply returns `401 reauth_required`.
- Only incoming top-level replies on your posts can be hidden; nested replies return `400 not_hideable`.

See the [Social Inbox guide](/inbox#threads-replies-and-mentions) for endpoints, payloads, and error handling.

## Media requirements

| Media | Requirement |
|-------|-------------|
| Image | JPEG or PNG. Max 8 MB. Up to 10 per post. |
| Video | MP4, up to 5 minutes, max 1 GB |

Threads supports a single image, a single video, or a carousel of up to 10 items per post. Carousel items can mix images and videos.

**Alt text.** Threads delivers per-media alt text natively. Pass it by using an object entry instead of a bare string: `{ "url": "https://example.com/bike.jpg", "alt": "A red bicycle leaning against a brick wall" }` in `media_urls` (or `{ "id": "...", "alt": "..." }` in `media_ids`, including per-part thread media; max 1500 characters). It is sent as the media container's `alt_text` on images and videos, single posts and carousel items alike.

## Limitations

- Threads enforces a 500 character limit per post (and per part of a thread)
- Posting a reply to an arbitrary post is not supported; `thread_parts` only chains replies to your own previous part, and the [Social Inbox](/inbox#threads-replies-and-mentions) replies to replies on your posts and to mentions
- Private Threads accounts cannot be connected
