⚡ AutomationsAI|Portal de Cursos →

Verificando acesso...

MÓDULO 3.3

🐦 X (Twitter API v2)

Do Developer Portal ao primeiro tweet automatizado: criar app, OAuth 2.0, lib twitter-api-v2, upload de mídia e threads programáticos.

6
Tópicos
40
Minutos
Intermediário
Nível
Prático
Tipo
1

🏢 Developer Portal X.com

Toda integração com o X começa no developer.x.com. Você precisa de uma conta X normal (com telefone verificado) e um cadastro de developer separado — mesma identidade, contexto diferente. O cadastro pede um caso de uso descrito em inglês.

O modelo é em três níveis: Account → Project → App. A Account é você; um Project agrupa apps relacionados (uma marca, um produto); um App contém as credenciais que sua aplicação usa. Para automação simples, basta um Project com um App.

📋 Checklist do cadastro

  • Telefone verificado na conta X — sem isso o Portal recusa o acesso.
  • Descrição de uso em inglês (300+ caracteres): o que sua app faz e por quê.
  • Aceite dos Restricted Use Cases — proibido spam, surveillance, ML scraping massivo.
  • Project criado com nome, caso de uso e descrição pública.
# Fluxo no Portal (manual no browser)
# 1. https://developer.x.com → Sign up
# 2. Use case: "Building tools for myself" (mais rápido de aprovar)
# 3. Criar Project: nome, caso, descrição
# 4. Dentro do Project → "+ Add App"
# 5. Anotar App ID e abrir "Keys and tokens"

Conceitos-chave

Developer Account

Cadastro paralelo à conta X normal.

Project

Agrupador de apps por contexto de negócio.

App

Onde vivem keys, secrets e tokens de acesso.

Use Case

Texto explicando o uso — base da aprovação.

2

💵 Custo: pay-per-use $0.01/post

O X reformulou os tiers em 2024-2025. O Free tier deixou de permitir POST /tweets em volume útil: você lê alguns endpoints e posta apenas como teste. Para automação real, há dois caminhos práticos.

O pay-per-use (lançado 2025) cobra $0.01 por post — ideal para baixo volume, sem mensalidade. Já o Basic, a $200/mês, libera 3.000 posts/mês + leitura ampliada. Acima disso: Pro ($5.000/mês).

⚡ Pay-per-use

  • $0.01 por POST /2/tweets
  • Sem mensalidade — só paga o que usa
  • Ideal: até ~5.000 posts/mês
  • Mesmo limite de rate da conta

📦 Basic — $200/mês

  • 3.000 posts/mês inclusos
  • 10.000 leituras/mês
  • OAuth 1.0a + OAuth 2.0 disponíveis
  • Quebra ponto: ~20.000 posts vs pay-per-use

💡 Dica prática

Comece pay-per-use mesmo se planeja crescer. Migre para Basic só quando seu custo_mensal_pay_per_use > $200 — a matemática é direta: posts_no_mes × 0.01 > 200, ou seja, ~20k posts. Abaixo disso, pay-per-use é estritamente mais barato.

Conceitos-chave

Free tier

Quase só leitura — inviável para automação.

Pay-per-use

Cobrança por chamada, sem assinatura.

Basic

Tier flat $200 com quota mensal.

Rate limit

Limite por janela de tempo, paralelo ao custo.

3

⚙️ Criar app + OAuth 2.0

Dentro do App, abra User authentication settings e habilite OAuth 2.0. Para automação no servidor, escolha tipo Confidential client (gera Client Secret) com flow Authorization Code + PKCE.

Configure a Callback URL (mesmo domínio do seu app, ex.: https://app.exemplo.com/oauth/x/callback) e selecione os scopes mínimos necessários — sempre o menor conjunto possível.

// scopes essenciais para postagem
const SCOPES = [
  "tweet.read",     // ler tweets
  "tweet.write",    // criar tweets — obrigatório
  "users.read",     // ler dados do usuário autenticado
  "offline.access"  // receber refresh_token (renovação automática)
] as const;

// URL de autorização — usuário vê tela de consent do X
const authUrl = new URL("https://x.com/i/oauth2/authorize");
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("client_id", process.env.X_CLIENT_ID!);
authUrl.searchParams.set("redirect_uri", "https://app.exemplo.com/oauth/x/callback");
authUrl.searchParams.set("scope", SCOPES.join(" "));
authUrl.searchParams.set("state", crypto.randomUUID());
authUrl.searchParams.set("code_challenge", codeChallenge);   // PKCE
authUrl.searchParams.set("code_challenge_method", "S256");

console.log(authUrl.toString());

✓ O que FAZER

  • Usar Confidential client em servidor backend.
  • Pedir offline.access para refresh automático.
  • Validar o state no callback contra CSRF.
  • Persistir refresh_token criptografado.

✗ O que NÃO fazer

  • Embutir Client Secret no frontend.
  • Reusar code_verifier entre requests.
  • Pedir scopes demais "por garantia" — quebra trust.
  • Usar http:// em callback de prod.

Conceitos-chave

OAuth 2.0

Padrão para autorização delegada.

PKCE

Code challenge que evita interceptação.

Callback URL

Endpoint que recebe o code.

Scopes

Permissões granulares concedidas pelo usuário.

4

📦 Lib twitter-api-v2

O cliente oficial-de-fato em Node/TypeScript é twitter-api-v2. Cobre v2 + v1.1 (necessária para upload de mídia), tem tipagem completa, faz refresh automático de token e expõe helpers para threads e paginação.

# Instalação
npm install twitter-api-v2
npm install -D @types/node typescript
// src/x-client.ts
import { TwitterApi } from "twitter-api-v2";

// Cliente OAuth 2.0 (usuário autenticado via Authorization Code)
export function userClient(accessToken: string) {
  return new TwitterApi(accessToken);
}

// Cliente App-only (apenas leitura pública, sem usuário)
export function appClient() {
  return new TwitterApi(process.env.X_BEARER_TOKEN!);
}

// Cliente OAuth 1.0a (necessário para upload v1.1)
export function userContextClient() {
  return new TwitterApi({
    appKey:       process.env.X_API_KEY!,
    appSecret:    process.env.X_API_SECRET!,
    accessToken:  process.env.X_ACCESS_TOKEN!,
    accessSecret: process.env.X_ACCESS_SECRET!,
  });
}

// Refresh automático (offline.access ligado)
export async function refresh(refreshToken: string) {
  const base = new TwitterApi({
    clientId:     process.env.X_CLIENT_ID!,
    clientSecret: process.env.X_CLIENT_SECRET!,
  });
  const { accessToken, refreshToken: newRefresh, expiresIn } =
    await base.refreshOAuth2Token(refreshToken);
  return { accessToken, refreshToken: newRefresh, expiresIn };
}

⚙️ Dica prática

A lib distingue três contextos de auth (user OAuth2, app-only, user OAuth1.0a) e cada um expõe endpoints diferentes. Memorize: postar texto = OAuth 2.0 user; upload de mídia = OAuth 1.0a (a v2 ainda não cobre); buscar tweets públicos = app-only com Bearer.

Conceitos-chave

User context

Age em nome de um usuário específico.

App-only

Bearer estático para leitura pública.

Refresh token

Troca por novo access token sem reconsentir.

Tipagem

Tipos completos para v2 — autocomplete real.

5

🖼️ Upload de mídia (ainda v1.1)

Em 2026 o upload de imagens/vídeos para o X ainda vive na v1.1 — o endpoint media/upload.json não foi migrado. O fluxo é: subir o arquivo, receber um media_id, e referenciá-lo na criação do tweet via v2.

Por isso o app costuma precisar de dois contextos de auth: OAuth 1.0a para o upload, OAuth 2.0 para o post. A lib twitter-api-v2 esconde essa complexidade — você só precisa instanciar os dois clientes.

// src/post-with-media.ts
import { TwitterApi, EUploadMimeType } from "twitter-api-v2";
import { readFile } from "node:fs/promises";

interface PostWithMediaInput {
  text: string;
  imagePath: string;     // ex.: "./assets/banner.png"
  altText?: string;      // acessibilidade
}

export async function postWithMedia(
  v1Client: TwitterApi,           // OAuth 1.0a — para upload
  v2Client: TwitterApi,           // OAuth 2.0 — para criar tweet
  input: PostWithMediaInput
) {
  // 1) Upload via v1.1 -> retorna media_id como string
  const buffer = await readFile(input.imagePath);
  const mediaId = await v1Client.v1.uploadMedia(buffer, {
    mimeType: EUploadMimeType.Png,
  });

  // 2) Anexar alt text (acessibilidade — obrigatório para boas práticas)
  if (input.altText) {
    await v1Client.v1.createMediaMetadata(mediaId, {
      alt_text: { text: input.altText },
    });
  }

  // 3) Criar tweet via v2 referenciando o media_id
  const { data } = await v2Client.v2.tweet({
    text: input.text,
    media: { media_ids: [mediaId] },
  });

  return { tweetId: data.id, mediaId };
}

📐 Limites de mídia

  • Imagem: até 5 MB (PNG, JPG, WebP) — máximo 4 por tweet.
  • GIF: até 15 MB — 1 por tweet, exclusivo.
  • Vídeo: até 512 MB / 140 s — upload em chunks (init/append/finalize).
  • media_id: válido por ~24 h se não consumido.

Conceitos-chave

v1.1 legacy

Endpoints antigos ainda usados para upload.

media_id

Referência string para usar no v2.tweet.

Chunked upload

Vídeos: init → append → finalize.

Alt text

Descrição para leitores de tela — sempre setar.

6

🧵 Threads programáticos

Thread no X é um encadeamento de tweets em que cada item responde ao anterior via in_reply_to_tweet_id. Não existe endpoint "criar thread" — você posta um a um, capturando o ID retornado e injetando no próximo.

A lib oferece um helper v2.tweetThread() que faz o loop por você. Use sempre delay entre tweets (200-500 ms) para não bater rate limit nem ser flagrado como spam.

// src/thread.ts
import { TwitterApi } from "twitter-api-v2";

interface ThreadItem {
  text: string;
  mediaIds?: string[];
}

export async function postThread(
  client: TwitterApi,
  items: ThreadItem[]
): Promise<string[]> {
  if (items.length === 0) throw new Error("Thread vazia");

  const postedIds: string[] = [];
  let replyTo: string | undefined;

  for (const [i, item] of items.entries()) {
    const { data } = await client.v2.tweet({
      text: item.text,
      ...(item.mediaIds && { media: { media_ids: item.mediaIds } }),
      ...(replyTo && { reply: { in_reply_to_tweet_id: replyTo } }),
    });

    postedIds.push(data.id);
    replyTo = data.id;

    // Throttle leve para não disparar anti-spam
    if (i < items.length - 1) {
      await new Promise(r => setTimeout(r, 300));
    }
  }

  return postedIds;
}

// Uso
const ids = await postThread(client, [
  { text: "1/ Por que automatizar threads no X é uma boa ideia 🧵" },
  { text: "2/ Você posta em horário ótimo sem estar online." },
  { text: "3/ Mede engajamento por item e itera com dados." },
  { text: "4/ E ainda paga $0.04 (4 posts × $0.01) por thread inteira." },
]);
console.log("Tweets criados:", ids);

🛡️ Dica prática

Sempre faça tudo-ou-nada: se o tweet N falhar no meio da thread, DELETE /2/tweets/:id os anteriores. Threads pela metade ficam órfãs e confundem o leitor. Envolva o loop em try/catch e guarde os IDs já postados para rollback.

Conceitos-chave

in_reply_to_tweet_id

Liga um tweet ao anterior na thread.

Encadeamento

Cada item conhece só seu pai imediato.

Throttle

Espaço entre posts para evitar bloqueio.

Rollback

Deletar parciais quando a thread falha.

🎯 Resumo do Módulo

Developer Portal configurado — conta dev aprovada, Project criado e App com keys em mãos.
Modelo de custo entendido — pay-per-use a $0.01/post até ~20k posts; Basic $200/mês acima disso.
OAuth 2.0 + PKCE habilitados — callback registrado, scopes mínimos (tweet.write, users.read, offline.access).
Cliente twitter-api-v2 integrado — user context, app-only e refresh automático.
Upload de mídia via v1.1media_id obtido e anexado ao tweet v2 com alt text.
Threads programáticos — loop com in_reply_to_tweet_id, throttle e rollback.

Próximo Módulo:

3.4 — Próxima rede social: integração e particularidades