twitterapi-io Agent Skill

repository·main·Indexed 19 days ago

https://github.com/kaitoinfra/twitterapi-io

An official agent skill providing AI agents with a REST API to read, write, and monitor real-time data from Twitter/X. It enables capabilities such as user profile retrieval, tweet searching, posting, liking, following, and real-time monitoring via webhooks and WebSockets without official OAuth. The skill supports integration with AI coding agents like Claude Code, Cursor, GitHub Copilot, Cline, and Windsurf via `npx skills add`.

Tokens
13.1K
Snippets
22
Records
42
Agent score
67%

What's inside twitterapi-io

  1. Core facts for twitterapi.io integration

    main

    The twitterapi-io skill provides a REST API to query Twitter/X data and perform authenticated actions without OAuth.

    Connection Details:

    • Base URL: https://api.twitterapi.io
    • Authentication: Use the x-api-key header with your API key.
    • Rate Limit: Approximately 200 requests per second per client.
    • Pricing Model: Pay-per-request (e.g., ~$0.15/1k tweets, ~$0.18/1k profiles).
    curl -s "https://api.twitterapi.io/twitter/user/info?userName=elonmusk" \
      -H "x-api-key: $TWITTERAPI_IO_KEY"
  2. Best practices for session and proxy management

    main

    When managing login_cookies and proxies for automated writes, follow these security and stability rules:

    • Session Security: Store login_cookies encrypted at rest. They function as session tokens.
    • Proxy Pinning: Pin one proxy per login session. Using the same login_cookies from different proxies is a high-risk signal that looks like a compromised account and will trigger X's anti-bot protections.
    • Error Handling: Cookies expire. You must catch 401 errors or the cookie_expired error code and trigger a re-login flow.
    • Credential Safety: Never log or commit login_cookies, proxy URLs, or totp_secret values to version control or logs.
  3. Rules for parameter naming and response shapes

    main

    When interacting with the API, be aware of these three critical rules to avoid errors:

    1. Parameter naming is per-endpoint

    There is no universal casing rule (snake_case vs camelCase). You must use the exact parameter name defined for each specific endpoint.

    • /twitter/user/followers?userName= (camelCase)
    • /twitter/user/verifiedFollowers?user_id= (snake_case)
    • /twitter/user/articles?username= (lowercase)

    2. Writes require specific body fields

    Every write/authenticated endpoint requires three components in the request body:

    1. login_cookies: A base64-encoded JSON string obtained from the /twitter/user_login_v2 endpoint.
    2. proxy: An HTTP/SOCKS proxy URL (configured in your twitterapi.io dashboard).
    3. Action-specific fields: These are almost always snake_case (e.g., tweet_id, user_id).

    3. Response shapes vary per endpoint

    Do not assume a consistent JSON structure. Responses follow different patterns:

    • data-wrapped: r["data"]["..."] (e.g., user/info, trends).
    • Flat with envelope: r["followers"] or r["tweets"] (e.g., followers, replies).
    • Flat without envelope: The root object contains the data directly (e.g., advanced_search).
    • Named top-level field: r["community_info"] (e.g., community/info).

    Best Practice: Use defensive access to handle varying shapes: r.get("tweets", r.get("data", {}).get("tweets", [])).

  4. How to perform write operations (Post, Like, Follow, etc.)

    main

    Write operations (e.g., posting a tweet, liking, following, or sending DMs) require a session established via login_cookies.

    To enable this, your agent can use your X account's credentials (email/username/password) to call the /twitter/user_login_v2 endpoint, which exchanges them for a login_cookies session. All subsequent write requests must follow the 'three-things-in-body' rule: include login_cookies, a proxy, and the specific action fields.

  5. Handle different API response envelope variants

    main

    The API returns data in several different JSON structures depending on the endpoint. To ensure your code is resilient, do not hardcode a single path to the data. Instead, use a fallback pattern to check for the most common locations of the result set.

    Recommended extraction pattern: r.get("tweets", r.get("data", {}).get("tweets", []))

    Known Response Variants:

    1. data-wrapped: Data is nested inside a data object. Used by: user/info, user_about, last_tweets, tweet_timeline, trends, check_follow, last_tweets/v2.
      • Format: { "status": "success", "msg": "success", "data": { ... } }
    2. flat-list with envelope: The list is at the top level alongside pagination metadata. Used by: followers, followings, replies, mentions, list/tweets_timeline, community/tweets.
      • Format: { "tweets": [...], "has_next_page": true, "next_cursor": "...", "status": "success", "msg": "success" }
    3. flat-list WITHOUT envelope: The list is at the top level with no metadata envelope. Used by: advanced_search, thread_context, user/search, get_tweets_from_all_community.
      • Format: { "tweets": [...], "has_next_page": true, "next_cursor": "..." }
    4. named top-level field: The data is under a specific named key. Used by: community/info, batch_info_by_ids, oapi/my/info.
      • Format: { "status": "success", "msg": "success", "community_info": {...} }

    Note on error/status messages: Most endpoints use the key msg for status messages, but check_follow_relationship uniquely uses the key message instead.

    // 1. data-wrapped
    { "status": "success", "msg": "success", "data": { ... } }
    
    // 2. flat-list with envelope
    { "tweets": [...], "has_next_page": true, "next_cursor": "...", "status": "success", "msg": "success" }
    
    // 3. flat-list WITHOUT envelope
    { "tweets": [...], "has_next_page": true, "next_cursor": "..." }
    
    // 4. named top-level field
    { "status": "success", "msg": "success", "community_info": {...} }
  6. Important API Conventions and Error Formats

    main

    When integrating with twitterapi.io, adhere to these critical rules:

    Parameter Naming

    Do not normalize parameter names. Parameter naming is per-endpoint and inconsistent. For example, one endpoint might use userName while another uses user_id or username. Always copy the exact name from the endpoint documentation.

    Response Shapes

    Response envelopes vary. Do not assume a universal structure. Some endpoints wrap data in a data: {...} object, some spread fields at the top level (e.g., tweets[], has_next_page, next_cursor), and others use named top-level keys like community_info or users.

    Error Handling

    • HTTP 400 / 422 / 500: Returns a FastAPI-style error: {"detail": "<reason>"}.
    • Semantic Failures (HTTP 200): If the request succeeds but the action failed, the response will be: {"status": "error", "msg": "..."}.
  7. Structure write operation request bodies

    main

    Every endpoint that modifies state on behalf of an X account requires three core components in the JSON body, plus the x-api-key header:

    1. login_cookies (plural): The base64-encoded JSON session token obtained from /twitter/user_login_v2.
    2. proxy: The HTTP/SOCKS proxy URL configured on your twitterapi.io dashboard.
    3. Action-specific fields: Parameters specific to the task (e.g., tweet_text, user_id), typically using snake_case.

    Important Implementation Details:

    • Most write endpoints use POST.
    • Profile updates use PATCH.
    • There is no DELETE method; instead, use specific 'un-' endpoints (e.g., unlike_tweet_v2 instead of deleting a like).
    import os, requests
    
    BASE = "https://api.twitterapi.io"
    HEADERS = {
        "x-api-key": os.environ["TWITTERAPI_IO_KEY"],
        "Content-Type": "application/json",
    }
    
    # Example: Create tweet
    body = {
        "login_cookies": cookies,
        "proxy":         "http://user:pass@host:port",
        "tweet_text":    "Hello from twitterapi.io",
        # optional: reply_to_tweet_id, quote_tweet_id, community_id, 
        # media_ids=["id1","id2"], attachment_url, is_note_tweet, 
        # schedule_for="2026-01-20T10:00:00.000Z"
    }
    r = requests.post(f"{BASE}/twitter/create_tweet_v2", json=body, headers=HEADERS)
  8. Implement the Write flow (Login, Tweet, Media)

    main

    Write operations require a login_cookies object obtained via the twitter/user_login_v2 endpoint.

    Important Technical Details:

    • Cookies: login_cookies is a base64-encoded JSON string. Pass it back to the API verbatim.
    • Media Uploads: Use multipart/form-data for upload_media_v2 and update_avatar_v2. Do not manually set the Content-Type header when using requests with the files parameter; let the library handle it.
    • Tweet Creation: Use tweet_text (not text) in the JSON body for create_tweet_v2.
    • Profile Updates: Use PATCH with JSON. Use description (not bio) to update the profile text.
    • Bookmarks: Use count (not pageSize) in the body for bookmarks_v2.
    # Example: Login, Upload Media, and Tweet
    import os, requests
    
    BASE = "https://api.twitterapi.io"
    H = {"x-api-key": os.environ["TWITTERAPI_IO_KEY"], "Content-Type": "application/json"}
    
    def login_v2(user_name, email, password, proxy, totp_secret=None):
        body = {"user_name": user_name, "email": email, "password": password, "proxy": proxy}
        if totp_secret: body["totp_secret"] = totp_secret
        r = requests.post(f"{BASE}/twitter/user_login_v2", json=body, headers=H)
        r.raise_for_status()
        return r.json()["login_cookies"]
    
    def upload_media(cookies, proxy, path, media_category=None, is_long_video=False):
        mime = "video/mp4" if path.lower().endswith(".mp4") else "image/jpeg"
        with open(path, "rb") as f:
            files = {"file": (os.path.basename(path), f, mime)}
            data  = {"login_cookies": cookies, "proxy": proxy, "is_long_video": str(is_long_video).lower()}
            if media_category: data["media_category"] = media_category
            r = requests.post(f"{BASE}/twitter/upload_media_v2", files=files, data=data, 
                               headers={"x-api-key": os.environ["TWITTERAPI_IO_KEY"]})
        r.raise_for_status(); return r.json()
    
    def create_tweet(cookies, proxy, text, *, media_ids=None):
        body = {"login_cookies": cookies, "proxy": proxy, "tweet_text": text}
        if media_ids: body["media_ids"] = media_ids
        r = requests.post(f"{BASE}/twitter/create_tweet_v2", json=body, headers=H)
        r.raise_for_status(); return r.json()
    
    # Execution
    cookies = login_v2(os.environ["X_USER"], os.environ["X_EMAIL"], os.environ["X_PASSWORD"], os.environ["X_PROXY"])
    proxy = os.environ["X_PROXY"]
    media_id = upload_media(cookies, proxy, "photo.jpg")["media_id"]
    create_tweet(cookies, proxy, "Check this out", media_ids=[media_id])
  9. Paginate through list endpoints

    main

    List endpoints provide pagination via next_cursor (string) and has_next_page (boolean). To avoid infinite loops or errors, always terminate your pagination loop when has_next_page is false. Do not rely solely on checking if the cursor is empty or null.

    cursor = ""
    while True:
        r = requests.get(url, params={..., "cursor": cursor}, headers=HEADERS).json()
        yield from r.get("items", [])
        if not r.get("has_next_page"):
            break
        cursor = r.get("next_cursor") or ""
  10. Perform authenticated writes (Post, Like, Follow, etc.)

    main

    To perform actions like posting tweets, liking, or following users, you must use a POST or PATCH request containing login_cookies and a proxy in the body.

    Required Body Fields for Writes:

    • login_cookies: Base64-encoded JSON from /twitter/user_login_v2.
    • proxy: Your configured proxy URL.
    • Action fields:
      • create_tweet_v2: Use tweet_text (not text) and reply_to_tweet_id (not in_reply_to_tweet_id).
      • update_profile_v2: Use description (not bio).
      • bookmarks_v2: Use count (not pageSize).
      • send_dm_to_user: Requires user_id and text.
  11. Monitor specific users for tweets

    main

    If you have a subscription, you can monitor specific users.

    Note: When adding a user, use the field x_user_name (the handle), NOT the user_id.

    1. Add user: POST to /oapi/x_user_stream/add_user_to_monitor_tweet with x_user_name.
    2. List monitored users: GET /oapi/x_user_stream/get_user_to_monitor_tweet. This returns an id_for_user.
    3. Remove user: POST to /oapi/x_user_stream/remove_user_to_monitor_tweet using the id_for_user obtained from the list step.
    # 1. Add user (use handle)
    requests.post(f"{BASE}/oapi/x_user_stream/add_user_to_monitor_tweet",
                  json={"x_user_name": "elonmusk"}, headers=H)
    
    # 2. List monitored users to get the internal ID
    data = requests.get(f"{BASE}/oapi/x_user_stream/get_user_to_monitor_tweet",
                        headers=H).json()
    for row in data.get("data", []):
        print(f"User: {row['x_user_screen_name']}, ID for removal: {row['id_for_user']}")
    
    # 3. Remove user (use id_for_user)
    requests.post(f"{BASE}/oapi/x_user_stream/remove_user_to_monitor_tweet",
                  json={"id_for_user": "abc123..."}, headers=H)