API de FactuKey
Facturación electrónica ante ARCA: comprobantes A, B y C, notas de crédito y débito, remitos, firma electrónica y contingencia.
Inicio rápido
Emití una factura a consumidor final y esperá el CAE en la misma respuesta (hasta 20 segundos):
curl -X POST "https://api.factukey.com.ar/v1/comprobantes?esperar=20" \
-H "Authorization: Bearer fk_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: venta-1043" \
-d '{
"items": [{ "descripcion": "Café en grano 1kg", "cantidad": 2, "precio_unitario": 18500 }],
"precios_con_iva": true
}'FactuKey elige la letra (A, B o C) según tu condición frente al IVA y la del receptor, calcula neto e IVA, toma el próximo número de ARCA y devuelve:
{
"id": "6f1c…",
"estado": "autorizado",
"tipo": "Factura B",
"numero_completo": "00003-00001043",
"importes": { "neto": 30578.51, "iva": 6421.49, "total": 37000, ... },
"cae": "76412345678901",
"cae_vencimiento": "2026-10-14",
"pdf_url": "https://api.factukey.com.ar/p/c/6f1c…?t=…",
"qr_url": "https://www.afip.gob.ar/fe/qr/?p=…"
}Si ARCA demora, la respuesta es 202 con estado: "pendiente": FactuKey sigue intentando sola y te avisa por webhook. Podés consultar con GET /v1/comprobantes/:id.
Autenticación
Todas las requests llevan Authorization: Bearer fk_live_… (producción) o fk_test_… (homologación). Las keys se crean en el panel y se muestran una sola vez, y quedan atadas al ambiente: cuando la cuenta pasa a producción, las fk_test_ dejan de funcionar (así una key de pruebas nunca emite facturas reales). Límite: 120 requests por minuto por key.
Idempotencia: mandá Idempotency-Key en cada POST. Si reintentás con la misma clave y el mismo cuerpo, recibís el mismo comprobante (header Idempotent-Replayed: true); con otro cuerpo, 409 idempotencia_conflicto. Así un corte de red nunca duplica una factura.
Comprobantes
POST/v1/comprobantes | Emite. ?esperar=N (máx. 30 s) espera el CAE. |
GET/v1/comprobantes | Lista. Filtros: estado, desde, hasta, tipo_comprobante, receptor, limite, antes_de. |
GET/v1/comprobantes/:id | Detalle. |
GET/v1/comprobantes/:id/pdf | PDF con QR (?descargar para adjunto). |
GET/v1/me | Datos de la cuenta, puntos de venta y letras habilitadas. |
GET/v1/parametros | Tablas de referencia (sin autenticación). |
Cuerpo
clase | factura (default), nota_credito, nota_debito |
tipo_comprobante | Código ARCA (1, 6, 11…). Opcional: si falta, FactuKey elige la letra. |
punto_venta | Opcional; por defecto el primero habilitado. |
concepto | productos (default), servicios, productos_y_servicios. Con servicios: servicio_desde, servicio_hasta, vencimiento_pago (por defecto, el mes en curso). |
fecha | YYYY-MM-DD, hoy por defecto (ARCA admite ±5 días para productos y ±10 para servicios). |
receptor | { tipo_doc, nro_doc, razon_social, domicilio, condicion_iva, email }. Omitilo para consumidor final anónimo. condicion_iva: responsable_inscripto, monotributista, exento, consumidor_final, etc. (obligatorio en ARCA desde la RG 5616). |
items[] | { descripcion, cantidad, precio_unitario, alicuota_iva, bonificacion_porcentaje, codigo, unidad }. alicuota_iva: 0, 2.5, 5, 10.5, 21 (default), 27, "exento" o "no_gravado". |
precios_con_iva | true si los precios son finales. El total del comprobante es exactamente la suma de los ítems. |
moneda, cotizacion | PES por defecto. Para DOL/060, informar la cotización. |
tributos[] | Percepciones u otros tributos: { id, descripcion, base_imponible, alicuota, importe }. |
comprobante_asociado | Obligatorio en notas: { tipo_comprobante, punto_venta, numero }. |
permitir_contingencia | true por defecto: si ARCA no responde y tenés CAEA, se autoriza en contingencia. |
Nota de crédito
{ "clase": "nota_credito",
"receptor": { "nro_doc": "30712345678", "condicion_iva": "responsable_inscripto", "razon_social": "Cliente SA" },
"items": [{ "descripcion": "Devolución", "precio_unitario": 1000 }],
"comprobante_asociado": { "tipo_comprobante": 1, "punto_venta": 3, "numero": 1043 } }Remitos
ARCA no tiene remito electrónico general, así que los remitos son documentos de FactuKey con numeración propia y PDF con espacio para la conformidad. Tipo X (interno) o R si el punto de venta tiene CAI de remitos.
POST/v1/remitos | { tipo, receptor, items[{descripcion, cantidad, unidad}], transporte, comprobante_id, valor_declarado } |
GET/v1/remitos | |
GET/v1/remitos/:id | |
GET/v1/remitos/:id/pdf | |
POST/v1/remitos/:id/anular | { motivo } |
Impresión en comandera (tickets)
Cada comprobante y remito autorizado trae ticket_url, la representación impresa para papel térmico de 80 o 58 mm (datos fiscales, ítems, totales, CAE y QR). El mismo ticket sale en cuatro formatos:
GET/v1/comprobantes/:id/ticket | Parámetros: ancho=80|58 (default 80), salida=html|pdf|escpos|json (default html), imprimir (el HTML llama a print() al cargar). |
GET/v1/remitos/:id/ticket | Igual, con espacio para la firma de conformidad. |
html | Para imprimir desde el navegador. Fija la página al ancho del rollo y al alto del contenido. |
pdf | Para guardar o compartir el ticket. |
escpos | Bytes ESC/POS listos para mandar crudos a la impresora (página de códigos PC850, QR nativo y corte de papel). |
json | Las líneas del ticket (texto, par, separador, qr…) para dibujarlo con tus propias plantillas. |
Desde el navegador (sin instalar nada)
La API key nunca va al navegador: tu servidor pide el ticket y lo sirve desde tu propio dominio. Así podés imprimirlo en un iframe oculto.
// app/api/facturas/[id]/ticket/route.ts (Next.js, en tu servidor)
export async function GET(_: Request, { params }: { params: { id: string } }) {
const r = await fetch(`https://api.factukey.com.ar/v1/comprobantes/${params.id}/ticket?ancho=80`, {
headers: { Authorization: `Bearer ${process.env.FACTUKEY_API_KEY}` },
});
return new Response(r.body, { status: r.status, headers: { "content-type": "text/html; charset=utf-8" } });
}
// En el cliente: imprimir sin abrir otra pestaña
function imprimirTicket(id: string) {
const f = document.createElement("iframe");
f.style.display = "none";
f.src = `/api/facturas/${id}/ticket`;
f.onload = () => { f.contentWindow?.print(); setTimeout(() => f.remove(), 60_000); };
document.body.appendChild(f);
}Configurá la comandera como impresora predeterminada de la PC de la caja. Para que imprima sin mostrar el diálogo, abrí Chrome con --kiosk-printing.
Con un agente local (impresión silenciosa, varias impresoras)
Si tu sistema ya tiene un agente en el local que imprime comandas, pedile el ticket en escpos y mandalo a la impresora tal cual. Por ejemplo, a una comandera de red en el puerto 9100:
import net from "node:net";
const r = await fetch(`https://api.factukey.com.ar/v1/comprobantes/${id}/ticket?ancho=80&salida=escpos`, {
headers: { Authorization: `Bearer ${FACTUKEY_API_KEY}` }, // o pedíselo a tu servidor
});
const bytes = Buffer.from(await r.arrayBuffer());
const s = net.connect(9100, "192.168.1.50", () => s.end(bytes));Si preferís usar tus propias plantillas (las mismas que para las comandas), usá salida=json y dibujá cada línea. El logo no se incluye en ESC/POS: conviene grabarlo una vez en la memoria de la impresora (NV) desde el agente.
Firma electrónica
Con la integración de Signet habilitada (API key en el panel), cualquier comprobante o remito se puede mandar a firmar:
POST /v1/remitos/:id/firma
{ "firmantes": [{ "rol": "receptor", "nombre": "Juan Pérez", "email": "[email protected]", "dni": "30123456" }],
"delivery": "email" }Respuesta 202 con la firma en curso. GET /v1/firmas/:id trae el estado y los links de firma (con delivery: "none" los mostrás vos en tu app). Al completarse: GET /v1/firmas/:id/pdf y webhook firma.completada.
Contingencia (CAEA)
Habilitala en el panel y agregá un punto de venta de tipo CAEA (en ARCA: "RECE para aplicativo y web services - CAEA"). FactuKey pide el CAEA de cada quincena y:
- Si ARCA no responde, tu request igual se autoriza con el CAEA en el punto de venta de contingencia (campo
autorizacion.modo: "CAEA") y FactuKey lo informa a ARCA cuando vuelve. - Si tu sistema se queda sin internet, pedí antes un lote de números y emití offline:
POST /v1/contingencia/lotes { "tipo_comprobante": 6, "cantidad": 50 }
→ { "lote_id": "…", "caea": "36401234567890", "punto_venta": 90, "desde": 121, "hasta": 170, "vigencia": {...} }
# Sin conexión: imprimí cada comprobante con ese CAEA (QR con tipoCodAut "A").
# Al volver la conexión, rendí cada uno:
POST /v1/contingencia/comprobantes { "lote_id": "…", "numero": 121, "generado_at": "2026-10-04T15:42:10-03:00",
"comprobante": { ...mismo cuerpo que /v1/comprobantes... } }
# Y cerrá el lote indicando el último número que usaste (desde - 1 si no usaste ninguno):
POST /v1/contingencia/lotes/:id/cerrar { "usados_hasta": 134 }Usá los números en orden y cerrá el lote apenas vuelvas a estar en línea: ARCA exige que los comprobantes CAEA se informen correlativos. GET /v1/contingencia muestra el CAEA vigente, los lotes abiertos y lo que falta informar.
Webhooks
Configurá la URL en el panel. Eventos: comprobante.autorizado, comprobante.rechazado, firma.completada, certificado.por_vencer. Cuerpo: { id, evento, creado, datos }. Se reintenta hasta 10 veces con backoff.
Verificá la firma: X-FactuKey-Firma: t=<unix>,v1=<hex>, donde v1 = HMAC-SHA256(secreto, t + "." + cuerpo).
const [t, v1] = req.headers["x-factukey-firma"].split(",").map((p) => p.split("=")[1]);
const esperado = crypto.createHmac("sha256", SECRETO).update(`${t}.${cuerpoCrudo}`).digest("hex");
if (esperado !== v1 || Date.now() / 1000 - Number(t) > 300) return res.status(401).end();MCP (agentes de IA)
FactuKey es un servidor MCP (Streamable HTTP). Conectalo a Claude u otro cliente MCP con tu API key:
{
"mcpServers": {
"factukey": {
"type": "http",
"url": "https://api.factukey.com.ar/mcp",
"headers": { "Authorization": "Bearer fk_live_..." }
}
}
}Herramientas: datos_emisor, previsualizar_comprobante, emitir_comprobante, consultar_comprobante, listar_comprobantes, emitir_remito, listar_remitos, solicitar_firma, consultar_firma, estado_contingencia.
Por WhatsApp funciona igual: los teléfonos autorizados en el panel le escriben al número de FactuKey ("facturale 3 cafés a consumidor final") y el asistente previsualiza, pide confirmación, emite y devuelve el PDF.
Errores
{ "error": { "codigo": "datos_invalidos", "mensaje": "Datos inválidos", "detalle": [{ "campo": "items", "mensaje": "…" }] } }400 | datos_invalidos, json_invalido |
401 | no_autorizado |
404 | no_encontrado |
409 | idempotencia_conflicto, no_autorizado (el comprobante todavía no tiene CAE), firma_no_habilitada |
422 | Reglas de negocio: letra_invalida, receptor_invalido, sin_punto_venta, fecha_fuera_de_rango, sin_caea… |
429 | limite_excedido |
Un comprobante rechazado por ARCA no es un error HTTP: la respuesta trae estado: "rechazado" con errores_arca y observaciones_arca. El número no se consume.