Meta Threads
OpenQuok CLI examples for Meta Threads — single posts, reply chains, the reconciliation flow, and per-post analytics.
Connect your agent today
Draft from chat, review in your calendar, and publish only what you approve.
Channel quick reference
| Property | Value |
|---|---|
| Provider identifier | threads |
| Max content length | 500 characters |
| Required attachments | None — text-only posts publish |
| OAuth scopes | threads_basic, threads_content_publish, threads_manage_replies, threads_manage_insights, threads_manage_mentions |
| OAuth setup | Meta Threads |
THREADS_ID=$(openquok integrations:list | jq -r '.[] | select(.identifier=="threads") | .id') Simple text post
openquok posts:create
-s "2026-01-15T10:00:00Z"
-t schedule
-c "Hello Threads!"
-i "$THREADS_ID" Post with media
Threads accepts at most one image or video per post. Upload the asset and pass the returned id/path:
MEDIA=$(openquok upload ./hero.png | jq -c '[{id: .data.id, path: .data.filePath}]')
openquok posts:create
-s "2026-01-15T10:00:00Z"
-t schedule
-c "Launch day. 🚀"
-i "$THREADS_ID"
-m "$MEDIA" Note
Threads caps every caption at 500 characters — main post, follow-up threads.replies[].message, thread finisher, engagement plug, and cross-account comment text. posts:create with -t schedule returns 400 when any of those fields exceed the cap (for example Threads caption exceeds 500 characters (523/500).). Use -t draft to save longer copy programmatically.
Scheduled reply chain (thread + follow-ups)
providerSettings.threads.replies[] carries follow-up comments that are published from the same account a fixed number of seconds after the main thread. Pass them on posts:create with --providerSettingsByIntegrationId:openquok posts:create
-s "2026-01-15T10:00:00Z"
-c "Thread 1/3: Why we built OpenQuok"
-i "$THREADS_ID"
--providerSettingsByIntegrationId "$(jq -nc --arg id "$THREADS_ID" '
{
($id): {
threads: {
replies: [
{ message: "Thread 2/3: The architecture", delaySeconds: 60 },
{ message: "Thread 3/3: What is next", delaySeconds: 120 }
]
}
}
}
')" Thread reply with image or video
Upload the reply attachment first, then nest media on the matching replies[] row (same { id, path } shape as the main post). Threads supports images and video on follow-ups:
REPLY_MEDIA=$(openquok upload ./thread-slide.jpg | jq -c '[{id: .data.id, path: (.data.path // .data.filePath)}]')
openquok posts:create
-s "2026-01-15T10:00:00Z"
-c "Thread 1/2: Why we built OpenQuok"
-i "$THREADS_ID"
--providerSettingsByIntegrationId "$(jq -nc
--arg id "$THREADS_ID"
--argjson media "$REPLY_MEDIA" '
{
($id): {
threads: {
replies: [
{ message: "Thread 2/2: Architecture diagram", delaySeconds: 60, media: $media }
]
}
}
}
')" JSON recipe: threads-follow-up-reply-with-image.json in the agent skill examples folder (agent/skills/openquok-core/resources/examples/).
Add a “finisher” reply
Enable a closing reply (e.g. “Thanks for reading!”) by toggling providerSettings.threads.enabled and providing a message:
openquok posts:create
-s "2026-01-15T10:00:00Z"
-c "Thread 1/2: A short story"
-i "$THREADS_ID"
--providerSettingsByIntegrationId "$(jq -nc --arg id "$THREADS_ID" '
{
($id): {
threads: {
enabled: true,
message: "Thanks for reading — like and follow for more!",
replies: [
{ message: "Thread 2/2: The punchline", delaySeconds: 30 }
]
}
}
}
')" Delayed engagement reply
providerSettings.threads.internalEngagementPlug schedules a same-account engagement reply (a self-comment that boosts engagement signals) some seconds after publish:openquok posts:create
-s "2026-01-15T10:00:00Z"
-c "Big announcement! 🧵"
-i "$THREADS_ID"
--providerSettingsByIntegrationId "$(jq -nc --arg id "$THREADS_ID" '
{
($id): {
internalEngagementPlug: {
enabled: true,
message: "Have questions? Reply here and I will get back to you.",
delaySeconds: 300
}
}
}
')" Cross-account comments
providerSettings.threads.crossAccountPlugs schedules comments from other Threads channels in the same workspace after the publishing channel’s post goes live. Use plug name threads-cross-account-comment and list acting channel UUIDs in integrationIds (not the publishing channel).Connect two or more Threads integrations, then pass settings on the publishing integration id:
THREADS_PUBLISH_ID=$(openquok integrations:list | jq -r '.[] | select(.identifier=="threads") | .id' | head -n1)
THREADS_OTHER_ID=$(openquok integrations:list | jq -r '.[] | select(.identifier=="threads") | .id' | sed -n '2p')
openquok posts:create
-s "2026-01-15T10:00:00Z"
-c "Main thread from our brand account"
-i "$THREADS_PUBLISH_ID"
--providerSettingsByIntegrationId "$(jq -nc
--arg publish "$THREADS_PUBLISH_ID"
--arg other "$THREADS_OTHER_ID" '
{
($publish): {
threads: {
crossAccountPlugs: [
{
plugName: "threads-cross-account-comment",
enabled: true,
delayMs: 120000,
integrationIds: [$other],
fields: { comment: "Great thread — sharing from our other account." }
}
]
}
}
}
')" Same account vs cross-account
threads.internalEngagementPlug (above) runs a delayed reply from the publishing channel. threads.crossAccountPlugs runs actions from other connected channels. delayMs is in milliseconds (120000 = two minutes; 300000 = five minutes; the web composer defaults to two minutes for Threads).
Mentions scope (cross-account only)
When the acting channel is not the publisher, OpenQuok publishes the comment using the threads_manage_mentions OAuth scope. Add it in your Meta app, then reconnect every acting Threads integration so stored tokens include the new scope.
Meta's allowed usage is replying to posts where the acting account is @mentioned. OpenQuok calls the reply API with the acting channel's threads_manage_mentions token and does not modify the root caption. Use a short delayMs (for example 120000) so the main thread is fully live before the comment runs.
Cross-account comments also need Advanced Access for threads_manage_mentions via Meta App Review (screencast: OAuth grant including the scope → publish → other workspace channel comments). Until that is approved, replies involving non-tester accounts may fail in production.
Reconnect a “missing” Threads post
Threads occasionally fails to return a release_id immediately after publish; OpenQuok stores it as missing and per-post analytics stay locked until you link the real id manually.
POST_ID=$(openquok posts:list
--start "2026-01-01T00:00:00Z"
--end "2026-02-01T00:00:00Z"
| jq -r --arg id "$THREADS_ID" '
.[]
| select(.integration_id==$id and .release_id=="missing")
| .id
'
| head -n1)
openquok posts:missing "$POST_ID" | jq '.[] | {id, url}'
openquok posts:connect "$POST_ID" -r "7321456789012345678"
openquok analytics:post "$POST_ID" -d 7 Per-channel analytics
openquok analytics:platform "$THREADS_ID" -d 30
| jq '.[] | {label, percentageChange}' openquok analytics:post <post-id> -d 30
| jq '.[] | {label, latest: .data[-1].total}'