Official client
Use the typed Facta client. The client never calculates fiscal amounts or signs documents; the API does that.
Install
Use the local @facta/api package
Methods
status()— inspect key status and limits.emitir()— prepare, sign and transmit a DTE.preparar()/firmar()— split preparation from transmission.consultar()/listDocuments()— query issued documents.invalidate()— invalidate a sealed document.downloadDocument()— download exact JSON or PDF bytes.listHolding()— inspect documents awaiting synchronization.getContract()— retrieve the published OpenAPI contract.
Example
// hola-factura — una factura real, sellada por Hacienda, en veinte líneas.
//
// Es la medida del §9 de `docs/plan/tareas/sdks-de-la-api.md`: si no cabe en
// veinte líneas legibles, la API pide demasiado y se corrige la API, no el
// ejemplo. Cuéntalas: de `const facta` al `console.log` hay dieciséis.
//
// Node: FACTA_API_KEY=facta_test_… FACTA_SIGN_KEY=factask_… \
// node --experimental-strip-types sdks/typescript/examples/hola-factura.ts
// Deno: FACTA_API_KEY=facta_test_… FACTA_SIGN_KEY=factask_… \
// deno run --allow-net --allow-env sdks/typescript/examples/hola-factura.ts
import { Facta, FactaError } from "../mod.ts";
// Las dos salen del gestor de secretos del entorno, nunca del repositorio — y
// a ser posible de sitios distintos: el token autentica, y la de firma es lo
// único que abre tu vault de firma. Juntas en el mismo .env se pierde la mitad
// de la defensa. La tercera, FACTA_UNLOCK_KEY, no aparece aquí porque no viaja.
// deno-lint-ignore no-explicit-any
const g = globalThis as any;
const env = (name: string): string => g.Deno?.env.get(name) ?? g.process?.env?.[name];
const facta = new Facta({ apiKey: env("FACTA_API_KEY"), signKey: env("FACTA_SIGN_KEY") });
try {
const dte = await facta.emitir({
tipoDte: "03",
receptor: { customerId: "374114b6-e957-4c7a-8911-dd6381b1e0ea" },
items: [{ descripcion: "Integración de la API", cantidad: 1, precioUni: 25 }],
});
// `totales` solo existe en el sellado: un 202 de contingencia todavía no
// tiene veredicto. La estrechez del tipo lo obliga, que es lo que se quiere.
console.log(dte.estado, dte.numeroControl);
if (dte.estado === "sellado") console.log("sello:", dte.selloRecibido, dte.totales.totalPagar);
} catch (error) {
if (error instanceof FactaError && error.isRejection) {
// Hacienda lo leyó y lo negó: el número YA se gastó, y el §167 permite
// corregir con ese mismo número.
console.error("rechazado:", error.message, "· número gastado:", error.spent?.numeroControl);
} else throw error;
}
See the HTTP reference for every operation.