AI 에이전트에게 트위터 리더를 쥐여 주세요: Claude, ChatGPT, Codex용 도구 정의

AI 에이전트에게 트위터 리더를 쥐여 주세요: Claude, ChatGPT, Codex용 도구 정의
Unif Agents team
Unif Agents team

업데이트

Claude, ChatGPT, Codex가 API 키 없이 공개 X 프로필, 게시물, 답글, 검색을 읽게 해 주는 복사해서 쓰는 도구 정의.

한마디로: 에이전트가 X를 읽으려면 네 가지가 필요합니다. 핸들이나 게시물 링크로 만들 수 있는 URL, 읽을 수 있는 JSON 또는 Markdown 본문, 분기할 수 있는 오류, 그리고 볼 수 있는 제한입니다. UnifAPI의 /tools/x/* 엔드포인트는 로그인 없이, 계정 없이, API 키 없이 이 네 가지를 모두 무료로 제공하고 방문자별 횟수 제한이 적용되므로, 도구 정의는 HTTP GET 하나입니다. 아래에 Anthropic의 tool use, OpenAI의 function calling, 그리고 Claude Code와 Codex용 SKILL.md 정의를 실었고, 모델에 필요한 세 가지 규칙도 함께 담았습니다. X-RateLimit-Remaining을 읽을 것, Retry-After만큼 쉴 것, 결과가 컨텍스트 윈도에 바로 들어갈 때는 Markdown을 요청할 것.

모델은 이미 X를 읽는 법을 압니다. Claude나 ChatGPT에게 @NASA가 이번 주에 무엇을 올렸는지 물어보면 시도는 합니다. 웹 검색, x.com 가져오기, 어쩌면 학습 데이터에서 꺼낸 Nitter URL까지. 검색은 요약을 돌려주고, 가져오기는 JavaScript 껍데기를 돌려주고, Nitter 인스턴스는 사라졌습니다. 그러면 모델은 추측하거나 사과합니다.

해결책은 더 똑똑한 모델이 아니라, 모델이 구성할 수 있는 URL과 파싱할 수 있는 본문을 가진 도구입니다. 이 글이 그 도구이고, 사람들이 실제로 배포하는 세 가지 형식으로 담았습니다.

엔드포인트

GET 여덟 개, 모두 https://unifapi.com/tools/x/ 아래에 있고, 모두 /llms.txt에 캡처된 샘플과 함께 문서화되어 있습니다.

엔드포인트파라미터반환
profilehandle, posts (1–10), cursor프로필, 참여율, 최근 게시물
postid (id 또는 URL)작성자, 미디어, 인용 게시물, 지표가 포함된 게시물 하나
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 tool use

endpoint enum을 가진 도구 하나가 모델에게는 도구 여덟 개보다 단순하고, 파라미터 이름이 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 한 줄입니다.

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

같은 모양을 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가 이번 주에 올린 글을 확인하고 가장 인기 있는 게시물의 답글을 요약해 줘"는 에이전트가 직접 작성하는 curl 두 번이 됩니다.

모델을 위한 세 가지 규칙

  1. 페이지를 넘기기 전에 남은 횟수를 읽습니다. X-RateLimit-Remaining은 모든 응답에 있습니다. 프로필 하나에 그 답글과 검색 한 번이면 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가 트위터를 읽을 수 있나요?

도구가 있으면 가능합니다. Claude는 x.com 자체를 불러올 수 없지만(페이지가 JavaScript 껍데기입니다), 위의 read_x 도구가 있으면 프로필, 게시물, 답글, 검색 결과를 JSON으로 읽고 그 위에서 추론합니다.

ChatGPT가 트윗을 읽을 수 있나요?

같은 답입니다. 위의 함수 정의를 커스텀 GPT나 Assistants 워크플로에 넣으면 됩니다. ChatGPT의 내장 브라우징은 x.com을 렌더링하지 못합니다.

API 키가 필요한가요?

아니요. 엔드포인트는 키 없이, 로그인 없이, 계정 없이 무료로 쓸 수 있으며, 방문자별 횟수 제한이 적용됩니다. 10분에 20회 조회이고, 검색은 2회로 계산됩니다. 키는 상한 없는 종량제 접근에만 필요합니다.

에이전트가 데이터로 할 수 있는 일에 제한이 있나요?

엔드포인트는 공개 데이터만 읽습니다. 보호된 계정은 존재 여부와 수치만 반환하고 게시물은 절대 반환하지 않습니다. 공개 콘텐츠에 대해 X의 약관이 허용하는 방식으로 데이터를 사용하세요.