AI エージェントに Twitter リーダーを持たせる: Claude、ChatGPT、Codex 向けのツール定義

AI エージェントに Twitter リーダーを持たせる: Claude、ChatGPT、Codex 向けのツール定義
Unif Agents team
Unif Agents team

更新日

Claude・ChatGPT・Codex が API キーなしで公開 X のプロフィール、投稿、返信、検索を読めるようにする、コピペで使えるツール定義。

要点: エージェントが X を読むには 4 つのものが要ります。ハンドルや投稿リンクから組み立てられる URL、読める JSON か Markdown の本文、分岐できるエラー、そして見える上限です。UnifAPI の /tools/x/* エンドポイントは、ログインもアカウントも API キーもなしに、訪問者ごとのレート制限付きでこの 4 つを無料で提供するので、ツール定義は HTTP GET 1 回で済みます。以下に、Anthropic のツール使用、OpenAI の関数呼び出し、Claude Code と Codex 向けの SKILL.md の定義を載せます。加えて、モデルに必要な 3 つのルールも。X-RateLimit-Remaining を読む、Retry-After の分だけ待つ、結果をそのままコンテキストウィンドウに入れるときは Markdown を要求する、です。

モデルはすでに X の読み方を知っています。Claude や ChatGPT に @NASA が今週何を投稿したか尋ねれば、試みはします。Web 検索、x.com のフェッチ、ことによると学習データにあった Nitter の URL。検索が返すのは要約、フェッチが返すのは JavaScript のシェル、Nitter のインスタンスはもうありません。モデルはそこで推測するか、謝ります。

解決策は賢いモデルではなく、モデルが組み立てられる URL と、パースできる本文を持つツールです。この記事はそのツールを、実際に人が使っている 3 つの形式で示します。

エンドポイント

8 つの GET。すべて https://unifapi.com/tools/x/ 配下で、すべて実際に取得したサンプル付きで /llms.txt にドキュメント化されています。

エンドポイントパラメータ返すもの
profilehandle、posts(1–10)、cursorプロフィール、エンゲージメント率、最近の投稿
postid(id または URL)著者、メディア、引用元の投稿、指標を含む投稿 1 件
mediahandle、cursorアカウントの写真・動画投稿
repliesid、sort=top|recent、cursor投稿とその公開返信
searchq、cursor最近の投稿。X の検索演算子が使える
trendslocation または woeid今トレンドのもの
communitiesq、cursorトピックに関する公開 X コミュニティ
followershandle、kind=followers|followingアカウントのフォロワー、またはフォロー中の相手

リストはすべて { items, hasMore, nextCursor }。エラーはすべて { error: { type, message } }。すべてのレスポンスに X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset が付き、429 には Retry-After が加わります。Markdown が欲しければ format=md を付けてください。

Anthropic のツール使用

endpoint の enum を持つツール 1 つのほうが、8 つのツールよりモデルには単純で、パラメータ名は API と一致しているのでモデルが間違えようがありません。

{
  "name": "read_x",
  "description": "Read public X (Twitter) data with no account: a profile and its recent posts, one post, an account's media, the replies under a post, a search over recent posts (X operators like from:, since:, min_faves:, filter:media work), trends by location, Communities by topic, or an account's followers/following. Free, rate-limited to 20 requests per 10 minutes; search costs 2. Returns JSON. Pass cursor from a previous result to page.",
  "input_schema": {
    "type": "object",
    "properties": {
      "endpoint": {
        "type": "string",
        "enum": [
          "profile",
          "post",
          "media",
          "replies",
          "search",
          "trends",
          "communities",
          "followers"
        ]
      },
      "handle": {
        "type": "string",
        "description": "X handle or profile URL (profile, media, followers)"
      },
      "id": { "type": "string", "description": "Post ID or x.com status URL (post, replies)" },
      "q": { "type": "string", "description": "Search query or Communities topic" },
      "location": {
        "type": "string",
        "description": "Trends location slug: worldwide, us, uk, japan, india, brazil, korea, germany, spain, mexico"
      },
      "sort": { "type": "string", "enum": ["top", "recent"] },
      "kind": { "type": "string", "enum": ["followers", "following"] },
      "cursor": { "type": "string" }
    },
    "required": ["endpoint"]
  }
}

ハンドラーは fetch 1 行です。

async function readX(input: { endpoint: string } & Record<string, string | undefined>) {
  const { endpoint, ...params } = input;
  const qs = new URLSearchParams(Object.entries(params).filter(([, v]) => v) as [string, string][]);
  const res = await fetch(`https://unifapi.com/tools/x/${endpoint}?${qs}`, {
    headers: { Accept: "application/json" },
  });
  const body = await res.json();
  if (res.status === 429)
    return { error: body.error, retryAfterSeconds: Number(res.headers.get("Retry-After")) };
  return { ...body, remaining: Number(res.headers.get("X-RateLimit-Remaining")) };
}

remaining をモデルに返すのは意図的です。残り 3 回と知らされれば、Claude はページングをやめ、壁にぶつかる代わりに手元にあるもので答えます。

OpenAI の関数呼び出し

同じ形を、OpenAI のラッパーで。

{
  "type": "function",
  "function": {
    "name": "read_x",
    "description": "Read public X (Twitter) data with no account. endpoint=profile|post|media|replies|search|trends|communities|followers. Free, 20 requests per 10 minutes. Returns JSON with { items, hasMore, nextCursor } for lists.",
    "parameters": {
      "type": "object",
      "properties": {
        "endpoint": {
          "type": "string",
          "enum": [
            "profile",
            "post",
            "media",
            "replies",
            "search",
            "trends",
            "communities",
            "followers"
          ]
        },
        "handle": { "type": "string" },
        "id": { "type": "string" },
        "q": { "type": "string" },
        "location": { "type": "string" },
        "sort": { "type": "string", "enum": ["top", "recent"] },
        "kind": { "type": "string", "enum": ["followers", "following"] },
        "cursor": { "type": "string" }
      },
      "required": ["endpoint"]
    }
  }
}

Claude Code と Codex 向けの SKILL.md

コーディングエージェントにツール定義は要りません。必要なのは、この URL パターンが存在すると教えてやることです。プロジェクト内のスキルファイルがそれをします。

---
name: read-x
description: Read public X (Twitter) profiles, posts, replies, search, trends, communities, and followers as JSON or Markdown with no API key, via https://unifapi.com/tools/x/*. Use when a task needs what an account or post says on X.
---

# Reading X

All endpoints are GET https://unifapi.com/tools/x/<endpoint>. No key. Free limit 20 requests
per 10 minutes (search costs 2), printed in X-RateLimit-* headers; on 429 wait Retry-After.

- profile?handle=nasa profile + recent posts (+ posts=1-10, cursor)
- post?id=<id or x.com URL> one post
- media?handle=nasa photo/video posts
- replies?id=<id>&sort=top replies under a post (sort=recent for newest)
- search?q=from:nasa%20filter:media recent posts; X operators work
- trends?location=us trending now
- communities?q=ai X Communities
- followers?handle=nasa&kind=following

Add &format=md for Markdown when the result goes into context. Lists are { items, hasMore,
nextCursor }; pass cursor= to page. Errors are { error: { type, message } }.
Full reference with samples: https://unifapi.com/llms.txt

このファイルがあれば、「@NASA が今週投稿したものを確認して、一番人気の投稿への返信を要約して」は、エージェントが自分で書く 2 つの curl になります。

モデルのための 3 つのルール

  1. ページングの前に残り回数を読む。 X-RateLimit-Remaining はすべてのレスポンスにあります。プロフィール 1 件とその返信と検索 1 回で、20 のうち 4 ユニット。すべてをページングするループなら、1 分で 20 ユニットです。
  2. Retry-After の分だけ待つ。ループで再試行しない。 429 の本文には upgrade の URL も書かれています。タスクが無料枠より大きいなら、行き先はそこです。ワークスペースキーで、フィールド名は同じです。
  3. 読むための結果なら Markdown、コードのための結果なら JSON を要求する。 format=md はプロフィールとその投稿を数百トークンで、引用符のオーバーヘッドなしに描画します。JSON は、モデルがデータをフィルタ、ソート、または別のツールへ渡すときのためのものです。

MCP

エージェントがすでに MCP を話せるなら、従量課金の X API は UnifAPI MCP サーバーにあり、同じ操作(プロフィール、投稿、検索、返信、フォロワーなど)をワークスペースキーの背後で従量課金で提供します。上の無料 HTTP エンドポイントは、まだキーがない場合や、キーを要するほどでもない数件の読み取りのためのものです。

FAQ

Claude は Twitter を読めますか?

ツールがあれば読めます。Claude 自身は x.com を読み込めません(ページは JavaScript のシェルです)が、上の read_x ツールを与えれば、プロフィール、投稿、返信、検索結果を JSON として読み、それをもとに推論します。

ChatGPT はツイートを読めますか?

答えは同じです。上の関数定義を使えば、カスタム GPT や Assistants のワークフローで読めます。ChatGPT 組み込みのブラウジングは x.com を描画できません。

API キーは必要ですか?

いいえ。エンドポイントは無料で使え、キーもログインもアカウントも不要で、訪問者ごとのレート制限があります。10 分あたり 20 回の照会で、検索は 2 回分として数えます。キーは、上限なしの従量課金アクセスのためだけのものです。

エージェントがデータでできることに制限はありますか?

エンドポイントは公開データだけを読みます。保護されたアカウントは存在とカウントを返しますが、投稿は決して返しません。公開コンテンツについて X の規約が許す範囲でデータを使ってください。