Esta guía es para el equipo técnico de una entidad (caja, fintech o IPS) que integra su ERP con el módulo Gestión Administrativa de Compense: talento humano, nómina, contabilidad y tesorería. Todo es server-to-server con la llave de la entidad; no hay usuario final involucrado. Los ejemplos usan el ERP de Caja Horizonte, que identifica sus documentos con referencias HORIZONTE-ERP-….

Superficie de la empresa, no del afiliado

Aquí los recursos son de la entidad: colaboradores, novedades, comprobantes, terceros, recaudos y extractos. Las rutas cuelgan de /v1/gateway/admin/… y, en los SDK, de client.admin (en .NET client.Admin, en Go client.Admin) con cuatro grupos: hr, payroll, accounting y treasury (en .NET Hr, Payroll, Accounting, Treasury; en Go HR, Payroll, Accounting, Treasury). La entidad debe tener contratado el módulo; si no, toda llamada responde 403 con «La entidad no tiene contratado el módulo Gestión Administrativa.».

Qué entra por API y qué se queda en el panel #

ÁreaPor APISe queda en el panel (persona con rol)
Talento humanoBuscar y leer colaboradores y contratos; alta o actualización de colaboradores (upsert por documento); registrar incapacidades, licencias, permisos y vacacionesAprobar o rechazar novedades, contratos, enlazar usuarios del panel, cambiar la cuenta bancaria de un colaborador
NóminaCargar novedades (horas extra, recargos, bonificaciones, deducciones), una o en lote; leer periodos, desprendibles y el estado de la nómina electrónicaCalcular, aprobar, revertir y contabilizar la nómina; generar y transmitir la nómina electrónica a la DIAN
ContabilidadCrear comprobantes en borrador; leer comprobantes; alta o actualización de terceros; simular impuestos y retencionesContabilizar, reversar o anular comprobantes; cerrar periodos; plan de cuentas; datos bancarios de terceros
TesoreríaRegistrar recaudos, egresos y cuentas de cobro en borrador; cargar extractos y lanzar el cruce automáticoContabilizar o anular los documentos; cerrar la conciliación; cuentas bancarias

Por qué este corte. Una llave de API no es una persona: no tiene rol, ni segregación de funciones, ni segundo factor. Todo lo que aprueba, liquida o contabiliza —y por lo tanto mueve dinero o cierra libros— exige a alguien con rol en el panel. Lo que tu ERP envía entra como solicitud (novedades de talento humano, en estado SOLICITADA) o como borrador (comprobantes y documentos de tesorería, en estado DRAFT), y queda auditado con el usuario de integración y el canal gateway.

flowchart LR
    ERP[ERP de Caja Horizonte] -->|POST por API| B[Solicitud / borrador<br/>SOLICITADA · DRAFT]
    B --> P{Panel de Compense<br/>persona con rol}
    P -->|aprueba / contabiliza| OK[Aprobado · contabilizado]
    P -->|rechaza / anula| X[Rechazado · anulado]
    B -.->|webhook admin.*| ERP

Cuentas bancarias. El cambio de cuenta bancaria es el vector clásico de fraude por desvío de pagos. Por API se acepta la cuenta de nómina solo en el alta de un colaborador; cualquier cambio posterior, o cualquier dato bancario de un tercero, responde 422 y se hace en el panel, que deja la alerta en la auditoría.

Datos sensibles (Ley 1581 de 2012). Puedes enviar el diagnóstico (CIE-10) de una incapacidad, pero nunca se devuelve por API: las novedades de salud se leen como «Ausencia justificada», con categoría OTRA.

Scopes #

ScopePermite
hr:readColaboradores, contratos y novedades de talento humano (lectura)
hr:writeAlta o actualización de colaboradores; registrar novedades de talento humano
payroll:readNovedades de nómina, periodos, desprendibles y nómina electrónica (lectura)
payroll:writeCargar novedades de nómina
accounting:readComprobantes, terceros y simulador de impuestos
accounting:writeComprobantes en borrador; alta o actualización de terceros
treasury:writeRecaudos, egresos y cuentas de cobro en borrador; extractos y cruce automático

Hay un preset admin-erp con los siete. Los presets read-only y full no los incluyen (son datos de colaboradores y salarios): se otorgan expresamente. payroll:write y treasury:write están en la lista de scopes que exigirán firma de la petición cuando se active esa protección.

Convenciones #

  • Envía Idempotency-Key en todo POST (salvo el simulador de impuestos, que es una consulta). Un reintento con la misma llave devuelve la respuesta original con idempotent_replay: true.
  • Colaboradores y terceros se nombran por id (UUID) o por número de documento; las cuentas bancarias de tesorería, por id o por número de cuenta.
  • Montos en COP como texto decimal ("1500000.00"); fechas AAAA-MM-DD.
  • Las lecturas devuelven el cuerpo del motor; las escrituras, el sobre estándar con "module": "admin".

Campos libres en cada SDK #

Los cuerpos del módulo son amplios (reutilizan los formularios del panel), así que los SDK piden como argumento solo los campos obligatorios y reciben el resto como campos libres, con los nombres del contrato en snake_case:

SDKCómo se pasan los campos libres
PythonArgumentos con nombre adicionales (**fields): upsert_employee(id_number="…", first_name="Laura")
JavaScript / TSObjeto fields: upsertEmployee({ idNumber: "…", fields: { first_name: "Laura" } })
PHPArgumentos con nombre en snake_case (variádicos): upsertEmployee(idNumber: '…', first_name: 'Laura')
.NETDiccionario fields: new Dictionary<string, object?> { ["first_name"] = "Laura" }
GoMapa Fields: Fields: map[string]any{"first_name": "Laura"}

Los valores nulos se descartan, y los SDK convierten a texto decimal los montos de los campos amount, subtotal, opening_balance y closing_balance.

Talento humano #

Alta o actualización de colaboradores #

POST/v1/gateway/admin/hr/employees/ hace upsert por id_number: si el colaborador no existe lo crea (201, created: true); si existe, actualiza solo los campos enviados (200, created: false). Campos: los de la ficha (first_name, middle_name, last_name, second_last_name, birth_date, sex, email_personal, email_work, phone, address, city, department, area_id, position_id, manager_id, status, hire_date, eps_code, pension_fund_code, severance_fund_code…). Se rechazan user_id (NOT_ALLOWED_BY_API) y el cambio de datos bancarios de un colaborador existente (BANK_CHANGE_NOT_ALLOWED).

import os
from compense_gateway import CompenseClient

client = CompenseClient(api_key=os.environ["COMPENSE_API_KEY"], environment="sandbox")

tx = client.admin.hr.upsert_employee(
    id_number="1032456789",
    id_type="CC",
    first_name="Laura",
    last_name="Pérez",
    hire_date="2026-11-01",
    email_work="laura.perez@horizonte.example",
    phone="3001234567",
    bank_name="Banco Uno", bank_account_type="AHORROS", bank_account_number="123456789",  # solo en el alta
    idempotency_key="horizonte-erp-emp-1032456789-v1",
)
print(tx.data["id"], tx.data["created"])

Consultar colaboradores y contratos #

hr.list_employees filtra por q (nombre, documento, código o correo), status (ACTIVO o RETIRADO), document (exacto), area y position. La ficha y los contratos (salario, tipo, estado y fechas) se piden por id o documento.

activos = client.admin.hr.list_employees(status="ACTIVO", area="Tesorería")
ficha = client.admin.hr.get_employee("1000000001")
contratos = client.admin.hr.list_employee_contracts("1000000001")
print(ficha["full_name"], [c["salary"] for c in contratos.results])

Novedades de talento humano #

Incapacidades, licencias, permisos y vacaciones. Siempre quedan SOLICITADA: talento humano las valida en el panel (un approve_now se ignora). Se validan días máximos, cruces con otras novedades y saldo de vacaciones. Tipos (type_code) del catálogo por defecto: VACACIONES, PERMISO_REMUNERADO, CITA_MEDICA, CALAMIDAD, LUTO, MATERNIDAD, PATERNIDAD, INCAPACIDAD_COMUN, INCAPACIDAD_LABORAL, LICENCIA_REMUNERADA, LICENCIA_NO_REMUNERADA, AUSENCIA y SUSPENSION (tu entidad puede tener los suyos).

tx = client.admin.hr.create_novelty(
    employee="1000000001", type_code="INCAPACIDAD_COMUN", start_date="2026-10-20",
    days=3, certificate_number="INC-998877", issuing_entity="EPS del colaborador",
    diagnosis_code="J069",                    # se guarda, pero nunca se devuelve
    idempotency_key="horizonte-erp-inc-2026-0457",
)
print(tx.data["number"], tx.data["status"], tx.data["category"])   # … SOLICITADA OTRA

pendientes = client.admin.hr.list_novelties(status="SOLICITADA", from_="2026-10-01", to="2026-10-31")

Nómina #

Cargar novedades de nómina #

POST/v1/gateway/admin/payroll/novelties/ recibe una novedad (campos en la raíz) o un lote en items (máximo 500, todo o nada: un error señala items[i].campo).

  • Horas extra y recargos (HED, HEN, HRN, HRDDF, HRNDF, HEDDF, HENDF) van con quantity en horas.
  • Bonificaciones, comisiones y otros devengos (BONIFICACION_S, BONIFICACION_NS, COMISION, AUXILIO_NS, OTRO_DEVENGO…) y deducciones (DESCUENTO, ANTICIPO, SINDICATO) van con amount.
  • Los conceptos que calcula el motor (salario, salud, pensión, retención, préstamos…) se rechazan.
  • period_id es opcional: si lo envías, el periodo debe estar ABIERTO o CALCULADO (si no, 409 PERIOD_CLOSED) y la fecha debe caer dentro; sin date se usa el inicio del periodo. Si el periodo ya tenía la prenómina calculada, la respuesta trae un aviso: nómina debe recalcularla.
periodos = client.admin.payroll.list_periods(year=2026, status="ABIERTO")
periodo = periodos.results[0]["id"]

# Una novedad
client.admin.payroll.create_novelties(
    period_id=periodo, employee="1000000001", concept_code="HED", quantity=4, date="2026-10-15",
    idempotency_key="horizonte-erp-he-2026-10-1000000001",
)

# Un lote (todo o nada)
tx = client.admin.payroll.create_novelties(
    period_id=periodo,
    items=[
        {"employee": "1000000001", "concept_code": "BONIFICACION_NS", "amount": "150000", "date": "2026-10-15"},
        {"employee": "1000000002", "concept_code": "DESCUENTO", "amount": "20000", "date": "2026-10-16",
         "description": "Descuento autorizado"},
    ],
    idempotency_key="horizonte-erp-lote-2026-10-A",
)
print(tx.data["created"], tx.data["warnings"])

Periodos, desprendibles y nómina electrónica #

  • payroll.list_periods(status, kind, year) y payroll.get_period(id): estado (ABIERTO, CALCULADO, APROBADO…), fechas y totales.
  • payroll.list_period_payslips(period_id, employee=…): desprendibles del periodo (devengos, deducciones, neto, aportes y provisiones). payroll.get_payslip(id) trae el detalle por concepto.
  • payroll.electronic(year, month, status): estado de la nómina electrónica (DIAN) del mes: proveedor, resumen por estado y documentos. La generación y transmisión se hacen en el panel.
  • payroll.list_novelties(employee, period, status, concept, from, to): lo que ya cargaste.

Cada consulta de desprendibles queda en la bitácora de nómina (quién consultó salarios). El recorrido completo, del cargue de novedades al asiento contable, está en la receta De la nómina a la contabilidad.

Contabilidad #

Comprobantes en borrador #

POST/v1/gateway/admin/accounting/entries/ crea un comprobante siempre en DRAFT (post: true responde 422 POST_NOT_ALLOWED). El contador lo revisa y lo contabiliza en el panel, donde se valida partida doble, cuentas de movimiento, tercero y centro de costo obligatorios, y periodo abierto.

  • external_id (tu id en el ERP) hace la creación idempotente en el motor: reenviar el mismo external_id devuelve el comprobante existente (200, created: false). Si el contador anula el borrador, puedes reenviarlo con el mismo external_id.
  • Los comprobantes del ERP llevan source_module: "gateway": el panel no los edita; se corrigen en el ERP y se reenvían.
  • Líneas: account (código), debit o credit, y opcionales third_party (id o documento), cost_center, description, tax_concept, base_amount, document_ref y due_date.
tx = client.admin.accounting.create_entry(
    date="2026-10-27",
    description="Venta de servicios octubre",
    lines=[
        {"account": "13050501", "debit": "1500000", "third_party": "900123456"},
        {"account": "41750101", "credit": "1500000"},
    ],
    external_id="HORIZONTE-ERP-AS-2026-000123",
    voucher_type="CC",
    reference="FV-1234",
    idempotency_key="horizonte-erp-as-2026-000123",
)
print(tx.data["id"], tx.data["status"], tx.data["total_debit"])   # … DRAFT 1500000.00

Para saber qué ya contabilizó el contador, consulta accounting.list_entries con status, voucher_type, date_from, date_to, external_id, source=gateway, q y offset (páginas de 100 con count total), o accounting.get_entry(id) para las líneas, el hash y el estado.

Terceros #

accounting.upsert_third_party hace upsert por tipo y número de documento. id_type acepta el código DIAN (31, 13, 22…) o el alias (NIT, CC, CE, PA, TI, NUIP…). El dígito de verificación del NIT se calcula (si lo envías y no coincide, 422). Los datos bancarios (bank_*) y user_id_link responden 422 BANK_DATA_NOT_ALLOWED; en las lecturas, la cuenta bancaria sale enmascarada (••••4321).

tx = client.admin.accounting.upsert_third_party(
    id_type="NIT", id_number="900123456",
    business_name="Papelería La Esquina SAS", is_supplier=True,
    email="pagos@laesquina.example", responsibilities=["O-13"],
    idempotency_key="horizonte-erp-tp-900123456",
)
proveedores = client.admin.accounting.list_third_parties(role="supplier", q="esquina", brief=True)
tercero = client.admin.accounting.get_third_party("900123456")

Simulador de impuestos y retenciones #

POST/v1/gateway/admin/accounting/taxes/compute/ es una consulta pura: no escribe, no consume cuota y no lleva Idempotency-Key (los SDK no la envían ni reintentan la llamada). Úsalo para calcular retenciones antes de armar las líneas del comprobante.

calculo = client.admin.accounting.compute_taxes(
    items=[{"concept": "RF_SERVICIOS", "base": "2000000", "iva": "380000"}],
    date="2026-10-27", third_party="900123456",
)
for r in calculo.results:
    print(r["concept"], r["base"], r["rate"], r["amount"])

Tesorería #

Todos los documentos quedan en DRAFT; el tesorero o el contador los contabilizan en el panel.

OperaciónDocumentoCampos principales
POST/v1/gateway/admin/treasury/receipts/Recaudo (recibo de caja)third_party, bank_account, date, amount, method, reference, applications ([{document, amount}] o [{account, document_ref, amount}])
POST/v1/gateway/admin/treasury/payments/Egreso (pago a proveedor)third_party, bank_account, date, amount, method (TRANSFERENCIA, PSE, CHEQUE, EFECTIVO, OTRO), reference, applications ([{invoice, amount}])
POST/v1/gateway/admin/treasury/receivables/Cuenta de cobro (cartera)third_party, issue_date, due_date, description, income_account, subtotal, iva_concept, cost_center
POST/v1/gateway/admin/treasury/statements/Extracto bancariobank_account, opening_balance, closing_balance, date_from, date_to y lines ([{date, description, reference, amount}], montos con signo) o content (CSV)
POST/v1/gateway/admin/treasury/statements/{statement_id}/auto-match/Cruce automáticoSin cuerpo

Recaudo, egreso y cuenta de cobro #

recaudo = client.admin.treasury.create_receipt(
    third_party="80111222", bank_account="0011223344", date="2026-10-27",
    amount="850000", method="TRANSFERENCIA", reference="TRX-998877",
    idempotency_key="horizonte-erp-rc-TRX-998877",
)
egreso = client.admin.treasury.create_payment(
    third_party="900123456", bank_account="0011223344", date="2026-10-28",
    amount="1200000", method="PSE", reference="PSE-4455",
    idempotency_key="horizonte-erp-eg-PSE-4455",
)
cobro = client.admin.treasury.create_receivable(
    third_party="900123456", issue_date="2026-10-27", due_date="2026-11-26",
    description="Arriendo de salón octubre", income_account="41552001", subtotal="500000",
    idempotency_key="horizonte-erp-cxc-2026-10-031",
)
print(recaudo.data["status"], egreso.data["status"], cobro.data["total"])   # DRAFT DRAFT …

Importar el extracto y conciliar #

Carga el extracto del banco con lines (montos con signo: positivo entra, negativo sale) o con content (CSV con columnas fecha/valor o débito/crédito). Luego lanza el cruce automático, que empareja cada movimiento del extracto con un movimiento contabilizado del banco por valor exacto, cercanía de fecha y referencia. El cruce no crea asientos ni cierra la conciliación: el tesorero revisa y cierra en el panel.

sequenceDiagram
    autonumber
    participant E as ERP de Caja Horizonte
    participant G as Gateway Compense
    participant T as Tesorero (panel)
    E->>G: POST treasury/statements/ {bank_account, lines}
    G-->>E: 201 extracto OPEN, líneas UNMATCHED
    G-)E: webhook admin.treasury.statement.imported
    E->>G: POST treasury/statements/{id}/auto-match/
    G-->>E: {matched: N}
    T->>T: revisa pendientes y cierra la conciliación
tx = client.admin.treasury.import_statement(
    bank_account="0011223344",
    opening_balance="0",
    lines=[
        {"date": "2026-10-02", "description": "Consignación", "reference": "TRX-998877", "amount": "850000"},
        {"date": "2026-10-03", "description": "Comisión", "reference": "C1", "amount": "-12000.50"},
    ],
    idempotency_key="horizonte-erp-ext-0011223344-2026-10",
)
extracto = tx.data["id"]

cruce = client.admin.treasury.auto_match_statement(
    extracto, idempotency_key=f"horizonte-erp-cruce-{extracto}")
print(cruce.data["matched"])

Errores frecuentes #

HTTPCódigo del motorCuándo
401—Falta X-API-Key
403—Llave sin el scope, IP no permitida o módulo Gestión Administrativa no contratado
404NOT_FOUNDColaborador, periodo, desprendible, comprobante o tercero inexistente
409PERIOD_CLOSEDEl periodo de nómina está aprobado o revertido
409—Conflicto de idempotencia, o conflicto de negocio (la novedad se cruza con otra)
422VALIDATION_ERRORdetail dice el campo y el motivo en español
422POST_NOT_ALLOWEDIntentaste contabilizar un comprobante por API
422BANK_CHANGE_NOT_ALLOWED / BANK_DATA_NOT_ALLOWEDDatos bancarios de un colaborador existente o de un tercero
422NOT_ALLOWED_BY_APICampos reservados al panel (por ejemplo user_id)
429—Límite por minuto o cuota mensual
502 / 504—El motor falló o no respondió (cita el trace_id)
JSON
{"type": "https://errors.compense.co/gateway/upstream-error", "title": "Request rejected by engine",
 "status": 422,
 "detail": "Por API los comprobantes se crean en borrador; el contador los contabiliza en el panel.",
 "trace_id": "9f1c2e7a4b8d4e6fa1c0d2e3f4a5b6c7"}

Eventos #

Se emiten en la misma transacción que el registro, con event_id derivado de la Idempotency-Key (un reintento no duplica el callback). Ninguno significa que se movió dinero: son solicitudes o borradores. Todos llevan channel: "gateway" y el sobre estándar; nunca diagnósticos ni datos bancarios.

Administrativo #

EventoDesdeCuándo se emite
admin.hr.novelty.received2026-10-27Se registró una novedad de talento humano.
admin.payroll.novelty.received2026-10-27Se cargó una novedad de nómina.
admin.accounting.entry.drafted2026-10-27Se creó un comprobante contable en borrador.
admin.treasury.document.registered2026-10-27Se registró un recaudo, egreso o cuenta de cobro en borrador (kind).
admin.treasury.statement.imported2026-10-27Se cargó un extracto bancario.
EventoCampos de resource
admin.payroll.novelty.receivednovelty_id, employee_id, concept_code, date, quantity, amount, period_id, status
admin.hr.novelty.receivednovelty_id, number, employee_id, category (OTRA si es de salud), fechas, status
admin.accounting.entry.draftedentry_id, external_id, voucher_type, date, totales, status
admin.treasury.document.registereddocument_id, kind (receipt, payment o receivable), third_party_id, date, amount, status, reference
admin.treasury.statement.importedstatement_id, bank_account_id, rango de fechas, lines, closing_balance

Hoy no hay evento cuando talento humano aprueba o rechaza una novedad, ni cuando nómina aprueba el periodo: consulta el estado con las lecturas.

Sandbox #

Con una llave de sandbox las respuestas son deterministas y no tocan datos reales:

  • Colaboradores de prueba: CC 1000000001 (Laura Sandbox) y CC 1000000002 (Andrés Sandbox) ya existen: un upsert responde created: false y enviar datos bancarios da 422. Cualquier otro documento se «crea». CC 9999999999 → 404.
  • Periodos: payroll.list_periods devuelve uno ABIERTO (per-O-…, octubre de 2026) y uno APROBADO (per-C-…, septiembre de 2026): cargarle novedades da 409 PERIOD_CLOSED. Los conceptos del sistema (SALARIO, SALUD…) → 422.
  • Comprobantes: siempre DRAFT; mismo external_id → mismo id; post: true → 422.
  • Terceros: NIT 900000001 ya existe (created: false); datos bancarios → 422.
  • Tesorería: documentos DRAFT; valor <= 0 → 422; el cruce automático responde matched: 0.
  • El simulador de impuestos aplica una tarifa fija del 4 % a cada base.

Lo que sigue #