SUNAT

API para consultar información de SUNAT.

Cada llamada usa la API key y descuenta una petición del plan.

En SUNAT están estos endpoints.

Tipo de cambio

GET https://orvixjoin.com/api/v1/tipo-cambio/sunat?month=10&year=2026

GET https://orvixjoin.com/api/v1/tipo-cambio/sunat

GET https://orvixjoin.com/api/v1/tipo-cambio/sunat?date=2026-10-02

Tipo de cambio del dólar publicado por SUNAT, de un día o de un mes. Sin parámetros devuelve el día que SUNAT tiene publicado. date pide un día ya guardado. month y year van juntos y listan ese mes.

Para qué sirve. El ERP, la contabilidad o una compra en dólares toman la cotización del día sin digitarla. La factura, la orden y el asiento quedan con el tipo de cambio que publicó SUNAT.

Headers

NombreValor
Content-Typeapplication/json
AuthorizationBearer <API_KEY>

Filtro por fecha (querystring)

NombreTipoDescripción
datedate (YYYY-MM-DD)Día en formato ISO.

Filtro por mes (querystring)

NombreTipoDescripción
monthintegerDel 1 al 12. Hay que enviarlo junto con year.
yearintegerAño de 4 dígitos, desde 1990.

Body

Un día responde un objeto. Un mes responde month, year y la lista rates con este mismo objeto por día.

NombreTipoDescripción
buy_pricestringPrecio de compra.
sell_pricestringPrecio de venta.
comprastringEl mismo precio de compra.
ventastringEl mismo precio de venta.
datedate (YYYY-MM-DD)Fecha del tipo de cambio.
fechadate (YYYY-MM-DD)La misma fecha.
monedastringSiempre USD.
fuentestringSiempre SUNAT.

Response

200404
{
  "buy_price": "3.540",
  "sell_price": "3.552",
  "compra": "3.540",
  "venta": "3.552",
  "date": "2026-10-02",
  "fecha": "2026-10-02",
  "moneda": "USD",
  "fuente": "SUNAT"
}

Si el día o el mes no están publicados, la respuesta es 404 con {"error": "..."}.

Consumir

Python, C#, PHP, Java y Node.js. La llamada trae el JSON. No se graba en una base.

import requests

respuesta = requests.get(
    "https://orvixjoin.com/api/v1/tipo-cambio/sunat?date=2026-10-02",
    headers={"Authorization": "Bearer TU_API_KEY", "Accept": "application/json"},
    timeout=30,
)
respuesta.raise_for_status()
print(respuesta.json())

Consulta de RUC

GET https://orvixjoin.com/api/v1/sunat/ruc?numero=20100070970

Ficha del padrón reducido de SUNAT. Incluye la dirección partida, distrito, provincia, departamento, y si el RUC es agente de retención o buen contribuyente. locales_anexos queda en null: ese padrón no publica locales anexos.

Para qué sirve. Antes de emitir una factura o una orden de compra, el sistema comprueba que el cliente o el proveedor exista, trae la razón social y la dirección, y ve si es agente de retención o buen contribuyente.

Headers

NombreValor
Content-Typeapplication/json
AuthorizationBearer <API_KEY>

Querystring

NombreTipoDescripción
numerostringRUC de 11 dígitos. También se acepta ruc.

Body

NombreTipoDescripción
razon_socialstringNombre o razón social.
numero_documentostringRUC.
rucstringEl mismo RUC.
nombrestringEl mismo nombre.
estadostringEstado del contribuyente. Ejemplo: ACTIVO.
condicionstringCondición. Ejemplo: HABIDO.
direccionstringDomicilio fiscal. Si no hay dato, un guion.
domiciliostringDomicilio tal como está en el padrón.
ubigeostringUbigeo según SUNAT.
via_tipostringTipo de vía. Ejemplo: AV.
via_nombrestringNombre de la vía.
zona_codigostringCódigo de zona.
zona_tipostringTipo de zona. Ejemplo: URB.
numerostringNúmero de puerta.
interiorstringInterior.
lotestringLote.
dptostringDepartamento del edificio.
manzanastringManzana.
kilometrostringKilómetro.
distritostringDistrito, a partir del ubigeo.
provinciastringProvincia.
departamentostringDepartamento.
dnistringDNI asociado, si el padrón lo trae.
es_agente_retencionbooltrue si está en la lista de agentes de retención.
es_buen_contribuyentebooltrue si está en la lista de buenos contribuyentes.
locales_anexosnullSiempre null en este padrón.
fuentestringSiempre SUNAT.

Response

200404
{
  "razon_social": "EMPRESA DE PRUEBA S.A.C.",
  "numero_documento": "20100070970",
  "ruc": "20100070970",
  "nombre": "EMPRESA DE PRUEBA S.A.C.",
  "estado": "ACTIVO",
  "condicion": "HABIDO",
  "direccion": "AV. EJEMPLO 100",
  "domicilio": "AV. EJEMPLO 100",
  "ubigeo": "150131",
  "via_tipo": "AV.",
  "via_nombre": "EJEMPLO",
  "zona_codigo": "-",
  "zona_tipo": "-",
  "numero": "100",
  "interior": "-",
  "lote": "-",
  "dpto": "-",
  "manzana": "-",
  "kilometro": "-",
  "distrito": "SAN ISIDRO",
  "provincia": "LIMA",
  "departamento": "LIMA",
  "dni": "",
  "es_agente_retencion": false,
  "es_buen_contribuyente": false,
  "locales_anexos": null,
  "fuente": "SUNAT"
}

Consumir

Python, C#, PHP, Java y Node.js. La llamada trae el JSON. No se graba en una base.

import requests

respuesta = requests.get(
    "https://orvixjoin.com/api/v1/sunat/ruc?numero=20100070970",
    headers={"Authorization": "Bearer TU_API_KEY", "Accept": "application/json"},
    timeout=30,
)
respuesta.raise_for_status()
print(respuesta.json())

SBS

Compra y venta del sistema bancario. Dólar y euro, en la serie diaria que publica el BCRP.

Cada llamada usa la API key y descuenta una petición del plan.

Tipo de cambio

GET https://orvixjoin.com/api/v1/tipo-cambio/sbs

GET https://orvixjoin.com/api/v1/tipo-cambio/sbs?currency=USD&date=2026-09-30

GET https://orvixjoin.com/api/v1/tipo-cambio/sbs?currency=EUR

GET https://orvixjoin.com/api/v1/tipo-cambio/sbs?currency=EUR&date=2026-09-30

Sin date devuelve la cotización reciente. Sin currency responde dólares. Para euros use EUR.

Para qué sirve. Tesorería, cobranza y los contratos que pactan el dólar o el euro del sistema bancario consultan esa serie, distinta del tipo de cambio de SUNAT.

Headers

NombreValor
Content-Typeapplication/json
AuthorizationBearer <API_KEY>

Querystring

NombreTipoDescripción
datedate (YYYY-MM-DD)Día de la serie. Si se omite, se usa la cotización reciente.
currencystringUSD o EUR. Si se omite, USD.

Body

NombreTipoDescripción
buy_pricestringPrecio de compra.
sell_pricestringPrecio de venta.
comprastringEl mismo precio de compra.
ventastringEl mismo precio de venta.
datedate (YYYY-MM-DD)Fecha de la cotización.
fechadate (YYYY-MM-DD)La misma fecha.
currencystringMoneda consultada.
fuentestringSiempre SBS.

Response

200404
{
  "date": "2026-09-30",
  "fecha": "2026-09-30",
  "currency": "USD",
  "buy_price": "3.365",
  "sell_price": "3.372",
  "compra": "3.365",
  "venta": "3.372",
  "fuente": "SBS"
}

Consumir

Python, C#, PHP, Java y Node.js. La llamada trae el JSON. No se graba en una base.

import requests

respuesta = requests.get(
    "https://orvixjoin.com/api/v1/tipo-cambio/sbs?currency=USD&date=2026-09-30",
    headers={"Authorization": "Bearer TU_API_KEY", "Accept": "application/json"},
    timeout=30,
)
respuesta.raise_for_status()
print(respuesta.json())

RENIEC

Consulta de DNI. Si la persona ya está guardada, se responde esa fila. Si no, se consulta y la siguiente llamada del mismo DNI sale de la base.

Cada llamada usa la API key y descuenta una petición del plan.

Consulta de DNI

GET https://orvixjoin.com/api/v1/dni?numero=46027897

Para qué sirve. El punto de venta, la planilla o el delivery completan nombres y apellidos a partir del DNI, sin pedir que la persona los dicte.

Headers

NombreValor
Content-Typeapplication/json
AuthorizationBearer <API_KEY>

Querystring

NombreTipoDescripción
numerostringDNI de 8 dígitos. También se acepta dni.

Body

NombreTipoDescripción
dnistringNúmero de DNI.
document_numberstringEl mismo DNI.
numero_documentostringEl mismo DNI.
first_namestringNombres.
nombresstringLos mismos nombres.
first_last_namestringApellido paterno.
apellido_paternostringEl mismo apellido paterno.
second_last_namestringApellido materno.
apellido_maternostringEl mismo apellido materno.
full_namestringNombre completo.
nombre_completostringEl mismo nombre completo.
razon_socialstringEl mismo nombre completo.
fuentestringSiempre RENIEC.

Response

200404
{
  "dni": "46027897",
  "document_number": "46027897",
  "numero_documento": "46027897",
  "first_name": "ROXANA KARINA",
  "nombres": "ROXANA KARINA",
  "first_last_name": "DELGADO",
  "apellido_paterno": "DELGADO",
  "second_last_name": "HUAMANI",
  "apellido_materno": "HUAMANI",
  "full_name": "DELGADO HUAMANI ROXANA KARINA",
  "nombre_completo": "DELGADO HUAMANI ROXANA KARINA",
  "razon_social": "DELGADO HUAMANI ROXANA KARINA",
  "fuente": "RENIEC"
}

Consumir

Python, C#, PHP, Java y Node.js. La llamada trae el JSON. No se graba en una base.

import requests

respuesta = requests.get(
    "https://orvixjoin.com/api/v1/dni?numero=46027897",
    headers={"Authorization": "Bearer TU_API_KEY", "Accept": "application/json"},
    timeout=30,
)
respuesta.raise_for_status()
print(respuesta.json())

AFP

Comisiones de las AFP de un mes: flujo, mixta, prima de seguro, aporte obligatorio y remuneración máxima asegurable.

Cada llamada usa la API key y descuenta una petición del plan.

Comisiones AFP

GET https://orvixjoin.com/api/v1/afp/comisiones?year=2025&month=8

Consultar las comisiones de las AFP de ese año y mes. La respuesta es una lista, una fila por AFP.

Para qué sirve. La planilla del mes toma la comisión por flujo, la mixta, la prima de seguro y el aporte obligatorio de cada AFP para calcular el descuento previsional.

Headers

NombreValor
Content-Typeapplication/json
AuthorizationBearer <API_KEY>

Querystring

NombreTipoDescripción
yearintegerAño de 4 dígitos. Hay que enviarlo junto con month.
monthintegerDel 1 al 12.

Body

NombreTipoDescripción
afpstringNombre del AFP.
periodostringPeriodo de la consulta.
comision_fijanumberComisión fija. Desde enero de 1997 no se cobra; suele venir vacío.
comision_sobre_flujonumberComisión sobre flujo, porcentaje de la remuneración bruta mensual.
comision_mixta_sobre_flujonumberComisión mixta sobre flujo, porcentaje de la remuneración bruta mensual.
comision_mixta_sobre_saldonumberComisión mixta: comisión anual sobre saldo.
comision_anual_sobre_saldonumberComisión anual sobre saldo.
prima_de_seguronumberPrima de seguro, porcentaje de la remuneración bruta mensual.
aporte_obligatorionumberAporte obligatorio al fondo de pensiones, porcentaje de la remuneración bruta mensual.
remuneracion_maxima_asegurablenumberRemuneración máxima asegurable.

Response

200404
[
  {
    "afp": "PRIMA",
    "periodo": "2025-08",
    "comision_fija": null,
    "comision_sobre_flujo": 1.60,
    "comision_mixta_sobre_flujo": null,
    "comision_mixta_sobre_saldo": null,
    "comision_anual_sobre_saldo": 1.25,
    "prima_de_seguro": 1.37,
    "aporte_obligatorio": 10.00,
    "remuneracion_maxima_asegurable": 12184.88
  },
  {
    "afp": "PROFUTURO",
    "periodo": "2025-08",
    "comision_fija": null,
    "comision_sobre_flujo": 1.69,
    "comision_mixta_sobre_flujo": null,
    "comision_mixta_sobre_saldo": null,
    "comision_anual_sobre_saldo": 0.68,
    "prima_de_seguro": 1.37,
    "aporte_obligatorio": 10.00,
    "remuneracion_maxima_asegurable": 12184.88
  },
  {
    "afp": "HABITAT",
    "periodo": "2025-08",
    "comision_fija": null,
    "comision_sobre_flujo": 1.47,
    "comision_mixta_sobre_flujo": null,
    "comision_mixta_sobre_saldo": null,
    "comision_anual_sobre_saldo": 1.25,
    "prima_de_seguro": 1.37,
    "aporte_obligatorio": 10.00,
    "remuneracion_maxima_asegurable": 12184.88
  },
  {
    "afp": "INTEGRA",
    "periodo": "2025-08",
    "comision_fija": null,
    "comision_sobre_flujo": 1.55,
    "comision_mixta_sobre_flujo": null,
    "comision_mixta_sobre_saldo": null,
    "comision_anual_sobre_saldo": 0.78,
    "prima_de_seguro": 1.37,
    "aporte_obligatorio": 10.00,
    "remuneracion_maxima_asegurable": 12184.88
  }
]

Consumir

Python, C#, PHP, Java y Node.js. La llamada trae el JSON. No se graba en una base.

import requests

respuesta = requests.get(
    "https://orvixjoin.com/api/v1/afp/comisiones?year=2025&month=8",
    headers={"Authorization": "Bearer TU_API_KEY", "Accept": "application/json"},
    timeout=30,
)
respuesta.raise_for_status()
print(respuesta.json())

DIGEMID

Establecimientos farmacéuticos. Un RUC puede traer varios locales. Cada uno incluye la ficha del botón Ver: representante legal, personal y actividad.

Cada llamada usa la API key y descuenta una petición del plan.

Establecimientos por RUC

GET https://orvixjoin.com/api/v1/digemid?numero=10715929746

Devuelve todos los locales de ese RUC. Si ya están guardados con su ficha, la respuesta sale de la base.

Para qué sirve. Compras y el almacén comprueban que la farmacia o el distribuidor figure en el registro público, con su dirección y su actividad, antes de comprarle o de recibirle mercadería.

Headers

NombreValor
Content-Typeapplication/json
AuthorizationBearer <API_KEY>

Querystring

NombreTipoDescripción
numerostringRUC de 11 dígitos. También se acepta ruc.

Body

La respuesta trae ruc y establecimientos, una lista. Cada local tiene estos campos.

NombreTipoDescripción
numero_registrostringNúmero de registro del local.
categoriastringCategoría del establecimiento.
nombre_comercialstringNombre comercial.
razon_socialstringRazón social.
rucstringRUC del titular.
direccionstringDirección del local.
ubigeostringUbigeo.
situacionstringSituación del registro.
empadronadostringSI o NO.
lugar_registrostringLugar donde se registró.
fecha_iniciostringFecha de inicio.
tipostringTipo de establecimiento.
motivostringMotivo del registro.
solicitantestringSolicitante.
horariostringHorario de atención.
representanteslistFicha Ver. Cada ítem trae cargo y nombre.
personallistFicha Ver. Cada ítem trae cargo, nombre y horario.
actividadeslistFicha Ver. Cada ítem trae actividad.
almaceneslistAlmacenes del local. Puede venir vacía.
plantaslistPlantas. Puede venir vacía.
tipos_productolistCada ítem trae tipo_producto y grupo_producto.
fuentestringSiempre DIGEMID.

Response

200404
{
  "ruc": "10715929746",
  "establecimientos": [
    {
      "numero_registro": "0100101",
      "categoria": "BOT",
      "nombre_comercial": "BOTICA EJEMPLO MIRAFLORES",
      "razon_social": "BOTICAS EJEMPLO S.A.C.",
      "ruc": "10715929746",
      "direccion": "AV. LARCO NRO 100",
      "ubigeo": "LIMA/LIMA/MIRAFLORES",
      "situacion": "ACTIVO",
      "empadronado": "SI",
      "lugar_registro": "Dirección de Redes Integradas de Salud Lima Centro",
      "fecha_inicio": "2019-03-12",
      "tipo": "ACTIVO",
      "motivo": "",
      "solicitante": "",
      "horario": "LUN A VIE: 08:00 A 22:00; SAB: 08:00 A 22:00; DOM: 09:00 A 13:00",
      "representantes": [
        {
          "cargo": "GERENTE GENERAL",
          "nombre": "PEREZ LOPEZ ANA MARIA"
        }
      ],
      "personal": [
        {
          "cargo": "DIRECTOR TECNICO",
          "nombre": "GARCIA RUIZ LUIS ALBERTO",
          "horario": "LUN A VIE: 08:00 A 14:00; SAB: 08:00 A 14:00"
        },
        {
          "cargo": "ASISTENTE",
          "nombre": "TORRES DIAZ CARMEN ROSA",
          "horario": "LUN A VIE: 14:00 A 22:00; SAB: 14:00 A 22:00; DOM: 09:00 A 13:00"
        }
      ],
      "actividades": [
        {
          "actividad": "DISPENSACION Y EXPENDIO DE PRODUCTOS FARMACEUTICOS, DISPOSITIVOS MEDICOS Y PRODUCTOS SANITARIOS"
        }
      ],
      "almacenes": [],
      "plantas": [],
      "tipos_producto": [],
      "fuente": "DIGEMID"
    },
    {
      "numero_registro": "0100102",
      "categoria": "BOT",
      "nombre_comercial": "BOTICA EJEMPLO SAN ISIDRO",
      "razon_social": "BOTICAS EJEMPLO S.A.C.",
      "ruc": "10715929746",
      "direccion": "AV. CONQUISTADORES NRO 250",
      "ubigeo": "LIMA/LIMA/SAN ISIDRO",
      "situacion": "ACTIVO",
      "empadronado": "NO",
      "lugar_registro": "Dirección de Redes Integradas de Salud Lima Centro",
      "fecha_inicio": "2021-06-08",
      "tipo": "ACTIVO",
      "motivo": "",
      "solicitante": "",
      "horario": "LUN A VIE: 09:00 A 21:00; SAB: 09:00 A 18:00",
      "representantes": [
        {
          "cargo": "GERENTE GENERAL",
          "nombre": "PEREZ LOPEZ ANA MARIA"
        }
      ],
      "personal": [
        {
          "cargo": "DIRECTOR TECNICO",
          "nombre": "QUISPE MENDOZA ROSA ELENA",
          "horario": "LUN A VIE: 09:00 A 15:00; SAB: 09:00 A 13:00"
        },
        {
          "cargo": "ASISTENTE",
          "nombre": "SALAZAR VEGA PEDRO JOSE",
          "horario": "LUN A VIE: 15:00 A 21:00; SAB: 13:00 A 18:00"
        }
      ],
      "actividades": [
        {
          "actividad": "DISPENSACION Y EXPENDIO DE PRODUCTOS FARMACEUTICOS, DISPOSITIVOS MEDICOS Y PRODUCTOS SANITARIOS"
        }
      ],
      "almacenes": [],
      "plantas": [],
      "tipos_producto": [],
      "fuente": "DIGEMID"
    },
    {
      "numero_registro": "0100103",
      "categoria": "DRG",
      "nombre_comercial": "DROGUERIA EJEMPLO",
      "razon_social": "BOTICAS EJEMPLO S.A.C.",
      "ruc": "10715929746",
      "direccion": "AV. ARGENTINA NRO 1200",
      "ubigeo": "LIMA/LIMA/LIMA",
      "situacion": "ACTIVO",
      "empadronado": "SI",
      "lugar_registro": "Dirección General de Medicamentos, Insumos y Drogas",
      "fecha_inicio": "2018-11-20",
      "tipo": "ACTIVO",
      "motivo": "",
      "solicitante": "",
      "horario": "LUN A VIE: 08:00 A 17:00",
      "representantes": [
        {
          "cargo": "GERENTE GENERAL",
          "nombre": "PEREZ LOPEZ ANA MARIA"
        }
      ],
      "personal": [
        {
          "cargo": "DIRECTOR TECNICO",
          "nombre": "RAMOS HUAMAN JORGE LUIS",
          "horario": "LUN A VIE: 08:00 A 17:00"
        }
      ],
      "actividades": [
        {
          "actividad": "ALMACENAMIENTO Y DISTRIBUCION DE PRODUCTOS FARMACEUTICOS Y DISPOSITIVOS MEDICOS"
        }
      ],
      "almacenes": [],
      "plantas": [],
      "tipos_producto": [
        {
          "tipo_producto": "Producto Farmacéutico",
          "grupo_producto": "ESPECIALIDAD FARMACEUTICA"
        },
        {
          "tipo_producto": "Dispositivo Médico",
          "grupo_producto": "DISPOSITIVO MEDICO CLASE I"
        }
      ],
      "fuente": "DIGEMID"
    }
  ]
}

Consumir

Python, C#, PHP, Java y Node.js. La llamada trae el JSON. No se graba en una base.

import requests

respuesta = requests.get(
    "https://orvixjoin.com/api/v1/digemid?numero=10715929746",
    headers={"Authorization": "Bearer TU_API_KEY", "Accept": "application/json"},
    timeout=30,
)
respuesta.raise_for_status()
print(respuesta.json())

PRODUCTOS

Verificación oficial de productos farmacéuticos y registros sanitarios.

Cada llamada usa la API key y descuenta una petición del plan. La respuesta sale del catálogo oficial guardado en la base.

Registro sanitario

Busca por número de registro o por nombre en el catálogo de productos farmacéuticos que DIGEMID publica a diario.

Para qué sirve. La farmacia, el punto de venta o la tienda verifican que el medicamento esté en el catálogo oficial, muestran titular y fabricante, y pueden poner un sello de registro verificado. Ese catálogo no trae la fecha de vencimiento del registro.

GET https://orvixjoin.com/api/v1/digemid/productos?registro=EE02436

GET https://orvixjoin.com/api/v1/digemid/productos?nombre=Panadol

El número se envía sin espacios ni guion. EE-02436 y EE02436 son la misma consulta. El nombre acepta hasta 80 caracteres. Hace falta uno de los dos. La respuesta trae hasta 100 productos; total dice cuántos hay en el catálogo.

Querystring

NombreTipoDescripción
registrostringNúmero de registro sanitario. También se acepta numero.
nombrestringNombre del producto. También se acepta producto.

Body

La respuesta trae productos. Cada uno es una presentación del catálogo.

NombreTipoDescripción
codigostringCódigo DIGEMID del producto.
nombrestringNombre del producto.
registro_sanitariostringNúmero de registro sanitario.
concentracionstringConcentración.
forma_farmaceuticastringForma farmacéutica.
presentacionstringPresentación.
fraccionstringFracción.
titularstringTitular del registro.
fabricantestringFabricante.
principio_activostringPrincipio activo.
rubrostringRubro.
situacionstringSituación en el catálogo, por ejemplo ACT.
fuentestringSiempre DIGEMID, en cada producto y en la raíz.

Ejemplo

{
  "fuente": "DIGEMID",
  "registro": "EE02436",
  "nombre": "",
  "total": 1,
  "productos": [
    {
      "codigo": "43691",
      "nombre": "PARACETAMOL",
      "concentracion": "500 mg",
      "forma_farmaceutica": "Tableta",
      "presentacion": "Caja x 100",
      "fraccion": "100",
      "registro_sanitario": "EE02436",
      "titular": "LABORATORIO EJEMPLO S.A.C.",
      "fabricante": "LABORATORIO EJEMPLO S.A.C.",
      "principio_activo": "PARACETAMOL",
      "rubro": "ESPECIALIDAD FARMACEUTICA",
      "situacion": "ACT",
      "fuente": "DIGEMID"
    }
  ]
}

Consumir

Python, C#, PHP, Java y Node.js. La llamada trae el JSON. No se graba en una base.

import requests

respuesta = requests.get(
    "https://orvixjoin.com/api/v1/digemid/productos?registro=EE02436",
    headers={"Authorization": "Bearer TU_API_KEY", "Accept": "application/json"},
    timeout=30,
)
respuesta.raise_for_status()
print(respuesta.json())

DIGESA

Registro sanitario de alimentos y bebidas procesadas.

Cada llamada usa la API key y descuenta una petición del plan. La respuesta sale del listado oficial guardado en la base.

Registro sanitario de alimentos

Busca por RUC, por número de registro, por nombre del producto o por empresa en el listado de DIGESA.

Para qué sirve. Compras puede frenar una orden o una recepción de almacén si el alimento o la bebida no tiene registro vigente: así no entra mercadería con el registro vencido. El punto de venta y la tienda virtual completan empresa, producto y vencimiento, y pueden mostrar un sello de registro sanitario verificado. Con la fecha de vencimiento un panel avisa, por ejemplo, 30 días antes, para iniciar la reinscripción a tiempo.

GET https://orvixjoin.com/api/v1/digesa/alimentos?ruc=20515108794

GET https://orvixjoin.com/api/v1/digesa/alimentos?registro=G7800215E

GET https://orvixjoin.com/api/v1/digesa/alimentos?producto=gomas

GET https://orvixjoin.com/api/v1/digesa/alimentos?empresa=colombina

Hace falta uno de los cuatro. El RUC son 11 dígitos. El registro acepta el código con o sin el sufijo: G7800215E y G7800215E/NACLDL encuentran el mismo registro. Producto y empresa aceptan hasta 80 caracteres. La respuesta trae hasta 100 filas; total dice cuántas hay en el catálogo.

estado es Vigente si la fecha de vencimiento es hoy o posterior, y Vencido si ya pasó. El Excel de DIGESA no separa cancelado, suspendido ni baja: para bloquear un vencido se usa la fecha.

Querystring

NombreTipoDescripción
rucstringRUC de la empresa. También se acepta numero.
registrostringNúmero de registro sanitario.
productostringTexto del producto. También se acepta nombre.
empresastringRazón social del titular.

Body

La respuesta trae alimentos. Cada fila es un registro del listado.

NombreTipoDescripción
rucstringRUC de la empresa.
empresastringRazón social.
registro_sanitariostringNúmero de registro sanitario.
productostringDenominación, marca y presentaciones, como las publica DIGESA.
estadostringVigente o Vencido, según la fecha de vencimiento.
fecha_vencimientostringYYYY-MM-DD. Vacío si el listado no trae fecha.
certificadostringNúmero de certificado.
expedientestringExpediente.
direccionstringDirección de la empresa.
departamentostringDepartamento del establecimiento.
establecimientostringNombre del establecimiento, si el listado lo trae.
fuentestringSiempre DIGESA, en cada fila y en la raíz.

Ejemplo

{
  "fuente": "DIGESA",
  "ruc": "20515108794",
  "registro": "",
  "producto": "",
  "empresa": "",
  "total": 1,
  "alimentos": [
    {
      "ruc": "20515108794",
      "empresa": "COLOMBINA DEL PERU S.A.C.",
      "registro_sanitario": "G7800215E/NACLDL",
      "producto": "Producto 1 : GOMAS DE MASCAR SABOR A TUTTI FRUTTI",
      "estado": "Vigente",
      "fecha_vencimiento": "2030-01-10",
      "certificado": "00005-2025",
      "expediente": "101609-2024-R",
      "direccion": "CAL.ALCANFORES NRO. 495 INT. 504 MIRAFLORES - LIMA - LIMA",
      "departamento": "LIMA",
      "establecimiento": "",
      "fuente": "DIGESA"
    }
  ]
}

Consumir

Python, C#, PHP, Java y Node.js. La llamada trae el JSON. No se graba en una base.

import requests

respuesta = requests.get(
    "https://orvixjoin.com/api/v1/digesa/alimentos?ruc=20515108794",
    headers={"Authorization": "Bearer TU_API_KEY", "Accept": "application/json"},
    timeout=30,
)
respuesta.raise_for_status()
print(respuesta.json())

FACTURACIÓN

Este plan es anual y se contrata aparte del de consultas. Sirve para emitir factura, boleta y notas.

Para qué sirve. El sistema de ventas emite factura, boleta, nota y guía y las deja en SUNAT sin entrar al portal. El cliente recibe el PDF y el XML del comprobante aceptado.

Cada comprobante sigue tres pasos: crear el XML, firmarlo y enviarlo a SUNAT. La guía de remisión agrega un cuarto: después de enviarla, se consulta el ticket. Factura, boleta y notas se pueden consultar de nuevo en SUNAT y, si ya fueron aceptadas, dar de baja.

El RUC, el certificado y el usuario secundario se cargan en Facturación del portal. La razón social, la dirección y el ubigeo salen del padrón. Ahí también van, si corresponde, el OSE y las credenciales de guías.

Crear XML

POST https://orvixjoin.com/api/v1/cpe/xml

Factura 01, boleta 03, nota de crédito 07, nota de débito 08 o guía 09. Responde el id y el XML sin firmar.

Para qué sirve. Arma el comprobante en el formato que SUNAT exige, antes de firmarlo.

Headers

NombreValor
Content-Typeapplication/json
AuthorizationBearer <API_KEY>

Body

NombreTipoDescripción
tipostring01, 03, 07, 08 o 09.
seriestring4 caracteres. F factura, B boleta, T guía. La nota empieza con F o B.
numeronumberCorrelativo.
fechastringYYYY-MM-DD. Si se omite, el día de hoy.
monedastringPEN o USD. En la guía no se usa.
clienteobjecttipo_documento, numero, nombre, direccion y, si aplica, contacto. En la guía el mismo objeto puede llamarse destinatario.
itemsarraycodigo, unidad, descripcion, cantidad y valor_unitario sin IGV, o precio_unitario con incluye_igv. afectacion 10, 20 o 30. Si no mandas unidad, queda NIU. UN, UND y UNIT se envían a SUNAT como NIU y en el PDF se sigue viendo el texto que mandaste.
contactostringSale en el PDF. También puede ir dentro de cliente.
orden_comprastringOrden de compra del PDF.
vendedorstringSi se omite, el PDF muestra un guion.
guia_remisionstringNúmero de guía, si la factura la tiene.
observacionesstringTexto bajo el monto en letras.
condicionstringCondición de venta. Si se omite, Contado.
documentoobjectSolo notas. tipo, serie y numero de la factura 01 o la boleta 03 que se modifica.
motivostringSolo notas. En la nota de crédito, catálogo 09. En la nota de débito, catálogo 10. Si se omite, 01.
motivo_descripcionstringEn las notas, texto del motivo. Si se omite, Anulación de la operación. En la guía, texto del traslado. Si se omite, VENTA.
partidaobjectSolo guía. ubigeo y direccion de salida.
llegadaobjectSolo guía. ubigeo y direccion de llegada.
pesonumberSolo guía. Kilos.
modalidadstringSolo guía. 01 transporte público, 02 privado.
motivo_trasladostringSolo guía. Catálogo 20. Si se omite, 01.
fecha_trasladostringSolo guía. YYYY-MM-DD. Si se omite, la fecha del comprobante.
conductorobjectSolo guía con modalidad 02. tipo_documento, numero, nombre, apellidos y licencia.
vehiculoobjectSolo guía. placa.
observacionstringSolo guía. Texto de la nota del XML. Si se omite, Traslado.

Firmar

POST https://orvixjoin.com/api/v1/cpe/firmar

Usa el certificado del emisor. El body es {"id": "cpe_…"}.

Para qué sirve. Sella el XML con el certificado de la empresa para que SUNAT lo acepte.

Enviar a SUNAT

POST https://orvixjoin.com/api/v1/cpe/enviar

Factura, boleta y notas salen por SUNAT directo o por el OSE configurado. La guía se envía a producción por el API de guías: no tiene ambiente de prueba. La respuesta trae el ticket.

Para qué sirve. Entrega el comprobante firmado a SUNAT o al OSE y guarda la respuesta.

Ver el CDR

GET https://orvixjoin.com/api/v1/cpe/cdr?id=cpe_…

Devuelve el archivo ZIP del CDR, con el nombre R-RUC-tipo-serie-numero.zip. No vuelve a llamar a SUNAT. El mismo Bearer token. Si el comprobante todavía no se envió, responde 404.

Para qué sirve. Recupera la constancia de aceptación de SUNAT para archivarla o enviarla al cliente.

Ver el PDF

GET https://orvixjoin.com/api/v1/cpe/pdf?id=cpe_…

Devuelve el PDF del comprobante aceptado, con el nombre RUC-tipo-serie-numero.pdf. El mismo Bearer token. Si SUNAT todavía no lo aceptó, responde 404.

Para qué sirve. Entrega la representación impresa para el cliente o para el punto de venta.

Consultar estado

POST https://orvixjoin.com/api/v1/cpe/estado

Factura, boleta y notas. Pregunta a SUNAT el estado por RUC, tipo, serie y número. Primero pide la constancia (getStatusCdr) y, si no hay archivo, el código de estado (getStatus). El body es {"id": "cpe_…"}.

Aunque el comprobante se haya emitido por OSE, esta consulta va a SUNAT directo con el usuario SOL guardado en facturación. En el ambiente de pruebas SUNAT no ofrece este servicio. La guía no entra aquí: se consulta con el ticket.

Si SUNAT devuelve la constancia o el código 0 / 0001, estado queda aceptado y, cuando hay archivo, se guarda el CDR. El código 98 deja el comprobante en proceso. El código 2 es rechazado y el 3 es baja. La respuesta trae codigo_respuesta y mensaje. Cada llamada cuenta en el cupo.

Para qué sirve. El ERP pregunta a SUNAT si esa factura, boleta o nota ya fue aceptada, sin volver a emitirla.

Consultar ticket

POST https://orvixjoin.com/api/v1/cpe/ticket

Solo guía. El body es el mismo id. Hay que enviarla antes para que exista el ticket.

Para qué sirve. Sigue la guía de remisión hasta que SUNAT responde el ticket.

Dar de baja

POST https://orvixjoin.com/api/v1/cpe/baja

Solo un comprobante que SUNAT ya aceptó. El body lleva el id y, si quieres, el motivo. Si omites el motivo, se envía ANULACION.

La factura y las notas de crédito o débito salen como comunicación de baja. La boleta sale en el resumen diario de baja. La guía no se anula por este servicio. Después de emitirla hay que entrar a SUNAT con la clave SOL, en Guías de Remisión, Baja de GRE.

Si SUNAT acepta, estado queda anulado y la respuesta trae id_baja y ticket. Si todavía procesa (código 98), el comprobante sigue aceptado: vuelve a llamar este mismo servicio con el mismo id. Cada llamada cuenta en el cupo.

Para qué sirve. Anula una factura, boleta o nota que SUNAT ya aceptó, dentro del plazo, sin entrar al portal.

{"id": "cpe_…", "motivo": "ANULACION"}

Ejemplo de factura o boleta

Este ejemplo es de factura o boleta. Con tipo 01 y serie que empieza con F es factura. Con tipo 03 y serie que empieza con B es boleta. El cuerpo de abajo es una factura de dos ítems: valor 375.00 y 125.00, base 500.00, IGV 90.00 y total 590.00. El cliente es RV EVENTOS PERU S.A.C.

En Apidog el método es POST, el cuerpo es JSON y el header es Authorization: Bearer <API_KEY>. El body va vacío en los GET del CDR y del PDF. Las capturas de más abajo muestran la factura que SUNAT aceptó en la prueba, F001-00000006: un ítem y total 442.50.

1. Crear el XML de factura o boleta

POST https://orvixjoin.com/api/v1/cpe/xml

Pega este cuerpo de factura. Para boleta cambia tipo a 03 y la serie a una que empiece con B. La respuesta trae id, estado xml y el XML sin firmar. Guarda el id: los pasos siguientes lo usan.

{
  "tipo": "01",
  "serie": "F001",
  "numero": 6,
  "fecha": "2026-10-08",
  "moneda": "PEN",
  "condicion": "Contado",
  "contacto": "Alexandra",
  "vendedor": "Administrador Sistema",
  "observaciones": "Duplicado de COTC-0000000023.",
  "cliente": {
    "tipo_documento": "6",
    "numero": "20600139437",
    "nombre": "RV EVENTOS PERU S.A.C.",
    "direccion": "AV. LOS FAISANES NRO 170 INT. 101 URB. LA CAMPIÑA ZONA DOS, CHORRILLOS, LIMA, LIMA"
  },
  "items": [
    {
      "codigo": "SERVARVAR0002",
      "unidad": "UN",
      "descripcion": "SERVICIO DE CAPACITACIÓN ERP ORVIX",
      "cantidad": 1,
      "valor_unitario": 375,
      "afectacion": "10"
    },
    {
      "codigo": "SERVARVAR0003",
      "unidad": "UN",
      "descripcion": "ACOMPAÑAMIENTO EN LA PUESTA EN MARCHA",
      "cantidad": 1,
      "valor_unitario": 125,
      "afectacion": "10"
    }
  ]
}

El PDF arma el monto en letras a partir del total de estos dos ítems: QUINIENTOS NOVENTA SOLES. El contacto, el vendedor y las observaciones no van al XML de SUNAT; se guardan con el comprobante y salen solo en el PDF.

Ejemplo de nota de crédito

Este ejemplo es una nota de crédito, tipo 07. La serie empieza con F porque modifica una factura. El objeto documento es la referencia: la factura 01 F001-00000006 del ejemplo de arriba. Lleva los mismos dos ítems, base 500.00, IGV 90.00 y total 590.00. El motivo 01 es anulación de la operación.

Si la nota modifica una boleta, documento.tipo es 03 y la serie de la nota empieza con B.

Crear el XML de la nota de crédito

POST https://orvixjoin.com/api/v1/cpe/xml

Mismo servicio que la factura. La respuesta trae el id de la nota y el XML sin firmar. Firmar, enviar, el CDR y el PDF usan ese id.

{
  "tipo": "07",
  "serie": "FC01",
  "numero": 1,
  "fecha": "2026-10-08",
  "moneda": "PEN",
  "condicion": "Contado",
  "contacto": "Alexandra",
  "vendedor": "Administrador Sistema",
  "observaciones": "Anula la factura F001-00000006.",
  "motivo": "01",
  "motivo_descripcion": "Anulación de la operación",
  "documento": {
    "tipo": "01",
    "serie": "F001",
    "numero": 6
  },
  "cliente": {
    "tipo_documento": "6",
    "numero": "20600139437",
    "nombre": "RV EVENTOS PERU S.A.C.",
    "direccion": "AV. LOS FAISANES NRO 170 INT. 101 URB. LA CAMPIÑA ZONA DOS, CHORRILLOS, LIMA, LIMA"
  },
  "items": [
    {
      "codigo": "SERVARVAR0002",
      "unidad": "UN",
      "descripcion": "SERVICIO DE CAPACITACIÓN ERP ORVIX",
      "cantidad": 1,
      "valor_unitario": 375,
      "afectacion": "10"
    },
    {
      "codigo": "SERVARVAR0003",
      "unidad": "UN",
      "descripcion": "ACOMPAÑAMIENTO EN LA PUESTA EN MARCHA",
      "cantidad": 1,
      "valor_unitario": 125,
      "afectacion": "10"
    }
  ]
}

En el XML la referencia sale como factura F001-00000006 y el monto en letras es QUINIENTOS NOVENTA SOLES.

Ejemplo de nota de débito

Este ejemplo es una nota de débito, tipo 08. La serie empieza con F porque modifica una factura. El objeto documento es la referencia: la factura 01 F001-00000006. Lleva dos ítems de aumento, base 150.00, IGV 27.00 y total 177.00. El motivo 02, del catálogo 10, es aumento en el valor.

Si la nota modifica una boleta, documento.tipo es 03 y la serie de la nota empieza con B.

Crear el XML de la nota de débito

POST https://orvixjoin.com/api/v1/cpe/xml

Mismo servicio que la factura y la nota de crédito. La respuesta trae el id de la nota y el XML sin firmar. Firmar, enviar, el CDR y el PDF usan ese id.

{
  "tipo": "08",
  "serie": "FD01",
  "numero": 1,
  "fecha": "2026-10-08",
  "moneda": "PEN",
  "condicion": "Contado",
  "contacto": "Alexandra",
  "vendedor": "Administrador Sistema",
  "observaciones": "Aumenta el valor de la factura F001-00000006.",
  "motivo": "02",
  "motivo_descripcion": "Aumento en el valor",
  "documento": {
    "tipo": "01",
    "serie": "F001",
    "numero": 6
  },
  "cliente": {
    "tipo_documento": "6",
    "numero": "20600139437",
    "nombre": "RV EVENTOS PERU S.A.C.",
    "direccion": "AV. LOS FAISANES NRO 170 INT. 101 URB. LA CAMPIÑA ZONA DOS, CHORRILLOS, LIMA, LIMA"
  },
  "items": [
    {
      "codigo": "SERVARVAR0002",
      "unidad": "UN",
      "descripcion": "AUMENTO DEL SERVICIO DE CAPACITACIÓN ERP ORVIX",
      "cantidad": 1,
      "valor_unitario": 100,
      "afectacion": "10"
    },
    {
      "codigo": "SERVARVAR0003",
      "unidad": "UN",
      "descripcion": "AUMENTO DEL ACOMPAÑAMIENTO EN LA PUESTA EN MARCHA",
      "cantidad": 1,
      "valor_unitario": 50,
      "afectacion": "10"
    }
  ]
}

En el XML la referencia sale como factura F001-00000006 y el monto en letras es CIENTO SETENTA Y SIETE SOLES.

Ejemplo de guía de remisión

Este ejemplo es una guía de remisión, tipo 09. La serie empieza con T. El traslado es privado, modalidad 02: lleva conductor, licencia y placa. Sale de Lima, ubigeo 150101, y llega a Chorrillos, ubigeo 150108, al cliente RV EVENTOS PERU S.A.C. Peso 12.500 kilos. El motivo de traslado 01 es venta. Lleva dos ítems.

La guía no tiene ambiente de prueba: al enviarla, todo va a producción. Para anularla hay que entrar a SUNAT con la clave SOL, en Guías de Remisión, Baja de GRE.

En el transporte público, modalidad 01, no se envía conductor. Después de enviarla, la respuesta trae el ticket y el estado se consulta con ese ticket.

Recordatorio. En motivo_traslado va el código del catálogo 20. Si se omite, queda 01.

CódigoMotivo
01Venta
02Compra
03Venta con entrega a terceros
04Traslado entre establecimientos de la misma empresa
05Consignación
06Devolución
07Recojo de bienes transformados
08Importación
09Exportación
13Otros
14Venta sujeta a confirmación del comprador
17Traslado de bienes para transformación
18Traslado emisor itinerante CP
19Traslado de mercancía extranjera o zona primaria

Crear el XML de la guía

POST https://orvixjoin.com/api/v1/cpe/xml

Mismo servicio que la factura. La respuesta trae el id de la guía y el XML sin firmar. Firmar y enviar usan ese id. El estado se sigue con consultar ticket.

{
  "tipo": "09",
  "serie": "T001",
  "numero": 1,
  "fecha": "2026-10-08",
  "fecha_traslado": "2026-10-08",
  "observacion": "Traslado por venta de la factura F001-00000006.",
  "motivo_traslado": "01",
  "motivo_descripcion": "VENTA",
  "peso": 12.5,
  "modalidad": "02",
  "destinatario": {
    "tipo_documento": "6",
    "numero": "20600139437",
    "nombre": "RV EVENTOS PERU S.A.C.",
    "direccion": "AV. LOS FAISANES NRO 170 INT. 101 URB. LA CAMPIÑA ZONA DOS, CHORRILLOS, LIMA, LIMA",
    "ubigeo": "150108"
  },
  "partida": {
    "ubigeo": "150101",
    "direccion": "AV. JAVIER PRADO ESTE 123, SAN ISIDRO, LIMA"
  },
  "llegada": {
    "ubigeo": "150108",
    "direccion": "AV. LOS FAISANES NRO 170 INT. 101 URB. LA CAMPIÑA ZONA DOS, CHORRILLOS, LIMA, LIMA"
  },
  "conductor": {
    "tipo_documento": "1",
    "numero": "12345678",
    "nombre": "JUAN",
    "apellidos": "PEREZ",
    "licencia": "Q12345678"
  },
  "vehiculo": {
    "placa": "ABC123"
  },
  "items": [
    {
      "codigo": "MAN0001",
      "unidad": "NIU",
      "descripcion": "CAJA DE MANUALES",
      "cantidad": 2
    },
    {
      "codigo": "EQP0001",
      "unidad": "NIU",
      "descripcion": "EQUIPO DE DEMOSTRACIÓN",
      "cantidad": 1
    }
  ]
}

En el XML la guía sale como T001-00000001, con peso 12.500 KGM, motivo 01 y la placa ABC123.

El correo, el teléfono y las cuentas bancarias no se mandan en este JSON. El cliente los escribe en Facturación, en Datos del comprobante. La dirección de la cabecera sale del padrón al guardar el RUC en esa pantalla. El nombre de la moneda sale del código moneda: PEN se imprime Sol Peruano y, si el código no está en el catálogo, dice Soles.

La respuesta de esta llamada es el id cpe_0d1cd74262d031. En el papel se sigue imprimiendo UN y en el XML va NIU.

POST crear XML de F001-00000006 con estado xml
Solicitud de F001-00000006. La respuesta 200 devuelve el id cpe_0d1cd74262d031, el número 00000006 y el estado xml.
Auth Bearer y respuesta del XML de F001-00000006
Auth en Bearer token. El XML sin firmar ya lleva la unidad NIU.

2. Firmar

POST https://orvixjoin.com/api/v1/cpe/firmar

Mismo token. El cuerpo solo lleva el id que devolvió el paso anterior. El estado pasa a firmado y la respuesta incluye xml_firmado.

{
  "id": "cpe_0d1cd74262d031"
}
POST firmar el id cpe_0d1cd74262d031 con estado firmado
Firma de F001-00000006, id cpe_0d1cd74262d031. La respuesta 200 deja el estado en firmado.
XML firmado de F001-00000006 con la firma SIGN-EMISOR
El mismo Bearer token. En xml se ve la firma SIGN-EMISOR.

3. Enviar a SUNAT

POST https://orvixjoin.com/api/v1/cpe/enviar

El cuerpo lleva el id de F001-00000006. En ambiente de pruebas SUNAT beta usa el usuario de pruebas. Si acepta, el estado queda aceptado, codigo_respuesta es 0 y la respuesta trae cdr_base64 y pdf_base64.

{
  "id": "cpe_0d1cd74262d031"
}
POST enviar F001-00000006 aceptada por SUNAT
Envío. Respuesta 200, estado aceptado, código 0: La Factura numero F001-00000006, ha sido aceptada.
Auth Bearer de la factura aceptada F001-00000006
El mismo Bearer token en el envío. El mensaje de SUNAT confirma la aceptación.

4. Bajar el CDR

GET https://orvixjoin.com/api/v1/cpe/cdr?id=cpe_0d1cd74262d031

Sin cuerpo. Apidog ofrece el ZIP R-20615552888-01-F001-00000006.zip. Dentro está la constancia de SUNAT. Este GET no vuelve a enviar el comprobante y no consume cupo.

GET del CDR que devuelve un archivo ZIP
GET con el id en la query. Respuesta 200, archivo application/zip, con Descargar.
Archivo R-20615552888-01-F001-00000006.zip descargado
El ZIP descargado: R-20615552888-01-F001-00000006.zip.

5. Bajar el PDF

GET https://orvixjoin.com/api/v1/cpe/pdf?id=cpe_0d1cd74262d031

Sin cuerpo. El archivo se llama 20615552888-01-F001-00000006.pdf. Ahí se ven el cliente, el ítem, el total 442.50, el contacto Alexandra, el vendedor y la observación. La unidad impresa es UN. El logo es el de impresión de la empresa.

GET del PDF de F001-00000006 visto en Apidog
GET del PDF. Respuesta 200, application/pdf, con la cabecera de LGZ ORVIX S.A.C. y el número F001-00000006.
PDF completo de la factura F001-00000006
El PDF: cliente RV EVENTOS PERU S.A.C., ítem SERVARVAR0002, unidad UN, total 442.50 y la observación del duplicado.

Respuesta

Crear devuelve estado xml y el XML sin firmar. Firmar agrega xml_firmado. Enviar, si SUNAT acepta, deja aceptado y agrega cdr_base64 y pdf_base64. Esos dos archivos también se bajan con los GET de CDR y PDF.

{
  "id": "cpe_ab12",
  "tipo": "01",
  "serie": "F001",
  "numero": "00000001",
  "estado": "aceptado",
  "ticket": "",
  "codigo_respuesta": "0",
  "mensaje": "La Factura ha sido aceptada",
  "xml": "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>",
  "xml_firmado": "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>",
  "cdr_base64": "UEsDBBQAAAA...",
  "pdf_base64": "JVBERi0xLjQK..."
}

Enviar la factura y guardarla

El cliente manda los datos de la factura. La API crea el XML, lo firma con el certificado del emisor y lo envía a SUNAT. Si SUNAT acepta, estado queda en aceptado y la misma respuesta trae los tres archivos para archivar en su base, no en disco:

Campo de la respuestaColumnaTipoQué es
xml_firmadoxml_firmadobyteaXML firmado, texto ISO-8859-1. Se convierte a bytes al grabar.
cdr_base64cdrbyteaZIP del CDR que devolvió SUNAT. Viene en base64.
pdf_base64pdfbyteaRepresentación en PDF del comprobante aceptado. Viene en base64.

En SQL Server esas columnas son varbinary(max). En MySQL, longblob. cdr_base64 y pdf_base64 salen cuando el estado es aceptado. Si SUNAT rechaza, la respuesta trae estado y mensaje, sin esos archivos.

CREATE TABLE comprobante_electronico (
    codigo varchar(24) PRIMARY KEY,
    tipo char(2) NOT NULL,
    serie varchar(4) NOT NULL,
    numero varchar(8) NOT NULL,
    estado varchar(20) NOT NULL,
    codigo_respuesta varchar(20) NOT NULL DEFAULT '',
    mensaje text NOT NULL DEFAULT '',
    xml_firmado bytea,
    cdr bytea,
    pdf bytea
);

Crear, firmar, enviar y grabar

Python, C#, PHP, Java y Node.js. Son tres POST con el mismo id. Al final se decodifican los tres archivos y se graban en la fila.

import base64
import requests
import psycopg

headers = {"Authorization": "Bearer TU_API_KEY", "Content-Type": "application/json"}

def post(url, cuerpo):
    respuesta = requests.post(url, headers=headers, json=cuerpo, timeout=90)
    respuesta.raise_for_status()
    return respuesta.json()

factura = {
    "tipo": "01",
    "serie": "F001",
    "numero": 1,
    "moneda": "PEN",
    "cliente": {
        "tipo_documento": "6",
        "numero": "20100070970",
        "nombre": "EMPRESA DE PRUEBA S.A.C.",
        "direccion": "AV. EJEMPLO 100",
    },
    "items": [{
        "descripcion": "Servicio",
        "cantidad": 1,
        "valor_unitario": 100,
        "afectacion": "10",
    }],
}
cpe = post("https://orvixjoin.com/api/v1/cpe/xml", factura)
cpe = post("https://orvixjoin.com/api/v1/cpe/firmar", {"id": cpe["id"]})
cpe = post("https://orvixjoin.com/api/v1/cpe/enviar", {"id": cpe["id"]})
if cpe["estado"] != "aceptado":
    raise SystemExit(cpe.get("mensaje") or cpe["estado"])

xml_firmado = cpe["xml_firmado"].encode("iso-8859-1")
cdr = base64.b64decode(cpe["cdr_base64"])
pdf = base64.b64decode(cpe["pdf_base64"])
with psycopg.connect("postgresql://usuario:clave@localhost/tu_base") as con:
    con.execute(
        """
        INSERT INTO comprobante_electronico
            (codigo, tipo, serie, numero, estado, codigo_respuesta, mensaje, xml_firmado, cdr, pdf)
        VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
        ON CONFLICT (codigo) DO UPDATE SET
            estado = EXCLUDED.estado,
            codigo_respuesta = EXCLUDED.codigo_respuesta,
            mensaje = EXCLUDED.mensaje,
            xml_firmado = EXCLUDED.xml_firmado,
            cdr = EXCLUDED.cdr,
            pdf = EXCLUDED.pdf
        """,
        (cpe["id"], cpe["tipo"], cpe["serie"], cpe["numero"], cpe["estado"],
         cpe["codigo_respuesta"], cpe["mensaje"], xml_firmado, cdr, pdf),
    )

Errores

Si la llamada no prospera, el cuerpo es {"error": "mensaje"}.

CódigoCuándo
400El RUC no tiene 11 dígitos, el DNI no tiene 8, la fecha no es YYYY-MM-DD, faltan year y month en AFP, la baja no corresponde (guía, comprobante sin aceptar, o ya anulado), o la consulta de estado no aplica (guía, ambiente de pruebas, o falta el usuario SOL).
401Falta la API key o no es válida.
403La suscripción está vencida, o la cuenta no tiene el plan de facturación electrónica.
404No hay dato para esa consulta.
429Se alcanzó el cupo. En el plan gratuito son 100 usos en total. En los planes de pago, el tope del mes.