API y desarrollo
Procesa imágenes con la API de SocialCutter desde PHP
Cliente de la API de SocialCutter en PHP con cURL y Guzzle: salud, plataformas, procesado por URL y fichero con CURLFile, lotes, historial, monedero y errores.
- PHP
- cURL
- Guzzle
- API
- SocialCutter
- CURLFile
- API key
Requisitos
PHP con la extensión cURL (viene de serie en la mayoría de instalaciones). Si prefieres Guzzle, instálalo con Composer:
composer require guzzlehttp/guzzle
export SOCIALCUTTER_API_URL="https://api.socialcutter.theboomer.dev"
export SOCIALCUTTER_API_KEY="sc_tu_clave"
La clave se crea en https://dash.socialcutter.theboomer.dev, en Perfil → API keys. Empieza por sc_ y se muestra una sola vez. La referencia completa está en https://docs.socialcutter.theboomer.dev.
Cliente mínimo con cURL
<?php
$apiUrl = getenv('SOCIALCUTTER_API_URL') ?: 'https://api.socialcutter.theboomer.dev';
$apiKey = getenv('SOCIALCUTTER_API_KEY'); // nunca la escribas en el codigo
function apiCall(string $method, string $path, ?array $json = null, array $multipart = []): array
{
global $apiUrl, $apiKey;
$ch = curl_init($apiUrl . $path);
$headers = ['X-API-Key: ' . $apiKey, 'Accept: application/json'];
if ($json !== null) {
$headers[] = 'Content-Type: application/json';
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($json));
} elseif ($multipart) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $multipart); // cURL pone el boundary
}
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($body === false) {
throw new RuntimeException('cURL: ' . curl_error($ch));
}
curl_close($ch);
$data = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException("HTTP $status: $body");
}
return $data;
}
Salud y credenciales
// Publico: no requiere clave
print_r(apiCall('GET', '/api/v1/health')); // status, version, uptime, database
// Identidad de la cuenta autenticada
print_r(apiCall('GET', '/api/v1/auth/me'));
// Usos disponibles
print_r(apiCall('GET', '/api/v1/credits'));
Si /api/v1/auth/me responde 401, la clave falta, está mal formada o fue revocada.
Listar plataformas y formatos
$data = apiCall('GET', '/api/v1/platforms');
foreach ($data['platforms'] as $plataforma => $formatos) {
foreach ($formatos as $f) {
echo "$plataforma {$f['format']} {$f['width']}x{$f['height']} {$f['aspect_ratio']}\n";
}
}
/platforms, /formats y /fit-modes son públicos. Úsalos para construir los destinos sin codificar medidas a mano.
Procesar una imagen por URL
$resp = apiCall('POST', '/api/v1/images/process', [
'source' => ['type' => 'url', 'value' => 'https://example.com/foto.jpg'],
'destinations' => [
['platform' => 'instagram', 'format' => 'post'],
['platform' => 'tiktok', 'format' => 'cover'],
],
]);
echo $resp['id'], ' ', $resp['status'], "\n";
Procesar un fichero local con CURLFile
$resp = apiCall('POST', '/api/v1/images/process/upload', null, [
'file' => new CURLFile('./foto.jpg', 'image/jpeg', 'foto.jpg'),
'destinations' => json_encode([['platform' => 'linkedin', 'format' => 'post']]),
]);
Con CURLFile no fijes Content-Type a mano: cURL genera el boundary del multipart. El fichero va en el campo file y destinations es una cadena JSON. El límite de subida es 5 MB; por encima la API responde 413.
Procesar un lote
$lote = apiCall('POST', '/api/v1/images/batch', [
'images' => [
['source' => ['type' => 'url', 'value' => 'https://example.com/a.jpg'],
'destinations' => [['platform' => 'instagram', 'format' => 'post']]],
['source' => ['type' => 'url', 'value' => 'https://example.com/b.jpg'],
'destinations' => [['platform' => 'twitter', 'format' => 'post']]],
],
]);
Lo mismo con Guzzle
Guzzle simplifica el manejo de errores y el multipart:
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => getenv('SOCIALCUTTER_API_URL') . '/',
'headers' => ['X-API-Key' => getenv('SOCIALCUTTER_API_KEY')],
'timeout' => 60,
]);
// Por URL
$resp = $client->post('api/v1/images/process', [
'json' => [
'source' => ['type' => 'url', 'value' => 'https://example.com/foto.jpg'],
'destinations' => [['platform' => 'instagram', 'format' => 'post']],
],
])->json();
// Fichero local (multipart)
$resp = $client->post('api/v1/images/process/upload', [
'multipart' => [
['name' => 'file', 'contents' => fopen('./foto.jpg', 'r'), 'filename' => 'foto.jpg'],
['name' => 'destinations', 'contents' => json_encode([['platform' => 'linkedin', 'format' => 'post']])],
],
])->json();
En Guzzle, json envía el cuerpo JSON y multipart arma el formulario. Patrón documentado en https://docs.guzzlephp.org/en/stable/request-options.html; las opciones y nombres pueden cambiar entre versiones, confírmalos en esa documentación.
Leer la respuesta y descargar
La respuesta trae el identificador del trabajo en id, un status y outputs, una entrada por destino con platform, format, url, width, height y size_bytes.
foreach ($resp['outputs'] as $out) {
echo "{$out['platform']} {$out['format']} {$out['width']}x{$out['height']}\n";
$bytes = file_get_contents($out['url']);
file_put_contents("{$out['platform']}-{$out['format']}.webp", $bytes);
}
Historial y monedero
// Ultimas 10 imagenes
print_r(apiCall('GET', '/api/v1/history?limit=10'));
// Solo las creadas desde la API
print_r(apiCall('GET', '/api/v1/history?origin=api&limit=10'));
// Cuota diaria, bolsa extra y saldo comprado
print_r(apiCall('GET', '/api/v1/wallet'));
limit admite de 1 a 100 y skip sirve para paginar. El filtro origin distingue browser (dashboard) de api.
Errores y reintentos con backoff
function conReintentos(callable $fn, int $intentos = 4, int $baseMs = 500)
{
for ($i = 0; $i < $intentos; $i++) {
try {
return $fn();
} catch (RuntimeException $e) {
preg_match('/HTTP (\d+)/', $e->getMessage(), $m);
$code = (int) ($m[1] ?? 0);
if (($code === 429 || $code >= 500) && $i < $intentos - 1) {
usleep($baseMs * 1000 * (2 ** $i)); // 0.5, 1, 2, 4 s
continue;
}
throw $e;
}
}
}
No reintentes 400, 401, 404 o 422: repetir la misma petición no la arregla. Solo 429 (cuota) y 5xx (fallo temporal) merecen otro intento.
Coste
- 1 uso por destino (plataforma y formato) por petición.
- Destinos repetidos en la misma petición no se cobran dos veces.
- Los procesamientos fallidos se devuelven.
Errores típicos
| Código | Significado |
|---|---|
| 400 | Payload inválido: plataforma, formato o modo desconocido, JSON mal formado, o clave ya activa |
| 401 | Credenciales ausentes, mal formadas, caducadas o revocadas |
| 404 | Recurso no encontrado (id de imagen o de fichero) |
| 413 | El fichero supera 5 MB |
| 422 | Error de validación de la petición |
| 429 | Cuota del monedero agotada |
| 500 | Fallo de procesamiento; los usos de esa petición se devuelven |
Siguientes pasos
- Guía de terminal: Procesa imágenes con la API desde la terminal (curl)
- Guía de Python: Procesa imágenes con la API de SocialCutter desde Python
- Guía de Node: Procesa imágenes con la API de SocialCutter desde Node.js
- Documentación de la API: https://docs.socialcutter.theboomer.dev
- Dashboard: https://dash.socialcutter.theboomer.dev
Preguntas frecuentes
¿Necesito Composer?
No si usas cURL, que viene con PHP. Guzzle sí se instala con Composer: composer require guzzlehttp/guzzle.
¿Dónde pongo la API key?
En la variable de entorno SOCIALCUTTER_API_KEY y la lees con getenv. Se envía en cada petición en la cabecera X-API-Key.
¿Cómo subo un fichero local con cURL?
Con CURLFile apuntando a la ruta del fichero y el campo file, más destinations como cadena JSON. El límite es 5 MB.
¿Cómo se cobra un lote?
1 uso por destino (plataforma y formato) y por imagen. Un lote de 3 imágenes con 2 destinos cada una son 6 usos.
¿Por qué recibo 429?
Porque la cuota del monedero está agotada. Consulta GET /api/v1/credits y GET /api/v1/wallet antes de lotes grandes.