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日時点の状態 |
|---|---|---|
| gotwi | v0.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倍かかります。
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アカウントにリスクはありません。
-
キーを取得する。 登録して、ダッシュボードからAPIキーをコピーします。最初の100リクエストは無料で、カードは不要、有効期限もありません。
-
モジュールを作り、キーを保存する。 キーは環境変数に置き、APIの呼び出しはサーバー側のコードからだけ行います。ブラウザや、ユーザーに配布するアプリからは決して呼び出さないでください。このガイドのコードにはGo 1.25以降が必要です。
Terminal go mod init example.com/xdata export SORSA_API_KEY="your_key" # PowerShell: $env:SORSA_API_KEY="your_key" -
ツイートを検索する。 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) } } -
ページを読む。 各レスポンスには最大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)に入れ、次の節のクライアントの隣に置きます。
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 のままです。
どのJSONキーにも一致しないタグは、Goではエラーになりません。フィールドがゼロ値のままになるだけです。公式X APIから移したコードで最初に現れます。Xはカウントを public_metrics の中で like_count と呼びますが、Sorsa APIはツイート自体に likes_count を返すからです。下で必要なフィールドを選ぶと、タグがAPIリファレンスに一致する構造体をコピーできます。
タグと型は、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 メソッドを呼び出します。
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 でエラーを見分けます。
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つのメソッドにまとめられます。
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 するとリクエストがその場で止まります。
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 がリストを分割してくれます。
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日分ずつです。共有クライアントがすべてのペースを調整します。
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: 演算子で日ごとに分割し、その日をプールに渡します。
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)
ワーカーは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です。
公式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(新しいタブで開きます) は、リクエストとして数えられることがありません。
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/recent | POST /search-tweets | 直近7日間だけでなく、2006年までの任意の日付 |
GET /2/tweets/search/all | POST /search-tweets | 同じエンドポイントで since: と until: を使用 |
GET /2/users/:id/tweets | POST /user-tweets(新しいタブで開きます) | ユーザー名だけで足り、3,200投稿の上限なし |
GET /2/tweets | POST /tweet-info-bulk | 1リクエストで100件のIDまたはリンク |
GET /2/users/by | GET /info-batch | 1リクエストで100件のユーザー名 |
GET /2/users/:id/followers | GET /followers | 1ページ200件のフォロワー |
GET /2/tweets/:id/retweeted_by | POST /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- Sorsa APIのエンドポイント
POST /user-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- Sorsa APIのエンドポイント
POST /retweeters(新しいタブで開きます)- 変わる点
- 同じカーソルのループ
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のリリース履歴も確認しました。
出典
- X API pay-per-usage pricing (docs.x.com)(新しいタブで開きます)
- X API PPU: expansions update (X Developers, October 23, 2026)(新しいタブで開きます)
- Tools and libraries: official SDKs and community libraries (docs.x.com)(新しいタブで開きます)
- gotwi releases (GitHub)(新しいタブで開きます)
- Go 1.23 release notes: range-over-func iterators(新しいタブで開きます)
- Go 1.25 release notes: WaitGroup.Go(新しいタブで開きます)
- Sorsa API reference, OpenAPI
- Sorsa API rate limits(新しいタブで開きます)
- Sorsa API pagination(新しいタブで開きます)