Constaia
SDKs

SDK de PHP

constaia/constaia-php, cliente oficial para PHP 8.1+ con integración para Laravel. Análisis, lotes, webhooks, excepciones tipadas y reintentos.

constaia/constaia-php es el cliente oficial de Constaia para PHP. Solo necesita las extensiones curl y json, e incluye un ServiceProvider y una facade para Laravel.

composer require constaia/constaia-php

Requisitos: PHP 8.1 o superior, ext-curl, ext-json. Recomendado: ext-fileinfo para detectar mejor el tipo MIME de los ficheros.

Configuración

$constaia = new \Constaia\Client(); // lee CONSTAIA_API_KEY del entorno

// o con opciones explícitas
$constaia = new \Constaia\Client(getenv('CONSTAIA_API_KEY'), [
    'timeout' => 60,     // segundos por intento (por defecto 60)
    'max_retries' => 2,  // reintentos en 429, 5xx y errores de red (por defecto 2)
]);
OpciónPor defectoDescripción
base_urlhttps://api.constaia.comURL base (o CONSTAIA_BASE_URL).
timeout60Segundos por intento.
max_retries2Reintentos automáticos.
headers[]Cabeceras que se añaden a todas las peticiones.

La clave es secreta: úsala solo en el servidor. Nunca la pongas en JavaScript, en HTML ni en una app móvil.

Analizar un documento

$analysis = $constaia->analyze('dni_valid.jpg', [
    'expect' => 'es_dni',
    'checks' => [
        'not_expired' => true,
        'holder' => ['full_name' => 'María García López'],
    ],
    'storage' => 'none',
    'language' => 'es',
]);

$analysis->verdictStatus();          // "valid" | "invalid" | "review" | null
$analysis->isValid();                // true
$analysis->isInvalid();              // false
$analysis->needsReview();            // false
$analysis->field('document_number'); // "12345678Z"
$analysis->warnings;                 // []

Las opciones van en snake_case, igual que en la API. Lista completa en Analizar un documento.

Entradas admitidas

EntradaEjemplo
Ruta local'dni.jpg' o '/var/uploads/dni.pdf'
URL http(s)'https://example.com/dni.jpg' (se envía como file_url)
Streamfopen('dni.jpg', 'rb')
SplFileInfoincluye el UploadedFile de Laravel y Symfony (se conserva el nombre original)
Base64['base64' => $b64, 'filename' => 'dni.jpg']
URL explícita['file_url' => 'https://…']

Si la entrada no trae nombre útil (por ejemplo un stream de memoria), pasa 'filename' => 'dni.jpg' en las opciones.

Leer la respuesta

Las respuestas son objetos ConstaiaObject: puedes leerlas como propiedades o como array, y convertirlas.

$analysis->document->type;          // "es_dni"
$analysis['verdict']['reasons'];    // lista de motivos
$analysis->toArray();               // array PHP
$analysis->toJson();                // JSON
$constaia->lastResponse->requestId; // req_… de la última petición

Clasificar

$result = $constaia->classify('passport.jpg', ['expect' => ['es_dni', 'passport']]);
$result->document->type;   // "passport"
$result->candidates;       // [{ type, confidence }, …]
$result->verdict->status;  // solo si pasas expect

Análisis guardados

$analysis = $constaia->analyses->get('an_01J…');

// Una página
$page = $constaia->analyses->list(['limit' => 20, 'status' => 'completed', 'type' => 'es_dni']);
foreach ($page as $item) { /* … */ }
$page->hasMore();

// Todas las páginas
foreach ($constaia->analyses->list(['metadata' => ['registration_id' => '123']])->autoPagingIterator() as $item) {
    echo $item->id, PHP_EOL;
}

$constaia->analyses->delete('an_01J…');

$bytes = $constaia->analyses->export('an_01J…', 'csv');                  // devuelve los bytes
$constaia->analyses->export('an_01J…', 'xlsx', storage_path('a.xlsx')); // o guarda en disco

Lotes

$batch = $constaia->batches->create([
    'files' => ['dni_valid.jpg', 'nie.jpg'],   // rutas, streams o UploadedFile
    'options' => ['expect' => ['es_dni', 'es_nie'], 'export' => ['xlsx']],
]);

// o con URLs
$batch = $constaia->batches->create([
    'items' => [['file_url' => 'https://example.com/1.jpg', 'options' => ['expect' => 'es_dni']]],
]);

$constaia->batches->get($batch->id);

Ver Lotes.

Catálogo, saldo y uso

$types = $constaia->documentTypes->list();
$dni = $constaia->documentTypes->get('es_dni');

$balance = $constaia->balance();   // credits_available, credits_reserved, …
$usage = $constaia->usage(['from' => '2026-09-01', 'to' => '2026-09-30']);

Webhooks

$endpoint = $constaia->webhookEndpoints->create([
    'url' => 'https://tuapp.example/webhooks/constaia',
    'events' => ['analysis.completed', 'analysis.failed'],
]);
$endpoint->secret; // whsec_… (solo en la creación)

// En tu handler: cuerpo crudo + cabeceras + secreto
$event = \Constaia\Webhook::verify(file_get_contents('php://input'), getallheaders(), getenv('CONSTAIA_WEBHOOK_SECRET'));

Webhook::verify comprueba la firma Standard Webhooks con una tolerancia de 5 minutos y devuelve el evento como array. Si falla, lanza SignatureVerificationException. La versión manual con hash_hmac está en Webhooks.

Opciones por petición

Cualquier método acepta estas opciones, que no se envían como parámetros de la API:

$constaia->analyze($path, [
    'expect' => 'es_dni',
    'idempotency_key' => 'registration-123-dni',
    'timeout' => 90,
    'max_retries' => 0,
]);

$constaia->analyses->get('an_01J…', ['timeout' => 10]);

En cada POST el SDK envía una Idempotency-Key (la tuya o una generada) y la reutiliza en los reintentos. Ver Idempotencia.

Excepciones

Todas heredan de Constaia\Exception\ConstaiaException y ofrecen getType(), getErrorCode(), getParam(), getRequestId() y getHttpStatus().

Clase (Constaia\Exception\…)Cuándo
InvalidRequestException400/422: parámetros incorrectos, fichero no admitido o no encontrado…
AuthenticationException401 o falta la clave.
PermissionException403.
NotFoundException404.
InsufficientCreditsException402: no queda saldo.
RateLimitException429 tras agotar reintentos; getRetryAfter() en segundos.
ApiException5xx.
ConnectionExceptionError de red o timeout.
SignatureVerificationExceptionFirma de webhook no válida.
use Constaia\Exception\InsufficientCreditsException;
use Constaia\Exception\InvalidRequestException;

try {
    $analysis = $constaia->analyze($path, ['expect' => 'es_dni']);
} catch (InsufficientCreditsException $e) {
    // avisa al administrador
} catch (InvalidRequestException $e) {
    error_log($e->getErrorCode() . ' ' . $e->getRequestId());
}

El SDK reintenta automáticamente las respuestas 429 y 5xx y los errores de red, con espera exponencial y respetando Retry-After (hasta 60 s).

Laravel

El paquete se registra solo gracias al autodescubrimiento de Laravel: añade un singleton Constaia\Client y la facade Constaia.

.env
CONSTAIA_API_KEY=ck_test_…
CONSTAIA_WEBHOOK_SECRET=whsec_…
# opcionales
CONSTAIA_TIMEOUT=60
CONSTAIA_MAX_RETRIES=2

Si quieres el fichero de configuración en tu proyecto:

php artisan vendor:publish --tag=constaia-config

Validar un documento subido

app/Http/Controllers/RegistrationDocumentController.php
namespace App\Http\Controllers;

use Constaia;
use Illuminate\Http\Request;

class RegistrationDocumentController
{
    public function store(Request $request)
    {
        $request->validate([
            'dni' => ['required', 'file', 'mimes:jpg,jpeg,png,webp,pdf', 'max:20480'],
        ]);

        $analysis = Constaia::analyze($request->file('dni'), [
            'expect' => ['es_dni', 'es_nie'],
            'checks' => [
                'not_expired' => true,
                'holder' => ['full_name' => $request->user()->name],
            ],
            'metadata' => ['user_id' => (string) $request->user()->id],
        ]);

        return match ($analysis->verdictStatus()) {
            'valid' => back()->with('status', 'Documento verificado.'),
            'invalid' => back()->withErrors(['dni' => $analysis->verdict->reasons[0]->message ?? 'Documento no válido.']),
            default => back()->with('status', 'Lo revisaremos manualmente.'),
        };
    }
}

Puedes pasar el UploadedFile directamente (conserva el nombre original) o su ruta con getRealPath(). Si prefieres inyección de dependencias, pide Constaia\Client $constaia en el constructor o en el método.

Para webhooks en Laravel (ruta sin CSRF, cuerpo crudo con $request->getContent() e idempotencia con Cache::add), mira el ejemplo completo en Webhooks y la guía de Laravel.

En esta página