⚡ AutomationsAI|Portal de Cursos →

Verificando acesso...

MÓDULO 4.1

🐘 WordPress REST API

Publicar posts no WordPress por HTTP: endpoints, autenticação, upload de mídia, taxonomias e agendamento — tudo via /wp-json sem plugin.

6
Tópicos
30
Minutos
Básico
Nível
Prático
Tipo
1

✅ Habilitar a REST API

Desde o WordPress 4.7 (dez/2016) a REST API vem ativa por padrão. Não precisa plugin, não precisa flag, não precisa mexer em functions.php. O endpoint raiz é /wp-json/ e ele já descreve toda a API por hipermídia.

Antes de qualquer coisa, confirme que sua instalação responde. Um simples GET público lista a estrutura — se vier 404 ou HTML, alguém desabilitou via plugin de segurança ou .htaccess agressivo.

# Descobrir o índice da API (público)
curl -s https://seublog.com/wp-json/ | head -40

# Resposta JSON esperada (trecho)
{
  "name": "Seu Blog",
  "description": "Just another WordPress site",
  "url": "https://seublog.com",
  "namespaces": ["oembed/1.0", "wp/v2", "wp-site-health/v1"],
  "authentication": {
    "application-passwords": {
      "endpoints": {
        "authorization": "https://seublog.com/wp-admin/authorize-application.php"
      }
    }
  },
  "routes": {
    "/wp/v2/posts": { "namespace": "wp/v2", "methods": ["GET","POST"] },
    "/wp/v2/media": { "namespace": "wp/v2", "methods": ["GET","POST"] }
  }
}

Listar posts publicados é igualmente público — útil pra confirmar que o roteamento de /wp-json está saudável antes de mexer em autenticação.

# Listar os 5 últimos posts publicados
curl -s "https://seublog.com/wp-json/wp/v2/posts?per_page=5&_fields=id,title,date"

# [
#   {"id":142,"title":{"rendered":"Post mais novo"},"date":"2026-05-20T10:00:00"},
#   {"id":141,"title":{"rendered":"Outro post"},"date":"2026-05-19T08:30:00"}
# ]

💡 Dica prática

Plugins como Wordfence e iThemes Security bloqueiam a REST API "anônima" por padrão. Se GET /wp-json/wp/v2/users retornar 401 mesmo autenticado, vá na configuração do plugin e libere o namespace wp/v2 pra usuários logados.

Conceitos-chave

/wp-json

Raiz da REST API — descreve namespaces e rotas.

wp/v2

Namespace principal (posts, pages, media, users).

Permalinks

REST exige permalinks ≠ "plain". Confirme em Settings.

_fields

Filtra campos do payload — reduz tráfego em listas.

2

🔑 Application Password

Senha de aplicativo é o método oficial do WordPress core (5.6+) para autenticar via API. Diferente da senha de login, ela é gerada por contexto (um app, um script), pode ser revogada individualmente e funciona com HTTP Basic Auth sobre HTTPS.

Para gerar: Users → Profile (no admin) → role até Application Passwords → dê um nome (ex: postiz-bot) → clique em Add New. Aparece uma senha de 24 caracteres com espaços — copie agora, ela não reaparece.

# Exemplo do que o WP gera (NÃO use essa, gere a sua)
xxxx yyyy zzzz aaaa bbbb cccc

# Como usar em Basic Auth (curl)
USER="seu_login"
APP_PASS="xxxx yyyy zzzz aaaa bbbb cccc"

# Teste autenticado: ver seu próprio usuário
curl -s -u "$USER:$APP_PASS" \
  https://seublog.com/wp-json/wp/v2/users/me

# {
#   "id": 1,
#   "name": "Nei",
#   "roles": ["administrator"],
#   "capabilities": { "edit_posts": true, "publish_posts": true }
# }

Em ambientes onde o cliente HTTP não suporta -u user:pass direto, monte o header manualmente — o WordPress aceita as duas formas.

# Montar header Authorization na mão
TOKEN=$(printf "%s" "$USER:$APP_PASS" | base64)

curl -s -H "Authorization: Basic $TOKEN" \
     https://seublog.com/wp-json/wp/v2/users/me

✓ O que FAZER

  • Uma senha por integração — facilita revogar sem quebrar as outras.
  • Guardar em variável de ambiente ou secret manager, nunca no código.
  • Usar sempre https:// — Basic Auth em HTTP vaza a senha.
  • Criar um usuário dedicado com role editor só pra automação.

✗ O que NÃO fazer

  • Usar a senha do login do admin via Basic Auth.
  • Compartilhar uma única App Password entre vários bots.
  • Remover os espaços da senha — eles fazem parte.
  • Commitar .env com a App Password no git.

Conceitos-chave

Basic Auth

Header Authorization: Basic base64(user:pass).

App Password

Credencial por contexto, revogável, separada do login.

Capabilities

Permissões do usuário — limitam o que o token pode fazer.

HTTPS

Obrigatório — Basic Auth sem TLS é texto puro.

3

📝 POST /wp-json/wp/v2/posts

Criar um post é um único POST com body JSON. Os campos mínimos são title, content e status. O resto (slug, excerpt, autor, categorias) é opcional — o WordPress preenche defaults sensatos.

# Criar um post como rascunho (mais seguro pra testar)
curl -s -X POST \
  -u "$USER:$APP_PASS" \
  -H "Content-Type: application/json" \
  -d '{
        "title": "Meu primeiro post via REST",
        "content": "<p>Olá mundo do <strong>WordPress API</strong>.</p>",
        "status": "draft",
        "excerpt": "Resumo curto que vai pro feed",
        "slug": "primeiro-post-via-rest"
      }' \
  https://seublog.com/wp-json/wp/v2/posts

A resposta traz o objeto completo do post recém-criado. Guarde o id — você vai usar pra anexar mídia destacada, atualizar status e fazer PATCH depois.

# Resposta esperada (201 Created)
{
  "id": 234,
  "date": "2026-05-24T14:32:10",
  "date_gmt": "2026-05-24T17:32:10",
  "guid": { "rendered": "https://seublog.com/?p=234" },
  "modified": "2026-05-24T14:32:10",
  "slug": "primeiro-post-via-rest",
  "status": "draft",
  "type": "post",
  "link": "https://seublog.com/?p=234",
  "title": { "rendered": "Meu primeiro post via REST" },
  "content": {
    "rendered": "<p>Olá mundo do <strong>WordPress API</strong>.</p>",
    "protected": false
  },
  "author": 1,
  "featured_media": 0,
  "categories": [1],
  "tags": []
}

Pra atualizar, use POST ou PUT no mesmo endpoint com o id na URL. Só envie os campos que mudaram — o WP faz merge.

# Atualizar só o status do post 234 pra publicado
curl -s -X POST \
  -u "$USER:$APP_PASS" \
  -H "Content-Type: application/json" \
  -d '{"status":"publish"}' \
  https://seublog.com/wp-json/wp/v2/posts/234

Dica prática

Sempre crie como draft primeiro e publique num segundo request. Se algo no payload estiver errado (HTML quebrado, categoria inexistente), você corrige antes de virar conteúdo público. Erro com post já publicado vira UX problem pros leitores.

Conceitos-chave

Content-Type

application/json obrigatório no POST.

rendered vs raw

Resposta separa HTML final do conteúdo bruto.

slug

URL amigável — se omitido, WP gera do título.

Idempotência

Update reenviado com mesmo body produz mesmo estado.

4

🖼️ Upload em /wp-json/wp/v2/media

Mídia (imagem destacada, imagens inline) sobe pelo endpoint /wp-json/wp/v2/media. Não é JSON — é multipart/form-data ou raw binary com header Content-Disposition. A resposta traz o id da mídia, que você usa em featured_media no post.

A forma mais simples no curl é raw binary com --data-binary — funciona pra qualquer imagem PNG/JPG/WebP.

# Upload de uma imagem como raw binary
curl -s -X POST \
  -u "$USER:$APP_PASS" \
  -H "Content-Type: image/jpeg" \
  -H "Content-Disposition: attachment; filename=\"capa.jpg\"" \
  --data-binary "@./capa.jpg" \
  https://seublog.com/wp-json/wp/v2/media

A resposta é o objeto da mídia. Anote o id (aqui 567) — esse vira o featured_media do post.

# Resposta 201 Created
{
  "id": 567,
  "date": "2026-05-24T14:35:00",
  "slug": "capa",
  "type": "attachment",
  "link": "https://seublog.com/capa/",
  "title": { "rendered": "capa" },
  "author": 1,
  "media_type": "image",
  "mime_type": "image/jpeg",
  "source_url": "https://seublog.com/wp-content/uploads/2026/05/capa.jpg",
  "media_details": {
    "width": 1920,
    "height": 1080,
    "file": "2026/05/capa.jpg",
    "sizes": {
      "thumbnail": { "width": 150, "height": 150, "source_url": "..." },
      "medium":    { "width": 300, "height": 169, "source_url": "..." },
      "large":     { "width": 1024, "height": 576, "source_url": "..." }
    }
  }
}

Agora associe a mídia ao post — patch no post 234 com featured_media: 567.

# Definir capa do post
curl -s -X POST \
  -u "$USER:$APP_PASS" \
  -H "Content-Type: application/json" \
  -d '{"featured_media": 567}' \
  https://seublog.com/wp-json/wp/v2/posts/234

# Atualizar alt text e legenda da mídia
curl -s -X POST \
  -u "$USER:$APP_PASS" \
  -H "Content-Type: application/json" \
  -d '{
        "alt_text": "Pessoa digitando em laptop com café ao lado",
        "caption": "Foto: Unsplash / @creator"
      }' \
  https://seublog.com/wp-json/wp/v2/media/567

📐 Dica prática

O WordPress regenera todos os tamanhos no upload (thumbnail, medium, large + os do tema). Mande imagens já otimizadas em 1920x1080 ou menor — subir arquivos de 6MB triplica o uso de disco e atrasa o request. Use cwebp ou mozjpeg antes do upload.

Conceitos-chave

multipart

Formato HTTP para enviar binário + metadados.

Content-Disposition

Header que carrega o filename original.

featured_media

ID da imagem destacada do post.

alt_text

Acessibilidade e SEO — sempre preencher.

5

🏷️ Categorias e tags

WordPress trabalha com IDs numéricos, não strings. Pra atribuir "Tecnologia" a um post, você precisa primeiro descobrir o ID dessa categoria. Os endpoints são /wp/v2/categories e /wp/v2/tags.

# Buscar categoria por slug
curl -s "https://seublog.com/wp-json/wp/v2/categories?slug=tecnologia&_fields=id,name,slug"

# [{"id": 7, "name": "Tecnologia", "slug": "tecnologia"}]

# Listar todas as categorias (até 100)
curl -s "https://seublog.com/wp-json/wp/v2/categories?per_page=100&_fields=id,name,slug"

Se a categoria não existe, crie sob demanda. O endpoint exige autenticação (capability manage_categories).

# Criar categoria
curl -s -X POST \
  -u "$USER:$APP_PASS" \
  -H "Content-Type: application/json" \
  -d '{"name":"Inteligência Artificial","slug":"ia"}' \
  https://seublog.com/wp-json/wp/v2/categories

# {"id": 12, "name": "Inteligência Artificial", "slug": "ia", "count": 0}

# Criar tag
curl -s -X POST \
  -u "$USER:$APP_PASS" \
  -H "Content-Type: application/json" \
  -d '{"name":"WordPress"}' \
  https://seublog.com/wp-json/wp/v2/tags

# {"id": 34, "name": "WordPress", "slug": "wordpress", "count": 0}

Com os IDs em mãos, mande no payload do post como arrays — mesmo que seja uma única categoria/tag.

# Atribuir categorias 7 + 12 e tags 34 + 88 ao post 234
curl -s -X POST \
  -u "$USER:$APP_PASS" \
  -H "Content-Type: application/json" \
  -d '{
        "categories": [7, 12],
        "tags": [34, 88]
      }' \
  https://seublog.com/wp-json/wp/v2/posts/234

📊 Padrão pra automação

  • 1. Cache local de IDs: seu script mantém um mapa {slug → id} em memória/Redis e só consulta a API quando o slug é novo.
  • 2. Get-or-create: primeiro GET ?slug=x; se array vazio, POST pra criar.
  • 3. Slugs estáveis: normalize antes (lowercase, sem acento) — evita criar "IA" e "ia" como categorias distintas.
  • 4. Tags primeiro, categorias depois: categorias podem ter parent/hierarquia, então resolva pai antes do filho.

Conceitos-chave

Taxonomy

Sistema de classificação (category, tag, custom).

Slug

Identificador URL-safe, único por taxonomia.

Get-or-create

Padrão idempotente: busca, e cria se não existe.

Hierarquia

Categorias podem ter parent; tags não.

6

📅 Status: draft / publish / future

O campo status controla o ciclo de vida do post. Os três que importam pra automação são draft (rascunho), publish (no ar) e future (agendado). Agendamento é nativo — não precisa cronjob nem plugin.

1

draft — rascunho

Visível só no admin pra autores e editores. Use pra criar e revisar antes de publicar.

{"status": "draft"}
2

publish — no ar agora

Vai pra home, feed RSS e indexação imediatamente. Sem volta sem dor.

{"status": "publish"}
3

future — agendado

Combine status: future + date no futuro. O WP-Cron publica sozinho na hora marcada.

{
  "status": "future",
  "date": "2026-06-01T09:00:00"
}

Exemplo completo: criar um post já agendado pra próxima segunda às 9h, com capa, categorias e tags — tudo num único request.

# Post agendado completo
curl -s -X POST \
  -u "$USER:$APP_PASS" \
  -H "Content-Type: application/json" \
  -d '{
        "title": "5 hábitos que mudaram meu workflow",
        "slug": "5-habitos-workflow",
        "content": "<p>Conteúdo completo aqui...</p>",
        "excerpt": "Os pequenos rituais que ganharam horas da minha semana.",
        "status": "future",
        "date": "2026-06-01T09:00:00",
        "categories": [7, 12],
        "tags": [34, 88],
        "featured_media": 567
      }' \
  https://seublog.com/wp-json/wp/v2/posts

Listar o que está na fila de agendamento é igualmente simples — filtre por status=future.

# Ver agenda futura
curl -s -u "$USER:$APP_PASS" \
  "https://seublog.com/wp-json/wp/v2/posts?status=future&_fields=id,date,title,slug"

# [
#   {"id": 234, "date": "2026-06-01T09:00:00", "title": {...}, "slug": "5-habitos-workflow"},
#   {"id": 235, "date": "2026-06-03T08:00:00", "title": {...}, "slug": "outro-post"}
# ]

Dica prática

O WP-Cron não é um cron real — ele dispara quando alguém visita o site. Se seu blog tem tráfego baixo, agendamentos atrasam. Em produção séria, desabilite o WP-Cron interno (DISABLE_WP_CRON=true no wp-config.php) e configure um cron de sistema que chama wp-cron.php a cada 5 min.

Conceitos-chave

status

draft, publish, future, pending, private, trash.

date

ISO 8601 no fuso do site; date_gmt em UTC.

WP-Cron

Scheduler interno baseado em hits HTTP.

Timezone

Settings → General define o fuso do date.

🎯 Resumo do Módulo

REST API confirmada/wp-json responde, namespaces wp/v2 ativos, permalinks OK.
Application Password gerada — credencial dedicada, Basic Auth sobre HTTPS, guardada fora do código.
POST /posts dominado — criar como draft, atualizar com PATCH, status final num segundo request.
Upload de mídia — raw binary em /media, featured_media anexado ao post, alt text preenchido.
Taxonomias resolvidas — get-or-create de categorias/tags por slug, IDs em cache pra evitar chamadas redundantes.
Agendamento nativostatus=future + date futura, com cron de sistema apoiando o WP-Cron.

Próximo Módulo:

4.2 — Ghost API: publicação programática num CMS moderno e headless-friendly