No hay SDK de PHP, así que esto usa la API REST directamente. Es menos limitación de lo que parece: la API es JSON plano sobre HTTPS con un token de portador, y PHP tiene todo lo necesario en la biblioteca estándar.
Si publicas específicamente desde WordPress, hay un plugin y una vía por RSS que puede suponer menos trabajo que escribir esto tú mismo.
Autenticación
Cada solicitud lleva tu clave de API como token de portador:
$apiKey = getenv('BULKPUBLISH_API_KEY');
$baseUrl = 'https://app.bulkpublish.com';
Guarda la clave en el entorno, no en el código fuente ni en un archivo de configuración que subas al repositorio.
Crear un borrador
$payload = [
'content' => 'Check out our latest update!',
'channels' => [
['channelId' => 1, 'platform' => 'x'],
['channelId' => 2, 'platform' => 'linkedin'],
],
'status' => 'draft',
];
$ch = curl_init("$baseUrl/api/posts");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"Authorization: Bearer $apiKey",
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status >= 400) {
error_log("BulkPublish error $status: $response");
} else {
$post = json_decode($response, true);
}
Empieza con draft. Aparece en la app, así que puedes ver qué produjo tu código antes de que se publique nada.
Lo mismo con Guzzle
Si ya tienes Guzzle, queda más limpio:
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://app.bulkpublish.com',
'headers' => [
'Authorization' => 'Bearer ' . getenv('BULKPUBLISH_API_KEY'),
'Content-Type' => 'application/json',
],
]);
$response = $client->post('/api/posts', [
'json' => [
'content' => 'Check out our new feature!',
'channels' => [['channelId' => 1, 'platform' => 'instagram']],
'status' => 'scheduled',
'scheduledAt' => '2026-04-10T14:00:00Z',
'timezone' => 'America/New_York',
],
]);
$post = json_decode((string) $response->getBody(), true);
Encontrar los ID de canal
$response = $client->get('/api/channels');
$channels = json_decode((string) $response->getBody(), true);
Búscalos o guárdalos en la configuración. Fijar en el código un ID sacado de un script puntual, dentro de algo que vaya a durar, es como un despliegue empieza a publicar en la cuenta equivocada.
Campos de programación
scheduledAt es una marca de tiempo ISO-8601 y timezone es un nombre IANA. Indica la zona horaria de forma explícita para cualquier cosa recurrente, o tu hora de publicación se desplazará una hora cuando cambie el horario de verano.
Gestiona bien los errores
El fallo típico de la publicación desde el servidor es que nadie está mirando. Una publicación que falla la validación a las 3 de la madrugada pasa desapercibida a menos que la hayas hecho ruidosa.
- Comprueba los códigos de estado, no des por hecho que una respuesta con cuerpo significa éxito
- Registra el cuerpo de la respuesta cuando falle, porque ahí dice qué salió mal
- Respeta los límites de velocidad y espera en lugar de reintentar de inmediato
| Free | Pro | Business | |
|---|---|---|---|
| Solicitudes de API/día | 30 | 5.000 | 50.000 |
| Claves de API | 1 | 5 | 10 |
| Las 30 diarias del plan Free son para evaluar. Cualquier cosa que corra según un horario necesita un plan de pago. |
La versión corta
No hace falta SDK. Haz POST de JSON a /api/posts con un token de portador, content, channels y un status. Empieza con borradores, busca los ID de canal, indica una zona horaria en las publicaciones programadas y registra los fallos como es debido, porque la publicación desde el servidor falla en silencio por defecto.