Consultar el RCV del SII con Python — Tutorial Paso a Paso
Tutorial práctico para consultar el Registro de Compras y Ventas del SII desde Python usando la API de BaseAPI. Código funcional, manejo de errores y exportación a Excel.
Si trabajas con datos tributarios en Chile, en algún momento vas a necesitar el Registro de Compras y Ventas de una empresa. Quizás estás construyendo un sistema contable, automatizando el cierre mensual de IVA, o simplemente necesitas cruzar las facturas de un cliente con lo que reporta el SII.
Este tutorial te muestra cómo hacerlo desde Python, paso a paso, usando la API de BaseAPI. Al terminar vas a tener un script funcional que consulta el RCV de cualquier contribuyente y exporta los resultados a Excel.
Antes de empezar
Necesitas tres cosas:
- Python 3.8 o superior instalado en tu máquina.
- Una API key de BaseAPI. Si no tienes una, crea tu cuenta y genera la key desde el panel de control.
- Las credenciales SII del contribuyente que vas a consultar: su RUT y clave tributaria.
Instala las dependencias que vamos a usar:
pip install requests openpyxl
requests para las llamadas HTTP y openpyxl para exportar a Excel al final.
Cómo funciona la API del RCV
El endpoint de RCV recibe tres datos: el RUT del contribuyente, su clave SII y el período a consultar. Retorna un JSON con todos los documentos tributarios registrados en ese período — facturas, notas de crédito, notas de débito y todo lo que el SII tenga en el registro.
La URL tiene esta estructura:
POST https://api.baseapi.cl/api/v1/sii/rcv/{periodo}/{tipo}
Donde {periodo} es el mes en formato YYYY-MM (por ejemplo, 2026-03) y {tipo} es compra o venta.
La autenticación se hace con el header X-API-Key usando tu API key de BaseAPI. Las credenciales del contribuyente van en el cuerpo de la solicitud.
Paso 1 — Tu primera consulta
Empecemos con lo más simple: consultar las compras de un mes.
import requests
API_KEY = "tu_api_key_de_baseapi"
BASE_URL = "https://api.baseapi.cl/api/v1"
def consultar_rcv(rut, password, periodo, tipo="compra"):
"""Consulta el RCV de un contribuyente para un período dado."""
url = f"{BASE_URL}/sii/rcv/{periodo}/{tipo}"
response = requests.post(url,
headers={
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
json={
"rut": rut,
"password": password,
},
)
response.raise_for_status()
return response.json()
# Ejemplo de uso
resultado = consultar_rcv(
rut="76543210-9",
password="mi_clave_sii",
periodo="2026-03",
tipo="compra",
)
datos = resultado["data"]["datos"]
total = resultado["data"]["totalRegistros"]
print(f"Se encontraron {total} documentos de compra.")
Si todo sale bien, datos contiene una lista de diccionarios donde cada uno es un documento tributario. Así se ve un registro:
{
"Nro": "1",
"Tipo Doc": "33",
"Tipo Compra": "DEL GIRO",
"RUT Proveedor": "77000000-0",
"Razon Social": "INVERSIONES ALPHA SPA",
"Folio": "100",
"Fecha Docto": "15/03/2026",
"Fecha Recepcion": "16/03/2026",
"Monto Exento": "0",
"Monto Neto": "350000",
"Monto IVA": "66500",
"Monto total": "416500"
}
Los nombres de los campos son los mismos que usa el SII. No los renombramos para que puedas comparar directamente con lo que ves en el portal.
Paso 2 — Manejo de errores
En producción no puedes asumir que todo va a funcionar. La clave SII puede estar mal, el plan puede haber alcanzado su límite, o el SII puede estar en mantenimiento. Agreguemos manejo de errores:
def consultar_rcv(rut, password, periodo, tipo="compra"):
"""Consulta el RCV con manejo robusto de errores."""
url = f"{BASE_URL}/sii/rcv/{periodo}/{tipo}"
try:
response = requests.post(url,
headers={
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
json={
"rut": rut,
"password": password,
},
timeout=30,
)
except requests.ConnectionError:
print("No se pudo conectar con la API. Verifica tu conexión.")
return None
except requests.Timeout:
print("La consulta tardó demasiado. Intenta de nuevo.")
return None
if response.status_code == 401:
print("Credenciales SII incorrectas. Verifica el RUT y la clave.")
return None
if response.status_code == 403:
error = response.json().get("error", "Sin acceso")
print(f"Acceso denegado: {error}")
return None
if response.status_code == 429:
print("Límite de consultas alcanzado. Revisa tu plan en baseapi.cl/precios")
return None
if not response.ok:
print(f"Error inesperado ({response.status_code}): {response.text}")
return None
return response.json()
Los códigos HTTP que vas a encontrar son los estándar:
| Código | Significado |
|---|---|
| 200 | Consulta exitosa |
| 401 | RUT o clave SII incorrectos |
| 403 | Sin acceso al endpoint (plan no incluye RCV) |
| 429 | Límite de consultas del plan alcanzado |
| 500 | Error en el servidor (generalmente por mantenimiento del SII) |
Paso 3 — Consultar compras y ventas del mismo período
Para tener el panorama completo de un mes necesitas ambos registros. Podemos hacer las dos consultas de forma secuencial:
def rcv_completo(rut, password, periodo):
"""Consulta compras y ventas de un período."""
compras = consultar_rcv(rut, password, periodo, tipo="compra")
ventas = consultar_rcv(rut, password, periodo, tipo="venta")
if compras and ventas:
docs_compra = compras["data"]["datos"]
docs_venta = ventas["data"]["datos"]
total_compras = sum(int(d.get("Monto total", d.get("Monto Total", 0))) for d in docs_compra)
total_ventas = sum(int(d.get("Monto total", d.get("Monto Total", 0))) for d in docs_venta)
iva_compras = sum(int(d.get("Monto IVA", 0)) for d in docs_compra)
iva_ventas = sum(int(d.get("Monto IVA", 0)) for d in docs_venta)
print(f"Compras: {len(docs_compra)} docs — Total ${total_compras:,.0f}")
print(f"Ventas: {len(docs_venta)} docs — Total ${total_ventas:,.0f}")
print(f"IVA CF: ${iva_compras:,.0f}")
print(f"IVA DF: ${iva_ventas:,.0f}")
print(f"IVA neto: ${iva_ventas - iva_compras:,.0f}")
return docs_compra, docs_venta
return [], []
El IVA neto (débito fiscal menos crédito fiscal) es la base del Formulario 29. Este cálculo simple ya te da una vista rápida de la posición de IVA del contribuyente.
Paso 4 — Consultar múltiples empresas
Si eres contador y manejas varias empresas, el siguiente paso natural es iterar sobre todas:
empresas = [
{"rut": "76543210-9", "password": "clave1", "nombre": "Empresa A"},
{"rut": "77888999-0", "password": "clave2", "nombre": "Empresa B"},
{"rut": "78111222-3", "password": "clave3", "nombre": "Empresa C"},
]
periodo = "2026-03"
for empresa in empresas:
print(f"\n{'='*50}")
print(f"{empresa['nombre']} ({empresa['rut']})")
print(f"{'='*50}")
rcv_completo(empresa["rut"], empresa["password"], periodo)
Una nota sobre seguridad: nunca dejes las claves SII en texto plano dentro del código fuente. En un entorno real, cárgalas desde variables de entorno o un gestor de secretos:
import os
API_KEY = os.environ["BASEAPI_API_KEY"]
# O desde un archivo .env con python-dotenv
from dotenv import load_dotenv
load_dotenv()
Paso 5 — Exportar a Excel
El JSON estructurado que retorna la API es conveniente para procesamiento, pero a la hora de entregar un reporte a un cliente o revisar los datos visualmente, un archivo Excel sigue siendo el formato preferido. Usamos openpyxl para generar el archivo:
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, numbers
def exportar_excel(docs_compra, docs_venta, periodo, nombre_empresa):
"""Exporta compras y ventas a un archivo Excel con formato."""
wb = Workbook()
# --- Hoja de Compras ---
ws_compras = wb.active
ws_compras.title = "Compras"
columnas = ["Folio", "Tipo Doc", "Fecha Docto", "RUT Proveedor",
"Razon Social", "Monto Neto", "Monto IVA", "Monto total"]
# Encabezados
header_fill = PatternFill(start_color="1a56db", end_color="1a56db", fill_type="solid")
header_font = Font(bold=True, color="FFFFFF", size=11)
for col, nombre in enumerate(columnas, 1):
cell = ws_compras.cell(row=1, column=col, value=nombre)
cell.fill = header_fill
cell.font = header_font
cell.alignment = Alignment(horizontal="center")
# Datos
for row, doc in enumerate(docs_compra, 2):
for col, campo in enumerate(columnas, 1):
valor = doc.get(campo, "")
# Convertir montos a número
if "Monto" in campo or "IVA" in campo:
try:
valor = int(valor)
except (ValueError, TypeError):
pass
ws_compras.cell(row=row, column=col, value=valor)
# Ajustar ancho de columnas
for col in range(1, len(columnas) + 1):
ws_compras.column_dimensions[chr(64 + col)].width = 18
# --- Hoja de Ventas ---
ws_ventas = wb.create_sheet("Ventas")
columnas_venta = ["Folio", "Tipo Doc", "Fecha Docto", "Rut cliente",
"Razon Social", "Monto Neto", "Monto IVA", "Monto total"]
for col, nombre in enumerate(columnas_venta, 1):
cell = ws_ventas.cell(row=1, column=col, value=nombre)
cell.fill = header_fill
cell.font = header_font
cell.alignment = Alignment(horizontal="center")
for row, doc in enumerate(docs_venta, 2):
for col, campo in enumerate(columnas_venta, 1):
valor = doc.get(campo, "")
if "Monto" in campo or "IVA" in campo:
try:
valor = int(valor)
except (ValueError, TypeError):
pass
ws_ventas.cell(row=row, column=col, value=valor)
for col in range(1, len(columnas_venta) + 1):
ws_ventas.column_dimensions[chr(64 + col)].width = 18
# Guardar
filename = f"rcv-{periodo}-{nombre_empresa.replace(' ', '_')}.xlsx"
wb.save(filename)
print(f"Archivo guardado: {filename}")
return filename
Con esto generas un Excel con dos hojas (Compras y Ventas), encabezados con formato y columnas de montos como números para que las fórmulas de Excel funcionen directamente.
Paso 6 — Consulta anual
Cuando necesitas el RCV de un año completo — por ejemplo, para preparar la declaración de renta o hacer un análisis financiero — puedes usar el endpoint anual en lugar de hacer 12 consultas mensuales:
def consultar_rcv_anual(rut, password, year, tipo="compra"):
"""Consulta el RCV consolidado de un año completo.
Consume 3 consultas del plan.
"""
url = f"{BASE_URL}/sii/rcv/anual/{year}/{tipo}"
response = requests.post(url,
headers={
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
json={
"rut": rut,
"password": password,
},
timeout=60, # la consulta anual puede tardar más
)
response.raise_for_status()
data = response.json()
# Consolidar documentos de todos los meses
todos_los_docs = []
for mes in data["data"]["meses"]:
if mes["status"] == "success" and mes.get("datos"):
todos_los_docs.extend(mes["datos"])
resumen = data["data"]["resumen"]
print(f"RCV Anual {year} ({tipo}): {resumen['totalRegistros']} documentos en {resumen['mesesConDatos']} meses")
return todos_los_docs
La respuesta del endpoint anual agrupa los documentos por mes. Cada mes tiene un status que puede ser "success" o "empty". Los meses vacíos simplemente no tienen documentos — es normal en empresas que no operan todo el año.
Ten en cuenta que esta consulta consume 3 llamadas de tu plan porque internamente consulta todos los meses.
El script completo
Acá va todo junto, listo para copiar y adaptar:
"""
rcv_baseapi.py — Descarga el RCV del SII usando BaseAPI.
Uso: python rcv_baseapi.py
"""
import os
import requests
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
API_KEY = os.environ.get("BASEAPI_API_KEY", "tu_api_key")
BASE_URL = "https://api.baseapi.cl/api/v1"
def consultar_rcv(rut, password, periodo, tipo="compra"):
url = f"{BASE_URL}/sii/rcv/{periodo}/{tipo}"
try:
response = requests.post(url,
headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
json={"rut": rut, "password": password},
timeout=30,
)
except (requests.ConnectionError, requests.Timeout) as e:
print(f"Error de conexión: {e}")
return None
if response.status_code == 401:
print("Credenciales SII incorrectas.")
return None
if response.status_code == 429:
print("Límite de consultas alcanzado.")
return None
if not response.ok:
print(f"Error {response.status_code}: {response.text}")
return None
return response.json()
def exportar_excel(docs_compra, docs_venta, periodo, nombre):
wb = Workbook()
header_fill = PatternFill(start_color="1a56db", end_color="1a56db", fill_type="solid")
header_font = Font(bold=True, color="FFFFFF")
for sheet_name, docs, cols in [
("Compras", docs_compra,
["Folio", "Tipo Doc", "Fecha Docto", "RUT Proveedor",
"Razon Social", "Monto Neto", "Monto IVA", "Monto total"]),
("Ventas", docs_venta,
["Folio", "Tipo Doc", "Fecha Docto", "Rut cliente",
"Razon Social", "Monto Neto", "Monto IVA", "Monto total"]),
]:
if sheet_name == "Compras":
ws = wb.active
ws.title = sheet_name
else:
ws = wb.create_sheet(sheet_name)
for c, name in enumerate(cols, 1):
cell = ws.cell(row=1, column=c, value=name)
cell.fill = header_fill
cell.font = header_font
cell.alignment = Alignment(horizontal="center")
for r, doc in enumerate(docs, 2):
for c, campo in enumerate(cols, 1):
valor = doc.get(campo, "")
if "Monto" in campo or "IVA" in campo:
try:
valor = int(valor)
except (ValueError, TypeError):
pass
ws.cell(row=r, column=c, value=valor)
for c in range(1, len(cols) + 1):
ws.column_dimensions[chr(64 + c)].width = 18
filename = f"rcv-{periodo}-{nombre.replace(' ', '_')}.xlsx"
wb.save(filename)
print(f"Guardado: {filename}")
def main():
empresas = [
{"rut": "76543210-9", "password": "clave_sii", "nombre": "Mi Empresa SpA"},
]
periodo = "2026-03"
for emp in empresas:
print(f"\n--- {emp['nombre']} ({emp['rut']}) ---")
res_compras = consultar_rcv(emp["rut"], emp["password"], periodo, "compra")
res_ventas = consultar_rcv(emp["rut"], emp["password"], periodo, "venta")
if not res_compras or not res_ventas:
print("No se pudieron obtener los datos. Saltando.")
continue
compras = res_compras["data"]["datos"]
ventas = res_ventas["data"]["datos"]
total_c = sum(int(d.get("Monto total", d.get("Monto Total", 0))) for d in compras)
total_v = sum(int(d.get("Monto total", d.get("Monto Total", 0))) for d in ventas)
print(f"Compras: {len(compras)} docs — ${total_c:,.0f}")
print(f"Ventas: {len(ventas)} docs — ${total_v:,.0f}")
exportar_excel(compras, ventas, periodo, emp["nombre"])
if __name__ == "__main__":
main()
Para ejecutarlo:
export BASEAPI_API_KEY="sk_live_..."
python rcv_baseapi.py
Tipos de documento que encontrarás en el RCV
El campo Tipo Doc usa los códigos oficiales del SII. Los más comunes:
| Código | Documento |
|---|---|
| 33 | Factura electrónica |
| 34 | Factura no afecta o exenta electrónica |
| 43 | Liquidación factura electrónica |
| 46 | Factura de compra electrónica |
| 56 | Nota de débito electrónica |
| 61 | Nota de crédito electrónica |
Para la referencia completa, revisa nuestra guía de tipos de DTE en Chile.
Diferencias entre compras y ventas en la respuesta
La mayoría de los campos son iguales en compras y ventas — Monto IVA, Monto total, Monto Neto, etc. Las diferencias están en los campos que identifican a la contraparte y la clasificación del documento:
| Campo | En compras | En ventas |
|---|---|---|
| Contraparte (RUT) | RUT Proveedor |
Rut cliente |
| Clasificación | Tipo Compra |
Tipo Venta |
Los campos de montos (Monto IVA, Monto total, Monto Neto, Monto Exento) son los mismos en ambos registros.
Próximos pasos
Con el RCV automatizado, hay varias direcciones naturales para seguir:
- Automatizar la conciliación de IVA cruzando compras y ventas para calcular el Formulario 29.
- Consultar DTEs emitidos y recibidos para obtener el detalle completo de cada documento, incluyendo el PDF y XML.
- Integrar con tu ERP siguiendo nuestra guía de integración SII con ERP.
- Consultar otros datos del contribuyente como la situación tributaria, giro comercial y razón social con el endpoint de información del contribuyente.
Toda la documentación técnica de los endpoints está disponible en el playground del panel de BaseAPI, donde puedes probar cada consulta antes de integrarla en tu código.
Preguntas frecuentes
¿Necesito instalar algo especial para usar la API desde Python?
Solo la librería requests, que se instala con pip install requests. Para exportar a Excel necesitas openpyxl. No hay SDK propietario ni dependencias complejas.
¿Puedo consultar el RCV de una empresa que no es mía?
Necesitas el RUT y la clave SII del contribuyente. Si eres contador o representante con autorización, puedes consultar legítimamente las empresas de tus clientes con sus credenciales.
¿La API retorna los mismos datos que el portal del SII?
Sí. Los campos del RCV se retornan con sus nombres originales tal como aparecen en el SII: Folio, Tipo Doc, RUT Proveedor, Razon Social, Monto Neto, Monto IVA, Monto total, entre otros.
¿Cuántas consultas puedo hacer con el plan gratuito?
El plan gratuito incluye consultas limitadas para que puedas probar la integración. Para volúmenes de producción, revisa los planes disponibles.
¿Puedo consultar un año completo de una sola vez?
Sí. El endpoint /api/v1/sii/rcv/anual/{year}/{tipo} retorna los 12 meses consolidados en una sola llamada. Consume 3 consultas del plan.
¿Los datos del RCV incluyen notas de crédito y débito?
Sí. El RCV incluye todos los tipos de DTE registrados por el SII: facturas electrónicas (código 33), facturas exentas (34), notas de débito (56), notas de crédito (61), entre otros.
Automatiza tu gestion tributaria
Activa todos los endpoints gratis.