Skip to content

Latest commit

 

History

History
220 lines (177 loc) · 8.76 KB

list.md

File metadata and controls

220 lines (177 loc) · 8.76 KB

X thread API

The posts of a thread, from the ID of any post in it: the post it started from, the author's follow-ups and the replies in between. X sets the page size, up to about 50.

Canonical page: https://okdata.online/apis/x/threads/posts/list/

  • Endpoint: GET /v1/x/threads/posts/list
  • ID: x.threads.posts.list
  • Price: $0.0003 per post · at most $0.015 per call
  • Returns: a page of about 20 posts (data.items, Post objects)
  • Status: preview
  • Good for: Content research

Price

You pay $0.0003 for each of the posts a call returns, at least one and at most $0.015 per call (50 posts). 1,000 posts cost $0.30. An empty page is billed as one item; failed calls are not charged.

Parameters

Name Type Required Default Description
id string yes The ID of any post in the thread. (e.g. 2120216594244849690)
cursor string no Opaque cursor from page.next_cursor of the previous call. Each page is a separate call.
view string: compact / standard / full no standard How much of each object comes back. full adds platform-specific fields under ext.
fields string[] no Fields to keep, by path (author.name), to save tokens. In a query string: comma-separated.

cursor, view and fields work as described in Requests.

Returned fields

Each item is a Post.

Field Type Description
id string Post ID on the platform.
url string Link to the post.
type string What kind of post it is.
title string | null Title, where the platform has one (videos, notes, articles).
text string Caption, body or description as plain text; articles come as Markdown.
lang string | null Language code, as reported or detected.
published_at string | null When it was published, ISO 8601 in UTC; null only where the source gives no time (wechat_mp.articles.metrics, wechat_channels.videos.metrics, youtube.channels.videos.list with type=short), then listed in meta.unavailable.
author AccountRef Who posted it.
metrics Metrics Counts at fetch time; a count the platform hides is null.
images string[] | null Image URLs for image posts and notes.
hashtags string[] | null Hashtags or topics, without the # sign.
ext object Platform-specific extras. Only returned with view=full.

Request examples

cURL

curl -G "https://api.okdata.online/v1/x/threads/posts/list" \
  -H "Authorization: Bearer $OKDATA_API_KEY" \
  --data-urlencode 'id=2120216594244849690'

Python

import os
import requests

resp = requests.get(
    "https://api.okdata.online/v1/x/threads/posts/list",
    headers={"Authorization": f"Bearer {os.environ['OKDATA_API_KEY']}"},
    params={"id": "2120216594244849690"},
    timeout=60,
)
body = resp.json()
if body["code"] != "ok":
    raise RuntimeError(f"{body['code']}: {body['message']} (charged: {body['charged']})")

for row in body["data"]["items"]:
    print(row["id"], row["metrics"]["likes"], row["text"][:60])
print("cost: $" + body["meta"]["billing"]["amount"])

TypeScript

const url = new URL('https://api.okdata.online/v1/x/threads/posts/list');
url.searchParams.set('id', "2120216594244849690");

const res = await fetch(url, {
  headers: {
    Authorization: `Bearer ${process.env.OKDATA_API_KEY}`,
  },
});
const body = await res.json();
if (body.code !== 'ok') throw new Error(`${body.code}: ${body.message}`);

for (const row of body.data.items) {
  console.log(row.id, row.metrics.likes, row.text.slice(0, 60));
}
console.log('cost: $' + body.meta.billing.amount);

MCP

{
  "name": "call",
  "arguments": {
    "capability": "x.threads.posts.list",
    "id": "2120216594244849690"
  }
}

Or ask an agent that has the MCP server connected:

Use X thread and list the five posts with the most likes, with links.

Response example

{
  "code": "ok",
  "request_id": "0f6c2a8e-4b1d-4e0a-9a51-3c7d2b9e1f20",
  "data": {
    "items": [
      {
        "id": "1856012345678901234",
        "url": "https://x.com/trailkit/status/1856012345678901234",
        "type": "text",
        "title": null,
        "text": "Shipped the lighter tent today. 1.1 kg packed, same footprint.",
        "lang": "en",
        "published_at": "2026-11-02T09:30:00Z",
        "author": {
          "id": "1450012345678901248",
          "username": "trailkit",
          "name": "Trail Kit",
          "avatar_url": "https://cdn.example.com/avatar/trailkit.jpg",
          "followers": 18230,
          "verified": true
        },
        "metrics": {
          "views": 1250000,
          "likes": 48200,
          "comments": 1312,
          "shares": null,
          "favorites": 9650,
          "reposts": 640
        },
        "images": null,
        "hashtags": null
      }
    ]
  },
  "page": {
    "next_cursor": "eyJvIjoyMH0",
    "has_more": true
  },
  "warnings": [],
  "meta": {
    "capability": "x.threads.posts.list",
    "billing": {
      "amount": "0.0003",
      "currency": "USD",
      "balance_after": "9.9997"
    },
    "unavailable": {
      "metrics.shares": "platform"
    },
    "cached": false,
    "replayed": false,
    "fetched_at": "2026-11-11T08:00:00Z"
  }
}

Errors

Errors share one shape: code, request_id, message and message_zh (a fixed sentence per code), charged, retry, retry_after_seconds, param and docs_url, with the specifics in problems. They are not charged; only settlement_unknown is unknown until looked up. The full table: https://okdata.online/docs/errors/

Questions

How much does the X thread API cost?

You pay $0.0003 for each of the posts a call returns, at least one and at most $0.015 per call (50 posts). 1,000 posts cost $0.30. An empty page is billed as one item; failed calls are not charged.

How do I get the next page?

Pass page.next_cursor from the response back as the cursor parameter. When page.has_more is false there is nothing left. Every page is its own call at $0.0003.

Do I need a X (Twitter) account, cookies or a QR login?

No. You send only your okdata API key. The data is public data from X (Twitter); nothing is fetched with your own X (Twitter) account.

Am I charged when a call fails?

No. Every error response says charged: false, except a timeout, which says charged: null until the outcome is known. Resend with the same Idempotency-Key and you are never charged twice.

What if X (Twitter) does not show a field?

The field is null, and meta.unavailable names it with the reason: platform (the platform never shows it) or capability (we cannot fetch it yet).

More X (Twitter) endpoints