# Twitter API Go Client with net/http Canonical: https://www.twitterapis.com/sdk/go Description: A complete Go client for the TwitterAPIs X data API on the standard library: typed structs, context timeouts, error handling and cursor pagination. Generated: 2026-09-17T01:57:09.984Z --- 1. [Home](/) 2. / [Language clients](/sdk) 3. / Go GO # Twitter API Go client ## How do I call the Twitter API from Go? The Go client for TwitterAPIs is net/http and encoding/json, both already in the standard library. Authentication is a single static header, so there is no credential machinery to build and no module to add to go.mod. One method on a small struct covers every read endpoint, with typed results and a context you control. No dependency. net/http and encoding/json are in the standard library. twitterapis.go Copy ``` package main import ( "encoding/json" "fmt" "net/http" "net/url" "os" ) const base = "https://api.twitterapis.com" func get(path string, params url.Values, out any) error { req, err := http.NewRequest("GET", base+"/twitter/"+path+"?"+params.Encode(), nil) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+os.Getenv("TWITTERAPIS_KEY")) res, err := http.DefaultClient.Do(req) if err != nil { return err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return fmt.Errorf("twitterapis: status %d", res.StatusCode) } return json.NewDecoder(res.Body).Decode(out) } type profile struct { User struct { ID string `json:"id"` FollowersCount int `json:"followers_count"` } `json:"user"` } func main() { var p profile if err := get("user/info", url.Values{"username": {"naval"}}, &p); err != nil { panic(err) } fmt.Println(p.User.ID, p.User.FollowersCount) } ``` ## A reusable client type Wrapping the key and an http.Client in a struct gives you one place to set the timeout, one place to inject a test transport, and a context-aware method that every call site can cancel. client.go Copy ``` type Client struct { Key string HTTP *http.Client } func New(key string) *Client { return &Client{Key: key, HTTP: &http.Client{Timeout: 30 * time.Second}} } func (c *Client) Get(ctx context.Context, path string, params url.Values, out any) error { u := "https://api.twitterapis.com/twitter/" + path + "?" + params.Encode() req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+c.Key) res, err := c.HTTP.Do(req) if err != nil { return err } defer res.Body.Close() if res.StatusCode != http.StatusOK { body, _ := io.ReadAll(io.LimitReader(res.Body, 2048)) return fmt.Errorf("twitterapis: status %d: %s", res.StatusCode, body) } return json.NewDecoder(res.Body).Decode(out) } ``` ## Walking a cursor-paginated endpoint Go has no generators, so the idiomatic shape is a callback the caller can abort by returning an error. That keeps the cursor logic in one function while leaving the decision to stop with the code that is paying for each page. How many times that loop runs is the number worth knowing before you start it, because billing is per call rather than per record. Across 396,817 successful tweet-returning read calls on our own billing logs, 13 to 17 August 2026, a timeline read returned 18.78 records on average and a search read returned 7.62, with 29.5% of search calls returning nothing at all and still counting. Size a job from the figure for the endpoint you are actually calling, not from the page size you requested. followers.go Copy ``` type followersPage struct { // The API returns this collection under "users". Naming it anything else // is silent: encoding/json leaves an unmatched slice nil rather than // erroring, so every page would decode to zero followers while the cursor // keeps coming back non-empty, and the loop below would never end. Users []User `json:"users"` NextCursor string `json:"next_cursor"` } // Followers walks every page and calls fn once per page. Each page is one // billed call, so the caller controls the loop by returning an error to stop. func (c *Client) Followers(ctx context.Context, username string, fn func(followersPage) error) error { params := url.Values{"username": {username}} const maxCalls = 500 calls := 0 seen := map[string]bool{} for { var page followersPage if err := c.Get(ctx, "user/followers", params, &page); err != nil { return err } // BOTH terminators, and the empty check runs BEFORE fn so the caller is // never handed the trailing empty page. user/followers is a follower-graph // endpoint and keeps returning a non-empty cursor after the last real // page, so the cursor check alone never fires. Search and list endpoints // null the cursor on a page that is still full, so the empty check alone // drops that page. if len(page.Users) == 0 { return nil } if err := fn(page); err != nil { return err } if page.NextCursor == "" { return nil } if seen[page.NextCursor] { // a stuck cursor AND any cycle return fmt.Errorf("cursor stopped advancing at %s", page.NextCursor) } seen[page.NextCursor] = true calls++ if calls >= maxCalls { return fmt.Errorf("hit the %d-call cap, resume from %s", maxCalls, page.NextCursor) } params.Set("cursor", page.NextCursor) } } ``` ## Where to go next The same client shape in [Python](/sdk/python) and [Node](/sdk/node), or all six languages on the [language clients](/sdk) page. The [REST API reference](/twitter-rest-api) covers the paging model and the status codes this client branches on, and [the rate limits page](/twitter-api-rate-limits) covers what concurrency you can actually run. ## Frequently Asked Questions ### Is there a Go SDK for the Twitter API? There is no package to install here, and the client above shows why one would add little. net/http handles the request, encoding/json handles the response, and the only credential work is setting a static header. That is the entire surface an SDK would wrap, so writing it yourself gives you a client you can read in one screen and change without waiting on an upstream release. ### Should I define structs or decode into a map? Define structs for the fields you use. Decoding into map\[string\]any works and is fine for exploration, but it pushes every field name into string literals scattered through your code, and a typo becomes a nil at runtime rather than a compile error. The public OpenAPI 3.1 specification will generate the structs for you if you would rather not write them. ### Can I run many requests concurrently? Yes. There is one ceiling to design around, 600 requests a minute and 20 concurrent per key, so a worker pool over a channel of usernames works with the pool size set at or under 20. Keep the pool bounded there anyway, because unbounded concurrency turns a transient upstream problem into a large number of simultaneous failures and a matching number of billed calls. ### How do I add timeouts properly in Go? Set a Timeout on the http.Client for a hard ceiling, and use http.NewRequestWithContext so a caller can cancel the request through its own context. Both matter: the client timeout stops a hung connection, and the context lets a request die when the work that needed it has already been abandoned. ### How do I paginate follower lists in Go? Read next\_cursor from each response and set it as the cursor parameter on the next request, stopping when it comes back empty. The helper on this page inverts control with a callback so the caller can stop early, which matters when each page is a separate billed call and a large account has a lot of them. ### What does a call cost from Go? $0.0008 for a standard read, the same everywhere, billed per call rather than per plan. New accounts start with $0.50 of credit, about 625 read calls and no card, which is enough to run a real pagination loop before you commit to a budget. The client makes exactly one request per Get, so your bill is the number of times your code calls it. Retries and pagination loops are where a count grows without anybody deciding it should. [Quickstart](/quickstart)[Start Free](/signup) [ TwitterAPIs ](/) The cheapest pay-as-you-go Twitter and X API. $0.0008 per call, which works out to $0.04 per 1,000 tweets on a full 20-tweet page. No subscriptions and no developer account. ## Product / API - [Pricing](/pricing) - [Cost Calculator](/twitter-api-cost-calculator) - [Rate Limits](/twitter-api-rate-limits) - [MCP Server](/mcp) - [Integrations](/integrations) - [Language Clients](/sdk) - [Changelog](/changelog) - [Status](/status) ## Developers - [Documentation](https://docs.twitterapis.com) - [API Reference](https://docs.twitterapis.com/docs/reference/search/tweet-advanced-search) - [User Info](https://docs.twitterapis.com/docs/reference/user-reads/user-info) - [User Tweets](https://docs.twitterapis.com/docs/reference/user-reads/user-tweets) - [Advanced Search](https://docs.twitterapis.com/docs/reference/search/tweet-advanced-search) - [Verified Followers](https://docs.twitterapis.com/docs/reference/follower-graph/user-verified-followers) ## Resources / Compare - [Answers](/answers) - [Reviews](/reviews) - [Free Tools](/tools) - [Twitter ID Finder](/tools/twitter-id-finder) - [Get a Twitter API Key](/twitter-api-key) - [Official X API Comparison](/twitter-api-pricing) - [Twitter API Use Cases](/twitter-api-usecases) - [Twitter API Alternatives](/twitter-api-alternatives) - [Twitter Unofficial API](/twitter-unofficial-api) - [Twitter Free API](/twitter-free-api) - [TwitterAPIs vs twitterapi.io](/twitterapis-vs-twitterapi-io) - [TwitterAPIs vs GetXAPI](/twitterapis-vs-getxapi) - [TwitterAPIs vs TweetAPI](/twitterapis-vs-tweetapi) - [TwitterAPIs vs TwexAPI](/twitterapis-vs-twexapi) - [TwitterAPIs vs RapidAPI](/twitterapis-vs-rapidapi) ## Legal - [About](/about) - [Security](/security) - [Trust](/privacy-and-data-handling) - [Terms of Service](/terms-of-service) - [Affiliates](/affiliates) - [Contact](/contact) - [Jobs](/jobs) © 2026 TwitterAPIs. All rights reserved. TwitterAPIs is an independent third-party API for developers and researchers. Not affiliated with, endorsed by, or sponsored by X Corp. All systems operational