Threads API
Schedule and automate Threads posts with RelayAPI — text posts, images, videos, carousels, and thread sequences.
Quick Reference
| Property | Value |
|---|---|
| Platform key | threads |
| Auth method | OAuth 2.0 (via Instagram/Facebook) |
| Character limit | 500 |
| Carousel items | 2–20 mixed images/videos |
| Image formats | JPEG, PNG |
| Image max size | 8 MiB |
| Video formats | MP4, MOV |
| Video max size | 100 MiB |
| Video max duration | 5 minutes |
| Post types | Text, Image, Video, Carousel, Thread sequence |
| Scheduling | Yes |
| Analytics | Limited (impressions, likes, comments, shares, views) |
Client options — TypeScript · Python REST/OpenAPI · Go · Java · REST API
Before You Start
Threads has a 500-character limit — the #1 failure cause when cross-posting from LinkedIn (3,000) or Facebook (63,000). Always use target_options.threads.content to provide a shorter version. Threads is connected via Instagram — losing Instagram access means losing Threads access. Requires an Instagram Business or Creator account with Threads enabled. Threads enforces a 250 API-published posts per 24-hour window limit. RelayAPI accepts JPEG and PNG images for Threads; WebP and GIF attachments are rejected by preflight.
Location tags require the threads_location_tagging grant. Existing Threads
connections must be reauthorized before using location_id; refreshing an old
token cannot add a scope that was never granted.
Quick Start
Post to Threads:
import Relay from '@relayapi/sdk';
const client = new Relay();
const post = await client.posts.create({
content: 'Hot take: the best API is the one with the best docs.',
targets: ['threads'],
scheduled_at: 'now',
});
console.log(post.id); // post_abc123Content Types
Text Post
One of the few platforms that supports text-only posts. Up to 500 characters.
const post = await client.posts.create({
content: 'Hot take: the best API is the one with the best docs.',
targets: ['threads'],
scheduled_at: 'now',
});Image Post
const post = await client.posts.create({
content: 'New office setup!',
targets: ['threads'],
media: [
{ url: 'https://cdn.example.com/office.jpg', type: 'image' },
],
scheduled_at: 'now',
});Video Post
Maximum 100 MiB, up to 5 minutes.
const post = await client.posts.create({
content: 'Behind the scenes',
targets: ['threads'],
media: [
{ url: 'https://cdn.example.com/video.mp4', type: 'video' },
],
scheduled_at: 'now',
});Carousel (2–20 Mixed Items)
Swipeable carousel containing 2–20 images, videos, or a mix of both.
const post = await client.posts.create({
content: 'Product launch day!',
targets: ['threads'],
media: [
{ url: 'https://cdn.example.com/feature1.jpg', type: 'image' },
{ url: 'https://cdn.example.com/feature2.jpg', type: 'image' },
{ url: 'https://cdn.example.com/feature3.jpg', type: 'image' },
],
scheduled_at: 'now',
});Thread Sequence
Create a connected sequence of posts. The first item is the root post, and each subsequent item is a reply. Each item can have its own content and media.
const post = await client.posts.create({
targets: ['threads'],
scheduled_at: 'now',
target_options: {
threads: {
thread: [
{
content: "Here's a thread about API design",
media: [{ url: 'https://cdn.example.com/cover.jpg', type: 'image' }],
},
{ content: '1/ First, REST principles...' },
{ content: '2/ Authentication is crucial...' },
{ content: '3/ Always version your API! /end' },
],
},
},
});Each item in a thread sequence has its own 500-character limit. The root post is published first, and each subsequent item becomes a reply to the previous one.
Media Requirements
Images
| Property | Requirement |
|---|---|
| Max per post | 20 (subject to 20 total carousel items) |
| Formats | JPEG, PNG |
| Max file size | 8 MiB |
| Recommended | 1080 x 1350 px (4:5 portrait) |
| Aspect ratios | 4:5, 1:1, 16:9 |
Videos
| Property | Requirement |
|---|---|
| Max per post | 20 (subject to 20 total carousel items) |
| Formats | MP4, MOV |
| Max file size | 100 MiB |
| Max duration | 5 minutes |
| Recommended | 1080p, H.264, AAC, 30fps |
Carousels require 2–20 total items and may mix supported images and videos. A single image or video is published as a normal media post rather than a carousel.
target_options Fields
All fields go inside target_options.threads on your post request.
| Field | Type | Description |
|---|---|---|
content | string | Override content for Threads specifically. Use this to provide a version under 500 characters when cross-posting. |
media | object[] | Override media for Threads specifically |
thread | object[] | Array of {content, media?} for thread sequences. Each item has its own 500-char limit. |
poll | object | Text-only poll containing 2–4 unique options, each 1–25 characters; cannot be combined with a Relay thread sequence |
quote_post_id | string | Numeric Threads media ID to quote |
location_id | string | Numeric Meta location ID; requires threads_location_tagging |
topic_tag | string | Topic tag attached to the post |
reply_control | string | everyone, accounts_you_follow, or mentioned_only |
link_attachment | URL | Explicit link attachment for a text post |
await client.posts.create({
content: 'Which guide should we ship next?',
targets: ['threads'],
scheduled_at: 'now',
target_options: {
threads: {
poll: { options: ['Media', 'Webhooks', 'Analytics'] },
location_id: '123456789012345',
reply_control: 'everyone',
},
},
});Common Errors
| Error | Cause | Fix |
|---|---|---|
| Text must be at most 500 characters | Content exceeds the 500-character limit | Use target_options.threads.content to provide a shorter version. |
| Unsupported image type | Media is WebP, GIF, or another unsupported image format | Use JPEG or PNG. RelayAPI rejects unsupported Threads media before provider I/O. |
| Media download failed (2207052) | URL returns HTML instead of media bytes | Use a direct CDN URL that returns the media bytes. |
| Instagram account restricted (2207050) | Linked Instagram account has policy violations | Resolve any policy violations on the Instagram account first. |
| Publishing failed | Max retries exhausted | Usually temporary. Wait and retry manually. |
Known Quirks
- 500-character limit — the #1 failure cause when cross-posting from platforms with higher limits like LinkedIn (3,000) or Facebook (63,000).
- Connected via Instagram — losing Instagram access means losing Threads access. Requires Instagram Business or Creator account.
- JPEG and PNG only — RelayAPI rejects channel-neutral GIF and WebP attachments for Threads.
- Cannot post new top-level comments — only replies are supported.
- Cannot like or unlike comments via the API.
- Cannot edit posts after publishing.
- No DMs available via the Threads API.
- 250 API-published posts per 24-hour window — this limit applies to all content types.
- Posts cannot be edited after publishing.
Automations
Threads automation uses its own Graph base (graph.threads.net/v1.0). Replies use the 2-step container → publish flow like public Threads posts.
Triggers
| Type | Fires on |
|---|---|
threads_reply | Reply to your thread |
threads_mention | @-mention |
threads_publish | Your thread publishes (useful for chained automations) |
Send nodes
| Node | Endpoint | Required fields |
|---|---|---|
threads_reply_to_post | 2-step: POST /{user}/threads then POST /{user}/threads_publish | text, reply_to_id |
threads_hide_reply | POST /{reply-id}/manage_reply body { hide: true } | reply_id |
Found something wrong? Help us improve this page.