API de Twitter en Go (Golang): código funcional
Para obtener datos de Twitter/X en Go, envía solicitudes HTTPS con net/http, decodifica el JSON en structs tipados y sigue el cursor página por página. X no publica un SDK oficial de Go, y la API oficial de X factura $0.005 por lectura de post.
Respuesta corta: X publica SDKs oficiales solo para Python y TypeScript, así que el código en Go llama a la API oficial de X mediante net/http o una librería de comunidad como gotwi, con una cuenta de desarrollador y créditos prepagados: $0.005 por post. Para datos públicos, usa Sorsa API desde net/http: una clave, 20 posts por solicitud de búsqueda desde $0.07 por 1,000; 100 solicitudes gratis cubren 2,000.
- Endpoint
POST /search-tweets- Tamaño de página
- 20 posts por solicitud
- Precio
- desde $0.07 por 1,000 posts
#¿X publica un SDK oficial de Go?
No. La documentación para desarrolladores de X lista SDKs oficiales solo para Python y TypeScript y pone Go entre las librerías de comunidad que X no mantiene (la página de herramientas y librerías de X (se abre en una pestaña nueva)). El único proyecto oficial de X en Go es xurl, una herramienta de línea de comandos que ejecutas en una terminal, no un paquete que importes.
Eso deja los clientes de comunidad, y solo uno de ellos sigue publicando versiones. Las fechas de abajo salen del historial de GitHub de cada proyecto:
| Cliente de Go | Última versión | Estado al 2 de noviembre de 2026 |
|---|---|---|
| gotwi | v0.18.5, 21 de agosto de 2026 | Con mantenimiento; cubre la mayoría de los endpoints v2 con OAuth 1.0a, un bearer token o PKCE |
| go-twitter v2 (g8rswimmer) | v2.1.5, 29 de diciembre de 2022 | Sin versiones en casi cuatro años |
| go-twitter (dghubble) | archivado en 2022 | Escrito para la API v1.1 retirada |
gotwi
- Última versión
- v0.18.5, 21 de agosto de 2026
- Estado al 2 de noviembre de 2026
- Con mantenimiento; cubre la mayoría de los endpoints v2 con OAuth 1.0a, un bearer token o PKCE
go-twitter v2 (g8rswimmer)
- Última versión
- v2.1.5, 29 de diciembre de 2022
- Estado al 2 de noviembre de 2026
- Sin versiones en casi cuatro años
go-twitter (dghubble)
- Última versión
- archivado en 2022
- Estado al 2 de noviembre de 2026
- Escrito para la API v1.1 retirada
Una librería solo da forma a las solicitudes. Cada llamada sigue gastando los créditos prepagados de X: $0.005 por post y $0.010 por usuario, hasta 3 millones de lecturas de posts al mes (la tabla de precios de X (se abre en una pestaña nueva)). Desde el 22 de noviembre de 2026, los autores y los medios que llegan mediante expansions se facturan a las mismas tarifas (el aviso de X del 23 de octubre (se abre en una pestaña nueva)), así que la búsqueda que envía la mayor parte del código en Go cuesta hasta tres veces el precio del post:
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
Lo que necesita la ruta oficial antes de la primera llamada
- Una cuenta de desarrollador en console.x.com, abierta con una cuenta de X, con el Developer Agreement de X aceptado.
- Una app y su bearer token, que X muestra una sola vez.
- Créditos comprados por adelantado y gastados en cada post y usuario que devuelve una respuesta.
- Tokens de usuario OAuth para cualquier cosa que publique, dé like o siga.
Leer posts, perfiles y seguidores públicos no necesita nada de esto. La misma elección para Tweepy y xdk se explica en la guía de API de Twitter en Python; el resto de esta página hace el trabajo en Go.
#Obtén posts en Go con una sola clave API
Para leer datos de X desde Go, usa Sorsa API, una API y scraper de Twitter (X): un encabezado ApiKey en lugar de OAuth, 20 solicitudes por segundo en todos los planes, cuentas gratuitas incluidas, búsqueda desde 2006 y resultados de búsqueda desde $0.07 por 1,000 posts con el perfil completo del autor dentro de cada uno. La biblioteca estándar es todo el cliente: net/http envía la solicitud y encoding/json lee la respuesta, así que go.mod no gana ninguna dependencia. Sorsa API nunca te pide tu inicio de sesión de X, así que tu cuenta de X no corre ningún riesgo.
-
Obtén una clave. Regístrate y copia la clave API del panel. Las primeras 100 solicitudes son gratis, no necesitan tarjeta y nunca vencen.
-
Inicia un módulo y guarda la clave. Mantén la clave en una variable de entorno y llama a la API solo desde código del lado del servidor, nunca desde un navegador ni desde una app que entregues a los usuarios. El código de esta guía necesita Go 1.25 o posterior.
Terminal go mod init example.com/xdata export SORSA_API_KEY="your_key" # PowerShell: $env:SORSA_API_KEY="your_key" -
Busca posts. Envía una consulta con la sintaxis de búsqueda de X a
POST /search-tweets(se abre en una pestaña nueva) con la clave en el encabezadoApiKey, y luego ejecutago 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) } } -
Lee la página. Cada respuesta contiene hasta 20 posts y un
next_cursorpara la página siguiente. Los valores de abajo son datos de demostración con la forma documentada.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" }
Cada post llega con sus conteos y su autor, así que la solicitud no define expansions ni listas de campos, y cada solicitud cuesta lo mismo sin importar lo que devuelva.
#Modela la respuesta con structs tipados
Define un struct Tweet y un struct User con los campos que lee tu código, y todo endpoint de Sorsa API que devuelve posts o usuarios decodifica en ellos, porque la API usa las mismas dos formas en todas partes. Ponlos en un paquete pequeño, aquí sorsa, junto al cliente de la sección siguiente.
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"`
}
Tres decisiones en estos structs importan más que el resto. Los IDs tienen 19 dígitos y llegan como cadenas JSON, así que se decodifican en string: un campo int64 detiene el decodificador con "cannot unmarshal string into Go struct field". Las fechas llegan en ISO 8601 UTC, que time.Time lee sin código personalizado. Un post citado o republicado es un post dentro de un post, así que necesita un puntero: Go rechaza un struct que se contiene a sí mismo por valor, y el puntero queda en nil cuando el post no cita nada.
Una etiqueta que no coincide con ninguna clave JSON no es un error en Go: el campo simplemente queda en cero. El código migrado de la API oficial de X lo muestra primero, porque X nombra el conteo like_count dentro de public_metrics mientras que Sorsa API devuelve likes_count en el propio post. Elige abajo los campos que necesitas y copia un struct cuyas etiquetas coincidan con la referencia de la API.
import "time"
type Tweet struct {
ID string `json:"id"`
FullText string `json:"full_text"`
CreatedAt time.Time `json:"created_at"`
LikesCount int `json:"likes_count"`
ViewCount int `json:"view_count"`
User User `json:"user"`
}
type User struct {
ID string `json:"id"`
Username string `json:"username"`
FollowersCount int `json:"followers_count"`
}
Las etiquetas y los tipos siguen la referencia OpenAPI vigente de Sorsa API; encoding/json simplemente omite los campos sin marcar.
#Construye un cliente con reintentos y un regulador de ritmo
Pon la clave, el cliente HTTP, las reglas de reintento y el regulador de ritmo en un solo Client, y comparte ese único valor entre todas las goroutines: http.Client es seguro para uso concurrente, y también lo es el ticker que regula los inicios. Todos los ejemplos siguientes llaman a su método 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()
}
}
}
Un 429 significa que se iniciaron más de 20 solicitudes en un segundo con tu clave, y un 5xx es una falla temporal del lado del servidor; ambos merecen un reintento. Un 401 o un 403 no: la clave es incorrecta o falta, o se agotaron sus solicitudes, y Message dice cuál. Las solicitudes fallidas nunca se cobran, así que un reintento no cuesta nada, y todo endpoint de Sorsa API solo lee, así que reintentar un POST como /search-tweets no puede crear nada dos veces.
El ticker entrega un inicio cada 1/19 de segundo, y cada intento espera uno, los reintentos incluidos. A 19 por segundo el regulador deja un inicio de margen, de modo que un tick guardado mientras el programa estaba inactivo nunca pueda empujar un segundo por encima del límite de 20 de Sorsa API. El contexto termina ambas esperas a la vez, así que un trabajo cancelado se detiene sin enviar las solicitudes que tenía en cola. Quien llama distingue los errores con 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)
}
#Recorre los resultados con un iterador
Un cursor es una clave opaca hacia la página siguiente de resultados: devuelve next_cursor sin cambios y detente cuando una respuesta llegue sin él. La búsqueda y las cronologías devuelven hasta 20 posts por página, y una página puede tener menos mientras quedan más, así que el conteo de elementos nunca te dice que llegaste al final. Desde Go 1.23 una función puede ser lo que recorre un bucle for range, lo que mantiene todo el bucle del cursor en un solo método:
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)
}
}
}
Quien llama lee las páginas como lee cualquier slice, y un break detiene las solicitudes al instante:
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)
}
}
Los endpoints POST reciben el cursor en el cuerpo JSON y los endpoints GET lo reciben como parámetro de consulta, que es la única diferencia entre los dos métodos. GET /followers (se abre en una pestaña nueva) devuelve hasta 200 perfiles por página, así que una lista de un millón de seguidores toma unas 5,000 solicitudes; la guía para exportar seguidores de Twitter escribe esa lista en un CSV. Trata maxPages como un presupuesto, de modo que una consulta amplia nunca gaste más de lo que planeaste, y mantén la consulta sin cambios mientras sigues un mismo cursor. La comprobación contra un cursor repetido es una protección barata que termina un bucle que de otro modo gastaría todo el presupuesto en una sola página.
#Obtén 100 posts o perfiles por solicitud
Cuando ya tienes IDs o nombres de usuario, envíalos en lotes. POST /tweet-info-bulk (se abre en una pestaña nueva) recibe hasta 100 IDs o enlaces de posts, y GET /info-batch (se abre en una pestaña nueva) recibe hasta 100 nombres de usuario, cada uno en una sola solicitud. slices.Chunk corta la lista por ti:
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...)
}
Cada solicitud cuesta lo mismo sin importar lo que devuelva, así que un lote de 100 posts cuesta lo que cuesta un post: desde $0.014 por 1,000 posts, más de 250 veces menos que los $5.00 de la API oficial de X. url.Values es un mapa de slices de cadenas, así que un lote entra tal cual y sale como el parámetro repetido que lee GET /info-batch. Las respuestas por lotes no tienen cursor: una respuesta contiene todo el lote, bajo tweets o bajo users.
#Ejecuta goroutines dentro de 20 solicitudes por segundo
Sorsa API permite 20 solicitudes por segundo por clave en todos los planes, y un programa en Go alcanza ese ritmo con un pequeño pool de goroutines una vez que el trabajo se divide en cadenas de cursor independientes. Una sola cadena no se puede dividir, porque cada página necesita el cursor de la anterior. Así que dale a cada goroutine cadenas completas, una cuenta o un día de una búsqueda a la vez, y deja que el cliente compartido las regule a todas:
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...)
}
Divide una búsqueda larga en días con los operadores since: y until: y entrega los días al pool:
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)
Veinte workers son más que suficientes: la mayor parte de su tiempo se va en esperar el siguiente tick, y más workers solo esperan más. wg.Go, agregado en Go 1.25, inicia cada goroutine y la cuenta en una sola llamada. Mantén un Client por clave y por proceso: dos procesos con un cliente cada uno pueden iniciar el doble del límite entre ambos, así que dale a cada proceso su propia clave o la mitad del ritmo. A 20 solicitudes por segundo, los lotes de 100 mueven hasta 2,000 posts por segundo, las páginas de seguidores 4,000 perfiles y las páginas de búsqueda 400 posts.
Para tener en cuenta
Collectsigue adelante cuando falla un día: guarda el error con la consulta que lo causó y los devuelve todos medianteerrors.Joinal final. Para detener toda la ejecución en el primer fallo, envuelve el contexto concontext.WithCancely llama acancel()donde se guarda el error; el regulador de ritmo y las esperas de reintento regresan en cuanto termina el contexto.
#Lo que cuesta un trabajo en Go
Sorsa API cobra por solicitud, sin importar lo que devuelva el endpoint: los posts obtenidos en lotes de 100 cuestan desde $0.014 por 1,000, los resultados de búsqueda desde $0.07, los seguidores desde $0.007, y las solicitudes fallidas nunca se cobran. La API oficial de X cobra en cambio por objeto: $5.00 por 1,000 posts, y desde el 22 de noviembre de 2026 hasta $15.00 por 1,000 cuando la solicitud expande el autor de cada post.
API oficial de X
$5,000
para 1M posts
$0.005 por post
Sorsa API
desde$14
para 1M posts
desde $0.014 por 1,000
más de 250 veces más barata
La tarifa por 1,000 solicitudes va de $4.90 en Starter a $1.40 en Hyperscale, y un plan la aplica por igual a todos los endpoints. Un servicio puede vigilar su saldo gratis: GET /key-usage-info (se abre en una pestaña nueva) nunca cuenta contra tus solicitudes.
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)
}
#Cómo pasar código de gotwi o go-twitter a Sorsa API
gotwi y go-twitter son capas delgadas sobre las rutas v2 de la API oficial de X, así que sus llamadas se asignan a Sorsa API por ruta. Cada ruta de abajo tiene un endpoint de Sorsa API, y el código se acorta: sin expansions ni listas de campos, porque cada post ya lleva a su autor y sus conteos.
| Ruta v2 de la API oficial de X | Endpoint de Sorsa API | Qué cambia |
|---|---|---|
GET /2/tweets/search/recent | POST /search-tweets | Cualquier fecha desde 2006, no solo los últimos 7 días |
GET /2/tweets/search/all | POST /search-tweets | El mismo endpoint con since: y until: |
GET /2/users/:id/tweets | POST /user-tweets (se abre en una pestaña nueva) | Basta un nombre de usuario; sin tope de 3,200 posts |
GET /2/tweets | POST /tweet-info-bulk | 100 IDs o enlaces por solicitud |
GET /2/users/by | GET /info-batch | 100 nombres de usuario por solicitud |
GET /2/users/:id/followers | GET /followers | 200 seguidores por página |
GET /2/tweets/:id/retweeted_by | POST /retweeters (se abre en una pestaña nueva) | El mismo bucle de cursor |
POST /2/tweets | ninguno | Solo lectura: publica con la API oficial de X |
GET /2/tweets/search/recent- Endpoint de Sorsa API
POST /search-tweets- Qué cambia
- Cualquier fecha desde 2006, no solo los últimos 7 días
GET /2/tweets/search/all- Endpoint de Sorsa API
POST /search-tweets- Qué cambia
- El mismo endpoint con
since:yuntil:
GET /2/users/:id/tweets- Endpoint de Sorsa API
POST /user-tweets(se abre en una pestaña nueva)- Qué cambia
- Basta un nombre de usuario; sin tope de 3,200 posts
GET /2/tweets- Endpoint de Sorsa API
POST /tweet-info-bulk- Qué cambia
- 100 IDs o enlaces por solicitud
GET /2/users/by- Endpoint de Sorsa API
GET /info-batch- Qué cambia
- 100 nombres de usuario por solicitud
GET /2/users/:id/followers- Endpoint de Sorsa API
GET /followers- Qué cambia
- 200 seguidores por página
GET /2/tweets/:id/retweeted_by- Endpoint de Sorsa API
POST /retweeters(se abre en una pestaña nueva)- Qué cambia
- El mismo bucle de cursor
POST /2/tweets- Endpoint de Sorsa API
- ninguno
- Qué cambia
- Solo lectura: publica con la API oficial de X
Los nombres de campo cambian con las rutas: text pasa a ser full_text, public_metrics.like_count pasa a ser likes_count, author_id con includes.users pasa a ser el user anidado, y meta.next_token pasa a ser next_cursor. El manejo de límites de tasa también se reduce, porque las ventanas de 15 minutos de X dan paso a un solo límite que se reinicia cada segundo. Para migrar una base de código completa, cada endpoint v2 y cada campo de respuesta está asignado en la guía para migrar de la API de Twitter/X.
#Ejecuta hoy tu primera búsqueda desde Go
Llevar datos de X a Go requiere la biblioteca estándar, una clave y cuatro piezas: structs tipados, un Client con reintentos y un regulador de ritmo, iteradores sobre el cursor y un pool de workers para muchas cadenas. Construye sobre Sorsa API, la API de Twitter para leer datos públicos de X a escala: 20 posts por solicitud de búsqueda desde $0.07 por 1,000, lotes de 100 desde $0.014, 20 solicitudes por segundo en todos los planes y 100 solicitudes gratis que no necesitan tarjeta y nunca vencen. Las mismas llamadas en otros lenguajes están en las guías de la API de Twitter en Node.js y la API de Twitter en PHP.
#Preguntas frecuentes
¿Existe una API de Twitter gratuita para Go?
La API oficial de X no tiene nivel gratuito para desarrolladores nuevos: gotwi y los demás clientes de Go son gratuitos, pero cada lectura gasta créditos prepagados a $0.005 por post. Sorsa API da a cada cuenta nueva 100 solicitudes gratis que nunca vencen y no necesitan tarjeta. Desde Go recolectan 2,000 posts por búsqueda, o hasta 10K posts en lotes de 100.
¿Qué API de Twitter debo usar con Go?
Para leer datos de X desde Go, usa Sorsa API: el net/http de la biblioteca estándar con una sola clave en lugar de OAuth, resultados de búsqueda desde $0.07 por 1,000 posts y lotes desde $0.014, 20 solicitudes por segundo en todos los planes para que las goroutines las compartan, búsqueda desde 2006 y ningún inicio de sesión de X, así que tu cuenta nunca corre riesgo. Para publicar también, combínala con la API oficial de X.
¿Puedo obtener datos de X en Go sin una cuenta de desarrollador?
Sí. Sorsa API solo necesita su propia clave: regístrate, define el encabezado ApiKey y llama a cualquiera de sus 45 endpoints desde Go, sin cuenta de desarrollador de X, revisión de app ni OAuth. Las librerías de Go que leen el sitio web de X inician sesión con las cookies de una cuenta o con un conjunto de cuentas, lo que pone esas cuentas en riesgo. Las opciones se comparan en API de Twitter sin cuenta de desarrollador.
¿Puedo publicar posts desde Go con Sorsa API?
No. Sorsa API es de solo lectura por diseño: lee posts, perfiles y seguidores públicos y nunca publica, da like, sigue ni envía mensajes, así que una clave no puede actuar sobre ninguna cuenta de X. Para publicar desde Go, llama a POST /2/tweets de la API oficial de X con un token de usuario OAuth, directamente o mediante gotwi, a $0.015 por post, y conserva Sorsa API para las lecturas.
¿Hay un SDK o módulo de Go para Sorsa API?
No hace falta ningún módulo: Sorsa API es REST simple con JSON, y el cliente de esta guía son unas 100 líneas de la biblioteca estándar. La referencia OpenAPI en vivo en api.sorsa.io/v3/swagger.json lista los 45 endpoints con sus campos, así que un generador como oapi-codegen puede producir código tipado de solicitudes y respuestas si prefieres tipos generados.
#Cómo lo comprobamos
Escribimos cada solicitud de esta guía a partir de la referencia OpenAPI en vivo de Sorsa API (45 endpoints) y su documentación sobre límites de tasa y paginación, leídas el 2 de noviembre de 2026, y cada construcción de Go a partir de la documentación y las notas de versión de la biblioteca estándar (iteradores y slices.Chunk desde Go 1.23, WaitGroup.Go desde Go 1.25; la versión actual es Go 1.27). No tuvimos una clave de Sorsa API ni un toolchain de Go en nuestra máquina para esta pasada, así que la respuesta muestra valores de demostración con la forma documentada, y revisamos cada bloque de código línea por línea contra esos documentos en lugar de ejecutarlo. El mismo día leímos la tabla de precios de la API oficial de X, el aviso de X sobre la facturación de expansions y su lista de SDKs oficiales y librerías de comunidad en docs.x.com, y el historial de versiones de gotwi, de ambos proyectos go-twitter y de xurl en GitHub.
Fuentes
- X API pay-per-usage pricing (docs.x.com) (se abre en una pestaña nueva)
- X API PPU: expansions update (X Developers, October 23, 2026) (se abre en una pestaña nueva)
- Tools and libraries: official SDKs and community libraries (docs.x.com) (se abre en una pestaña nueva)
- gotwi releases (GitHub) (se abre en una pestaña nueva)
- Go 1.23 release notes: range-over-func iterators (se abre en una pestaña nueva)
- Go 1.25 release notes: WaitGroup.Go (se abre en una pestaña nueva)
- Sorsa API reference, OpenAPI
- Sorsa API rate limits (se abre en una pestaña nueva)
- Sorsa API pagination (se abre en una pestaña nueva)