Generar (solo firmar)
Firma un XML UBL ya armado y lo devuelve en base64, sin enviarlo a SUNAT. Útil si quieres controlar el envío por separado.
Autenticación: token Bearer de empresa (cpe:sign).
Consideraciones
- Compatibilidad con proveedores que separan firmar de enviar.
- Puedes declarar el entorno en la ruta:
/api/pro/demo/cpe/generaro/api/pro/produccion/cpe/generar(y el aliasgenerar-demo, que sólo declara demo). Si lo que declaras no coincide con el entorno de tu empresa respondemos409y no se firma nada. - Devuelve el XML firmado (
xml, base64) y suexternal_id; guárdalo para enviarlo luego con enviar. - No envía a SUNAT: no genera CDR en esta llamada.
- El
nombre_archivosigueRUC-TIPO-SERIE-NUMEROy su RUC debe coincidir con la empresa del token.
Equivale a emitir en la API v1, pero recibiendo el XML ya armado en vez de JSON.
Firmar no manda nada a SUNAT: el comprobante queda válido y listo para imprimir, pero todavía no declarado. El segundo paso es enviar, que es donde llega el CDR.
Declarar el entorno (recomendado)
Tu sistema tiene su propio interruptor demo/producción y nosotros el nuestro. Si no dicen lo mismo, o emites comprobantes reales creyendo que pruebas, o pruebas creyendo que facturas — y ninguna de las dos avisa sola.
Por eso el entorno puede viajar en la ruta:
| Ruta | Qué afirma |
|---|---|
/api/cpe/generar |
Nada: mandas lo que diga tu empresa. |
/api/pro/demo/cpe/generar |
«Estoy en demo.» |
/api/pro/produccion/cpe/generar |
«Estoy en producción.» |
/api/cpe/generar-demo |
«Estoy en demo» (alias de otros proveedores). |
Si afirmas un entorno y no es el tuyo, respondemos 409 sin firmar nada. Es una
línea de configuración en tu cliente y convierte un fallo caro y silencioso en un
error inmediato. Las rutas sin declaración siguen funcionando igual que siempre.
El entorno vuelve además en cada respuesta (entorno), y en demo los textos
llevan el prefijo [DEMO] para que se vea en pantalla.
Headers
| Name | Type | Description |
|---|---|---|
| Accept | string | application/json |
| Content-Type | string | application/json |
| Authorization | string | Bearer <token>. Genéralo desde tu panel, en Tokens de API. |
Body
| Name | Type | Description |
|---|---|---|
| nombre_archivo* | string | Nombre en formato RUC-TIPO-SERIE-NUMERO (sin extensión). Acepta también xml_filename. |
| contenido_archivo* | string | XML UBL sin firmar en base64. Acepta también xml_content_base64 o xml. |
Ejemplo de solicitud
curl -X POST https://api.xmlperu.dev/api/cpe/generar \
-H "Authorization: Bearer $TOKEN_EMPRESA" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"nombre_archivo": "20123456789-03-B001-777",
"contenido_archivo": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0i…"
}'<?php
$token_empresa = 'pega-aqui-tu-token';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.xmlperu.dev/api/cpe/generar');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $token_empresa,
'Accept: application/json',
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
{
"nombre_archivo": "20123456789-03-B001-777",
"contenido_archivo": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0i…"
}
JSON);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);const TOKEN_EMPRESA = 'pega-aqui-tu-token';
const res = await fetch('https://api.xmlperu.dev/api/cpe/generar', {
method: 'POST',
headers: {
Authorization: `Bearer ${TOKEN_EMPRESA}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"nombre_archivo": "20123456789-03-B001-777",
"contenido_archivo": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0i…"
}),
});
const data = await res.json();import requests
TOKEN_EMPRESA = "pega-aqui-tu-token"
headers = {
"Authorization": f"Bearer {TOKEN_EMPRESA}",
"Accept": "application/json",
}
payload = {
"nombre_archivo": "20123456789-03-B001-777",
"contenido_archivo": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0i…"
}
res = requests.post("https://api.xmlperu.dev/api/cpe/generar", json=payload, headers=headers)
data = res.json()Respuesta
xml (base64). Envíalo después con enviar usando el mismo nombre_archivo o el external_id.{
"success": true,
"message": "XML firmado correctamente",
"xml": "PD94bWwg…ZmlybWFkbz4=",
"hash": "S0Gohv79sZvdhaLnJ1JwKRbE0HI=",
"external_id": "162c9e5f-e001-4b31-a1fd-83a4e3a30a51",
"codigo_hash": "S0Gohv79sZvdhaLnJ1JwKRbE0HI=",
"mensaje": "XML firmado correctamente",
"estado": 200,
"entorno": "demo",
"time": 0.63
}{
"success": false,
"estado": 409,
"mensaje": "No se emitió nada: la ruta declara entorno «demo» y el RUC 20123456789 está configurado en «produccion». …",
"entorno_declarado": "demo",
"entorno_empresa": "produccion"
}{
"success": false,
"message": "El RUC 20123456789 ingresado no corresponde al RUC registrado."
}