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
| Nombre | Valor |
|---|---|
| Content-Type | application/json |
| Authorization | Bearer <API_KEY> |
Filtro por fecha (querystring)
| Nombre | Tipo | Descripción |
|---|---|---|
| date | date (YYYY-MM-DD) | Día en formato ISO. |
Filtro por mes (querystring)
| Nombre | Tipo | Descripción |
|---|---|---|
| month | integer | Del 1 al 12. Hay que enviarlo junto con year. |
| year | integer | Añ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.
| Nombre | Tipo | Descripción |
|---|---|---|
| buy_price | string | Precio de compra. |
| sell_price | string | Precio de venta. |
| compra | string | El mismo precio de compra. |
| venta | string | El mismo precio de venta. |
| date | date (YYYY-MM-DD) | Fecha del tipo de cambio. |
| fecha | date (YYYY-MM-DD) | La misma fecha. |
| moneda | string | Siempre USD. |
| fuente | string | Siempre SUNAT. |
Response
{
"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())
using System.Net.Http.Headers;
var url = "https://orvixjoin.com/api/v1/tipo-cambio/sunat?date=2026-10-02";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "TU_API_KEY");
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Console.WriteLine(await http.GetStringAsync(url));
["Authorization: Bearer TU_API_KEY", "Accept: application/json"],
CURLOPT_RETURNTRANSFER => true,
]);
echo curl_exec($ch);
curl_close($ch);
var cliente = HttpClient.newHttpClient();
var pedido = HttpRequest.newBuilder()
.uri(URI.create("https://orvixjoin.com/api/v1/tipo-cambio/sunat?date=2026-10-02"))
.header("Authorization", "Bearer TU_API_KEY")
.header("Accept", "application/json")
.GET().build();
System.out.println(cliente.send(pedido, HttpResponse.BodyHandlers.ofString()).body());
const respuesta = await fetch("https://orvixjoin.com/api/v1/tipo-cambio/sunat?date=2026-10-02", {
headers: { Authorization: "Bearer TU_API_KEY", Accept: "application/json" },
});
console.log(await 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
| Nombre | Valor |
|---|---|
| Content-Type | application/json |
| Authorization | Bearer <API_KEY> |
Querystring
| Nombre | Tipo | Descripción |
|---|---|---|
| numero | string | RUC de 11 dígitos. También se acepta ruc. |
Body
| Nombre | Tipo | Descripción |
|---|---|---|
| razon_social | string | Nombre o razón social. |
| numero_documento | string | RUC. |
| ruc | string | El mismo RUC. |
| nombre | string | El mismo nombre. |
| estado | string | Estado del contribuyente. Ejemplo: ACTIVO. |
| condicion | string | Condición. Ejemplo: HABIDO. |
| direccion | string | Domicilio fiscal. Si no hay dato, un guion. |
| domicilio | string | Domicilio tal como está en el padrón. |
| ubigeo | string | Ubigeo según SUNAT. |
| via_tipo | string | Tipo de vía. Ejemplo: AV. |
| via_nombre | string | Nombre de la vía. |
| zona_codigo | string | Código de zona. |
| zona_tipo | string | Tipo de zona. Ejemplo: URB. |
| numero | string | Número de puerta. |
| interior | string | Interior. |
| lote | string | Lote. |
| dpto | string | Departamento del edificio. |
| manzana | string | Manzana. |
| kilometro | string | Kilómetro. |
| distrito | string | Distrito, a partir del ubigeo. |
| provincia | string | Provincia. |
| departamento | string | Departamento. |
| dni | string | DNI asociado, si el padrón lo trae. |
| es_agente_retencion | bool | true si está en la lista de agentes de retención. |
| es_buen_contribuyente | bool | true si está en la lista de buenos contribuyentes. |
| locales_anexos | null | Siempre null en este padrón. |
| fuente | string | Siempre SUNAT. |
Response
{
"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())
using System.Net.Http.Headers;
var url = "https://orvixjoin.com/api/v1/sunat/ruc?numero=20100070970";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "TU_API_KEY");
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Console.WriteLine(await http.GetStringAsync(url));
["Authorization: Bearer TU_API_KEY", "Accept: application/json"],
CURLOPT_RETURNTRANSFER => true,
]);
echo curl_exec($ch);
curl_close($ch);
var cliente = HttpClient.newHttpClient();
var pedido = HttpRequest.newBuilder()
.uri(URI.create("https://orvixjoin.com/api/v1/sunat/ruc?numero=20100070970"))
.header("Authorization", "Bearer TU_API_KEY")
.header("Accept", "application/json")
.GET().build();
System.out.println(cliente.send(pedido, HttpResponse.BodyHandlers.ofString()).body());
const respuesta = await fetch("https://orvixjoin.com/api/v1/sunat/ruc?numero=20100070970", {
headers: { Authorization: "Bearer TU_API_KEY", Accept: "application/json" },
});
console.log(await 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
| Nombre | Valor |
|---|---|
| Content-Type | application/json |
| Authorization | Bearer <API_KEY> |
Querystring
| Nombre | Tipo | Descripción |
|---|---|---|
| date | date (YYYY-MM-DD) | Día de la serie. Si se omite, se usa la cotización reciente. |
| currency | string | USD o EUR. Si se omite, USD. |
Body
| Nombre | Tipo | Descripción |
|---|---|---|
| buy_price | string | Precio de compra. |
| sell_price | string | Precio de venta. |
| compra | string | El mismo precio de compra. |
| venta | string | El mismo precio de venta. |
| date | date (YYYY-MM-DD) | Fecha de la cotización. |
| fecha | date (YYYY-MM-DD) | La misma fecha. |
| currency | string | Moneda consultada. |
| fuente | string | Siempre SBS. |
Response
{
"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())
using System.Net.Http.Headers;
var url = "https://orvixjoin.com/api/v1/tipo-cambio/sbs?currency=USD&date=2026-09-30";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "TU_API_KEY");
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Console.WriteLine(await http.GetStringAsync(url));
["Authorization: Bearer TU_API_KEY", "Accept: application/json"],
CURLOPT_RETURNTRANSFER => true,
]);
echo curl_exec($ch);
curl_close($ch);
var cliente = HttpClient.newHttpClient();
var pedido = HttpRequest.newBuilder()
.uri(URI.create("https://orvixjoin.com/api/v1/tipo-cambio/sbs?currency=USD&date=2026-09-30"))
.header("Authorization", "Bearer TU_API_KEY")
.header("Accept", "application/json")
.GET().build();
System.out.println(cliente.send(pedido, HttpResponse.BodyHandlers.ofString()).body());
const respuesta = await fetch("https://orvixjoin.com/api/v1/tipo-cambio/sbs?currency=USD&date=2026-09-30", {
headers: { Authorization: "Bearer TU_API_KEY", Accept: "application/json" },
});
console.log(await 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
| Nombre | Valor |
|---|---|
| Content-Type | application/json |
| Authorization | Bearer <API_KEY> |
Querystring
| Nombre | Tipo | Descripción |
|---|---|---|
| numero | string | DNI de 8 dígitos. También se acepta dni. |
Body
| Nombre | Tipo | Descripción |
|---|---|---|
| dni | string | Número de DNI. |
| document_number | string | El mismo DNI. |
| numero_documento | string | El mismo DNI. |
| first_name | string | Nombres. |
| nombres | string | Los mismos nombres. |
| first_last_name | string | Apellido paterno. |
| apellido_paterno | string | El mismo apellido paterno. |
| second_last_name | string | Apellido materno. |
| apellido_materno | string | El mismo apellido materno. |
| full_name | string | Nombre completo. |
| nombre_completo | string | El mismo nombre completo. |
| razon_social | string | El mismo nombre completo. |
| fuente | string | Siempre RENIEC. |
Response
{
"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())
using System.Net.Http.Headers;
var url = "https://orvixjoin.com/api/v1/dni?numero=46027897";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "TU_API_KEY");
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Console.WriteLine(await http.GetStringAsync(url));
["Authorization: Bearer TU_API_KEY", "Accept: application/json"],
CURLOPT_RETURNTRANSFER => true,
]);
echo curl_exec($ch);
curl_close($ch);
var cliente = HttpClient.newHttpClient();
var pedido = HttpRequest.newBuilder()
.uri(URI.create("https://orvixjoin.com/api/v1/dni?numero=46027897"))
.header("Authorization", "Bearer TU_API_KEY")
.header("Accept", "application/json")
.GET().build();
System.out.println(cliente.send(pedido, HttpResponse.BodyHandlers.ofString()).body());
const respuesta = await fetch("https://orvixjoin.com/api/v1/dni?numero=46027897", {
headers: { Authorization: "Bearer TU_API_KEY", Accept: "application/json" },
});
console.log(await 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
| Nombre | Valor |
|---|---|
| Content-Type | application/json |
| Authorization | Bearer <API_KEY> |
Querystring
| Nombre | Tipo | Descripción |
|---|---|---|
| year | integer | Año de 4 dígitos. Hay que enviarlo junto con month. |
| month | integer | Del 1 al 12. |
Body
| Nombre | Tipo | Descripción |
|---|---|---|
| afp | string | Nombre del AFP. |
| periodo | string | Periodo de la consulta. |
| comision_fija | number | Comisión fija. Desde enero de 1997 no se cobra; suele venir vacío. |
| comision_sobre_flujo | number | Comisión sobre flujo, porcentaje de la remuneración bruta mensual. |
| comision_mixta_sobre_flujo | number | Comisión mixta sobre flujo, porcentaje de la remuneración bruta mensual. |
| comision_mixta_sobre_saldo | number | Comisión mixta: comisión anual sobre saldo. |
| comision_anual_sobre_saldo | number | Comisión anual sobre saldo. |
| prima_de_seguro | number | Prima de seguro, porcentaje de la remuneración bruta mensual. |
| aporte_obligatorio | number | Aporte obligatorio al fondo de pensiones, porcentaje de la remuneración bruta mensual. |
| remuneracion_maxima_asegurable | number | Remuneración máxima asegurable. |
Response
[
{
"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())
using System.Net.Http.Headers;
var url = "https://orvixjoin.com/api/v1/afp/comisiones?year=2025&month=8";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "TU_API_KEY");
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Console.WriteLine(await http.GetStringAsync(url));
["Authorization: Bearer TU_API_KEY", "Accept: application/json"],
CURLOPT_RETURNTRANSFER => true,
]);
echo curl_exec($ch);
curl_close($ch);
var cliente = HttpClient.newHttpClient();
var pedido = HttpRequest.newBuilder()
.uri(URI.create("https://orvixjoin.com/api/v1/afp/comisiones?year=2025&month=8"))
.header("Authorization", "Bearer TU_API_KEY")
.header("Accept", "application/json")
.GET().build();
System.out.println(cliente.send(pedido, HttpResponse.BodyHandlers.ofString()).body());
const respuesta = await fetch("https://orvixjoin.com/api/v1/afp/comisiones?year=2025&month=8", {
headers: { Authorization: "Bearer TU_API_KEY", Accept: "application/json" },
});
console.log(await 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
| Nombre | Valor |
|---|---|
| Content-Type | application/json |
| Authorization | Bearer <API_KEY> |
Querystring
| Nombre | Tipo | Descripción |
|---|---|---|
| numero | string | RUC de 11 dígitos. También se acepta ruc. |
Body
La respuesta trae ruc y establecimientos, una lista. Cada local tiene estos campos.
| Nombre | Tipo | Descripción |
|---|---|---|
| numero_registro | string | Número de registro del local. |
| categoria | string | Categoría del establecimiento. |
| nombre_comercial | string | Nombre comercial. |
| razon_social | string | Razón social. |
| ruc | string | RUC del titular. |
| direccion | string | Dirección del local. |
| ubigeo | string | Ubigeo. |
| situacion | string | Situación del registro. |
| empadronado | string | SI o NO. |
| lugar_registro | string | Lugar donde se registró. |
| fecha_inicio | string | Fecha de inicio. |
| tipo | string | Tipo de establecimiento. |
| motivo | string | Motivo del registro. |
| solicitante | string | Solicitante. |
| horario | string | Horario de atención. |
| representantes | list | Ficha Ver. Cada ítem trae cargo y nombre. |
| personal | list | Ficha Ver. Cada ítem trae cargo, nombre y horario. |
| actividades | list | Ficha Ver. Cada ítem trae actividad. |
| almacenes | list | Almacenes del local. Puede venir vacía. |
| plantas | list | Plantas. Puede venir vacía. |
| tipos_producto | list | Cada ítem trae tipo_producto y grupo_producto. |
| fuente | string | Siempre DIGEMID. |
Response
{
"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())
using System.Net.Http.Headers;
var url = "https://orvixjoin.com/api/v1/digemid?numero=10715929746";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "TU_API_KEY");
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Console.WriteLine(await http.GetStringAsync(url));
["Authorization: Bearer TU_API_KEY", "Accept: application/json"],
CURLOPT_RETURNTRANSFER => true,
]);
echo curl_exec($ch);
curl_close($ch);
var cliente = HttpClient.newHttpClient();
var pedido = HttpRequest.newBuilder()
.uri(URI.create("https://orvixjoin.com/api/v1/digemid?numero=10715929746"))
.header("Authorization", "Bearer TU_API_KEY")
.header("Accept", "application/json")
.GET().build();
System.out.println(cliente.send(pedido, HttpResponse.BodyHandlers.ofString()).body());
const respuesta = await fetch("https://orvixjoin.com/api/v1/digemid?numero=10715929746", {
headers: { Authorization: "Bearer TU_API_KEY", Accept: "application/json" },
});
console.log(await 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
| Nombre | Tipo | Descripción |
|---|---|---|
| registro | string | Número de registro sanitario. También se acepta numero. |
| nombre | string | Nombre del producto. También se acepta producto. |
Body
La respuesta trae productos. Cada uno es una presentación del catálogo.
| Nombre | Tipo | Descripción |
|---|---|---|
| codigo | string | Código DIGEMID del producto. |
| nombre | string | Nombre del producto. |
| registro_sanitario | string | Número de registro sanitario. |
| concentracion | string | Concentración. |
| forma_farmaceutica | string | Forma farmacéutica. |
| presentacion | string | Presentación. |
| fraccion | string | Fracción. |
| titular | string | Titular del registro. |
| fabricante | string | Fabricante. |
| principio_activo | string | Principio activo. |
| rubro | string | Rubro. |
| situacion | string | Situación en el catálogo, por ejemplo ACT. |
| fuente | string | Siempre 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())
using System.Net.Http.Headers;
var url = "https://orvixjoin.com/api/v1/digemid/productos?registro=EE02436";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "TU_API_KEY");
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Console.WriteLine(await http.GetStringAsync(url));
["Authorization: Bearer TU_API_KEY", "Accept: application/json"],
CURLOPT_RETURNTRANSFER => true,
]);
echo curl_exec($ch);
curl_close($ch);
var cliente = HttpClient.newHttpClient();
var pedido = HttpRequest.newBuilder()
.uri(URI.create("https://orvixjoin.com/api/v1/digemid/productos?registro=EE02436"))
.header("Authorization", "Bearer TU_API_KEY")
.header("Accept", "application/json")
.GET().build();
System.out.println(cliente.send(pedido, HttpResponse.BodyHandlers.ofString()).body());
const respuesta = await fetch("https://orvixjoin.com/api/v1/digemid/productos?registro=EE02436", {
headers: { Authorization: "Bearer TU_API_KEY", Accept: "application/json" },
});
console.log(await 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
| Nombre | Tipo | Descripción |
|---|---|---|
| ruc | string | RUC de la empresa. También se acepta numero. |
| registro | string | Número de registro sanitario. |
| producto | string | Texto del producto. También se acepta nombre. |
| empresa | string | Razón social del titular. |
Body
La respuesta trae alimentos. Cada fila es un registro del listado.
| Nombre | Tipo | Descripción |
|---|---|---|
| ruc | string | RUC de la empresa. |
| empresa | string | Razón social. |
| registro_sanitario | string | Número de registro sanitario. |
| producto | string | Denominación, marca y presentaciones, como las publica DIGESA. |
| estado | string | Vigente o Vencido, según la fecha de vencimiento. |
| fecha_vencimiento | string | YYYY-MM-DD. Vacío si el listado no trae fecha. |
| certificado | string | Número de certificado. |
| expediente | string | Expediente. |
| direccion | string | Dirección de la empresa. |
| departamento | string | Departamento del establecimiento. |
| establecimiento | string | Nombre del establecimiento, si el listado lo trae. |
| fuente | string | Siempre 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())
using System.Net.Http.Headers;
var url = "https://orvixjoin.com/api/v1/digesa/alimentos?ruc=20515108794";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "TU_API_KEY");
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Console.WriteLine(await http.GetStringAsync(url));
["Authorization: Bearer TU_API_KEY", "Accept: application/json"],
CURLOPT_RETURNTRANSFER => true,
]);
echo curl_exec($ch);
curl_close($ch);
var cliente = HttpClient.newHttpClient();
var pedido = HttpRequest.newBuilder()
.uri(URI.create("https://orvixjoin.com/api/v1/digesa/alimentos?ruc=20515108794"))
.header("Authorization", "Bearer TU_API_KEY")
.header("Accept", "application/json")
.GET().build();
System.out.println(cliente.send(pedido, HttpResponse.BodyHandlers.ofString()).body());
const respuesta = await fetch("https://orvixjoin.com/api/v1/digesa/alimentos?ruc=20515108794", {
headers: { Authorization: "Bearer TU_API_KEY", Accept: "application/json" },
});
console.log(await 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
| Nombre | Valor |
|---|---|
| Content-Type | application/json |
| Authorization | Bearer <API_KEY> |
Body
| Nombre | Tipo | Descripción |
|---|---|---|
| tipo | string | 01, 03, 07, 08 o 09. |
| serie | string | 4 caracteres. F factura, B boleta, T guía. La nota empieza con F o B. |
| numero | number | Correlativo. |
| fecha | string | YYYY-MM-DD. Si se omite, el día de hoy. |
| moneda | string | PEN o USD. En la guía no se usa. |
| cliente | object | tipo_documento, numero, nombre, direccion y, si aplica, contacto. En la guía el mismo objeto puede llamarse destinatario. |
| items | array | codigo, 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. |
| contacto | string | Sale en el PDF. También puede ir dentro de cliente. |
| orden_compra | string | Orden de compra del PDF. |
| vendedor | string | Si se omite, el PDF muestra un guion. |
| guia_remision | string | Número de guía, si la factura la tiene. |
| observaciones | string | Texto bajo el monto en letras. |
| condicion | string | Condición de venta. Si se omite, Contado. |
| documento | object | Solo notas. tipo, serie y numero de la factura 01 o la boleta 03 que se modifica. |
| motivo | string | Solo notas. En la nota de crédito, catálogo 09. En la nota de débito, catálogo 10. Si se omite, 01. |
| motivo_descripcion | string | En 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. |
| partida | object | Solo guía. ubigeo y direccion de salida. |
| llegada | object | Solo guía. ubigeo y direccion de llegada. |
| peso | number | Solo guía. Kilos. |
| modalidad | string | Solo guía. 01 transporte público, 02 privado. |
| motivo_traslado | string | Solo guía. Catálogo 20. Si se omite, 01. |
| fecha_traslado | string | Solo guía. YYYY-MM-DD. Si se omite, la fecha del comprobante. |
| conductor | object | Solo guía con modalidad 02. tipo_documento, numero, nombre, apellidos y licencia. |
| vehiculo | object | Solo guía. placa. |
| observacion | string | Solo 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ódigo | Motivo |
|---|---|
| 01 | Venta |
| 02 | Compra |
| 03 | Venta con entrega a terceros |
| 04 | Traslado entre establecimientos de la misma empresa |
| 05 | Consignación |
| 06 | Devolución |
| 07 | Recojo de bienes transformados |
| 08 | Importación |
| 09 | Exportación |
| 13 | Otros |
| 14 | Venta sujeta a confirmación del comprador |
| 17 | Traslado de bienes para transformación |
| 18 | Traslado emisor itinerante CP |
| 19 | Traslado 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.
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"
}
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"
}
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.
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.
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 respuesta | Columna | Tipo | Qué es |
|---|---|---|---|
| xml_firmado | xml_firmado | bytea | XML firmado, texto ISO-8859-1. Se convierte a bytes al grabar. |
| cdr_base64 | cdr | bytea | ZIP del CDR que devolvió SUNAT. Viene en base64. |
| pdf_base64 | bytea | Representació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),
)
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using Npgsql;
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "TU_API_KEY");
async Task Post(string url, string json)
{
var cuerpo = new StringContent(json, Encoding.UTF8, "application/json");
using var doc = JsonDocument.Parse(await (await http.PostAsync(url, cuerpo)).Content.ReadAsStringAsync());
return doc.RootElement.Clone();
}
var 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"}]}
""";
var cpe = await Post("https://orvixjoin.com/api/v1/cpe/xml", factura);
var id = cpe.GetProperty("id").GetString();
cpe = await Post("https://orvixjoin.com/api/v1/cpe/firmar", $"{{\"id\":\"{id}\"}}");
cpe = await Post("https://orvixjoin.com/api/v1/cpe/enviar", $"{{\"id\":\"{id}\"}}");
if (cpe.GetProperty("estado").GetString() != "aceptado")
throw new Exception(cpe.GetProperty("mensaje").GetString());
var xml = Encoding.GetEncoding("iso-8859-1").GetBytes(cpe.GetProperty("xml_firmado").GetString()!);
var cdr = Convert.FromBase64String(cpe.GetProperty("cdr_base64").GetString()!);
var pdf = Convert.FromBase64String(cpe.GetProperty("pdf_base64").GetString()!);
await using var con = new NpgsqlConnection("Host=localhost;Username=usuario;Password=clave;Database=tu_base");
await con.OpenAsync();
await using var cmd = new NpgsqlCommand("""
INSERT INTO comprobante_electronico
(codigo, tipo, serie, numero, estado, codigo_respuesta, mensaje, xml_firmado, cdr, pdf)
VALUES (@codigo, @tipo, @serie, @numero, @estado, @codigo_respuesta, @mensaje, @xml, @cdr, @pdf)
ON CONFLICT (codigo) DO UPDATE SET
estado = EXCLUDED.estado, xml_firmado = EXCLUDED.xml_firmado, cdr = EXCLUDED.cdr, pdf = EXCLUDED.pdf
""", con);
cmd.Parameters.AddWithValue("codigo", id);
cmd.Parameters.AddWithValue("tipo", cpe.GetProperty("tipo").GetString());
cmd.Parameters.AddWithValue("serie", cpe.GetProperty("serie").GetString());
cmd.Parameters.AddWithValue("numero", cpe.GetProperty("numero").GetString());
cmd.Parameters.AddWithValue("estado", cpe.GetProperty("estado").GetString());
cmd.Parameters.AddWithValue("codigo_respuesta", cpe.GetProperty("codigo_respuesta").GetString());
cmd.Parameters.AddWithValue("mensaje", cpe.GetProperty("mensaje").GetString());
cmd.Parameters.AddWithValue("xml", xml);
cmd.Parameters.AddWithValue("cdr", cdr);
cmd.Parameters.AddWithValue("pdf", pdf);
await cmd.ExecuteNonQueryAsync();
true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer TU_API_KEY", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode($cuerpo),
CURLOPT_RETURNTRANSFER => true,
]);
$dato = json_decode(curl_exec($ch), true);
curl_close($ch);
return $dato;
}
$cpe = orvix("https://orvixjoin.com/api/v1/cpe/xml", [
"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 = orvix("https://orvixjoin.com/api/v1/cpe/firmar", ["id" => $cpe["id"]]);
$cpe = orvix("https://orvixjoin.com/api/v1/cpe/enviar", ["id" => $cpe["id"]]);
if ($cpe["estado"] !== "aceptado") {
exit($cpe["mensaje"] ?? $cpe["estado"]);
}
$xml = mb_convert_encoding($cpe["xml_firmado"], "ISO-8859-1", "UTF-8");
$cdr = base64_decode($cpe["cdr_base64"]);
$pdf = base64_decode($cpe["pdf_base64"]);
$pdo = new PDO("pgsql:host=localhost;dbname=tu_base", "usuario", "clave");
$sql = "INSERT INTO comprobante_electronico
(codigo, tipo, serie, numero, estado, codigo_respuesta, mensaje, xml_firmado, cdr, pdf)
VALUES (?, ?, ?, ?, ?, ?, ?, decode(?, 'hex'), decode(?, 'hex'), decode(?, 'hex'))
ON CONFLICT (codigo) DO UPDATE SET
estado = EXCLUDED.estado, xml_firmado = EXCLUDED.xml_firmado, cdr = EXCLUDED.cdr, pdf = EXCLUDED.pdf";
$pdo->prepare($sql)->execute([
$cpe["id"], $cpe["tipo"], $cpe["serie"], $cpe["numero"], $cpe["estado"],
$cpe["codigo_respuesta"], $cpe["mensaje"], bin2hex($xml), bin2hex($cdr), bin2hex($pdf),
]);
public class EmitirFactura {
static final HttpClient cliente = HttpClient.newHttpClient();
static final ObjectMapper mapper = new ObjectMapper();
static JsonNode post(String url, String json) throws Exception {
var pedido = HttpRequest.newBuilder().uri(URI.create(url))
.header("Authorization", "Bearer TU_API_KEY")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json)).build();
return mapper.readTree(cliente.send(pedido, HttpResponse.BodyHandlers.ofString()).body());
}
public static void main(String[] args) throws Exception {
var 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"}]}
""";
var cpe = post("https://orvixjoin.com/api/v1/cpe/xml", factura);
var id = cpe.get("id").asText();
cpe = post("https://orvixjoin.com/api/v1/cpe/firmar", "{\"id\":\"" + id + "\"}");
cpe = post("https://orvixjoin.com/api/v1/cpe/enviar", "{\"id\":\"" + id + "\"}");
if (!"aceptado".equals(cpe.get("estado").asText()))
throw new IllegalStateException(cpe.get("mensaje").asText());
var xml = cpe.get("xml_firmado").asText().getBytes(StandardCharsets.ISO_8859_1);
var cdr = Base64.getDecoder().decode(cpe.get("cdr_base64").asText());
var pdf = Base64.getDecoder().decode(cpe.get("pdf_base64").asText());
try (var con = DriverManager.getConnection("jdbc:postgresql://localhost/tu_base", "usuario", "clave");
var cmd = con.prepareStatement("""
INSERT INTO comprobante_electronico
(codigo, tipo, serie, numero, estado, codigo_respuesta, mensaje, xml_firmado, cdr, pdf)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (codigo) DO UPDATE SET
estado = EXCLUDED.estado, xml_firmado = EXCLUDED.xml_firmado, cdr = EXCLUDED.cdr, pdf = EXCLUDED.pdf
""")) {
cmd.setString(1, id);
cmd.setString(2, cpe.get("tipo").asText());
cmd.setString(3, cpe.get("serie").asText());
cmd.setString(4, cpe.get("numero").asText());
cmd.setString(5, cpe.get("estado").asText());
cmd.setString(6, cpe.get("codigo_respuesta").asText());
cmd.setString(7, cpe.get("mensaje").asText());
cmd.setBytes(8, xml);
cmd.setBytes(9, cdr);
cmd.setBytes(10, pdf);
cmd.executeUpdate();
}
}
}
async function orvix(url, cuerpo) {
const respuesta = await fetch(url, {
method: "POST",
headers: { Authorization: "Bearer TU_API_KEY", "Content-Type": "application/json" },
body: JSON.stringify(cuerpo),
});
if (!respuesta.ok) throw new Error(await respuesta.text());
return respuesta.json();
}
let cpe = await orvix("https://orvixjoin.com/api/v1/cpe/xml", {
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 = await orvix("https://orvixjoin.com/api/v1/cpe/firmar", { id: cpe.id });
cpe = await orvix("https://orvixjoin.com/api/v1/cpe/enviar", { id: cpe.id });
if (cpe.estado !== "aceptado") throw new Error(cpe.mensaje || cpe.estado);
const xml = Buffer.from(cpe.xml_firmado, "latin1");
const cdr = Buffer.from(cpe.cdr_base64, "base64");
const pdf = Buffer.from(cpe.pdf_base64, "base64");
const { Client } = require("pg");
const con = new Client({ connectionString: "postgresql://usuario:clave@localhost/tu_base" });
await con.connect();
await con.query(
`INSERT INTO comprobante_electronico
(codigo, tipo, serie, numero, estado, codigo_respuesta, mensaje, xml_firmado, cdr, pdf)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)
ON CONFLICT (codigo) DO UPDATE SET
estado = EXCLUDED.estado, 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, cdr, pdf]
);
await con.end();
Errores
Si la llamada no prospera, el cuerpo es {"error": "mensaje"}.
| Código | Cuándo |
|---|---|
| 400 | El 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). |
| 401 | Falta la API key o no es válida. |
| 403 | La suscripción está vencida, o la cuenta no tiene el plan de facturación electrónica. |
| 404 | No hay dato para esa consulta. |
| 429 | Se alcanzó el cupo. En el plan gratuito son 100 usos en total. En los planes de pago, el tope del mes. |