Valhalla Imprenta Digital — Documentación
Referencia técnica

Integrar con la API Fiscal de Valhalla

Para los equipos de AdminPos, MHP, AdminPro, AdminFood, CondoSaaS y cualquier otro sistema hermano que necesite emitir documentos fiscales venezolanos bajo la modalidad de imprenta digital (Providencia Administrativa SNAT/2024/000121) en nombre de sus propios clientes.

Versión 1.0 Actualizado 2026-08-29 Base URL valhallaimprenta.com/api/v1
01

Modelo de negocio: quién es quién

Es fundamental entender esta relación antes de escribir una sola línea de integración.

Cliente final (ej. un hotel)
   │
   │  usa el sistema SaaS de ustedes (ej. MHP)
   ▼
Sistema SaaS hermano (MHP, AdminPos, AdminFood, CondoSaaS...)
   │
   │  llama a la API de Valhalla EN NOMBRE del cliente final
   ▼
Valhalla Imprenta Digital (este sistema)
   │
   │  emite el documento fiscal, lo homologa, genera PDF/XML
   ▼
SENIAT (vía folios/control numbers autorizados)

Puntos clave

  • El cliente final es el tenant en Valhalla, no el SaaS hermano. Cada uno tiene su propia base de datos aislada y su propio token de API.
  • El SaaS hermano nunca maneja folios directamente. Llama a la API con el token del cliente final y recibe el documento fiscal ya emitido.
  • El cliente final paga sus folios vía SimplexPay, dentro del panel de Valhalla (/app → Billetera). No se puede disparar desde la API — ver sección 6.
  • Un mismo cliente final puede usar más de un SaaS hermano contra el mismo tenant, siempre que ambos usen el mismo token de API.
02

Antes de integrar

  • El cliente final ya fue dado de alta como tenant en Valhalla (equipo Methacortex, panel Admin — sección 3).
  • Tenés el API token del cliente final (el mismo valor visible en el panel Admin al crear el tenant).
  • Sabés el email asociado a ese token.
  • El cliente final tiene una suscripción activa con folios en su billetera — sin esto, todo intento de emitir documentos responde 402.
  • Revisaste el catálogo de endpoints (sección 7) y el contrato de errores (sección 8).
03

Onboarding de un cliente final

Lo ejecuta el equipo de Methacortex desde el panel Admin (/admin → Empresas), no el SaaS hermano. Al crear un tenant:

  • Se provisiona una base de datos aislada para el cliente (dvi_<nombre>).
  • Se genera un API token (o se fija uno manualmente).
  • Se crea el usuario dueño con acceso al panel /app del cliente.
  • Se crea el usuario de autenticación de API, con el mismo token, contra el que autentica el endpoint de la sección 4.
  • Se le entrega al cliente (o directamente al SaaS hermano) el email del dueño y el API token.
Requisito adicional

El cliente final debe tener una suscripción activa asignada (panel Admin → Suscripciones) antes de poder emitir ningún documento — ver sección 6.

04

Autenticación

POST/auth/token
{
  "userName": "<email del usuario del tenant>",
  "userPassword": "<API token del tenant>"
}

Respuesta exitosa · 200

{
  "success": true,
  "message": "Token de Acceso",
  "token": "1|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

Credenciales inválidas · 401

{ "success": false, "message": "No existe coincidencia de usuario y API Token" }

El token es un Bearer token de Sanctum. Usalo en todos los endpoints siguientes:

Authorization: Bearer <token>
Accept: application/json
Content-Type: application/json

No expira por tiempo — es válido hasta que se revoque manualmente desde el panel. No hace falta renovarlo cada hora.

05

Idempotencia

Obligatorio en /invoices: cada request requiere transaccionId, un string hexadecimal de exactamente 50 caracteres, generado por ustedes.

Si reenvían la misma transaccionId (timeout, reintento de red), la API detecta el duplicado:

HTTP 201
{ "success": false, "message": "Yo detecté un intento duplicado de inyección para este transaccionId." }
201 no siempre significa éxito

Chequeen el campo success, no asuman que un 2xx significa éxito. El 201 aquí confirma "ya fue procesada" — nunca reintenten con el mismo transaccionId esperando un resultado distinto.

Este mecanismo solo existe en /invoices. Los demás endpoints validan duplicados por número de documento — ver sección 8.

06

Modelo de folios y billetera

Cada documento emitido consume un folio de la billetera comercial del tenant, que el cliente final recarga pagando con SimplexPay dentro de Valhalla.

Esto es distinto del rango de numeración autorizado por el SENIAT (la "serie" fiscal). Un tenant puede tener un rango SENIAT enorme y aun así quedarse sin saldo pagado — son cosas independientes.

Sin saldo disponible

HTTP 402 Payment Required
{ "success": false, "message": "Pago requerido: Saldo de folios agotado." }
HTTP 402 Payment Required
{ "success": false, "message": "Pago requerido: No encontré una suscripción activa." }
Manejen el 402 explícitamente

No es un error de su lado — es una señal de negocio. Muestren al cliente un mensaje claro y un enlace a su panel de Valhalla (/app → Billetera) para recargar.

Por qué no se puede recargar desde la API

El checkout con SimplexPay vive exclusivamente dentro del panel Filament de Valhalla, autenticado por sesión web. No existe, ni existirá por diseño, un endpoint de API para disparar una recarga en nombre del cliente.

Emisión por lote

/invoices, /dispatch-guides y /delivery-orders aceptan varios documentos en un solo request. El chequeo de saldo se hace contra el tamaño total del lote antes de procesar nada: si el tenant tiene 3 folios y el lote pide 5, la API rechaza todo con 402 sin crear ningún documento. Nunca reciben un resultado parcial.

07

Catálogo de endpoints

Base URL: https://valhallaimprenta.com/api/v1. Todos requieren Authorization: Bearer <token> salvo donde se indique lo contrario.

POST/invoicesEmisión de factura
{
  "transaccionId": "<50 caracteres hex>",
  "numeroSerie": "A001",
  "facturas": [
    {
      "numeroDocumento": "F-0001",
      "documentoIdentidadCliente": "J-12345678-9",
      "tipoDocumentoCliente": "J",
      "nombreCliente": "Cliente Demo, C.A.",
      "fechaEmision": "2026-08-29",
      "montoGravadoTotal": 100.0,
      "tipoCambioBCV": 1.16,
      "impuestosSubtotal": [
        { "codigoTotalImp": "G", "alicuotaImp": 16.0, "baseImponibleImp": 100.0, "valorTotalImp": 16.0 }
      ],
      "productos": [
        { "codigoProducto": "P001", "nombreProducto": "Servicio", "cantidad": 1, "precio": 100.0, "tipoImpuesto": "general" }
      ],
      "pagos": [
        { "payment_method_id": 1, "amount": 116.0, "reference": "REF-001", "bank_id": 1 }
      ]
    }
  ]
}

tipoDocumentoCliente: V, J, E, P, G, C (catálogo 7 SENIAT). codigoTotalImp: G 16% · R 8% · A 31% · E exento · P · X (catálogo 9).

Respuesta exitosa · 200

{
  "success": true,
  "message": "Facturas guardadas satisfactoriamente.",
  "invoice_list_success": [
    { "invoice_number": "FACT-000123", "control_number": "A001-00000045", "invoice_pdf": "https://...", "fiscal_exchange_xml": "<?xml ...?>" }
  ]
}

nombreProducto no puede contener la palabra "total" aislada, sin importar mayúsculas — es una validación fiscal, no un bug.

POST/credit-notesy /credit-notes/with-products

credit-notes anula el 100% de una factura. credit-notes/with-products permite devolución parcial por producto.

Campos comunes: serieFacturaAfectada, numeroFacturaAfectada, fechaFacturaAfectada, montoFacturaAfectada, numeroNotaCredito, razonEmision — deben coincidir exactamente con la factura original almacenada, o la API rechaza con 203.

Con productos, además: productos[].codigoProducto, cantidad, precio, tipoImpuesto, nombreProducto — no pueden exceder lo facturado originalmente por ese código.

Número de nota duplicado → HTTP 201 con success: false (mismo patrón que idempotencia, sección 5).

POST/debit-notes

Mismos campos "afectados" que nota de crédito, más productos[] obligatorio con nombreProducto. Incrementa el balance del cliente en la factura afectada.

GET/POST/iva-retentions

POST acepta alias en español o inglés por campo (numeroComprobante/voucher_number, rifAgente/provider_doc). porcentajeRetencion solo admite 75 o 100.

GET/POST/islr-retentions

Igual que IVA, más tipoPersona (PNR, PJD, PNNR) y codigoConceptoISLR, validados contra el catálogo interno de conceptos ISLR.

POST/dispatch-guidesy /delivery-orders

Aceptan lotes (guias[] / entregas[]). Cada ítem requiere cliente, productos, vehículo (placa, modelo, color) y empleados (nombre, cedula, rol).

GET/catalogs/banksy /catalogs/payment-methods

Devuelven 404 si el tenant no tiene bancos/métodos de pago configurados.

GET/reports/sales-book

Parámetros month (1-12), year (2000-2100). Devuelve un archivo .xlsx binario, no JSON.

POST/correlatives/request

Para integraciones que solo necesitan un número de control SENIAT válido, sin que Valhalla genere la factura completa. Body opcional: external_invoice_number, amount.

{ "success": true, "control_number": "00-0001234", "remaining_balance": 87 }
POST/invoices/externalflujo sin backend

Para integradores sin backend propio: un script de precarga (precarga.js) redirige al usuario al panel Admin con los datos prellenados. Autenticación por headers X-Username / X-Password, no Bearer token.

No emite el documento directamente — crea una "factura pendiente" y devuelve una URL al panel Admin para confirmación manual. Úsenlo solo si no pueden implementar el flujo completo de arriba.

08

Catálogo de errores

HTTPSignificadoQué hacer
200Procesado correctamentePersistir el/los PDF y XML devueltos
201 + success:falseDuplicado — idempotenciaNo reintentar; ya fue procesado
203Rechazo de regla fiscalCorregir el payload y reintentar con transaccionId nuevo
400Validación o regla de negocioRevisar message — indica el campo o motivo exacto
401Token inválido, ausente o expiradoRe-autenticar con /auth/token
402Sin folios / sin suscripción activaDirigir al cliente a recargar en su panel Valhalla
403Cuota de almacenamiento excedida (solo /external-documents/upload)
404Catálogo vacío para el tenantAvisar al equipo Methacortex

El contrato de errores no es 100% uniforme entre endpoints por razones históricas — la tabla refleja el comportamiento real verificado contra el código, no un ideal. Ante la duda, siempre miren success en el body antes que el status HTTP.

09

Ejemplo end-to-end

# 1. Autenticar
curl -X POST https://valhallaimprenta.com/api/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{"userName":"cliente@ejemplo.com","userPassword":"<API_TOKEN>"}'
# → { "token": "1|xxxx..." }

# 2. Emitir factura
curl -X POST https://valhallaimprenta.com/api/v1/invoices \
  -H "Authorization: Bearer 1|xxxx..." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{ "transaccionId": "...", "numeroSerie": "A001", "facturas": [...] }'

# 3. Si responde 402: mostrar al cliente el link a
#    https://valhallaimprenta.com/app (Billetera) para que recargue folios.
10

Preguntas frecuentes

¿Puedo cachear el token para siempre?

No expira por tiempo, pero puede ser revocado manualmente desde el panel. Si reciben 401 en cualquier momento, vuelvan a autenticar.

¿Qué pasa si el tenant tiene 2 SaaS hermanos integrados a la vez?

Ambos usan el mismo token y comparten la misma billetera de folios — cada documento que cualquiera de los dos emita consume del mismo saldo.

¿Puedo emitir facturas en bolívares?

El sistema calcula todo en bolívares, con soporte opcional de totalesOtraMoneda + tipoCambioBCV para mostrar el equivalente en moneda extranjera — no lo reemplaza, lo complementa.

¿Cómo sé si mi lote de facturas falló completo o parcial?

Nunca parcial. Todo el lote es una única transacción atómica: o se crean todos los documentos del array, o ninguno.