PostWharf

Agende e publique posts no Instagram, TikTok, X, LinkedIn, YouTube, Facebook, Threads, Pinterest, Bluesky e Telegram a partir de qualquer cliente MCP, com comprovante de entrega por rede.

Servidor MCP hospedado

npx add-mcp 'https://mcp.postwharf.com/mcp'

Instala no Claude Code, Codex, Cursor e outros

Documentação

Autenticação

Toda requisição carrega uma chave de workspace como token bearer. As chaves são limitadas a um único workspace, então uma chave de uma marca nunca pode publicar em outra. Crie e revogue-as em Configurações → Chaves de API.

# every request
curl https://api.postwharf.com/v1/channels \
  -H "Authorization: Bearer wf_live_..."

Nunca envie uma chave para um navegador. As chaves publicam em seu nome. Mantenha-as no lado do servidor, em uma variável de ambiente, e rotacione imediatamente se uma vazar.

API REST

URL base https://api.postwharf.com/v1. JSON entra, JSON sai.

GET/platforms

GET/channels

GET/connector-requests

POST/connector-requests

POST/media/upload-url

POST/media

GET/media

PATCH/media/:id

POST/posts

GET/posts

GET/posts/:id

PATCH/posts/:id

DELETE/posts/:id

GET/analytics

GET/events

Toda rota, com seus parâmetros e recusas, está na referência da API.

Quatro conectores são somente sob solicitação. Reddit, Discord, Snapchat e WordPress permanecem visíveis, mas ainda não podem criar canais ou publicar. POST /connector-requests registra a demanda do workspace uma vez por plataforma.

Criar uma publicação

Defina qualquer conjunto de canais como alvo. Forneça publish_at para agendar, ou omita-o para publicar imediatamente. As substituições por canal permitem que um único payload carregue uma legenda diferente para cada rede.

POST /v1/posts
{
  "text": "Most founders think they have a marketing problem.",
  "media": ["med_8f2a"],
  "channels": ["ch_x_kevin", "ch_li_rm", "ch_ig_hs"],
  "publish_at": "2026-08-18T09:00:00+04:00",
  "overrides": {
    "ch_x_kevin": { "text": "Short version for X →" }
  }
}

Resposta

{
  "id": "post_31ba",
  "status": "scheduled",
  "deliveries": [
    { "channel": "ch_x_kevin", "status": "pending" },
    { "channel": "ch_li_rm",  "status": "pending" }
  ]
}

Consulte GET /posts/:id ou assine um webhook. Uma vez publicada, cada entrega ganha um url apontando para a publicação na rede, e um url_state explicando o motivo quando não houver: none (nada foi publicado naquele trecho), pending (foi publicada e o endereço ainda não é conhecido, então vale a pena perguntar novamente), ou not_reported (foi publicada e essa rede não retorna nenhum endereço).

O array media é ordenado. Os arquivos são publicados na ordem em que você os lista, não na ordem em que foram enviados, então um carrossel chega da forma como você o montou. Enviar media em PATCH /posts/:id substitui a lista inteira, que também é como você reordena uma.

Pergunte o que cada rede aceita

GET /platforms retorna as regras como dados: o limite de texto para cada rede, o limite menor que se aplica quando um arquivo é anexado, quantas imagens e vídeos uma publicação pode carregar, os formatos que ela publica e se esta implantação publica nela. Cada número é aquele contra o qual as recusas são escritas, então não há nada a descobrir ao ter uma publicação rejeitada.

GET /v1/platforms
{
  "platforms": [
    { "platform": "x", "text_limit": 280, "caption_limit": 280,
      "media": { "images": 4, "videos": 1, "mixed": false },
      "post_types": ["feed", "long_form"], "publishes": "live", "measures": true }
  ],
  "upload": { "available": true, "max_image_bytes": 26214400, "max_video_bytes": 536870912 }
}

Verifique uma publicação antes de criá-la

Envie dry_run: true e a chamada executa todas as verificações que a chamada real executa e depois não grava nada. Uma publicação que seria recusada é recusada de forma idêntica, com o mesmo corpo — então um lote pode ser validado antes de o primeiro item ser enfileirado, em vez de ser descoberto no meio do caminho. Uma execução de teste não gasta uma chave de idempotência e não conta contra suas publicações mensais.

Arquivos que você ainda não registrou podem ser descritos com media_preview, então um ensaio não custa upload. Isso é aceito apenas em uma execução de teste.

POST /v1/posts
{
  "text": "Most founders think they have a marketing problem.",
  "channels": ["ch_x_kevin", "ch_ig_hs"],
  "media_preview": [{ "kind": "video" }],
  "dry_run": true
}

Executando a mesma chamada duas vezes

Envie um cabeçalho Idempotency-Key e uma repetição da mesma chamada retorna a publicação que a primeira criou, com um 200 em vez de um 201. Um lote que falha no meio pode ser reexecutado por inteiro: as chamadas que já foram concluídas não custam nada e não criam nada.

POST /v1/posts
Idempotency-Key: sept-campaign-day-03-x

Salvar um rascunho

Envie draft: true e a publicação é mantida sem ser enviada. Um rascunho é a única publicação que pode ainda não ter canais, porque as pessoas escrevem primeiro e escolhem as redes depois. Ele não conta contra suas publicações mensais até ser publicado.

POST /v1/posts
{
  "text": "Most founders think they have a marketing problem.",
  "draft": true
}

Leia a gaveta de volta com GET /posts?status=draft. Um status não reconhecido é um 422, não uma lista vazia, então um erro de digitação não pode ser lido como "sem rascunhos".

Editar uma publicação

PATCH /posts/:id altera um rascunho, ou uma publicação que ainda não foi publicada. Todo campo é opcional e omitir um significa deixá-lo como está, então mover um horário não pode apagar o texto por omissão. O que é verificado é a publicação mesclada, não o patch: adicionar um canal reverifica o texto contra o limite daquela rede, e mover o horário reverifica contra o relógio.

PATCH /v1/posts/post_31ba
{
  "publish_at": "2026-08-19T09:00:00+04:00",
  "channels": ["ch_x_kevin", "ch_bs_kevin"]
}

Definir draft: false é como um rascunho é liberado para a fila; draft: true puxa uma publicação agendada de volta para a gaveta. Uma vez que uma publicação começou a ser publicada, ela está finalizada, e a chamada é recusada com 409 post_not_editable em vez de ser aplicada parcialmente. post_type é o único campo que não pode ser alterado: uma publicação mantém o formato com o qual foi criada, e transformar silenciosamente um Story em uma publicação de feed é a substituição que esta API existe para impedir.

Enviando um arquivo

Você não precisa hospedar nada. POST /media/upload-url recebe um tipo de conteúdo e uma contagem de bytes e retorna um endereço PUT pré-assinado mais o endereço público que o arquivo terá. Envie os bytes diretamente para ele — eles nunca passam pela API — e a linha de mídia volta na mesma resposta, pronta para ser nomeada em uma publicação.

POST /v1/media/upload-url
{ "content_type": "video/mp4", "bytes": 18234112 }

{
  "upload_url": "https://…",
  "public_url": "https://media.postwharf.com/…",
  "media": { "id": "med_8f2a" }
}

Ambos os cabeçalhos com os quais o endereço pré-assinado foi assinado — Content-Type e Content-Length — precisam ser enviados no PUT, e o comprimento precisa corresponder à contagem de bytes que você solicitou.

POST /media é a outra metade: um arquivo que você já hospeda, registrado por endereço. Ele não busca o endereço por padrão. Passe verify: true para que seja verificado uma vez, ali mesmo, para que um link expirado seja recusado enquanto você ainda o segura, em vez de quatro semanas depois como uma publicação agendada sem nada por trás.

Descrevendo uma imagem

POST /media e POST /media/upload-url ambos aceitam um alt opcional: uma descrição do arquivo, de até 2.000 caracteres. Uma descrição com apenas espaços em branco é armazenada como inexistente. GET /media a retorna. Redes que carregam uma descrição por imagem a publicam — no Bluesky ela se torna o texto alternativo da imagem.

POST /v1/media
{
  "url": "https://cdn.example.com/desk.jpg",
  "kind": "image",
  "alt": "Two people at a standing desk, mid-conversation."
}

Para adicionar ou alterar uma descrição depois, PATCH /media/:id com alt. Envie null para remover a descrição. O campo é obrigatório nesta chamada — um corpo sem alt é um 422 alt_required em vez de uma operação nula silenciosa.

PATCH /v1/media/med_8f2a
{ "alt": "Two people at a standing desk, mid-conversation." }

Servidor MCP

O conector expõe seus canais a qualquer cliente MCP como ferramentas. Uma chave de workspace limita cada chamada a um workspace, então um agente só pode agir na marca que você entregou a ele — e se você opera várias marcas, isso significa uma conexão por marca, cada uma com sua própria chave.

Aponte um cliente hospedado para https://mcp.postwharf.com/mcp com sua chave de workspace.

Como cada cliente se conecta, o que cada ferramenta faz e o que cada rede aceita estão descritos na página do servidor MCP de mídia social.

O caminho mais rápido é pelo painel. Agente e MCP oferece um bloco com sua chave já incluída: copie-a, cole-a no seu agente como uma mensagem, e o agente se configura sozinho. O servidor então diz ao agente como enviar um arquivo, publicar um vídeo e ajustar um conjunto de imagens em cada rede, então você pede em palavras simples e ele faz o trabalho sozinho.

get_workspace Qual marca esta chave alcança, seu plano e o fuso horário em que agenda

list_channels Cada canal conectado e a saúde de seu token

describe_networks O que cada rede aceita: formatos, contagens de arquivos, limites de texto, suas próprias configurações

connect_channel Anexar um canal ao workspace

stage_upload Uma URL de upload e um endereço público para um arquivo que você acabou de criar

upload_media Registrar uma imagem ou vídeo que você já hospeda, obter um id de mídia

describe_media Escrever ou substituir o texto alternativo em um arquivo já registrado

create_post Publicar em um ou vários canais

schedule_post Enfileirar uma publicação para um determinado horário

schedule_many Enfileirar um lote — um mês de calendário em uma única chamada

save_draft Escrever uma publicação e deixá-la para uma pessoa aprovar

list_drafts Cada rascunho no workspace, salvo mas não enfileirado

update_post Editar um rascunho ou uma publicação enfileirada antes de ser publicada

publish_draft Enviar um rascunho, ou enfileirá-lo para um horário

get_status Estado de entrega, link permanente, texto de erro

list_queue O que está agendado e quando

cancel_post Retirar algo da fila

list_events O feed de recibos de entrega

health Se a API está acessível e em quais redes ela realmente publica

CLI

Ideal para cron, CI, um hook pós-exportação, um agente ou memória muscular. Cada comando, com as flags para agendamento, execuções de teste e tentativas, está na página da CLI de mídia social.

# authenticate once — or set POSTWHARF_API_KEY and skip this entirely
wharf auth --key wf_live_…

# what this key can do, before you queue a month of posts
wharf whoami
plan      pro
posts     18 used of 1000, 982 left this month

# what each network takes, without sending a post to find out
wharf platforms

# a local file. It is uploaded for you; nothing needs hosting
wharf post "New reel" --to instagram,tiktok --media ./out/reel.mp4 --at "tue 9am"

# check it against every network first. Writes nothing, uploads nothing
wharf post "Same idea, two lengths" --to instagram,pinterest \
           --text-for pinterest:"the short one" --dry-run

# re-run a batch that died halfway. The ones that landed cost nothing
wharf post "Day 3" --to x --idempotency-key sept-day03-x

# what happened this week
wharf status --since 7d --metrics
✓ 41 live   ⚠ 1 retrying   ✗ 0 failed

# and which of them worked
wharf analytics --days 7 --by post

# pipe a caption in
cat caption.txt | wharf post --to linkedin,x

Webhooks

Esta é a parte que todo outro agendador deixa de fora. Você é notificado quando uma publicação é publicada e quando falha, em vez de descobrir uma semana depois.

{
  "event": "delivery.failed",
  "post": "post_31ba",
  "channel": "ch_ig_hs",
  "attempt": 3,
  "error": "token_expired",
  "action": "reconnect_required"
}

Eventos: delivery.succeeded, delivery.failed, delivery.retrying, channel.token_expiring, channel.disconnected.

token_expiring dispara cedo. As redes expiram tokens OAuth a cada 60–90 dias. Você recebe o aviso antes de o canal ficar inativo, não depois de uma semana publicando no vazio.

Clientes de IA

C

Claude Code

Adicione o conector e seus canais se tornam ferramentas. Rascunho, enfileiramento e status funcionam todos a partir do terminal.

M

Clientes MCP

Qualquer cliente que fale Streamable HTTP MCP pode se conectar ao servidor hospedado com uma chave de workspace.

⏱

Cron e n8n

A CLI sai com código de erro diferente de zero em caso de falha, então um trabalho agendado falha de forma ruidosa em vez de silenciosa.

{ }

Seu próprio código

REST simples com um token bearer. Nenhum SDK é necessário, embora um esteja por vir.

Limites e erros

Não publicamos nenhum limite de taxa por chave. A taxa de publicação é limitada pelos limites de cada rede, não pelos nossos, e nós enfileiramos contra eles em vez de falhar. Entrar, cadastrar e redefinir senha são limitados por endereço e por conta.

CódigoSignificadoO que fazer
401Chave inválida ou revogadaReemita em Configurações → Chaves de API
403Chave está limitada a outro workspaceUse a chave daquele workspace
409Canal precisa ser reconectadoToken expirado — execute o OAuth novamente
422Conteúdo, plataforma ou conector somente sob solicitação inválidoUse o código de erro e a dica; connector_request_only significa solicite-o em vez disso
429Limite de taxa atingidoRecue; Retry-After está definido