Give your AI agent a Twitter reader: tool definitions for Claude, ChatGPT and Codex

Give your AI agent a Twitter reader: tool definitions for Claude, ChatGPT and Codex
Unif Agents team
Unif Agents team

Updated

Copy-paste tool definitions that let Claude, ChatGPT or Codex read public X profiles, posts, replies and search with no API key.

In short: an agent needs four things to read X: a URL it can build from a handle or a post link, a JSON or Markdown body it can read, an error it can branch on, and a limit it can see. The /tools/x/* endpoints on UnifAPI give all four for free, with no login, no account and no API key, rate-limited per visitor, so a tool definition is one HTTP GET. Below are the definitions for Anthropic's tool use, OpenAI's function calling, and a SKILL.md for Claude Code and Codex, plus the three rules the model needs: read X-RateLimit-Remaining, sleep on Retry-After, and ask for Markdown when the result is going straight into the context window.

Models already know how to read X. Ask Claude or ChatGPT what @NASA posted this week and they will try: a web search, a fetch of x.com, maybe a Nitter URL from training data. The search returns summaries, the fetch returns a JavaScript shell, and the Nitter instance is gone. The model then guesses or apologizes.

The fix is not a smarter model, it is a tool with a URL the model can construct and a body it can parse. This post is that tool, in the three formats people actually deploy.

The endpoints

Eight GETs, all under https://unifapi.com/tools/x/, all documented with a captured sample in /llms.txt:

EndpointParametersReturns
profilehandle, posts (1–10), cursorprofile, engagement rate, recent posts
postid (id or URL)one post with author, media, quoted post, metrics
mediahandle, cursoran account's photo and video posts
repliesid, sort=top|recent, cursorthe post and its public replies
searchq, cursorrecent posts; X search operators work
trendslocation or woeidwhat is trending now
communitiesq, cursorpublic X Communities for a topic
followershandle, kind=followers|followingwho follows an account, or who it follows

Every list is { items, hasMore, nextCursor }. Every error is { error: { type, message } }. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset; a 429 adds Retry-After. Add format=md for Markdown.

Anthropic tool use

One tool with an endpoint enum is simpler for the model than eight tools, and the parameter names match the API so the model cannot get them wrong.

{
  "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"]
  }
}

The handler is one line of fetch:

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")) };
}

Returning remaining to the model is deliberate. Told it has 3 lookups left, Claude stops paging and answers with what it has instead of hitting the wall.

OpenAI function calling

Same shape, OpenAI's wrapper:

{
  "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"]
    }
  }
}

A SKILL.md for Claude Code and Codex

Coding agents do not need a tool definition; they need to be told the URL pattern exists. A skill file in the project does that:

---
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

With that file present, "check what @NASA posted this week and summarize the replies to the top post" becomes two curls the agent writes itself.

Three rules for the model

  1. Read the remaining count before paging. X-RateLimit-Remaining is on every response. A profile plus its replies plus one search is 4 units of 20; a loop that pages everything is 20 units in a minute.
  2. Sleep on Retry-After, never retry in a loop. The 429 body also names the upgrade URL; if the task is bigger than the free tier, that is where it goes, with a workspace key and the same field names.
  3. Ask for Markdown when the result is for reading, JSON when it is for code. format=md renders a profile and its posts in a few hundred tokens with no quoting overhead. JSON is for when the model is going to filter, sort, or hand the data to another tool.

MCP

If the agent already speaks MCP, the metered X API is on the UnifAPI MCP server with the same operations (profile, post, search, replies, followers, and more) behind a workspace key, pay as you go. The free HTTP endpoints above are for the case where there is no key yet, or the task is a handful of reads that should not need one.

FAQ

Can Claude read Twitter?

With a tool, yes. Claude cannot load x.com itself (the page is a JavaScript shell), but given the read_x tool above it reads profiles, posts, replies, and search results as JSON and reasons over them.

Can ChatGPT read tweets?

Same answer: with the function definition above, in a custom GPT or an Assistants workflow. ChatGPT's built-in browsing cannot render x.com.

Does this need an API key?

No. The endpoints are free to use with no key, login or account, and rate-limited per visitor: 20 lookups per 10 minutes, a search counting as two. A key is only for uncapped, metered access.

Is there a limit on what an agent can do with the data?

The endpoints read public data only. Protected accounts return their existence and counts, never their posts. Use the data the way X's terms allow for public content.