Skip to main content
Back to API docs

Threads keyword search API

Threads Keyword Search with the Wahdx Connection API

Keyword search finds public Threads posts that match a word or a hashtag. integrations use it to watch what people say about a topic, then decide in their own workflow whether to engage. The endpoint reads from Threads on every call and caches the answer briefly, so repeated identical queries return fast without spending API calls.

Endpoint and authentication

Authenticate with X-API-Key (server-side only) or a Bearer session. The route also requires a connected Threads account in active status. When no active account exists, the call fails with a 404 and the message names the missing account.

GET /api/engagement/keywords/search?q=kopi&searchMode=KEYWORD&searchType=TOP

Query parameters

Only q is required. The rest narrow or bound the search.

  • q: 1 to 200 characters. The word or phrase to look for.
  • searchMode: KEYWORD (default) or TAG. TAG restricts matches to hashtags.
  • searchType: TOP (default) or RECENT.
  • limit: 1 to 100, default 25.
  • since and until: Unix timestamps, both 1688540400 (2023-07-05) or later, and since must not be later than until.
  • mediaType: for example TEXT or IMAGE. Send without it to include every type.
  • accountId: internal ID of the connected Threads account to search as (accountId from GET /api/content/accounts where platform is threads). Must belong to your tenant and be active; unknown or inactive IDs return 404 ENGAGEMENT_ACCOUNT_NOT_FOUND. Without it the oldest active Threads account is used.

Choosing which Threads account searches

The search runs with the access token of one of your connected Threads accounts. List them first, then pass the chosen accountId.

  • Call GET /api/content/accounts with the same X-API-Key or Bearer session and keep entries where platform is threads.
  • Pick the accountId of the account to search as and send it as accountId. The account must belong to your tenant and be active.
  • Omit accountId only when any active account will do; the API then uses your oldest active Threads account.
  • Each accountId has its own 60 second result cache and its own Meta quota of 2200 queries per 24 hours.
curl "https://api.wahdx.com/api/content/accounts" \
  -H "X-API-Key: $WAHDX_API_KEY"

curl -G "https://api.wahdx.com/api/engagement/keywords/search" \
  -H "X-API-Key: $WAHDX_API_KEY" \
  --data-urlencode "q=kopi" \
  --data-urlencode "accountId=YOUR_CONNECTED_THREADS_ACCOUNT_ID"

Example request

A keyword search for recent posts about kopi, limited to 10 results.

curl -G "https://api.wahdx.com/api/engagement/keywords/search" \
  -H "X-API-Key: $WAHDX_API_KEY" \
  --data-urlencode "q=kopi" \
  --data-urlencode "searchMode=KEYWORD" \
  --data-urlencode "searchType=RECENT" \
  --data-urlencode "limit=10"

Response shape

Every result carries the post text, author, timestamp, and a permalink to open it on Threads. Media URLs are signed CDN links that expire after a few days, so the response is cached for 60 seconds only and the URLs are never written to the database.

{
  "success": true,
  "data": {
    "available": true,
    "keywordSearchEnabled": true,
    "scope": "threads_keyword_search",
    "searchScope": "public",
    "disclosure": "Results include public posts from other accounts, so the keyword search permission is active.",
    "query": { "q": "kopi", "searchMode": "KEYWORD", "searchType": "RECENT", "limit": 10, "since": null, "until": null, "mediaType": null },
    "results": [
      {
        "mediaId": "18000000000000000",
        "text": "kopi tubruk pagi ini enak",
        "mediaType": "TEXT_POST",
        "mediaUrl": null,
        "thumbnailUrl": null,
        "altText": null,
        "permalink": "https://www.threads.com/@someone/post/abc",
        "timestamp": "2026-09-01T10:00:00.000Z",
        "username": "someone",
        "hasReplies": false,
        "isReply": false,
        "isQuotePost": false
      }
    ],
    "truncated": false,
    "cached": false
  }
}

Reading searchScope before acting on results

Meta limits an unapproved app to the authenticated user’s own posts. That state is honest but easy to misread as "nothing matched". The searchScope field tells the integration which of the three states the results reflect.

  • public: results include posts from other accounts, so the permission is active.
  • self_only: every result belongs to the authenticated user, so public search is not active yet.
  • unknown: no results, which means either nothing matched or public search is not active. The disclosure field states this in plain language.

Limits and quotas

Three layers guard the endpoint, plus the limits Threads itself enforces.

  • Burst limit: 10 requests per 60 seconds per tenant. Free is stopped by the plan gate first; paid plans can make 10 calls in each window.
  • The Free plan cannot use keyword search and receives 403 ENGAGEMENT_KEYWORD_PLAN_REQUIRED. Premium, Professional, and Maximum can search without a plan daily cap; the burst limit and Meta quota still apply.
  • Result cache: identical queries for the same accountId hit a 60 second cache and do not call Threads again.
  • Meta quota: 2200 keyword search queries per 24 hours per Threads user behind accountId, shared across every app that calls with that user token. Empty results do not count.
  • A timestamp before 1688540400 is rejected with a 400.
  • Threads needs the threads_keyword_search permission approved before search reaches other people’s posts. It stays off by default behind THREADS_ENABLE_KEYWORD_SEARCH until App Review grants it.

Errors

Failures name their cause in plain language so the integration can react without guessing.

  • 400 ENGAGEMENT_INVALID_KEYWORD_QUERY: q is missing or too long, or a mode, type, or timestamp value is invalid.
  • 401 ENGAGEMENT_KEYWORD_TOKEN_EXPIRED: the Threads session expired, so reconnect the account.
  • 404 ENGAGEMENT_ACCOUNT_NOT_FOUND: no active Threads account for this tenant, or the requested accountId is unknown, inactive, or belongs to another tenant.
  • 403 ENGAGEMENT_KEYWORD_PLAN_REQUIRED: public keyword search requires Premium, Professional, or Maximum. This applies to API-key and Bearer authentication. The check happens before the burst limiter and before any request is sent to Threads.
  • 429 ENGAGEMENT_KEYWORD_RATE_LIMITED: Threads is throttling, so back off and retry later.
  • 504 ENGAGEMENT_KEYWORD_TIMEOUT: Threads took too long to search; try again. The request timeout is configurable with THREADS_KEYWORD_SEARCH_TIMEOUT_MS (default: 30000 ms).
  • 502 ENGAGEMENT_KEYWORD_SEARCH_FAILED: Threads is temporarily unavailable.

FAQ

Common questions about this API topic.

Why does search return only my own posts?

Threads restricts an unapproved app to the authenticated user’s own posts. Check searchScope: self_only means exactly that state, and the disclosure field says it in plain language. After Meta approves the threads_keyword_search permission, the same queries return public posts.

Do empty results consume the daily quota?

Meta does not count empty results against its own quota. Paid plans have no daily quota from Wahdx, but every request still passes through the burst limiter. Identical queries inside the 60 second cache window return the cached answer without a new call.

How do I choose which account searches?

Call GET /api/content/accounts, keep entries where platform is threads, then send the chosen accountId to GET /api/engagement/keywords/search. Omit accountId only when any active account will do; the API then uses your oldest active Threads account.

How do I read my own recent searches?

Call GET /api/engagement/keywords/recent with the same accountId you searched with. It reads the authenticated user’s own recently_searched_keywords field, so it never exposes another account’s queries.

Can I store the media URLs from the results?

Do not store them. They are signed CDN links that expire after a few days. Request them fresh when the post needs to render.