FetchLayer fetchlayer.dev Sign in

+ Integration Guide

Twitter/X API with Go: Complete Guide

How to search X/Twitter and scrape tweets using Go and the FetchLayer API. Clean, idiomatic Go examples with proper error handling and struct decoding.

Written by Alex P.

  • Go
  • Golang
  • twitter scraping
  • X API
  • Twitter API
  • API integration

This guide shows how to use the FetchLayer Twitter/X API with Go. All examples use the standard library — no third-party HTTP clients needed.


Setup

  1. Get a free API key (no credit card)
  2. Go 1.21+ (for slog and modern stdlib features)

API Client

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

const baseURL = "https://api.fetchlayer.dev/twitter"

var apiKey = os.Getenv("FETCHLAYER_API_KEY")

func twitter(endpoint string, body any, result any) error {
	payload, err := json.Marshal(body)
	if err != nil {
		return fmt.Errorf("marshal: %w", err)
	}

	req, err := http.NewRequest("POST", baseURL+"/"+endpoint, bytes.NewReader(payload))
	if err != nil {
		return fmt.Errorf("request: %w", err)
	}

	req.Header.Set("Authorization", "Bearer "+apiKey)
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return fmt.Errorf("do: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("API returned %d", resp.StatusCode)
	}

	return json.NewDecoder(resp.Body).Decode(result)
}

Types

type TwitterAuthor struct {
	Handle         string `json:"handle"`
	DisplayName    string `json:"displayName"`
	IsVerified     bool   `json:"isVerified"`
	FollowersCount int    `json:"followersCount,omitempty"`
}

type Tweet struct {
	ID           string        `json:"id"`
	Text         string        `json:"text"`
	Author       TwitterAuthor `json:"author"`
	CreatedAt    string        `json:"createdAt"`
	LikeCount    int           `json:"likeCount"`
	RetweetCount int           `json:"retweetCount"`
	ReplyCount   int           `json:"replyCount"`
	ViewCount    int           `json:"viewCount,omitempty"`
	URL          string        `json:"url"`
}

type SearchResponse struct {
	Results []Tweet `json:"results"`
	Cursor  string  `json:"cursor,omitempty"`
}

type TweetDetailResponse struct {
	ID           string        `json:"id"`
	Text         string        `json:"text"`
	Author       TwitterAuthor `json:"author"`
	CreatedAt    string        `json:"createdAt"`
	LikeCount    int           `json:"likeCount"`
	RetweetCount int           `json:"retweetCount"`
	ReplyCount   int           `json:"replyCount"`
	QuoteCount   int           `json:"quoteCount"`
	ViewCount    int           `json:"viewCount"`
	URL          string        `json:"url"`
	Lang         string        `json:"lang"`
}

type UserProfile struct {
	Handle         string `json:"handle"`
	DisplayName    string `json:"displayName"`
	Description    string `json:"description"`
	IsVerified     bool   `json:"isVerified"`
	FollowersCount int    `json:"followersCount"`
	FollowingCount int    `json:"followingCount"`
	TweetsCount    int    `json:"tweetsCount"`
	JoinedAt       string `json:"joinedAt"`
	Location       string `json:"location,omitempty"`
	Website        string `json:"website,omitempty"`
}

Search Twitter/X

func main() {
	var data SearchResponse
	err := twitter("search", map[string]any{
		"query":   "best Go web frameworks",
		"product": "Top",
		"count":   10,
	}, &data)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(1)
	}

	for _, tweet := range data.Results {
		fmt.Printf("[%d likes] @%s: %s\n", tweet.LikeCount, tweet.Author.Handle, truncate(tweet.Text, 100))
	}
}

Run it:

FETCHLAYER_API_KEY=sk-your-key go run main.go

Get a Tweet by ID

var tweet TweetDetailResponse
err := twitter("tweet-detail", map[string]any{
	"tweetId": "1942939879222220800",
}, &tweet)
if err != nil {
	log.Fatal(err)
}

fmt.Printf("@%s: %s\n", tweet.Author.Handle, tweet.Text)
fmt.Printf("%d likes · %d retweets · %d replies\n", tweet.LikeCount, tweet.RetweetCount, tweet.ReplyCount)

Get a User Profile

var profile UserProfile
err := twitter("user-profile-details", map[string]any{
	"handle": "openai",
}, &profile)
if err != nil {
	log.Fatal(err)
}

fmt.Printf("%s (@%s)\n", profile.DisplayName, profile.Handle)
fmt.Printf("Followers: %d | Following: %d | Tweets: %d\n",
	profile.FollowersCount, profile.FollowingCount, profile.TweetsCount)

Get a User’s Tweets

type UserTweetsResponse struct {
	Tweets []Tweet `json:"tweets"`
	Cursor string  `json:"cursor,omitempty"`
}

var data UserTweetsResponse
err := twitter("user-tweets", map[string]any{
	"handle": "rauchg",
	"count":  20,
}, &data)
if err != nil {
	log.Fatal(err)
}

for _, tweet := range data.Tweets {
	fmt.Printf("[%d likes] %s\n", tweet.LikeCount, truncate(tweet.Text, 120))
}

Get Followers

type AccountEntry struct {
	Handle         string `json:"handle"`
	DisplayName    string `json:"displayName"`
	IsVerified     bool   `json:"isVerified"`
	FollowersCount int    `json:"followersCount"`
}

type FollowersResponse struct {
	Accounts []AccountEntry `json:"accounts"`
	Cursor   string         `json:"cursor,omitempty"`
}

var followers FollowersResponse
err := twitter("followers", map[string]any{
	"handle": "openai",
	"count":  50,
}, &followers)
if err != nil {
	log.Fatal(err)
}

for _, account := range followers.Accounts {
	fmt.Printf("@%s%d followers (verified: %v)\n",
		account.Handle, account.FollowersCount, account.IsVerified)
}

Full Example: Twitter Audience Analyzer

// analyzer.go — run with: FETCHLAYER_API_KEY=sk-... go run analyzer.go <handle>
func main() {
	if len(os.Args) < 2 {
		fmt.Fprintf(os.Stderr, "Usage: analyzer <handle>\n")
		os.Exit(1)
	}
	handle := os.Args[1]

	var profile UserProfile
	if err := twitter("user-profile-details", map[string]any{"handle": handle}, &profile); err != nil {
		log.Fatal(err)
	}

	var data UserTweetsResponse
	if err := twitter("user-tweets", map[string]any{"handle": handle, "count": 50}, &data); err != nil {
		log.Fatal(err)
	}

	totalLikes := 0
	for _, t := range data.Tweets {
		totalLikes += t.LikeCount
	}
	avgLikes := float64(totalLikes) / float64(max(1, len(data.Tweets)))

	fmt.Printf("%s (@%s)\n", profile.DisplayName, profile.Handle)
	fmt.Printf("Followers: %d\n", profile.FollowersCount)
	fmt.Printf("Avg engagement: %.0f likes per tweet\n", avgLikes)
	if profile.FollowersCount > 0 {
		fmt.Printf("Engagement rate: %.2f%%\n", (avgLikes/float64(profile.FollowersCount))*100)
	}
}

func truncate(s string, maxLen int) string {
	if len(s) <= maxLen {
		return s
	}
	return s[:maxLen] + "..."
}

What’s Next