Curso de PHP

Consumir APIs REST con PHP: cURL, JSON y manejo de errores

Por Víctor Peña · Publicado el

Hola, ¿cómo están? Último tema complementario del curso de PHP: cómo hacer que tu aplicación hable con otras.

Consumir APIs es algo que vas a necesitar en casi cualquier proyecto real: pasarelas de pago, servicios de mensajería, mapas, tipos de cambio, o el propio backend de una aplicación móvil.

¡Empecemos!

Qué es una API REST

Una API REST es un servicio al que le haces peticiones HTTP y te responde con datos, normalmente en formato JSON.

Los métodos HTTP tienen un significado convenido:

Método Qué hace
GET Obtener datos
POST Crear algo nuevo
PUT / PATCH Actualizar
DELETE Eliminar

Y la respuesta trae un código de estado que indica qué pasó:

Código Significado
200 Todo bien
201 Creado correctamente
400 La petición está mal formada
401 No autenticado
403 Autenticado pero sin permiso
404 No existe
429 Demasiadas peticiones
500 Error del servidor remoto

Comprobar ese código es lo primero que hay que hacer con cualquier respuesta.

La forma rápida: file_get_contents

Para una consulta simple sin autenticación:

<?php
$respuesta = file_get_contents('https://api.ejemplo.com/productos');
$datos = json_decode($respuesta, true);

Funciona, pero tiene limitaciones serias: no puedes ver el código de estado con comodidad, no controlas el tiempo de espera y el manejo de errores es pobre. Para cualquier cosa seria, usa cURL.

Petición GET con cURL

<?php
declare(strict_types=1);

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => 'https://api.ejemplo.com/productos',
    CURLOPT_RETURNTRANSFER => true,   // devolver en vez de imprimir
    CURLOPT_TIMEOUT => 10,            // máximo 10 segundos
    CURLOPT_CONNECTTIMEOUT => 5,
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
    ],
]);

$respuesta = curl_exec($curl);
$codigo = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$error = curl_error($curl);

curl_close($curl);

if ($error !== '') {
    throw new RuntimeException("Error de conexión: $error");
}

if ($codigo !== 200) {
    throw new RuntimeException("La API respondió con código $codigo");
}

$datos = json_decode($respuesta, true, 512, JSON_THROW_ON_ERROR);

Fíjate en tres cosas que la gente suele omitir:

  • CURLOPT_RETURNTRANSFER. Sin esto, cURL imprime la respuesta en pantalla en lugar de devolverla.
  • Los tiempos de espera. Sin ellos, si la API remota se cuelga, tu página se queda esperando indefinidamente. Es de los fallos más frustrantes de diagnosticar.
  • JSON_THROW_ON_ERROR, que lanza una excepción si la respuesta no es JSON válido en lugar de devolver null en silencio.

Petición POST con JSON

<?php
$cuerpo = json_encode([
    'nombre' => 'Teclado mecánico',
    'precio' => 450,
], JSON_THROW_ON_ERROR);

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => 'https://api.ejemplo.com/productos',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $cuerpo,
    CURLOPT_TIMEOUT => 10,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Accept: application/json',
    ],
]);

$respuesta = curl_exec($curl);
$codigo = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);

La cabecera Content-Type: application/json es obligatoria: le dice al servidor cómo interpretar lo que envías. Sin ella, muchas APIs rechazan la petición.

Autenticación con token

La forma más común hoy:

<?php
CURLOPT_HTTPHEADER => [
    'Authorization: Bearer ' . $token,
    'Content-Type: application/json',
],

Y recuerda lo que vimos en seguridad: ese token no va escrito en el código. Va en un archivo de configuración fuera del repositorio.

Trabajar con JSON

<?php
// De JSON a array de PHP
$datos = json_decode($respuesta, true, 512, JSON_THROW_ON_ERROR);

// De PHP a JSON
$json = json_encode($datos, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);

Dos banderas que conviene usar siempre:

  • JSON_THROW_ON_ERROR convierte los fallos silenciosos en excepciones.
  • JSON_UNESCAPED_UNICODE evita que las tildes se conviertan en á.

Y el segundo parámetro de json_decode() en true devuelve arrays asociativos en lugar de objetos, que suele ser más cómodo.

Una clase cliente reutilizable

Repetir el bloque de cURL en cada llamada es insostenible. Vale la pena encapsularlo, aplicando lo que vimos en el curso:

<?php
declare(strict_types=1);

class ClienteApi
{
    public function __construct(
        private readonly string $urlBase,
        private readonly string $token,
        private readonly int $tiempoEspera = 10,
    ) {}

    public function get(string $ruta, array $parametros = []): array
    {
        $url = $this->urlBase . $ruta;

        if ($parametros !== []) {
            $url .= '?' . http_build_query($parametros);
        }

        return $this->peticion('GET', $url);
    }

    public function post(string $ruta, array $datos): array
    {
        return $this->peticion('POST', $this->urlBase . $ruta, $datos);
    }

    private function peticion(string $metodo, string $url, ?array $datos = null): array
    {
        $curl = curl_init();

        $opciones = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $this->tiempoEspera,
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_CUSTOMREQUEST => $metodo,
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer ' . $this->token,
                'Content-Type: application/json',
                'Accept: application/json',
            ],
        ];

        if ($datos !== null) {
            $opciones[CURLOPT_POSTFIELDS] = json_encode($datos, JSON_THROW_ON_ERROR);
        }

        curl_setopt_array($curl, $opciones);

        $respuesta = curl_exec($curl);
        $codigo = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        $error = curl_error($curl);

        curl_close($curl);

        if ($error !== '') {
            throw new RuntimeException("Fallo de conexión: $error");
        }

        if ($codigo >= 400) {
            throw new RuntimeException("La API respondió $codigo: $respuesta", $codigo);
        }

        return json_decode((string) $respuesta, true, 512, JSON_THROW_ON_ERROR) ?? [];
    }
}

Y al usarla:

<?php
$api = new ClienteApi('https://api.ejemplo.com', $config['token']);

$productos = $api->get('/productos', ['categoria' => 'accesorios']);
$nuevo = $api->post('/productos', ['nombre' => 'Mouse', 'precio' => 60]);

El código que la usa ya no sabe nada de cURL. Y si mañana cambias a Guzzle, cambias esta clase y nada más.

http_build_query() es la función que convierte un array en parámetros de URL, escapando correctamente los caracteres especiales.

Guzzle: la alternativa estándar

En proyectos reales lo habitual es usar Guzzle, la librería de referencia. Se instala con Composer:

composer require guzzlehttp/guzzle
<?php
use GuzzleHttp\Client;

$cliente = new Client([
    'base_uri' => 'https://api.ejemplo.com',
    'timeout' => 10,
    'headers' => ['Authorization' => 'Bearer ' . $token],
]);

$respuesta = $cliente->get('/productos');
$datos = json_decode($respuesta->getBody()->getContents(), true);

Vale la pena aprender cURL primero para entender qué está pasando por debajo, y luego usar Guzzle por comodidad.

Buenas prácticas

  • Siempre pon tiempos de espera. Una API lenta no debe colgar tu aplicación.
  • Comprueba el código de estado antes de usar la respuesta.
  • Cachea lo que no cambia. Si consultas el tipo de cambio, no lo pidas en cada carga de página.
  • Registra los fallos con error_log(), incluyendo la URL y el código.
  • Ten un plan B. Si la API no responde, ¿qué muestra tu sistema? Un error controlado, no una pantalla en blanco.
  • Respeta los límites de peticiones. Un código 429 significa que estás llamando demasiado.

Errores comunes

  • Olvidar CURLOPT_RETURNTRANSFER y encontrarte la respuesta impresa en medio de la página.
  • No poner timeout.
  • Ignorar el código de estado y procesar un mensaje de error como si fueran datos.
  • Poner el token en el código.
  • No comprobar si json_decode() funcionó.
  • Desactivar la verificación SSL con CURLOPT_SSL_VERIFYPEER => false para «que funcione». Eso anula la seguridad de HTTPS: si falla, arregla los certificados.

Para cerrar

Consumir APIs es la habilidad que conecta tu sistema con el resto del mundo. La base es simple —una petición HTTP y una respuesta JSON—, y lo que separa el código robusto del frágil son los detalles: tiempos de espera, códigos de estado y un plan para cuando el otro lado falle.

Con esto cerramos los cinco temas complementarios del curso de PHP. Si quieres dar el siguiente paso, el camino natural es un framework.

Saludos y éxitos.

Norvic Software

Desarrollamos el software que tu empresa necesita

Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.

Solicitar cotizaciónVer todos los servicios

Cotización sin costo · Respuesta directa por WhatsApp