Autenticación y modos
Toda petición a /v1 lleva una llave de API en la cabecera Authorization:
curl https://api.emitay.com/v1/company \ -H "Authorization: Bearer $EMITAY_API_KEY"La llave decide dos cosas: la empresa que emite y el modo en que lo hace. No hay otro parámetro para elegirlos.
| Llave | Modo | A dónde va el comprobante |
|---|---|---|
sk_test_… |
Prueba | Al ambiente beta de SUNAT, firmado con un certificado de prueba. Sin valor tributario |
sk_live_… |
Producción | A producción de SUNAT, firmado con el certificado de tu empresa |
Con el SDK, la llave se toma de la variable de entorno EMITAY_API_KEY:
import { Emitay } from "emitay";
const emitay = new Emitay(); // o new Emitay("sk_test_…")const empresa = await emitay.company.get();console.log(empresa.legal_name, empresa.ruc);Las llaves
Sección titulada «Las llaves»- Se crean y se revocan en el panel, en Llaves de API. Al registrar tu empresa ya recibes la primera, de prueba.
- Una llave se muestra una sola vez, al crearla. Emitay guarda solo su resumen: si la pierdes, crea otra y revoca la anterior.
- Revocar es inmediato: la siguiente petición con esa llave responde
401. - Una llave es un secreto de servidor. No la pongas en una app móvil ni en el navegador: la API no acepta llamadas desde páginas de otros orígenes. Desde el navegador, llama a tu propio servidor.
Los dos modos están separados
Sección titulada «Los dos modos están separados»Cada modo tiene sus propios comprobantes, series, eventos, endpoints de webhook y registro
de peticiones. Lo que creas con una llave de prueba no existe para una llave de producción: pedir
su id responde 404. La serie F001 empieza en 1 en cada modo.
Solo los layouts de PDF son de la empresa y sirven a los dos modos.
Qué hace el modo de prueba
Sección titulada «Qué hace el modo de prueba»- No necesita configuración. Ni certificado digital ni usuario SOL: Emitay firma con un certificado de prueba y usa las credenciales públicas del ambiente beta de SUNAT.
- La respuesta es de SUNAT. Facturas, boletas, notas, retenciones y percepciones se
envían de verdad al ambiente beta, que valida la estructura del XML, la firma y los
cálculos. Lo que ves en
sunat.codeysunat.descriptionlo escribió SUNAT. - Las guías de remisión se simulan. SUNAT no tiene ambiente de pruebas para guías: en
modo de prueba Emitay las valida, las arma y las firma, y responde
acceptedconsunat.simulated: true. El PDF lo dice en una leyenda. Nunca se presenta como una aceptación real. - Los correos sí se envían, a las direcciones que indiques, con el aviso «Comprobante de
prueba» y el asunto precedido de
[Prueba].
Pasar a producción
Sección titulada «Pasar a producción»El código no cambia: cambia la llave. Antes, la empresa necesita su certificado digital y su
usuario secundario SOL. Mientras falten, una llave de producción responde
409 company_not_configured y no consume ningún número.
Los pasos están en Pasar a producción.
Lo que trae toda respuesta
Sección titulada «Lo que trae toda respuesta»| Cabecera | Qué es |
|---|---|
Request-Id |
El id de la petición (req_…). Cítalo al pedir soporte; también lo ves con emitay logs |
RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset |
El límite de peticiones, cuántas quedan y en cuántos segundos vuelve a empezar la cuenta |
El límite es de 600 peticiones por minuto por llave; el envío de correos tiene además el
suyo, de 30 por minuto. Al pasarlo, la API responde 429 con
Retry-After, y la petición no se ejecuta.
Convenciones de la API
Sección titulada «Convenciones de la API»- JSON en
snake_case, conContent-Type: application/jsonen toda petición con cuerpo. - Un campo desconocido se rechaza, no se ignora: un nombre mal escrito responde
422con el campo, en lugar de emitir algo distinto de lo que querías. - Importes como texto decimal (
"118.00"), para no perder precisión. Al enviar se aceptan también números. - Fechas
YYYY-MM-DDen hora de Lima; instantes en ISO 8601 UTC. - Códigos de SUNAT tal cual: el tipo de documento
"6"es RUC, la unidadNIU, la afectación10. No hay equivalentes propios que aprender. - Listados con
?limit=y?cursor=, del más nuevo al más antiguo:{ "object": "list", "data": […], "has_more": true, "next_cursor": "doc_…" }.