本文へスキップ

Go(Golang)でのTwitter API:動作するコード

GoでTwitter/Xのデータを取得するには、net/http でHTTPSリクエストを送り、JSONを型付き構造体にデコードして、カーソルでページを順にたどります。Xは公式のGo SDKを出しておらず、公式X APIは投稿の読み取り1件ごとに$0.005を課金します。

要点: Xが公式SDKを公開しているのはPythonとTypeScriptだけなので、Goのコードは net/http か、gotwiなどのコミュニティのライブラリを通じて公式X APIを呼び出します。開発者アカウントと前払いクレジットが必要で、投稿1件$0.005です。公開データなら、net/http からSorsa APIを使えます。キー1つで、検索1リクエストあたり20ツイート、1,000件あたり$0.07からで、100回分の無料リクエストで2,000件を取得できます。

エンドポイント
POST /search-tweets
1ページの件数
1リクエストあたり20ツイート
料金
1,000ツイートあたり$0.07から

#Xは公式のGo SDKを公開していますか?

いいえ。Xの開発者向けドキュメントに載っている公式SDKはPythonとTypeScriptだけで、GoはXが保守していないコミュニティのライブラリとして扱われています(Xのツールとライブラリのページ(新しいタブで開きます))。Xが公式に出している唯一のGoプロジェクトはxurlで、ターミナルで実行するコマンドラインツールであり、importして使うパッケージではありません。

そのため選択肢はコミュニティのクライアントになりますが、今もリリースが続いているのは1つだけです。日付は各プロジェクトのGitHubの履歴に基づきます。

Goクライアント最新リリース2026年11月2日時点の状態
gotwiv0.18.5、2026年8月21日保守中。OAuth 1.0a、ベアラートークン、PKCEでv2のほとんどのエンドポイントに対応
go-twitter v2(g8rswimmer)v2.1.5、2022年12月29日ほぼ4年間リリースなし
go-twitter(dghubble)2022年にアーカイブ廃止されたv1.1 API向け
  • gotwi

    最新リリース
    v0.18.5、2026年8月21日
    2026年11月2日時点の状態
    保守中。OAuth 1.0a、ベアラートークン、PKCEでv2のほとんどのエンドポイントに対応
  • go-twitter v2(g8rswimmer)

    最新リリース
    v2.1.5、2022年12月29日
    2026年11月2日時点の状態
    ほぼ4年間リリースなし
  • go-twitter(dghubble)

    最新リリース
    2022年にアーカイブ
    2026年11月2日時点の状態
    廃止されたv1.1 API向け

ライブラリが整えるのはリクエストの形だけです。呼び出しのたびにXの前払いクレジットが消費されます。投稿は1件$0.005、ユーザーは1人$0.010で、月間300万件の投稿読み取りが上限です(Xの料金表(新しいタブで開きます))。2026年11月22日からは、expansionで返る投稿者とメディアも同じ料金で課金されるため(Xの10月23日の通知(新しいタブで開きます))、多くのGoコードが送る検索は、投稿の料金の最大3倍かかります。

x-api.go
q := url.Values{
	"query":       {"golang lang:en"},
	"max_results": {"100"},
	"expansions":  {"author_id"},
}
req, _ := http.NewRequestWithContext(ctx, http.MethodGet,
	"https://api.x.com/2/tweets/search/recent?"+q.Encode(), nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("X_BEARER_TOKEN"))
// 100 posts: $0.50. From Nov 22, 2026, with authors: up to $1.50

公式の経路で最初の呼び出しの前に必要なもの

  • console.x.comの開発者アカウント。Xアカウントで開設し、Xの開発者契約への同意が必要です。
  • アプリと、そのベアラートークン。Xが表示するのは一度だけです。
  • 事前に購入しておくクレジット。レスポンスが返す投稿とユーザーのすべてに消費されます。
  • 投稿、いいね、フォローを行う操作のためのOAuthユーザートークン。

公開の投稿、プロフィール、フォロワーの読み取りには、これらは何も要りません。TweepyとxdkについてPythonで同じ選択をする場合は、PythonでのTwitter APIのガイドにまとめています。このページの残りは、Goで同じ作業を行います。

#APIキー1つで、Goからツイートを取得する

GoでXのデータを読むには、Twitter(X)API兼スクレイピングツールであるSorsa APIを使います。OAuthの代わりに ApiKey ヘッダー1つで、無料アカウントを含む全プラン毎秒20リクエスト、2006年までさかのぼる検索ができ、検索結果は1,000ツイートあたり$0.07から、各ツイートに投稿者の完全なプロフィールが含まれます。標準ライブラリだけでクライアントになります。net/http がリクエストを送り、encoding/json が応答を読むので、go.mod に依存関係は増えません。Sorsa APIがXのログイン情報を求めることはないので、Xアカウントにリスクはありません。

  1. キーを取得する。 登録して、ダッシュボードからAPIキーをコピーします。最初の100リクエストは無料で、カードは不要、有効期限もありません。

  2. モジュールを作り、キーを保存する。 キーは環境変数に置き、APIの呼び出しはサーバー側のコードからだけ行います。ブラウザや、ユーザーに配布するアプリからは決して呼び出さないでください。このガイドのコードにはGo 1.25以降が必要です。

    Terminal
    go mod init example.com/xdata
    export SORSA_API_KEY="your_key"  # PowerShell: $env:SORSA_API_KEY="your_key"
    
  3. ツイートを検索する。 Xの検索構文のクエリを、ApiKey ヘッダーにキーを入れて POST /search-tweets(新しいタブで開きます) に送り、go run . を実行します。

    package main
    
    import (
    	"bytes"
    	"encoding/json"
    	"fmt"
    	"log"
    	"net/http"
    	"os"
    	"time"
    )
    
    func main() {
    	key := os.Getenv("SORSA_API_KEY") // or "YOUR_API_KEY" to try it
    	body, _ := json.Marshal(map[string]string{
    		"query": "golang lang:en",
    		"order": "latest",
    	})
    	req, err := http.NewRequest(http.MethodPost,
    		"https://api.sorsa.io/v3/search-tweets", bytes.NewReader(body))
    	if err != nil {
    		log.Fatal(err)
    	}
    	req.Header.Set("ApiKey", key)
    	req.Header.Set("Content-Type", "application/json")
    
    	client := &http.Client{Timeout: 30 * time.Second}
    	resp, err := client.Do(req)
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer resp.Body.Close()
    	if resp.StatusCode != http.StatusOK {
    		log.Fatalf("sorsa: %s", resp.Status)
    	}
    
    	var page struct {
    		Tweets []struct {
    			ID       string `json:"id"`
    			FullText string `json:"full_text"`
    			User     struct {
    				Username string `json:"username"`
    			} `json:"user"`
    		} `json:"tweets"`
    		NextCursor string `json:"next_cursor"`
    	}
    	if err := json.NewDecoder(resp.Body).Decode(&page); err != nil {
    		log.Fatal(err)
    	}
    	for _, t := range page.Tweets {
    		fmt.Println(t.User.Username, t.FullText)
    	}
    }
    
  4. ページを読む。 各レスポンスには最大20件のツイートと、次のページ用の next_cursor が入っています。以下の値は、文書化された形に合わせたデモデータです。

    Response200 OK
    {
      "tweets": [
        {
          "id": "1985611093417201664",
          "full_text": "Rewrote our ingest worker with range-over-func iterators.",
          "created_at": "2026-11-01T21:14:52Z",
          "lang": "en",
          "likes_count": 57,
          "retweet_count": 9,
          "reply_count": 4,
          "quote_count": 2,
          "view_count": 4120,
          "bookmark_count": 11,
          "is_reply": false,
          "quoted_status": null,
          "user": {
            "id": "1601739518260654080",
            "username": "gopher_ingest_notes",
            "display_name": "Gopher Ingest Notes",
            "created_at": "2022-12-11T09:03:18Z",
            "followers_count": 2310
          }
        }
      ],
      "next_cursor": "DAABCgABF7Zq0xLwAAA"
    }
    

すべてのツイートが、各種カウントと投稿者つきで届くので、リクエストにexpansionやフィールドのリストは不要で、1リクエストの料金は何を返しても変わりません。

#型付き構造体でレスポンスをモデル化する

コードが読むフィールドについて、Tweet と User の構造体を1つずつ定義すれば、ツイートまたはユーザーを返すSorsa APIのどのエンドポイントも、同じ2つの形で返すのでそのままデコードできます。これらは小さなパッケージ(ここでは sorsa)に入れ、次の節のクライアントの隣に置きます。

sorsa/types.go
package sorsa

import "time"

type Tweet struct {
	ID              string    `json:"id"`
	FullText        string    `json:"full_text"`
	CreatedAt       time.Time `json:"created_at"`
	Lang            string    `json:"lang"`
	LikesCount      int       `json:"likes_count"`
	RetweetCount    int       `json:"retweet_count"`
	ReplyCount      int       `json:"reply_count"`
	QuoteCount      int       `json:"quote_count"`
	ViewCount       int       `json:"view_count"`
	IsReply         bool      `json:"is_reply"`
	User            User      `json:"user"`
	QuotedStatus    *Tweet    `json:"quoted_status"`
	RetweetedStatus *Tweet    `json:"retweeted_status"`
}

type User struct {
	ID              string    `json:"id"`
	Username        string    `json:"username"`
	DisplayName     string    `json:"display_name"`
	Description     string    `json:"description"`
	CreatedAt       time.Time `json:"created_at"`
	FollowersCount  int       `json:"followers_count"`
	FollowingsCount int       `json:"followings_count"`
	TweetsCount     int       `json:"tweets_count"`
	Verified        bool      `json:"verified"`
}

// TweetsPage and UsersPage are the two list shapes. NextCursor
// is empty on the last page.
type TweetsPage struct {
	Tweets     []Tweet `json:"tweets"`
	NextCursor string  `json:"next_cursor"`
}

type UsersPage struct {
	Users      []User `json:"users"`
	NextCursor string `json:"next_cursor"`
}

この構造体では、3つの選択が他のすべてより重要です。IDは19桁でJSONの文字列として届くので、string にデコードします。int64 のフィールドだと、デコーダーが「cannot unmarshal string into Go struct field」で止まります。日付はISO 8601のUTCで届き、time.Time が追加のコードなしで読み取れます。引用またはリポストされたツイートは、ツイートの中のツイートなのでポインターが必要です。Goは自分自身を値として含む構造体を受け付けず、ポインターは投稿が何も引用していなければ nil のままです。

Sorsa API tweet JSON mapped to a Go Tweet struct SORSA APIのJSON GOの構造体フィールド "id": "1985611093417201664" int64ではなくstring ID string "created_at": "2026-11-01T21:14:52Z" ISO 8601、UTC CreatedAt time.Time "likes_count": 57 タグがキーと一致 LikesCount int "user": { "username": ... } ネストした構造体 User User "quoted_status": null 無いときはnil QuotedStatus *Tweet ツイートオブジェクト1つ。どのユーザーエンドポイントでも同じUser形式が返る
IDには文字列、日付にはtime.Time、入れ子のツイートにはポインター。構造体がデコードできるかを左右する3つの型です。

どのJSONキーにも一致しないタグは、Goではエラーになりません。フィールドがゼロ値のままになるだけです。公式X APIから移したコードで最初に現れます。Xはカウントを public_metrics の中で like_count と呼びますが、Sorsa APIはツイート自体に likes_count を返すからです。下で必要なフィールドを選ぶと、タグがAPIリファレンスに一致する構造体をコピーできます。

ツイートのフィールド
投稿者のフィールド(user)
structをコピー
タグと型は、Sorsa APIの最新のOpenAPIリファレンスに従っています。チェックを外したフィールドは、encoding/jsonが読み飛ばします。

The tags and types follow Sorsa API's live OpenAPI reference; unticked fields are simply skipped by encoding/json.

#リトライとペーサーを備えたクライアントを1つ作る

キー、HTTPクライアント、リトライのルール、レートのペーサーを1つの Client にまとめ、その1つの値をすべてのgoroutineで共有します。http.Client は並行利用に対して安全で、開始のタイミングを刻むティッカーも同様です。以降の例はすべて、その Do メソッドを呼び出します。

sorsa/client.go
package sorsa

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"time"
)

const baseURL = "https://api.sorsa.io/v3"

// Client is safe for concurrent use: share one per API key.
type Client struct {
	key  string
	http *http.Client
	pace *time.Ticker
}

// New paces requests at 19 starts a second, one under the limit.
// Pass the key from os.Getenv("SORSA_API_KEY"), or "YOUR_API_KEY".
func New(key string) *Client {
	return &Client{
		key:  key,
		http: &http.Client{Timeout: 30 * time.Second},
		pace: time.NewTicker(time.Second / 19),
	}
}

// APIError is a reply other than 200, with the API's message.
type APIError struct {
	Status  int    `json:"-"`
	Message string `json:"message"`
}

func (e *APIError) Error() string {
	return fmt.Sprintf("sorsa: %d %s", e.Status, e.Message)
}

// Do sends one request and decodes the reply into out. It retries
// 429 and 5xx up to 4 times, after 1, 2, 4 and 8 seconds.
func (c *Client) Do(ctx context.Context, method, path string,
	query url.Values, body, out any) error {
	var payload []byte
	if body != nil {
		var err error
		if payload, err = json.Marshal(body); err != nil {
			return err
		}
	}
	u := baseURL + path
	if len(query) > 0 {
		u += "?" + query.Encode()
	}
	for attempt := 0; ; attempt++ {
		select { // wait for a start slot
		case <-c.pace.C:
		case <-ctx.Done():
			return ctx.Err()
		}
		req, err := http.NewRequestWithContext(ctx, method, u,
			bytes.NewReader(payload))
		if err != nil {
			return err
		}
		req.Header.Set("ApiKey", c.key)
		req.Header.Set("Content-Type", "application/json")
		resp, err := c.http.Do(req)
		if err != nil {
			return err
		}
		if resp.StatusCode == http.StatusOK {
			defer resp.Body.Close()
			return json.NewDecoder(resp.Body).Decode(out)
		}
		apiErr := &APIError{Status: resp.StatusCode}
		_ = json.NewDecoder(io.LimitReader(resp.Body, 4096)).Decode(apiErr)
		resp.Body.Close()
		retry := resp.StatusCode == http.StatusTooManyRequests ||
			resp.StatusCode >= 500
		if !retry || attempt == 4 {
			return apiErr
		}
		select {
		case <-time.After(time.Second << attempt):
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

429は、キーで1秒間に20を超えるリクエストを開始したことを意味し、5xxはサーバー側の一時的な障害です。どちらも再試行する価値があります。401と403は違います。キーが誤っているか、ない、あるいはリクエストを使い切っており、どれなのかは Message が教えてくれます。失敗したリクエストに課金されることはないので、再試行に費用はかかりません。またSorsa APIのエンドポイントはすべて読み取りだけなので、/search-tweets のようなPOSTを再試行しても、何かが二重に作られることはありません。

ティッカーは1/19秒ごとに開始の枠を1つ配り、再試行を含むすべての試行が1つ待ちます。毎秒19なら、ペーサーはあと1回分の余裕を残すので、プログラムが待機中にためたティックが、Sorsa APIの上限である20を超える1秒を作ることはありません。コンテキストは両方の待機を同時に終わらせるため、キャンセルされたジョブは、待機させていたリクエストを送らずに止まります。呼び出し側は errors.As でエラーを見分けます。

Go
var apiErr *sorsa.APIError
if errors.As(err, &apiErr) && apiErr.Status == http.StatusForbidden {
	log.Fatal("check the key or its remaining requests: ", apiErr.Message)
}

#イテレーターで結果のページを順にたどる

カーソルは、次の結果ページへの不透明なキーです。next_cursor をそのまま送り返し、カーソルのないレスポンスが来たら止めます。検索とタイムラインは1ページあたり最大20件のツイートを返し、残りがあってもページの件数が少ないことがあるので、件数で最後に着いたかどうかを判断することはできません。Go 1.23から、関数を for range ループで回せるようになったので、カーソルのループ全体を1つのメソッドにまとめられます。

sorsa/pages.go
package sorsa

import (
	"context"
	"iter"
	"net/http"
	"net/url"
)

// SearchPages yields pages of tweets until the cursor runs out
// or maxPages pages have been read.
func (c *Client) SearchPages(ctx context.Context, query string,
	maxPages int) iter.Seq2[[]Tweet, error] {
	return func(yield func([]Tweet, error) bool) {
		body := map[string]string{"query": query, "order": "latest"}
		for range maxPages {
			var page TweetsPage
			err := c.Do(ctx, http.MethodPost, "/search-tweets",
				nil, body, &page)
			if !yield(page.Tweets, err) || err != nil {
				return
			}
			next := page.NextCursor
			if next == "" || next == body["next_cursor"] {
				return
			}
			body["next_cursor"] = next
		}
	}
}

// Followers yields an account's followers, up to 200 a page.
func (c *Client) Followers(ctx context.Context, username string,
	maxPages int) iter.Seq2[[]User, error] {
	return func(yield func([]User, error) bool) {
		q := url.Values{"username": {username}}
		for range maxPages {
			var page UsersPage
			err := c.Do(ctx, http.MethodGet, "/followers",
				q, nil, &page)
			if !yield(page.Users, err) || err != nil {
				return
			}
			next := page.NextCursor
			if next == "" || next == q.Get("next_cursor") {
				return
			}
			q.Set("next_cursor", next)
		}
	}
}

呼び出し側は、ほかのスライスと同じようにページを読み、break するとリクエストがその場で止まります。

main.go
client := sorsa.New(os.Getenv("SORSA_API_KEY"))
ctx := context.Background()

for tweets, err := range client.SearchPages(ctx, "golang lang:en", 50) {
	if err != nil {
		log.Fatal(err)
	}
	for _, t := range tweets {
		fmt.Println(t.ID, t.CreatedAt.Format(time.DateOnly), t.User.Username)
	}
}

POSTのエンドポイントはカーソルをJSON本文で受け取り、GETのエンドポイントはクエリパラメーターで受け取ります。2つのメソッドの違いはそれだけです。GET /followers(新しいタブで開きます) は1ページあたり最大200件のプロフィールを返すので、フォロワー100万人のリストには約5,000リクエストかかります。そのようなリストをCSVに書き出す方法は、Twitterフォロワーのエクスポートのガイドにあります。maxPages は予算として扱い、広いクエリでも計画以上に使わないようにしてください。1つのカーソルをたどっている間は、クエリを変えないでください。カーソルの繰り返しを検出するチェックは安価な安全策で、そのままでは1ページに予算のすべてを使い切るループを終わらせます。

#1リクエストで100件のツイートまたはプロフィールを取得する

IDやユーザー名がすでに手元にあるときは、バッチで送ります。POST /tweet-info-bulk(新しいタブで開きます) は最大100件のツイートIDまたはリンクを、GET /info-batch(新しいタブで開きます) は最大100件のユーザー名を、それぞれ1リクエストで受け取ります。slices.Chunk がリストを分割してくれます。

Go
var tweets []sorsa.Tweet
for batch := range slices.Chunk(tweetIDs, 100) {
	var page sorsa.TweetsPage
	body := map[string][]string{"tweet_links": batch}
	err := client.Do(ctx, http.MethodPost, "/tweet-info-bulk",
		nil, body, &page)
	if err != nil {
		return err
	}
	tweets = append(tweets, page.Tweets...)
}

var users []sorsa.User
for batch := range slices.Chunk(usernames, 100) {
	var page sorsa.UsersPage
	q := url.Values{"usernames": batch} // usernames=a&usernames=b
	err := client.Do(ctx, http.MethodGet, "/info-batch", q, nil, &page)
	if err != nil {
		return err
	}
	users = append(users, page.Users...)
}

1リクエストの料金は何を返しても同じなので、100ツイートのバッチも、ツイート1件の料金と同じです。1,000ツイートあたり$0.014からで、公式X APIの$5.00の250分の1を下回ります。url.Values は文字列スライスのマップなので、バッチはそのまま入れられ、GET /info-batch が読む繰り返しのパラメーターとして送られます。バッチのレスポンスにカーソルはなく、1つのレスポンスにバッチ全体が tweets または users の下に入っています。

#毎秒20リクエストの範囲でgoroutineを動かす

Sorsa APIはキー1つにつき、全プラン毎秒20リクエストまで認めており、作業を独立したカーソルの連鎖に分けられれば、Goプログラムは少数のgoroutineのプールでその速度に届きます。1つの連鎖は分けられません。各ページには前のページのカーソルが必要だからです。そこで、各goroutineに連鎖をまるごと渡します。1つのアカウント、または検索の1日分ずつです。共有クライアントがすべてのペースを調整します。

sorsa/collect.go
package sorsa

import (
	"context"
	"errors"
	"fmt"
	"sync"
)

// Collect runs every query through a pool of workers. They share
// one Client, so its pacer caps the key's total rate.
func Collect(ctx context.Context, c *Client, queries []string,
	workers int) ([]Tweet, error) {
	jobs := make(chan string)
	var (
		mu   sync.Mutex
		all  []Tweet
		errs []error
		wg   sync.WaitGroup
	)
	for range workers {
		wg.Go(func() {
			for q := range jobs {
				for tweets, err := range c.SearchPages(ctx, q, 100) {
					mu.Lock()
					if err != nil {
						errs = append(errs, fmt.Errorf("%s: %w", q, err))
					} else {
						all = append(all, tweets...)
					}
					mu.Unlock()
				}
			}
		})
	}
	for _, q := range queries {
		jobs <- q
	}
	close(jobs)
	wg.Wait()
	return all, errors.Join(errs...)
}

長い検索は since: と until: 演算子で日ごとに分割し、その日をプールに渡します。

main.go
start := time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC)
var days []string
for d := range 31 { // October 1 to November 1
	day := start.AddDate(0, 0, d)
	days = append(days, fmt.Sprintf("golang lang:en since:%s until:%s",
		day.Format(time.DateOnly), day.AddDate(0, 0, 1).Format(time.DateOnly)))
}
tweets, err := sorsa.Collect(ctx, client, days, 20)
A Go worker pool sharing one pacer under Sorsa API's 20 requests per second ジョブチャネル ゴルーチン20個、各1チェーン 共有クライアント1つ since:10-01 since:10-02 since:10-03 since:10-31 1日1クエリ ゴルーチン1 ゴルーチン2 ゴルーチン3 ゴルーチン20 next_cursorでページをつなぐ c.pace 19 全ワーカー共通で 1秒に1回開始 Sorsa API 20 毎秒 APIキーごと all []Tweet muで保護
ワーカーの数が同時に動く連鎖の数を決め、共有Clientの1つのティッカーが速度を決めます。

ワーカーは20個あれば十分です。時間のほとんどは次のティックを待つことに使われ、ワーカーを増やしても待つ時間が長くなるだけです。Go 1.25で追加された wg.Go は、goroutineの開始とカウントを1回の呼び出しで行います。Client はキーごと、プロセスごとに1つにしてください。クライアントを持つ2つのプロセスは、合わせて上限の2倍で開始できてしまうので、プロセスごとに別のキーを使うか、レートを半分ずつにします。毎秒20リクエストなら、100件のバッチで毎秒最大2,000ツイート、フォロワーのページで4,000プロフィール、検索ページで400ツイートを処理できます。

知っておきたいこと

Collect は、ある日が失敗しても続行します。そのクエリとともにエラーを保存し、最後に errors.Join ですべて返します。最初の失敗で実行全体を止めたい場合は、コンテキストを context.WithCancel で包み、エラーを保存する箇所で cancel() を呼びます。ペーサーとリトライの待機は、コンテキストが終了するとすぐに戻ります。

#Goのジョブの費用

Sorsa APIは、エンドポイントが何を返しても、リクエスト単位で課金します。100件のバッチで取得するツイートは1,000件あたり$0.014から、検索結果は$0.07から、フォロワーは$0.007からで、失敗したリクエストには課金されません。公式X APIは代わりにオブジェクト単位で課金します。投稿は1,000件あたり$5.00、2026年11月22日からは、リクエストで各投稿の投稿者をexpansionすると1,000件あたり最大$15.00です。

GoサービスがIDで読むツイート数

公式X API

$5,000

ツイート100万件の場合

1投稿$0.005

Sorsa API

$14から

ツイート100万件の場合

1,000件あたり$0.014から

250分の1を下回るコスト

1,000リクエストあたりの単価はStarterの$4.90からHyperscaleの$1.40までで、プランはその単価をすべてのエンドポイントに同じように適用します。サービスは残高を無料で確認できます。GET /key-usage-info(新しいタブで開きます) は、リクエストとして数えられることがありません。

Go
var usage struct {
	Remaining  int       `json:"remaining_requests"`
	ValidUntil time.Time `json:"valid_until"`
}
err := client.Do(ctx, http.MethodGet, "/key-usage-info", nil, nil, &usage)
if err == nil {
	fmt.Println(usage.Remaining, "left until", usage.ValidUntil)
}

#gotwiやgo-twitterのコードをSorsa APIに移す

gotwiとgo-twitterは、公式X APIのv2のパスを薄く包んだだけの層なので、その呼び出しはパスでSorsa APIに対応づけられます。下の各パスにSorsa APIのエンドポイントが1つずつあり、コードは短くなります。すべてのツイートが投稿者とカウントをすでに持つため、expansionもフィールドのリストも要らなくなるからです。

公式X API v2のパスSorsa APIのエンドポイント変わる点
GET /2/tweets/search/recentPOST /search-tweets直近7日間だけでなく、2006年までの任意の日付
GET /2/tweets/search/allPOST /search-tweets同じエンドポイントで since: と until: を使用
GET /2/users/:id/tweetsPOST /user-tweets(新しいタブで開きます)ユーザー名だけで足り、3,200投稿の上限なし
GET /2/tweetsPOST /tweet-info-bulk1リクエストで100件のIDまたはリンク
GET /2/users/byGET /info-batch1リクエストで100件のユーザー名
GET /2/users/:id/followersGET /followers1ページ200件のフォロワー
GET /2/tweets/:id/retweeted_byPOST /retweeters(新しいタブで開きます)同じカーソルのループ
POST /2/tweetsなし読み取り専用。投稿は公式X API経由
  • GET /2/tweets/search/recent

    Sorsa APIのエンドポイント
    POST /search-tweets
    変わる点
    直近7日間だけでなく、2006年までの任意の日付
  • GET /2/tweets/search/all

    Sorsa APIのエンドポイント
    POST /search-tweets
    変わる点
    同じエンドポイントで since: と until: を使用
  • GET /2/users/:id/tweets

    変わる点
    ユーザー名だけで足り、3,200投稿の上限なし
  • GET /2/tweets

    Sorsa APIのエンドポイント
    POST /tweet-info-bulk
    変わる点
    1リクエストで100件のIDまたはリンク
  • GET /2/users/by

    Sorsa APIのエンドポイント
    GET /info-batch
    変わる点
    1リクエストで100件のユーザー名
  • GET /2/users/:id/followers

    Sorsa APIのエンドポイント
    GET /followers
    変わる点
    1ページ200件のフォロワー
  • GET /2/tweets/:id/retweeted_by

    変わる点
    同じカーソルのループ
  • POST /2/tweets

    Sorsa APIのエンドポイント
    なし
    変わる点
    読み取り専用。投稿は公式X API経由

フィールド名もパスとともに変わります。text は full_text、public_metrics.like_count は likes_count、author_id と includes.users は入れ子の user、meta.next_token は next_cursor になります。レート制限の処理も小さくなります。Xの15分の枠が、毎秒リセットされる1つの制限に置き換わるからです。コードベース全体を移すには、v2のすべてのエンドポイントとレスポンスのフィールドを対応づけたTwitter/X APIからの移行のガイドを参照してください。

#今日から、Goで最初の検索を実行する

GoにXのデータを取り込むのに必要なのは、標準ライブラリ、キー1つ、そして4つの部品です。型付き構造体、リトライとペーサーを備えた Client、カーソルを回すイテレーター、多数の連鎖を扱うワーカープールです。公開Xデータを大規模に読むためのTwitter APIであるSorsa APIを土台にしてください。検索1リクエストあたり20ツイートが1,000件あたり$0.07から、100件のバッチは$0.014から、全プラン毎秒20リクエストで、カード不要・有効期限なしの100回分の無料リクエストが付きます。ほかの言語での同じ呼び出しは、Node.jsでのTwitter APIとPHPでのTwitter APIのガイドにあります。

#よくある質問

Go向けの無料のTwitter APIはありますか?

新規の開発者向けに、公式X APIに無料プランはありません。gotwiなどのGoクライアントは無料ですが、読み取りのたびに前払いクレジットが1投稿$0.005で消費されます。Sorsa APIは、すべての新規アカウントに、カード不要で有効期限のない100回分の無料リクエストを付与します。Goからは、検索で2,000ツイート、または100件のバッチで最大1万ツイートを収集できます。

Goで使うには、どのTwitter APIがよいですか?

GoからXのデータを読むなら、Sorsa APIを使ってください。標準ライブラリの net/http とOAuthの代わりにキー1つで、検索結果は1,000ツイートあたり$0.07から、バッチは$0.014から、goroutineで共有できる全プラン毎秒20リクエスト、2006年までさかのぼる検索に対応し、Xのログインも不要なのでアカウントが危険にさらされることがありません。投稿もする場合は、公式X APIと組み合わせてください。

開発者アカウントなしで、GoでXのデータを取得できますか?

できます。Sorsa APIに必要なのは自分のキーだけです。登録して ApiKey ヘッダーを設定すれば、45のエンドポイントのどれでもGoから呼び出せ、Xの開発者アカウントも、アプリの審査も、OAuthも要りません。Xのウェブサイトを読み取るGoのライブラリは、代わりにアカウントのCookieやアカウントのプールでログインするので、それらのアカウントが危険にさらされます。各手段の比較は、開発者アカウントなしのTwitter APIにあります。

GoからSorsa APIでツイートを投稿できますか?

できません。Sorsa APIは設計上、読み取り専用です。公開の投稿、プロフィール、フォロワーを読むだけで、投稿、いいね、フォロー、メッセージ送信は決して行わないので、キーでXアカウントを操作することはできません。Goから投稿するには、公式X APIの POST /2/tweets をOAuthユーザートークンで、直接またはgotwi経由で呼び出します。1投稿$0.015で、読み取りにはSorsa APIを使い続けてください。

Sorsa API用のGo SDKやモジュールはありますか?

モジュールは不要です。Sorsa APIはJSONを使う素のRESTで、このガイドのクライアントは標準ライブラリで約100行です。api.sorsa.io/v3/swagger.json の実際のOpenAPIリファレンスに、45のエンドポイントがフィールドとともに一覧になっているので、生成された型を使いたい場合は、oapi-codegenなどのジェネレーターで、型付きのリクエストとレスポンスのコードを作れます。

#検証の方法

このガイドのすべてのリクエストは、2026年11月2日に読んだ、Sorsa APIの実際のOpenAPIリファレンス(45エンドポイント)と、レート制限およびページネーションのドキュメントに基づいて書いています。Goの構文は、標準ライブラリのドキュメントとリリースノートに基づきます(イテレーターと slices.Chunk はGo 1.23、WaitGroup.Go はGo 1.25、現行リリースはGo 1.27)。今回はSorsa APIのキーも、手元のGoツールチェーンもなかったので、レスポンスは文書化された形に合わせたデモの値であり、各コードブロックは実行せずに、これらの文書と1行ずつ照らし合わせて確認しました。同じ日に、docs.x.comで公式X APIの料金表、expansion課金に関するXの通知、公式SDKとコミュニティのライブラリの一覧を読み、GitHubでgotwi、2つのgo-twitterプロジェクト、xurlのリリース履歴も確認しました。

Sorsa APIチーム 読み取り専用のX(Twitter)APIとスクレイピングツール、Sorsa APIを開発・運営しています。2022年から本番稼働し(旧TweetScout API)、累計60億リクエスト以上を処理しています。 Sorsa APIについて

変更点
  • 全面的に書き直しました。公式X API向けGoクライアントの現状とリリース日、11月22日のexpansion課金を反映した現在の公式X APIの料金、そしてAPIキー1つで動くGoの経路(型付き構造体、リトライとペーサーを備えたクライアント、イテレーターによるページ送り、100件のバッチ、毎秒20リクエストのワーカープール、v2のパス対応表)。
  • 初回公開。