Pular para o conteúdo

API do Twitter em PHP: obtenha dados do X com cURL, Guzzle e Laravel

Para obter dados do Twitter (X) em PHP, envie requisições HTTPS com cURL ou Guzzle, coloque a chave de API em um header, decodifique o JSON e siga o cursor página por página; a API oficial do X cobra US$ 0,005 por post.

Resposta curta: O X publica SDKs oficiais só para Python e TypeScript, então o PHP chega à API oficial do X por clientes da comunidade, como o TwitterOAuth, com uma conta de desenvolvedor e créditos pré-pagos: US$ 0,005 por post e US$ 0,015 com o autor, a partir de 22 de novembro de 2026. Para dados públicos, use a Sorsa API: uma chave, 20 tweets por requisição a partir de US$ 0,07 por 1.000; 100 requisições grátis cobrem 2.000.

Endpoint
POST /user-tweets
Tamanho da página
20 tweets por requisição
Preço
a partir de US$ 0,07 por 1.000 tweets

#Existe um SDK oficial da API do X para PHP?

Não. O X publica SDKs oficiais só para Python e TypeScript, e a página de ferramentas dele lista três clientes PHP da comunidade que o X não mantém: twitter-api-v2-php, bird-elephant e TwitterOAuth (ferramentas e bibliotecas do X (abre em uma nova aba)). O TwitterOAuth é o mais instalado dos três, com cerca de 240 mil instalações por mês no Composer. A versão 9.0.0 dele, lançada em 1º de novembro de 2026, fala apenas com a API v2 e exige PHP 8.3 ou posterior.

Os três são clientes da API oficial do X, então cada chamada que fazem exige uma conta de desenvolvedor, as chaves de um app e créditos pré-pagos. Novos desenvolvedores não têm plano gratuito, e o X cobra cada objeto que uma resposta devolve: US$ 0,005 por post e US$ 0,010 por usuário, até 3 milhões de posts lidos por mês (tabela de preços do X (abre em uma nova aba)). A partir de 22 de novembro de 2026, os autores e as mídias trazidos por expansões são cobrados pelas mesmas tarifas (aviso do X de 23 de outubro (abre em uma nova aba)), então a busca usual do TwitterOAuth paga por cada autor que anexa:

TwitterOAuth
use Abraham\TwitterOAuth\TwitterOAuth;

$x = new TwitterOAuth($apiKey, $apiSecret, null, $bearerToken);
$posts = $x->get('tweets/search/recent', [
    'query' => 'laravel lang:en',
    'max_results' => 100,
    'expansions' => 'author_id',
]);
// 100 posts: $0.50. From Nov 22, 2026, with authors: up to $1.50

Respostas mais antigas apontam para mais longe no passado. O primeiro resultado do Google para esta busca é o J7mbo/twitter-api-php, um wrapper para chamadas da v1.1 cujo repositório no GitHub está arquivado e foi alterado pela última vez em abril de 2020. Todas as tarifas, limites e datas atuais da rota oficial estão em preços da API do X.

O que a rota oficial exige antes da primeira chamada

  • Uma conta de desenvolvedor em console.x.com, aberta com uma conta do X e com o Contrato de Desenvolvedor do X aceito.
  • Um app com a sua API key, API secret e bearer token, que o X mostra uma única vez (guia de acesso do X (abre em uma nova aba)).
  • Créditos comprados antecipadamente e gastos por post e por usuário devolvido.
  • Tokens de usuário OAuth 1.0a, um access token e o respectivo secret, para qualquer coisa que poste; o construtor de quatro argumentos do TwitterOAuth os recebe.

Nada disso é necessário para ler posts e perfis públicos. A próxima seção faz isso com uma chave e a extensão cURL que a maioria das builds do PHP já inclui.

#Obtenha tweets em PHP com uma só chave de API

Para ler dados do X a partir do PHP, use a Sorsa API, uma API e scraper do Twitter (X): um header ApiKey no lugar do OAuth, 20 requisições por segundo em todos os planos, inclusive nas contas grátis, e timelines e busca a partir de US$ 0,07 por 1.000 tweets. Toda resposta é JSON simples que o json_decode transforma em arrays, com o perfil completo do autor dentro de cada tweet. Ela nunca pede o seu login do X, então a sua conta do X não corre risco.

  1. Obtenha uma chave. Cadastre-se e copie a sua chave de API no painel. As primeiras 100 requisições são grátis, não pedem cartão e nunca expiram.

  2. Mantenha a chave fora do código. Defina-a como variável de ambiente, ou no .env do Laravel, e chame a API apenas a partir de PHP no servidor: uma chave em JavaScript no navegador fica visível para todo visitante.

    Terminal
    export SORSA_API_KEY="your_key"  # PowerShell: $env:SORSA_API_KEY="your_key"
    
  3. Busque os tweets mais recentes de uma conta. Envie o nome de usuário para POST /user-tweets (abre em uma nova aba) com a chave no header ApiKey. Experimente primeiro pelo terminal:

    Terminal
    curl -X POST "https://api.sorsa.io/v3/user-tweets" \
      -H "ApiKey: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"username": "php_release_notes"}'
    
  4. Chame-a a partir do PHP. O cURL simples não precisa de nenhum pacote do Composer. Com o Guzzle, termine o base_uri com uma barra e comece o caminho sem ela, ou a parte /v3 se perde.

    <?php
    $key = getenv('SORSA_API_KEY') ?: 'YOUR_API_KEY';
    
    $ch = curl_init('https://api.sorsa.io/v3/user-tweets');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            'ApiKey: ' . $key,
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode(['username' => 'php_release_notes']),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
    ]);
    $body = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    
    $data = json_decode($body, true, flags: JSON_THROW_ON_ERROR);
    if ($status !== 200) {
        exit("HTTP $status: {$data['message']}\n");
    }
    foreach ($data['tweets'] as $tweet) {
        echo $tweet['created_at'], ' ', mb_substr($tweet['full_text'], 0, 60), "\n";
    }
    
  5. Leia a página. Cada resposta traz até 20 tweets e um next_cursor enquanto ainda restam resultados. Os valores abaixo são dados de demonstração no formato documentado.

    Response200 OK
    {
      "tweets": [
        {
          "id": "1985432176540981234",
          "full_text": "PHP 8.5.12 is out with two security fixes. Upgrade today.",
          "created_at": "2026-11-01T18:42:07Z",
          "lang": "en",
          "likes_count": 42,
          "retweet_count": 7,
          "reply_count": 3,
          "quote_count": 1,
          "view_count": 3810,
          "bookmark_count": 5,
          "is_reply": false,
          "user": {
            "id": "1592041833471827968",
            "username": "php_release_notes",
            "display_name": "PHP Release Notes",
            "followers_count": 1840,
            "verified": false
          }
        }
      ],
      "next_cursor": "DAABCgABF7Y8kQ3wAAA"
    }
    

Cada tweet chega com as suas contagens e o seu autor, então um script PHP não define expansões nem listas de campos. Estes são os campos que a maioria dos scripts lê:

CampoO que contém
idO ID do tweet: 19 dígitos, enviado como string
full_textO texto completo do post
created_atO horário em ISO 8601, UTC, pronto para new DateTimeImmutable()
likes_count, retweet_count, reply_count, quote_countContagens de engajamento
view_count, bookmark_countVisualizações e itens salvos
userO perfil do autor: 19 campos, como username e followers_count
quoted_status, retweeted_statusO tweet citado ou repostado, no mesmo formato
  • id

    O que contém
    O ID do tweet: 19 dígitos, enviado como string
  • full_text

    O que contém
    O texto completo do post
  • created_at

    O que contém
    O horário em ISO 8601, UTC, pronto para new DateTimeImmutable()
  • likes_count, retweet_count, reply_count, quote_count

    O que contém
    Contagens de engajamento
  • view_count, bookmark_count

    O que contém
    Visualizações e itens salvos
  • user

    O que contém
    O perfil do autor: 19 campos, como username e followers_count
  • quoted_status, retweeted_status

    O que contém
    O tweet citado ou repostado, no mesmo formato

#Uma função de requisição com retentativas

Envie toda chamada por uma única função que repete apenas o que uma nova tentativa pode resolver. Um 429 significa que mais de 20 requisições começaram dentro de um segundo na sua chave, e um 5xx ou uma conexão interrompida é uma falha temporária; as duas se resolvem sozinhas. A sorsa() repete até quatro vezes, esperando 1, 2, 4 e 8 segundos, e envia os parâmetros GET como chaves repetidas, a forma que os endpoints de lote abaixo leem. O resto deste guia se apoia nela.

sorsa.php
<?php
const SORSA_API = 'https://api.sorsa.io/v3';
const RETRY = [429, 500, 502, 503, 504];

/** Build a query string; a list becomes a repeated key: a=1&a=2. */
function query(array $params): string
{
    $parts = [];
    foreach ($params as $name => $value) {
        foreach ((array) $value as $one) {
            $parts[] = rawurlencode($name) . '=' . rawurlencode($one);
        }
    }
    return implode('&', $parts);
}

/** Send one request; retry 429, 5xx and network errors up to 4 times. */
function sorsa(string $method, string $path, array $params = []): array
{
    $url = SORSA_API . $path;
    $headers = ['ApiKey: ' . (getenv('SORSA_API_KEY') ?: 'YOUR_API_KEY')];
    $options = [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30];

    if ($method === 'GET') {
        $url .= $params ? '?' . query($params) : '';
    } else {
        $headers[] = 'Content-Type: application/json';
        $options[CURLOPT_POSTFIELDS] = json_encode($params);
    }
    $options[CURLOPT_HTTPHEADER] = $headers;

    for ($attempt = 0; ; $attempt++) {
        $ch = curl_init($url);
        curl_setopt_array($ch, $options);
        $raw = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); // 0: no answer
        $again = in_array($status, [0, ...RETRY], true);
        if (!$again || $attempt === 4) {
            break;
        }
        sleep(2 ** $attempt); // 1, 2, 4, 8 seconds
    }

    if ($raw === false) {
        throw new RuntimeException('Network error: ' . curl_error($ch));
    }
    $data = json_decode($raw, true, flags: JSON_THROW_ON_ERROR);
    if ($status !== 200) {
        throw new RuntimeException("HTTP $status: " . ($data['message'] ?? ''));
    }
    return $data;
}

Um 401 ou um 403 nunca merece nova tentativa: a chave está errada ou ausente, ou suas requisições acabaram, e o campo message diz qual é o caso. Requisições que falham nunca são cobradas, então repetir não custa nada, e todo endpoint da Sorsa API só lê, então repetir um POST não cria nada em duplicidade. A função roda no PHP 8.0 e posteriores. Ela dispensa o curl_close(), que não faz nada desde o PHP 8.0, porque os handles do cURL agora se fecham sozinhos.

#Percorra os resultados com next_cursor

Um cursor é uma chave opaca para a próxima página de resultados: devolva o next_cursor sem alterações e pare quando uma resposta vier sem ele. As timelines e o POST /search-tweets (abre em uma nova aba) devolvem até 20 tweets por página, e uma página pode trazer menos enquanto ainda restam resultados, então a contagem de itens nunca indica que o fim chegou. Um gerador entrega uma página por vez e mantém a memória estável por mais longo que seja o job.

PHP
require 'sorsa.php';

/** Yield pages of a POST endpoint until the cursor runs out. */
function pages(string $path, array $body, int $maxPages = 100): Generator
{
    for ($i = 0; $i < $maxPages; $i++) {
        $page = sorsa('POST', $path, $body);
        yield $page;
        if (empty($page['next_cursor'])) {
            return;
        }
        $body['next_cursor'] = $page['next_cursor'];
    }
}

foreach (pages('/search-tweets', ['query' => 'laravel lang:en']) as $page) {
    foreach ($page['tweets'] as $tweet) {
        echo $tweet['id'], ' @', $tweet['user']['username'], "\n";
    }
}

Mantenha a consulta e o order inalterados enquanto um cursor estiver em uso e trate $maxPages como um orçamento: 100 páginas são 100 requisições e no máximo 2.000 tweets. Os endpoints GET recebem o cursor na query string. O GET /followers (abre em uma nova aba) devolve até 200 perfis por página:

PHP
$params = ['username' => 'php_release_notes'];
$followers = [];
do {
    $page = sorsa('GET', '/followers', $params);
    array_push($followers, ...$page['users']);
    $params['next_cursor'] = $page['next_cursor'] ?? null;
} while ($params['next_cursor']);

Duas tarefas têm scripts completos próprios: baixar todos os tweets de um usuário retoma uma timeline inteira a partir de um cursor salvo, e o guia para exportar seguidores do Twitter grava listas de seguidores em CSV.

#Busque 100 tweets ou perfis por requisição

Quando você já tem IDs de tweets ou nomes de usuário, envie-os em lotes. O POST /tweet-info-bulk (abre em uma nova aba) recebe até 100 IDs ou links de tweets, e o GET /info-batch (abre em uma nova aba) recebe até 100 nomes de usuário. As respostas em lote não têm cursor: uma resposta traz o lote inteiro.

PHP
$tweets = [];
foreach (array_chunk($tweetIds, 100) as $batch) {
    $res = sorsa('POST', '/tweet-info-bulk', ['tweet_links' => $batch]);
    array_push($tweets, ...$res['tweets']);
}

$profiles = [];
foreach (array_chunk($usernames, 100) as $batch) {
    $res = sorsa('GET', '/info-batch', ['usernames' => $batch]);
    array_push($profiles, ...$res['users']);
}

Uma requisição custa o mesmo seja qual for o retorno, então um lote de 100 tweets custa o mesmo que um tweet: a partir de US$ 0,014 por 1.000 tweets, mais de 250 vezes menos que os US$ 5,00 da API oficial do X. As 100 requisições grátis cobrem 10 mil tweets dessa forma.

Bom saber

O GET /info-batch lê uma lista como uma chave repetida, usernames=a&usernames=b, a forma que a referência OpenAPI dele define. O http_build_query() do PHP escreve usernames%5B0%5D=a&usernames%5B1%5D=b em vez disso, e o mesmo fazem a opção query com array do Guzzle e o Http::get($url, $array) do Laravel, que montam a consulta da mesma maneira. Use o helper query() acima, ou o GuzzleHttp\Psr7\Query::build(), que escreve chaves repetidas.

$params = ['usernames' => ['php_release_notes', 'laravel_weekly_digest']]; COLCHETES http_build_query($params) ou a opção query do Guzzle usernames%5B0%5D=php_release_notes &usernames%5B1%5D=laravel_weekly_digest Dois parâmetros, usernames[0] e usernames[1], o hábito de nomes do próprio PHP CHAVE REPETIDA query($params) ou GuzzleHttp\Psr7\Query::build($params) usernames=php_release_notes &usernames=laravel_weekly_digest Um parâmetro, usernames, com dois valores: a forma que o GET /info-batch define
O padrão do PHP escreve nomes com colchetes; o endpoint de lote lê um único nome repetido, então monte você mesmo essa query string.

#Rode lotes em paralelo a 19 requisições por segundo

O PHP executa requisições lado a lado com o curl_multi, e um único processo alcança as 20 requisições por segundo por chave da Sorsa API assim que o trabalho se divide em requisições independentes. Os lotes se dividem bem, porque cada um é autônomo. Uma cadeia de cursor não pode ser dividida, já que cada página precisa do cursor da anterior, então rode contas ou janelas de datas separadas lado a lado. A parallel() inicia no máximo 19 requisições por segundo, uma abaixo do limite, e recoloca na fila qualquer 429 ou 5xx.

PHP
/** POST many bodies side by side, starting at most $rate a second. */
function parallel(string $path, array $bodies, float $rate = 19): array
{
    $multi = curl_multi_init();
    $queue = array_keys($bodies);
    $tries = $results = [];
    $next = microtime(true);
    $running = 0;

    while ($queue || $running) {
        if ($queue && microtime(true) >= $next) {
            $key = array_shift($queue);
            $ch = curl_init(SORSA_API . $path);
            curl_setopt_array($ch, [
                CURLOPT_HTTPHEADER => [
                    'ApiKey: ' . (getenv('SORSA_API_KEY') ?: 'YOUR_API_KEY'),
                    'Content-Type: application/json',
                ],
                CURLOPT_POSTFIELDS => json_encode($bodies[$key]),
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_TIMEOUT => 30,
                CURLOPT_PRIVATE => (string) $key,
            ]);
            curl_multi_add_handle($multi, $ch);
            $next = max($next, microtime(true) - 1 / $rate) + 1 / $rate;
        }
        curl_multi_exec($multi, $running);
        while ($done = curl_multi_info_read($multi)) {
            $ch = $done['handle'];
            $key = curl_getinfo($ch, CURLINFO_PRIVATE);
            $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
            $tries[$key] = ($tries[$key] ?? 0) + 1;
            if (in_array($status, [0, ...RETRY], true) && $tries[$key] < 5) {
                $queue[] = $key; // back in line, it waits its turn
            } else {
                $results[$key] = json_decode(curl_multi_getcontent($ch), true);
            }
            curl_multi_remove_handle($multi, $ch);
        }
        curl_multi_select($multi, 0.01);
    }
    return $results;
}

$bodies = array_map(fn ($batch) => ['tweet_links' => $batch],
    array_chunk($tweetIds, 100));
$tweets = array_merge(...array_column(parallel('/tweet-info-bulk', $bodies),
    'tweets'));
parallel('/tweet-info-bulk', $bodies) 100 lotes de 100 IDs de tweets Um segundo em uma chave de API cada barra é uma requisição em andamento 0 s 1 s 19 inícios por segundo, um abaixo do limite de 20 por chave 1.900 tweets por segundo 19 lotes de 100 IDs Cerca de 5 s para 10 mil tweets 100 requisições, 19 por segundo 429 ou 5xx: volta para a fila espera início livre O loop só adiciona uma requisição na hora de seu início, por mais que as respostas anteriores demorem.
Os inícios seguem um relógio fixo, então respostas lentas ou rápidas nunca levam a chave além de 20 requisições por segundo.

Com 19 inícios por segundo, 100 lotes movem 10 mil tweets em cerca de 5 segundos, 1.900 tweets por segundo. Mantenha um único loop parallel() por chave: dois processos rodando lado a lado iniciam o dobro do limite entre eles. O Pool do Guzzle parece a ferramenta certa para isto, mas a opção concurrency dele limita as requisições em andamento, não os inícios por segundo, então um pool de respostas rápidas ainda pode ultrapassar 20 por segundo e depender de retentativas.

#Laravel: as mesmas chamadas com o cliente Http

O cliente Http do Laravel envolve o Guzzle, então um app Laravel não precisa de nenhum pacote extra. Uma macro define a URL base, a chave e uma regra de retentativa que repete apenas 429, respostas 5xx e conexões interrompidas; todo controller, job ou comando Artisan chama então Http::sorsa().

// config/services.php: 'sorsa' => ['key' => env('SORSA_API_KEY')],
// .env: SORSA_API_KEY=YOUR_API_KEY

// app/Providers/AppServiceProvider.php
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;

// inside the class
public function boot(): void
{
    Http::macro('sorsa', fn () => Http::baseUrl('https://api.sorsa.io/v3')
        ->withHeaders(['ApiKey' => config('services.sorsa.key')])
        ->timeout(30)
        ->retry(5, fn (int $attempt) => 1000 * 2 ** ($attempt - 1),
            fn (Throwable $e) => $e instanceof ConnectionException
                || ($e instanceof RequestException
                    && ($e->response->status() === 429
                        || $e->response->serverError()))));
}

O retry() espera 1, 2, 4 e 8 segundos entre as tentativas e lança uma RequestException quando as tentativas acabam ou o status não vale uma nova tentativa, como um 401 ou um 403, então uma chamada que falhou nunca vai parar no cache. Rode jobs longos, como milhares de requisições em lote, a partir de um job em fila ou de um comando Artisan, e não dentro de uma requisição de página.

Um feed em cache gasta requisições pelo relógio, não por visitante. O planejador transforma o número de feeds e o tempo de cache em requisições por dia e por mês, e no plano da Sorsa API que as comporta.

Feeds no seu site (contas ou buscas)
Manter cada feed em cache por
Requisições por dia
288

seja qual for o tráfego

Requisições por mês
8,640

30 dias

Plano da Sorsa API que comporta
Starter

10K requisições por mês, 86% usadas

As 100 requisições grátis duram
cerca de 8 h

sem cartão, nunca expiram

Todo visitante no mesmo período de 5 minutos lê a cópia em cache, então 100 visualizações de página e 1M de visualizações custam as mesmas 288 requisições por dia.

As requisições dependem de com que frequência o cache é atualizado, nunca das visualizações de página.

#Quanto custa um job em PHP

A Sorsa API cobra por requisição, seja qual for o retorno do endpoint: timelines e busca custam a partir de US$ 0,07 por 1.000 tweets, lotes a partir de US$ 0,014, e requisições com falha nunca são cobradas. A API oficial do X cobra por objeto: US$ 5,00 por 1.000 posts e, a partir de 22 de novembro de 2026, até US$ 15,00 por 1.000 quando o código expande o autor de cada post, como faz a maioria das buscas do TwitterOAuth.

Tweets que seu código PHP coleta

API oficial do X

US$ 5.000

para 1 milhão tweets

US$ 0,005 por post

Sorsa API

a partir deUS$ 70

para 1 milhão tweets

a partir de US$ 0,07 por 1.000

O perfil do autor vem dentro de cada tweet

A tarifa por 1.000 requisições vai de US$ 4,90 no Starter a US$ 1,40 no Hyperscale, e cada plano a aplica igualmente a todos os endpoints. Consultar o que resta não custa nada: GET /key-usage-info (abre em uma nova aba) nunca desconta das suas requisições.

PHP
$usage = sorsa('GET', '/key-usage-info');
echo $usage['remaining_requests'], ' left until ', $usage['valid_until'], "\n";

#Como migrar o código do TwitterOAuth para a Sorsa API

A maioria das leituras do TwitterOAuth corresponde a um endpoint da Sorsa API, e o código abandona as expansões e as listas de campos, porque cada tweet já traz o autor e as contagens. A tabela une caminhos comuns da v2 aos seus endpoints; para migrar uma base de código inteira, o guia para migrar da API do Twitter (X) mapeia todos os endpoints da v2 e campos de resposta.

Chamada do TwitterOAuthEndpoint da Sorsa APIO que muda
get('tweets/search/recent')POST /search-tweetsUm só endpoint para busca recente e de arquivo, desde 2006
get("users/$id/tweets")POST /user-tweetsBasta o nome de usuário, e não há limite de 3.200 posts
get('tweets') com idsPOST /tweet-info-bulk100 IDs ou links por requisição
get('users/by') com usernamesGET /info-batch100 nomes de usuário por requisição
get("users/$id/followers")GET /followers200 seguidores por página
post('tweets')nenhumSomente leitura: poste pela API oficial do X
  • get('tweets/search/recent')

    Endpoint da Sorsa API
    POST /search-tweets
    O que muda
    Um só endpoint para busca recente e de arquivo, desde 2006
  • get("users/$id/tweets")

    Endpoint da Sorsa API
    POST /user-tweets
    O que muda
    Basta o nome de usuário, e não há limite de 3.200 posts
  • get('tweets') com ids

    Endpoint da Sorsa API
    POST /tweet-info-bulk
    O que muda
    100 IDs ou links por requisição
  • get('users/by') com usernames

    Endpoint da Sorsa API
    GET /info-batch
    O que muda
    100 nomes de usuário por requisição
  • get("users/$id/followers")

    Endpoint da Sorsa API
    GET /followers
    O que muda
    200 seguidores por página
  • post('tweets')

    Endpoint da Sorsa API
    nenhum
    O que muda
    Somente leitura: poste pela API oficial do X

Os nomes dos campos mudam também: text vira full_text, public_metrics->like_count vira likes_count, author_id com includes->users vira o array user, e meta->next_token vira next_cursor. O TwitterOAuth devolve objetos, enquanto json_decode($body, true) dá os arrays que este guia usa. A timeline de usuário também passa dos 3.200 posts mais recentes, onde a timeline oficial para (abre em uma nova aba).

#Faça hoje a sua primeira requisição em PHP

Ler dados do X em PHP exige a extensão cURL, uma chave e quatro funções curtas: sorsa() para as retentativas, pages() para os cursores, array_chunk() com os endpoints de lote para 100 itens por requisição e parallel() para 19 requisições por segundo. Comece pela Sorsa API, a API do Twitter feita para ler dados públicos do X em escala: 20 tweets por requisição de timeline ou busca a partir de US$ 0,07 por 1.000, 100 por lote a partir de US$ 0,014 e 100 requisições grátis que não pedem cartão e nunca expiram. As mesmas chamadas em outras linguagens estão nos guias da API do Twitter em Python e da API do Twitter em Node.js.

#Perguntas frequentes

Qual API do Twitter devo usar com PHP?

Para ler dados do X em PHP, use a Sorsa API: uma chave em um header ApiKey no lugar do OAuth, JSON simples para o json_decode, timelines e busca a partir de US$ 0,07 por 1.000 tweets e lotes a partir de US$ 0,014, 20 requisições por segundo em todos os planos, busca desde 2006 e nenhum login do X, então a sua conta nunca corre risco. Para postar também, combine-a com a API oficial do X.

Existe uma API do Twitter grátis para PHP?

A API oficial do X não tem plano gratuito para novos desenvolvedores: o TwitterOAuth é grátis, mas cada leitura gasta créditos pré-pagos a US$ 0,005 por post. A Sorsa API dá a toda conta 100 requisições grátis que nunca expiram e não pedem cartão. No PHP, elas buscam 2.000 tweets por timelines ou busca, ou até 10 mil tweets em lotes de 100. O panorama completo está em a API do Twitter é grátis?.

O Twitter (X) usa PHP?

Não. O Twitter nasceu em Ruby on Rails e, a partir de 2010, passou o back end para a JVM, com serviços em Scala e Java, como descreve o blog de engenharia do Twitter (abre em uma nova aba). O código de recomendação que o X publicou em 2023 e de novo em janeiro de 2026 é em sua maior parte Scala, Java, Rust e Python, sem nenhum PHP. Mesmo assim, o PHP funciona bem como cliente: os dados do X chegam a um script PHP como JSON por HTTPS.

Posso postar tweets em PHP com a Sorsa API?

Não. A Sorsa API é somente leitura por princípio: ela lê posts, perfis e seguidores públicos e nunca posta, curte, segue nem envia mensagens, então uma chave não pode agir em nenhuma conta do X. Para postar em PHP, use a API oficial do X com tokens de usuário OAuth 1.0a, como o post('tweets', ...) do TwitterOAuth, a US$ 0,015 por post, e deixe a Sorsa API para as leituras.

Posso usar a Sorsa API no WordPress?

Sim. As funções HTTP do próprio WordPress enviam a mesma requisição: wp_remote_post() com o header ApiKey e um corpo JSON, depois wp_remote_retrieve_body() e json_decode(). Guarde o resultado com set_transient() por alguns minutos, para que visualizações de página nunca virem requisições, e armazene a chave no wp-config.php ou em uma variável de ambiente, nunca em um script que o navegador carregue.

Existe um pacote do Composer para a Sorsa API?

Nenhum pacote é necessário: a Sorsa API é REST simples com JSON, então a extensão cURL, o Guzzle ou o cliente Http do Laravel cobrem os 45 endpoints, e as funções deste guia cabem em cerca de 100 linhas. A referência OpenAPI em api.sorsa.io/v3/swagger.json lista todos os endpoints, então um gerador como o openapi-generator pode criar um cliente PHP tipado, se você quiser um.

#Como verificamos

Escrevemos cada requisição deste guia a partir da referência OpenAPI ao vivo da Sorsa API (45 endpoints) e da documentação dela sobre rate limits e paginação, lidas em 2 de novembro de 2026. Não tivemos uma chave da Sorsa API nesta rodada, então a resposta exibida usa valores de demonstração, e rodamos cada bloco PHP no PHP 8.4.27 com Guzzle 8.3.1 e illuminate/http 13 do Laravel contra um mock local que responde nos formatos documentados e devolve 429 acima de 20 requisições por segundo: as primeiras requisições, as retentativas, os dois loops de cursor, os lotes de 100, as query strings, a macro do Laravel e a parallel(), que enviou 100 requisições de lote a 19 por segundo com no máximo um 429 por execução, sempre recuperado pela retentativa. No mesmo dia lemos a tabela de preços do X, a página de ferramentas, a documentação de timelines e o aviso sobre expansões, e as versões do TwitterOAuth, do Guzzle e do Laravel no Packagist.

Pela equipe da Sorsa API. Construímos e operamos a Sorsa API, a API e scraper somente leitura do Twitter (X), em produção desde 2022 (antiga TweetScout API), com mais de 6 bilhões de requisições atendidas. Sobre a Sorsa API

O que mudou
  • Reescrito do zero: preços atuais da API oficial do X com a cobrança das expansões a partir de 22 de novembro, TwitterOAuth 9.0.0 e um caminho em PHP com uma só chave de API: requisições com cURL e Guzzle, retentativas, cursores, lotes de 100 com chaves de consulta repetidas, requisições paralelas a 19 por segundo com curl_multi, o cliente Http do Laravel com cache e um mapa de métodos do TwitterOAuth.
  • Primeira publicação.