SDK de TypeScript
emitay es el cliente de la API para TypeScript y JavaScript. No tiene dependencias, sus
tipos se generan del contrato de la API y trae la línea de comandos.
npm install emitayCorre en Node 18 o superior, Deno, Bun y workers de borde: solo usa fetch y Web Crypto. Se
publica como módulo ES y como CommonJS, así que sirve igual con import que con require.
El cliente
Sección titulada «El cliente»import { Emitay } from "emitay";
const emitay = new Emitay(); // toma la llave de EMITAY_API_KEY
const empresa = await emitay.company.get();console.log(`${empresa.legal_name} (RUC ${empresa.ruc})`);Sin argumentos toma la llave de la variable EMITAY_API_KEY. También se le puede dar, con
sus opciones:
import { Emitay } from "emitay";
const emitay = new Emitay(process.env.EMITAY_API_KEY, { maxRetries: 2, // reintentos de cada llamada; 0 los desactiva timeoutMs: 60_000, // cuánto espera una respuesta});Qué trae
Sección titulada «Qué trae»| Recurso | Métodos |
|---|---|
emitay.invoices, receipts, creditNotes, debitNotes, despatchAdvices, retentions, perceptions |
create |
emitay.documents |
get, list, pages, waitFor, void, pdf, xml, cdr, sendEmail, emails |
emitay.events |
get, list, pages, stream |
emitay.webhookEndpoints |
create, get, list, pages, update, delete, rotateSecret, deliveries, retryDelivery |
emitay.layouts |
create, get, list, pages, update, preview |
emitay.company |
get, series, requestLogs |
Todo lo que emite devuelve el mismo recurso, Document.
Emitir y esperar
Sección titulada «Emitir y esperar»import { outcomeOf } from "emitay";
const emitida = await emitay.receipts.create( { series: "B001", items: [{ description: "Polo de algodón", quantity: 1, unit_price: "59.00", price_includes_tax: true }], }, { wait: 10 },);const boleta = await emitay.documents.waitFor(emitida);const sunat = outcomeOf(boleta);
if (sunat.valid) console.log(boleta.links.pdf);else console.error(sunat.code, sunat.description);waitespera a SUNAT en la misma llamada, hasta 30 segundos.documents.waitForsigue consultando un comprobante pendiente; uno que ya tiene veredicto vuelve sin hacer ninguna petición.outcomeOfresume la respuesta de SUNAT en un objeto cuyo tipo sigue alstatus;validestruesolo enacceptedyobserved.
El detalle está en El veredicto de SUNAT.
Opciones por llamada
Sección titulada «Opciones por llamada»const controlador = new AbortController();
await emitay.invoices.create(body, { idempotencyKey: "factura-pedido-8412", // tu propia llave de idempotencia wait: 10, // segundos que la respuesta espera a SUNAT signal: controlador.signal, // para cancelar});Si no das idempotencyKey, el SDK genera una por llamada y la repite en sus reintentos: ver
Idempotencia.
Listados
Sección titulada «Listados»list trae una página; pages las recorre todas, pidiendo cada una cuando el bucle la
necesita.
const pagina = await emitay.documents.list({ series: "F001", number: 123 });console.log(pagina.data.length, pagina.has_more);
for await (const cadaPagina of emitay.documents.pages({ status: "rejected" })) { for (const documento of cadaPagina.data) console.log(documento.series, documento.number);}Los listados que cuelgan de otro recurso llevan el id delante:
emitay.documents.emails.list(id) y emitay.webhookEndpoints.deliveries.list(id).
Errores
Sección titulada «Errores»Todo lo que el cliente lanza extiende EmitayError, con mensajes en español:
| Clase | Cuándo | Qué trae |
|---|---|---|
EmitayApiError |
La API respondió con un problema | status, code, detail, errors, fields, requestId |
EmitayConnectionError |
No hubo respuesta tras los reintentos | La causa |
EmitayTimeoutError |
waitFor agotó su plazo |
El comprobante, aún pendiente, en document |
EmitayWebhookError |
Una entrega no pasó la verificación | El motivo |
El manejo de cada una está en Errores.
Webhooks
Sección titulada «Webhooks»verifyWebhook comprueba la firma de una entrega y devuelve el evento con su tipo;
signWebhook firma una entrega de prueba, para los tests de tu receptor.
import { signWebhook, verifyWebhook } from "emitay";
const secret = "whsec_c2VjcmV0by1kZS1wcnVlYmE=";const payload = JSON.stringify({ id: "evt_1", type: "document.accepted" });const headers = await signWebhook({ payload, secret, id: "evt_1" });
const event = await verifyWebhook({ payload, headers, secret });console.log(event.type);Ver Webhooks.
Los tipos de cada petición y de cada recurso se exportan desde el paquete, con la descripción de cada campo en tu editor:
import type { CreateInvoice, Document, Event } from "emitay";
const cuerpo: CreateInvoice = { series: "F001", customer: { document_number: "20100066603", name: "CLIENTE S.A." }, items: [{ description: "Servicio de consultoría", quantity: 2, unit_price: "50.00" }],};
function alRecibir(evento: Event): Document | undefined { return evento.type === "document.accepted" ? evento.data.object : undefined;}Los códigos de los catálogos de SUNAT son string donde tu sistema ya los guarda como texto
(el tipo de documento del cliente, el motivo de una nota): pasas tu valor sin convertirlo y
la API lo valida.
En el navegador
Sección titulada «En el navegador»No uses el SDK con tu llave en el navegador: una llave de API es un secreto de servidor, y la API no acepta llamadas desde páginas de otros orígenes. Desde el navegador, llama a tu propio servidor.