Dê ao seu agente de IA um leitor de Twitter: definições de ferramenta para Claude, ChatGPT e Codex

Dê ao seu agente de IA um leitor de Twitter: definições de ferramenta para Claude, ChatGPT e Codex
Unif Agents team
Unif Agents team

Atualizado

Definições de ferramentas prontas para copiar que deixam Claude, ChatGPT ou Codex ler perfis, posts, respostas e buscas do X sem API key.

Em resumo: um agente precisa de quatro coisas para ler o X: uma URL que ele consiga montar a partir de um handle ou do link de um post, um corpo em JSON ou Markdown que ele consiga ler, um erro no qual ele consiga ramificar e um limite que ele consiga ver. Os endpoints /tools/x/* da UnifAPI entregam as quatro grátis, sem fazer login, sem precisar de conta e sem chave de API, com limite por visitante, então uma definição de ferramenta é um único HTTP GET. Abaixo estão as definições para o tool use da Anthropic, o function calling da OpenAI e um SKILL.md para o Claude Code e o Codex, mais as três regras de que o modelo precisa: ler X-RateLimit-Remaining, dormir pelo Retry-After e pedir Markdown quando o resultado vai direto para a janela de contexto.

Os modelos já sabem como ler o X. Pergunte ao Claude ou ao ChatGPT o que a @NASA postou nesta semana e eles vão tentar: uma busca na web, um fetch de x.com, talvez uma URL do Nitter vinda dos dados de treinamento. A busca devolve resumos, o fetch devolve uma casca em JavaScript e a instância do Nitter não existe mais. O modelo então chuta ou pede desculpas.

A solução não é um modelo mais inteligente, é uma ferramenta com uma URL que o modelo consiga construir e um corpo que ele consiga interpretar. Este post é essa ferramenta, nos três formatos que as pessoas realmente colocam em produção.

Os endpoints

Oito GETs, todos em https://unifapi.com/tools/x/, todos documentados com uma amostra capturada em /llms.txt:

EndpointParâmetrosRetorna
profilehandle, posts (1–10), cursorperfil, taxa de engajamento, posts recentes
postid (id ou URL)um post com autor, mídia, post citado, métricas
mediahandle, cursoros posts com foto e vídeo de uma conta
repliesid, sort=top|recent, cursoro post e suas respostas públicas
searchq, cursorposts recentes; os operadores de busca do X funcionam
trendslocation ou woeido que está em alta agora
communitiesq, cursorComunidades públicas do X para um tema
followershandle, kind=followers|followingquem segue uma conta, ou quem ela segue

Toda lista é { items, hasMore, nextCursor }. Todo erro é { error: { type, message } }. Toda resposta traz X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset; um 429 acrescenta Retry-After. Adicione format=md para Markdown.

Tool use da Anthropic

Uma única ferramenta com um enum endpoint é mais simples para o modelo do que oito ferramentas, e os nomes dos parâmetros batem com os da API, então o modelo não tem como errá-los.

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

O handler é uma linha de 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")) };
}

Devolver remaining ao modelo é intencional. Sabendo que tem 3 consultas restantes, o Claude para de paginar e responde com o que tem em vez de bater na parede.

Function calling da OpenAI

Mesmo formato, no invólucro da 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"]
    }
  }
}

Um SKILL.md para o Claude Code e o Codex

Agentes de programação não precisam de definição de ferramenta; precisam que alguém diga a eles que o padrão de URL existe. Um arquivo de skill no projeto faz isso:

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

Com esse arquivo presente, "veja o que a @NASA postou nesta semana e resuma as respostas ao post principal" vira dois curls que o agente escreve sozinho.

Três regras para o modelo

  1. Leia a contagem restante antes de paginar. X-RateLimit-Remaining está em toda resposta. Um perfil mais as respostas dele mais uma busca são 4 unidades de 20; um loop que pagina tudo gasta 20 unidades em um minuto.
  2. Durma pelo Retry-After, nunca tente de novo em loop. O corpo do 429 também informa a URL de upgrade; se a tarefa é maior do que o nível gratuito, é para lá que ela vai, com uma chave de workspace e os mesmos nomes de campo.
  3. Peça Markdown quando o resultado for para leitura, JSON quando for para código. format=md renderiza um perfil e seus posts em algumas centenas de tokens, sem o custo das aspas. JSON é para quando o modelo vai filtrar, ordenar ou passar os dados para outra ferramenta.

MCP

Se o agente já fala MCP, a API medida do X está no servidor MCP da UnifAPI com as mesmas operações (perfil, post, busca, respostas, seguidores e mais) atrás de uma chave de workspace, pay as you go. Os endpoints HTTP gratuitos acima são para o caso em que ainda não há chave, ou em que a tarefa é um punhado de leituras que não deveria precisar de uma.

FAQ

O Claude consegue ler o Twitter?

Com uma ferramenta, sim. O Claude não consegue carregar o x.com por conta própria (a página é uma casca em JavaScript), mas, com a ferramenta read_x acima, ele lê perfis, posts, respostas e resultados de busca em JSON e raciocina sobre eles.

O ChatGPT consegue ler tweets?

Mesma resposta: com a definição de function acima, em um GPT personalizado ou em um fluxo com Assistants. A navegação embutida do ChatGPT não consegue renderizar o x.com.

Isso precisa de chave de API?

Não. Os endpoints são grátis, sem chave, sem fazer login e sem precisar de conta, com limite por visitante: 20 consultas a cada 10 minutos, e uma busca conta como duas. A chave serve só para acesso medido, sem teto.

Existe limite para o que um agente pode fazer com os dados?

Os endpoints leem apenas dados públicos. Contas protegidas devolvem sua existência e suas contagens, nunca seus posts. Use os dados do jeito que os termos do X permitem para conteúdo público.