PostWharf

Programa y publica publicaciones en Instagram, TikTok, X, LinkedIn, YouTube, Facebook, Threads, Pinterest, Bluesky y Telegram desde cualquier cliente MCP, con un recibo de entrega por red.

Servidor MCP alojado

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

Se instala en Claude Code, Codex, Cursor y más

Documentación

Autenticación

Cada solicitud lleva una clave de workspace como token de portador. Las claves están limitadas a un solo workspace, por lo que una clave para una marca nunca puede publicar en otra. Créalas y revócalas en Configuración → Claves de API.

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

Nunca envíes una clave a un navegador. Las claves publican en tu nombre. Mantenlas en el lado del servidor, en una variable de entorno, y rota inmediatamente si una se filtra.

API REST

URL base https://api.postwharf.com/v1. JSON de entrada, JSON de salida.

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

Cada ruta, con sus parámetros y rechazos, está en la referencia de la API.

Cuatro conectores son solo de solicitud. Reddit, Discord, Snapchat y WordPress permanecen visibles, pero aún no pueden crear canales ni publicar. POST /connector-requests registra la demanda del workspace una vez por plataforma.

Crear una publicación

Selecciona cualquier conjunto de canales. Proporciona publish_at para programar, u omítelo para publicar de inmediato. Las anulaciones por canal permiten que una sola carga lleve un pie de foto diferente para cada red.

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 →" }
  }
}

Respuesta

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

Consulta GET /posts/:id o suscríbete a un webhook. Una vez publicada, cada entrega obtiene un url que apunta a la publicación en la red, y un url_state que explica por qué cuando no lo tiene: none (no se publicó nada en esa etapa), pending (se publicó y la dirección aún no se conoce, por lo que vale la pena preguntar de nuevo), o not_reported (se publicó y esa red no devuelve ninguna dirección).

El array media está ordenado. Los archivos se publican en el orden en que los listas, no en el orden en que se subieron, por lo que un carrusel llega tal como lo ensamblaste. Enviar media en PATCH /posts/:id reemplaza la lista completa, que también es la forma de reordenar una.

Preguntar qué acepta cada red

GET /platforms devuelve las reglas como datos: el límite de texto para cada red, el más corto que se aplica una vez que se adjunta un archivo, cuántas imágenes y videos puede llevar una publicación, las formas que publica y si esta implementación publica allí en absoluto. Cada número es aquel contra el que se escriben los rechazos, por lo que no hay nada que descubrir al hacer que una publicación sea rechazada.

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 }
}

Verificar una publicación antes de crearla

Envía dry_run: true y la llamada ejecuta todas las verificaciones que ejecuta la real y luego no escribe nada. Una publicación que sería rechazada se rechaza de manera idéntica, con el mismo cuerpo, para que un lote pueda validarse antes de que se ponga en cola el primero en lugar de descubrirse a mitad de camino. Una ejecución de prueba no gasta una clave de idempotencia y no cuenta contra tus publicaciones mensuales.

Los archivos que aún no has registrado se pueden describir con media_preview, por lo que un ensayo no cuesta ninguna subida. Se acepta solo en una ejecución de prueba.

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
}

Ejecutar la misma llamada dos veces

Envía un encabezado Idempotency-Key y una repetición de la misma llamada devuelve la publicación que hizo la primera, con un 200 en lugar de un 201. Un lote que falla a mitad de camino se puede volver a ejecutar completo: las llamadas que ya se realizaron no cuestan nada y no crean nada.

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

Guardar un borrador

Envía draft: true y la publicación se guarda sin enviarse. Un borrador es la única publicación que puede no tener canales todavía, porque la gente escribe primero y elige las redes después. No cuenta contra tus publicaciones mensuales hasta que se publica.

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

Lee el cajón de vuelta con GET /posts?status=draft. Un estado no reconocido es un 422, no una lista vacía, por lo que un error tipográfico no puede leerse como "sin borradores".

Editar una publicación

PATCH /posts/:id cambia un borrador, o una publicación que aún no se ha publicado. Cada campo es opcional y omitir uno significa dejarlo solo, por lo que mover una hora no puede dejar el texto en blanco por omisión. Lo que se verifica es la publicación fusionada, no el parche: agregar un canal vuelve a verificar el texto contra el límite de esa red, y mover la hora lo vuelve a verificar contra el reloj.

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

Establecer draft: false es cómo se libera un borrador a la cola; draft: true devuelve una publicación programada al cajón. Una vez que una publicación ha comenzado a publicarse, está terminada, y la llamada se rechaza con 409 post_not_editable en lugar de aplicarse a medias. post_type es el único campo que no se puede parchear: una publicación mantiene la forma con la que se creó, y convertir silenciosamente una Story en una publicación de feed es la sustitución que esta API existe para prevenir.

Subir un archivo

No tienes que alojar nada. POST /media/upload-url toma un tipo de contenido y un recuento de bytes y devuelve una dirección PUT prefirmada más la dirección pública que tendrá el archivo. Envía los bytes directamente a ella — nunca pasan por la API — y la fila de medios vuelve en la misma respuesta, lista para nombrar en una publicación.

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 encabezados con los que se firmó la dirección prefirmada — Content-Type y Content-Length — deben enviarse en el PUT, y la longitud debe coincidir con el recuento de bytes que solicitaste.

POST /media es la otra mitad: un archivo que ya alojas, registrado por dirección. No obtiene la dirección por defecto. Pasa verify: true para que se verifique una vez, allí y entonces, para que un enlace que ha caducado se rechace mientras aún lo sostienes en lugar de cuatro semanas después como una publicación programada sin nada detrás.

Describir una imagen

POST /media y POST /media/upload-url ambos toman un alt opcional: una descripción del archivo, de hasta 2.000 caracteres. Una descripción de solo espacios en blanco se almacena como ninguna. GET /media la devuelve. Las redes que llevan una descripción por imagen la publican — en Bluesky se convierte en el texto alternativo de la imagen.

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

Para agregar o cambiar una descripción más tarde, PATCH /media/:id con alt. Envía null para quitar la descripción. El campo es obligatorio en esta llamada — un cuerpo sin alt es un 422 alt_required en lugar de una no operación silenciosa.

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

Servidor MCP

El conector expone tus canales a cualquier cliente MCP como herramientas. Una clave de workspace limita cada llamada a un solo workspace, por lo que un agente solo puede actuar sobre la marca que le entregaste — y si ejecutas varias marcas, eso significa una conexión por marca, cada una con su propia clave.

Apunta un cliente alojado a https://mcp.postwharf.com/mcp con tu clave de workspace.

Cómo se conecta cada cliente, qué hace cada herramienta y qué acepta cada red se detallan en la página del servidor MCP de redes sociales.

La forma más rápida de entrar es desde el panel de control. Agente y MCP te da un bloque con tu clave ya incluida: cópiala, pégala en tu agente como mensaje, y el agente se configura solo. El servidor luego le dice al agente cómo subir un archivo, publicar un video y ajustar un conjunto de imágenes a cada red, para que preguntes en palabras simples y haga ese trabajo por sí mismo.

get_workspace Qué marca alcanza esta clave, su plan y la zona horaria en la que programa

list_channels Cada canal conectado y su estado de token

describe_networks Qué acepta cada red: formas, recuentos de archivos, límites de texto, sus propios ajustes

connect_channel Adjuntar un canal al workspace

stage_upload Una URL de subida y una dirección pública para un archivo que acabas de hacer

upload_media Registrar una imagen o video que ya alojas, obtener un id de medio

describe_media Escribir o reemplazar el texto alternativo en un archivo ya registrado

create_post Publicar en uno o muchos canales

schedule_post Poner en cola una publicación para una hora determinada

schedule_many Poner en cola un lote — un mes de un calendario en una sola llamada

save_draft Escribir una publicación y dejarla para que una persona la apruebe

list_drafts Cada borrador en el workspace, guardado pero no en cola

update_post Editar un borrador o una publicación en cola antes de que se publique

publish_draft Enviar un borrador, o ponerlo en cola para una hora

get_status Estado de entrega, enlace permanente, texto de error

list_queue Qué está programado y cuándo

cancel_post Sacar algo de la cola

list_events El feed de recibos de entrega

health Si la API es accesible y a qué redes realmente publica

CLI

Ideal para cron, CI, un hook posterior a la exportación, un agente o memoria muscular. Cada comando, con las banderas para programación, ejecuciones de prueba y reintentos, está en la página de la CLI de redes sociales.

# 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 es la parte que todos los demás programadores omiten. Se te notifica cuando una publicación se publica y cuando falla, en lugar de descubrirlo una semana después.

{
  "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 se dispara temprano. Las redes caducan los tokens OAuth cada 60–90 días. Recibes la advertencia antes de que el canal se apague, no después de una semana publicando en la nada.

Clientes de IA

C

Claude Code

Agrega el conector y tus canales se convierten en herramientas. Redacción, cola y estado funcionan desde la terminal.

M

Clientes MCP

Cualquier cliente que hable Streamable HTTP MCP puede conectarse al servidor alojado con una clave de workspace.

⏱

Cron y n8n

La CLI sale con código distinto de cero en caso de fallo, por lo que un trabajo programado falla de forma audible en lugar de silenciosa.

{ }

Tu propio código

REST simple con un token de portador. No se requiere SDK, aunque uno está en camino.

Límites y errores

No publicamos ningún límite de tasa por clave. El rendimiento de publicación está limitado por los propios límites de cada red, no por los nuestros, y hacemos cola contra ellos en lugar de fallar. El inicio de sesión, el registro y el restablecimiento de contraseña tienen límite de tasa por dirección y por cuenta.

CódigoSignificadoQué hacer
401Clave incorrecta o revocadaReemite en Configuración → Claves de API
403La clave está limitada a otro workspaceUsa la clave de ese workspace
409El canal necesita reconexiónToken caducado — vuelve a ejecutar OAuth
422Contenido, plataforma o conector solo de solicitud no válidoUsa el código de error y la pista; connector_request_only significa solicítalo en su lugar
429Límite de tasaRetrocede; Retry-After está establecido