libro / blog / consumir-api-php-curl

// apis

Cómo consumir APIs externas en PHP con cURL

Gustavo Arias9 min de lectura

TL;DR Para hablar con una API desde PHP, cURL te da control total: método, cabeceras, cuerpo JSON y timeouts. Envuelve la lógica en una función que compruebe el código HTTP y los errores de red, guarda las claves de API fuera del código y cachea las respuestas que no cambian a menudo.

Cotizaciones, clima, pagos, envíos, inteligencia artificial: buena parte del valor de una aplicación moderna viene de conectarse con servicios externos a través de sus APIs. Desde PHP es sencillo, pero hay detalles (timeouts, códigos de error, credenciales) que separan un script que funciona en tu máquina de uno que aguanta en producción. En el libro PHP desde cero hasta experto aprendes a consumir APIs externas y a crear las tuyas; aquí nos centramos en consumirlas bien.

file_get_contents o cURL

file_get_contentscURL
SimplicidadUna línea para un GETAlgo más de código
Métodos, cabeceras, cuerpoPosible con un “stream context”, incómodoControl total
Timeouts y errores detalladosLimitadosCompletos
Úsalo paraPruebas rápidasCualquier integración real

Si tu proyecto usa Composer, la librería Guzzle ofrece una interfaz más cómoda sobre lo mismo. Entender cURL primero te ayudará a usar cualquier cliente HTTP.

Petición GET y lectura de JSON

Este ejemplo consulta la API pública de GitHub para obtener datos del repositorio de PHP:

<?php
$ch = curl_init('https://api.github.com/repos/php/php-src');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,   // devolver la respuesta en vez de imprimirla
    CURLOPT_TIMEOUT        => 10,     // segundos máximos de espera
    CURLOPT_HTTPHEADER     => [
        'Accept: application/vnd.github+json',
        'User-Agent: mi-app-php',     // GitHub exige un User-Agent
    ],
]);

$respuesta = curl_exec($ch);
$codigo = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($respuesta === false || $codigo !== 200) {
    exit('No se pudo consultar la API.');
}

$repo = json_decode($respuesta, true);   // true = arrays asociativos
echo "{$repo['full_name']} tiene {$repo['stargazers_count']} estrellas.";

Una función reutilizable para cualquier API

Repetir esas líneas en cada llamada es una invitación a olvidarse del timeout o de comprobar el código de respuesta. Mejor encapsularlo una vez:

<?php
declare(strict_types=1);

function apiRequest(string $metodo, string $url, ?array $datos = null, array $cabeceras = []): ?array
{
    $cabeceras[] = 'Accept: application/json';
    $opciones = [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST  => $metodo,
        CURLOPT_CONNECTTIMEOUT => 5,    // tiempo para conectar
        CURLOPT_TIMEOUT        => 15,   // tiempo total
    ];

    if ($datos !== null) {
        $opciones[CURLOPT_POSTFIELDS] = json_encode($datos, JSON_THROW_ON_ERROR);
        $cabeceras[] = 'Content-Type: application/json';
    }
    $opciones[CURLOPT_HTTPHEADER] = $cabeceras;

    $ch = curl_init($url);
    curl_setopt_array($ch, $opciones);
    $cuerpo = curl_exec($ch);

    if ($cuerpo === false) {
        throw new RuntimeException('Error de red: ' . curl_error($ch));
    }

    $codigo = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    if ($codigo >= 400) {
        throw new RuntimeException("La API respondió con el código $codigo", $codigo);
    }

    return $cuerpo === '' ? null : json_decode($cuerpo, true, flags: JSON_THROW_ON_ERROR);
}

La función lanza una excepción ante cualquier problema, así que quien la usa decide qué hacer: reintentar, mostrar un mensaje o registrar el error. Lo vemos a fondo en manejo de errores y excepciones.

POST con JSON y autenticación

<?php
declare(strict_types=1);
require_once __DIR__ . '/lib/http.php';

// La clave de la API, desde una variable de entorno: nunca escrita en el código
$token = getenv('API_ENVIOS_TOKEN');

try {
    $envio = apiRequest('POST', 'https://api.ejemplo.com/v1/envios', [
        'destinatario' => 'Ana Pérez',
        'direccion'    => 'Av. Siempre Viva 742',
        'peso_kg'      => 1.2,
    ], ["Authorization: Bearer $token"]);

    echo 'Envío creado con número de seguimiento ' . htmlspecialchars($envio['tracking']);
} catch (RuntimeException $e) {
    error_log($e->getMessage());
    echo 'No pudimos crear el envío. Intenta de nuevo en unos minutos.';
}
Trata la respuesta de una API externa como cualquier dato no confiable: valida que tiene los campos que esperas y escápala con htmlspecialchars() antes de mostrarla.

Caché para no saturar la API

Muchas APIs limitan el número de peticiones por minuto, y además cada llamada añade espera a tu página. Si el dato no cambia a cada segundo (una cotización, el clima), guárdalo un rato:

<?php
declare(strict_types=1);

function conCache(string $clave, int $segundos, callable $obtener): mixed
{
    $archivo = sys_get_temp_dir() . '/cache_' . md5($clave) . '.json';

    if (is_file($archivo) && time() - filemtime($archivo) < $segundos) {
        return json_decode(file_get_contents($archivo), true);
    }

    $datos = $obtener();
    file_put_contents($archivo, json_encode($datos), LOCK_EX);
    return $datos;
}

// La API solo se consulta, como mucho, una vez cada 10 minutos
$repo = conCache('github-php-src', 600, fn() => apiRequest('GET', 'https://api.github.com/repos/php/php-src', null, ['User-Agent: mi-app-php']));

Errores comunes

Consume y crea tus propias APIs

El libro PHP desde cero hasta experto enseña a consumir APIs externas y a crear tus propias APIs RESTful con PHP y MySQL.

Comprar en Amazon