ACI Colombia Volver al sitio Entrar a la plataforma
API externa v1 · Salud EPS

Integra afiliaciones de salud paso a paso

Esta guia explica como autenticar tu sistema, consultar los catalogos, enviar una afiliacion de salud con documentos PDF en base64, identificarla con tu propio ID, consultar su estado y descargar el formulario soporte.

Inicio rapido

La integracion completa se puede resumir en seis pasos. No escribas nombres libres cuando exista un catalogo: envia siempre el id entregado por la API.

1Genera tus claves

Entra a tu usuario y abre Configuracion > Integracion API.

2Prueba el acceso

Consulta GET /me con las dos cabeceras.

3Carga catalogos

Obtiene IDs de EPS, IPS, documentos y ubicaciones.

4Prepara documentos

Convierte cada PDF de una pagina a base64.

5Envia la solicitud

Usa POST /affiliation-requests.

6Haz seguimiento

Consulta por tu external_id y descarga el PDF.

Sandbox de pruebas

Antes de tocar produccion, integra contra el sandbox. Tiene las mismas rutas, los mismos campos y las mismas respuestas: cuando termines, cambias la URL base y las credenciales y ya estas en produccion.

Lo que envias al sandbox no llega a ninguna EPS, no lo procesa ningun robot, no le aparece a nadie en el portal y se borra solo a los 8 dias. Aun asi, no mandes datos de personas reales: son pruebas, no un archivo.

URL base del sandbox

POSThttps://acicolombia.com.co/api/external/sandbox/v1

Credenciales publicas

Estas credenciales son compartidas y estan aqui a proposito: no tienes que pedirle una cuenta a nadie para empezar.

X-ACI-API-Key: aci_test_S4XKFEANS5PzgCtoflEgKUhrj81QEEnLOAmWYRphUwb6eMV3oW1r5uklLE5EwgYN
X-ACI-Secret-Key: aci_test_secret_Rvv2uXgqZeLJeAJOfPZwNncrWEeSiuo47Rg6qgVQIsTvXXkyadVhdv80LEpKl49pFlJJgbKZ

Las credenciales del sandbox solo funcionan en /api/external/sandbox/v1 y las de produccion solo en /api/external/v1. Si las cruzas recibes 403. Es deliberado: evita que una prueba termine radicando de verdad.

Probar el acceso

curl --request GET "https://acicolombia.com.co/api/external/sandbox/v1/me" \
  --header "X-ACI-API-Key: aci_test_S4XKFEANS5PzgCtoflEgKUhrj81QEEnLOAmWYRphUwb6eMV3oW1r5uklLE5EwgYN" \
  --header "X-ACI-Secret-Key: aci_test_secret_Rvv2uXgq..." \
  --header "Accept: application/json"
{
  "data": {
    "id": 966,
    "name": "SANDBOX API",
    "environment": "sandbox"
  }
}

Provocar el desenlace

En produccion el resultado lo da la EPS y tarda. En el sandbox lo provocas tu, para poder probar tu webhook sin esperar.

POSThttps://acicolombia.com.co/api/external/sandbox/v1/affiliation-requests/{external_id}/simulate
{ "status": "RADICADA" }
{
  "status": "RECHAZADA",
  "rejection_reason": "FALTAN_DOCUMENTOS",
  "rejection_notes": "Falta el documento de identidad."
}

El cambio dispara tu webhook por el mismo camino que en produccion, asi que lo que recibas aqui es exactamente lo que vas a recibir alla.

Empezar de cero

POSThttps://acicolombia.com.co/api/external/sandbox/v1/reset

Borra todas tus solicitudes de prueba de una vez, sin esperar los 8 dias.

DiferenciaSandboxProduccion
Llega a la EPSNoSi
Cobra creditosNoSi
Se borra soloA los 8 diasNo
Endpoint /simulateSiNo existe
CredencialesPublicas y compartidasTuyas, desde Configuracion
Prefijo de la llaveaci_test_aci_live_

API key y secret key

Cada usuario genera sus propias credenciales desde Configuracion > Integracion API. Las dos son obligatorias y representan los permisos y el alcance de ese usuario.

Las credenciales completas se muestran una sola vez. Guardalas en el administrador de secretos de tu servidor. Nunca las publiques en JavaScript del navegador, repositorios, capturas o correos.

Cabeceras obligatorias

X-ACI-API-Key: aci_live_TU_API_KEY
X-ACI-Secret-Key: aci_secret_TU_SECRET_KEY
Accept: application/json
Content-Type: application/json

Comprobar autenticacion

GEThttps://acicolombia.com.co/api/external/v1/me
curl --request GET "https://acicolombia.com.co/api/external/v1/me" \
  --header "X-ACI-API-Key: aci_live_TU_API_KEY" \
  --header "X-ACI-Secret-Key: aci_secret_TU_SECRET_KEY" \
  --header "Accept: application/json"
{
  "data": {
    "id": 25,
    "name": "USUARIO INTEGRACION",
    "email": "integracion@empresa.com"
  }
}

Forma de las respuestas

Todo lo que devuelve esta API va envuelto en data. No hay excepciones: ni al crear, ni al consultar, ni en los catalogos. Si tu cliente lee el cuerpo directamente va a encontrar un objeto con una sola llave.

// GET /affiliation-requests/AFI-394900
{ "data": { "id": 394900, "tracking_id": "AFI-394900", ... } }

// GET /catalogs/ips?eps_id=3
{ "data": [ { "id": 1193, "code": "1193", "name": "..." }, ... ],
  "meta": { "total": 1224, "returned": 1224, "truncated": false, "limit": 5000 } }

Con que identificas una afiliacion

Hay tres cosas que la identifican y no sirven para lo mismo:

CampoEjemploPara que
tracking_idAFI-394900Este es el que debes guardar. Es el identificador de seguimiento y el que va en la URL de consulta.
external_idVENTA-2026-000184Tu propia referencia, la que mandaste al crear. Tambien sirve en la URL de consulta.
id394900Nuestro consecutivo interno. Se devuelve por transparencia y la URL lo acepta, pero no es el campo de seguimiento: no lo guardes como tal.

El campo links

Cada afiliacion trae links.detail, links.status y links.support_pdf ya armados, en el mismo entorno desde el que preguntaste: si consultaste el sandbox, los enlaces son del sandbox.

Corregido el 5 de octubre de 2026. Hasta esa fecha links devolvia siempre las URL de produccion, tambien cuando la respuesta venia del sandbox. Seguir uno de esos enlaces con una llave de sandbox daba 401, porque las credenciales de los dos entornos no se cruzan. Era un error nuestro. Si lo rodeaste armando la URL a mano, puedes seguir haciendolo -es igual de valido- o volver a confiar en links.

Armarla a mano es siempre una via valida; la URL es predecible:

https://acicolombia.com.co/api/external/v1/affiliation-requests/{tracking_id|external_id}
https://acicolombia.com.co/api/external/v1/affiliation-requests/{tracking_id|external_id}/status
https://acicolombia.com.co/api/external/v1/affiliation-requests/{tracking_id|external_id}/support-pdf

Catalogos necesarios

Consulta los catalogos antes de construir el formulario. Conserva en cache los resultados y actualizalos periodicamente; las respuestas incluyen cabeceras de cache para evitar llamadas innecesarias.

EndpointPara que se usaFiltro
GET /catalogs/document-typesTipo de documento del afiliado, empresa y beneficiarios.Sin filtro.
GET /catalogs/epsEPS activas y reglas de cada entidad.Sin filtro.
GET /catalogs/ipsIPS disponibles para la EPS y la ciudad.eps_id, city_id.
GET /catalogs/arlsARL informada dentro de la afiliacion de salud.Sin filtro.
GET /catalogs/pension-fundsFondo de pension informado dentro de la afiliacion de salud.Sin filtro.
GET /catalogs/countriesPais de nacimiento.Sin filtro.
GET /catalogs/departmentsDepartamentos por pais.country_id opcional.
GET /catalogs/citiesCiudades por departamento.department_id recomendado.
GET /catalogs/affiliation-statusesEstados por los que pasa una solicitud.Sin filtro.
GET /catalogs/rejection-reasonsCausales con que se rechaza una afiliacion.Sin filtro.

Como mapear tus catalogos con los nuestros

Es la duda mas frecuente, asi que aqui va completa. Cada catalogo devuelve tres campos y cada uno tiene su oficio:

CampoQue esComo se usa
idNuestra llave interna.Es lo que se manda al crear la afiliacion (eps_id, ips_id, document_type_id, expedition_city_id...). No lo deduzcas ni lo escribas a mano: sacalo del catalogo.
codeEl codigo oficial de la entidad.Es por aqui por donde se mapea. En departamentos y ciudades es el codigo DANE (2 y 5 digitos). En EPS, ARL y fondos de pension es el codigo de la entidad. En paises, ISO de dos letras.
nameEl nombre para mostrar.Para la pantalla. No mapees por nombre: cambia, lleva tildes y hay homonimos (BOGOTA aparece como departamento y como ciudad).

La receta: cruza tu catalogo con el nuestro por code, guarda nuestro id, y manda el id. Cruzar por nombre es lo que falla.

Los dos que van encadenados

Dos catalogos dependen de otro y no se pueden pedir sueltos con sentido:

  • Ciudades: cuelgan del departamento. Pide GET /catalogs/departments, y para cada uno GET /catalogs/cities?department_id={id}. Cada ciudad trae su department_id, asi que tambien puedes pedirlas todas de una vez y agrupar.
  • IPS: cuelgan de la EPS y de la ciudad. GET /catalogs/ips?eps_id={id}&city_id={id}. La IPS que mandes tiene que ser de la EPS de la afiliacion; si no, la creacion falla con un error de validacion.

Comprueba que recibiste el catalogo completo

Los catalogos grandes traen la cuenta en meta. Miralo antes de dar tu mapeo por bueno:

{ "data": [ ... ],
  "meta": { "total": 2161, "returned": 2161, "truncated": false, "limit": 5000 } }
Campo de metaQue dice
totalCuantos hay en total con el filtro que pediste.
returnedCuantos van en este cuerpo.
truncatedtrue si no caben todos. Entonces hay que filtrar (por eps_id o city_id las IPS, por department_id las ciudades).

Corregido el 5 de octubre de 2026. GET /catalogs/ips sin filtros devolvia como maximo 1.000 registros de las 2.161 que hay activas, y no lo decia en ninguna parte: quien pedia el catalogo entero para construir su equivalencia se quedaba con menos de la mitad, y las IPS que faltaban le daban error al crear la afiliacion. Hoy vienen todas y, si algun dia no cupieran, meta.truncated lo avisa. Si tu mapeo de IPS es de antes de esa fecha, vuelve a descargarlo.

Dos avisos sobre el codigo DANE de las ciudades

  • El catalogo solo devuelve lo activo. Un municipio que se desactiva desaparece de la lista: si lo tenias mapeado, deja de mandarlo.
  • Quedan unas pocas ciudades con un code que no es DANE (empieza por CO-). Son variantes mal escritas que arrastra una importacion vieja y las estamos depurando. Si tu cruce por code no encuentra alguna, es casi seguro una de esas: cruza ese caso por nombre a mano o preguntanos.

Tipos de documento

Estos 6 son los que acepta document_type_id hoy. La tabla se arma leyendo el catalogo, asi que siempre esta completa.

idcodeNombre
1CCCEDULA DE CIUDADANIA
2CECEDULA DE EXTRANJERIA
3NITNIT
10PTPERMISO POR PROTECCION TEMPORAL (PPT)
6RCREGISTRO CIVIL DE NACIMIENTO
5TITARJETA DE IDENTIDAD

ARL

Las 10 administradoras de riesgos laborales autorizadas para operar el ramo en Colombia. Va en arl_id.

idcodeNombre
10ALFAARL ALFA
7AURORAARL AURORA
3COLMENAARL COLMENA
6COLSANITASARL COLSANITAS
8EQUIDADARL LA EQUIDAD
9MAPFREARL MAPFRE
5BOLIVARARL SEGUROS BOLIVAR
1SURAARL SURA
4AXAAXA COLPATRIA
2POSITIVAPOSITIVA COMPANIA DE SEGUROS

Fondos de pension

Va en pension_fund_id. Si el afiliado no tiene fondo, envia el que aparece como NINGUNO.

idcodeNombre
4COLFONDOSCOLFONDOS
3COLPENSIONESCOLPENSIONES
6NINGUNONINGUNO
2PORVENIRPORVENIR
1PROTECCIONPROTECCION
5SKANDIASKANDIA

EPS

idcodeNombreIndependientes
7 CAPITAL_SALUD CAPITAL SALUD Si
10 COMFENALCO_VALLE COMFENALCO VALLE Si
8 COOSALUD COOSALUD No
4 COMPENSAR EPS COMPENSAR No
2 SANITAS EPS SANITAS Si, con contrato
1 SURA EPS SURA No
6 FAMISANAR FAMISANAR Si, con contrato
3 NUEVA_EPS NUEVA EPS Si
5 SALUD_TOTAL SALUD TOTAL Si, con contrato

Ejemplo: EPS e IPS

GET https://acicolombia.com.co/api/external/v1/catalogs/eps
GET https://acicolombia.com.co/api/external/v1/catalogs/ips?eps_id=2&city_id=477

En todos los campos terminados en _id envia la propiedad id del catalogo, no su code. Por ejemplo, Bogota tiene codigo DANE 11001, pero en esta instalacion su ID de catalogo es 477.

Los campos arl_id y pension_fund_id son datos de la afiliacion de salud. Enviarlos no crea solicitudes separadas de ARL o pension.

Tu identificador externo

Puedes enviar external_id para relacionar la afiliacion con el consecutivo de tu sistema. Es opcional, pero muy recomendado.

  • Puede ser texto o numero y admite letras, numeros, punto, guion, guion bajo y dos puntos.
  • Longitud maxima: 120 caracteres.
  • Es unico dentro de tu usuario.
  • Tambien se acepta id como alias, pero recomendamos usar external_id.
  • Si repites el mismo valor, la API responde 409 y devuelve la afiliacion existente. No cobra otro credito ni crea un duplicado.
{
  "external_id": "VENTA-2026-000184",
  "eps_id": 2,
  "...": "otros campos"
}

Idempotencia

Si se te cae la red despues de enviar una afiliacion, no sabes si quedo creada. Reintentar a ciegas la crearia dos veces y te cobraria dos creditos. Para eso esta la cabecera Idempotency-Key.

Como se usa

Genera un valor unico por cada solicitud que envias (un UUID sirve) y mandalo en el POST. Si tienes que reintentar, manda el mismo valor.

POST https://acicolombia.com.co/api/external/v1/affiliation-requests
Idempotency-Key: 6f1b7a4e-2c53-4f0b-9a8e-7d1c2b3a4f55
Content-Type: application/json
SituacionQue recibes
Primer envio201 Created con la afiliacion nueva.
Reintento con la misma llave y el mismo cuerpoLa misma respuesta del primer envio, con la cabecera Idempotency-Replayed: true. No se crea nada nuevo.
Misma llave con un cuerpo distinto422. No es un reintento: usa una llave nueva.
Dos envios simultaneos con la misma llaveUno pasa y el otro recibe 409. Reintenta ese en unos segundos.
El primer envio fallo por validacionLa llave no queda reservada: corrige y reintenta con la misma.

Las llaves se recuerdan 24 horas. Pasado ese tiempo, la misma llave vuelve a valer como una solicitud nueva.

El external_id te protege igual pero de otra forma: si repites uno ya usado, la API responde 409 con la afiliacion existente. Puedes usar los dos a la vez; son cinturon y tirantes.

Limites de uso

Hay un tope de peticiones por minuto. No es contra ti: es para que el consumo de un cliente no deje sin servicio a los demas.

EntornoLecturaCrear afiliaciones
Produccion120 por minuto30 por minuto
Sandbox60 por minuto (y 300 entre todos)15 por minuto (y 60 entre todos)
Sin credenciales validas20 por minuto por IP10 por minuto por IP

El limite se cuenta por credencial, no por IP: si sales por la misma IP que otro cliente, su consumo no te afecta.

Cuando te pasas

Recibes 429 Too Many Requests con la cabecera Retry-After en segundos. Espera ese tiempo y reintenta; no insistas en bucle.

HTTP/1.1 429 Too Many Requests
Retry-After: 37

Si tu volumen real necesita mas, escribenos: se sube por cliente. Lo que no funciona es reintentar mas rapido.

Referencia de campos

Datos de la solicitud

CampoTipoReglaDescripcion
external_idstringOpcionalID propio para seguimiento e idempotencia.
request_typestringOpcionalSi se envia debe ser EPS. La API lo asigna automaticamente.
eps_idintegerObligatorioID del catalogo de EPS.
ips_idinteger|nullSegun EPSIPS elegida para la afiliacion.
arl_idintegerObligatorioARL actual informada en el formulario.
pension_fund_idintegerObligatorioFondo de pension actual.
affiliation_typestringObligatorioNUEVO, TRASLADO, INCLUSION o NOVEDAD.
contributor_typestringObligatorioDEPENDIENTE o INDEPENDIENTE.
salarynumberObligatorioDebe cumplir el salario minimo vigente al dia de la solicitud.
positionstringObligatorioCargo, maximo 120 caracteres.
entry_datedateObligatorioFormato YYYY-MM-DD; entre 60 dias atras y 15 dias adelante.
is_satbooleanOpcionalSolo para traslados realizados mediante SAT.
sat_numberstringCondicionalObligatorio con SAT, alfanumerico y minimo 16 caracteres.

Persona principal

Campo dentro de personReglaDescripcion
document_type_idObligatorioID de tipo de documento.
document_numberObligatorioNumero del documento, maximo 40 caracteres.
first_name / first_last_nameObligatoriosPrimer nombre y primer apellido.
middle_name / second_last_nameOpcionalesSegundo nombre y segundo apellido.
sexObligatorioMASCULINO, FEMENINO o NO BINARIO.
gender_identityObligatorioMASCULINO, FEMENINO, NO BINARIO, HOMBRE TRANS, MUJER TRANS, GENERO FLUIDO, OTRA o PREFIERE NO INFORMAR.
birth_dateObligatorioFecha de nacimiento YYYY-MM-DD.
expedition_department_id / expedition_city_idObligatoriosLugar de expedicion; la ciudad debe pertenecer al departamento.
expedition_dateObligatorioNo puede ser posterior al dia actual.
birth_country_idObligatorioPais de nacimiento.
birth_department_id / birth_city_idCondicionalesObligatorios cuando el pais de nacimiento es Colombia.
residence_department_id / residence_city_idObligatoriosLugar de residencia.
address / neighborhoodObligatoriosDireccion y barrio.
residence_typeObligatorioURBANA o RURAL.
email / phoneObligatoriosDatos de contacto del afiliado.

Empresa para dependientes

El objeto company es obligatorio cuando contributor_type es DEPENDIENTE y se omite para independientes.

Campo dentro de companyReglaDescripcion
document_type_idObligatorioTipo de documento de la empresa.
document_numberObligatorioNIT o documento registrado.
business_nameObligatorioRazon social.
department_id / city_idObligatoriosUbicacion de la empresa.
address / phone / emailObligatoriosDatos de contacto.
hr_contactObligatorioNombre del contacto de talento humano.

Documentos PDF en base64

Los documentos se envian dentro del objeto documents. Cada archivo debe ser un PDF real, tener una sola pagina y pesar maximo 4 MB una vez decodificado.

"documents": {
  "principal": {
    "file_name": "formulario-afiliacion.pdf",
    "content_base64": "JVBERi0xLjQKJ..."
  },
  "independent_contract": null,
  "withdrawal": null,
  "sat_proof": null,
  "others": []
}
PropiedadCuando se usa
principalSiempre. Documento principal de la afiliacion.
independent_contractCuando la EPS exige contrato para independientes.
withdrawalSoporte de retiro de otra EPS cuando aplique.
sat_proofObligatorio si is_sat es true.
othersHasta 10 documentos adicionales.

Tambien se acepta el base64 como texto directo o como data URL: data:application/pdf;base64,JVBERi0....

Crear una afiliacion de salud

POSThttps://acicolombia.com.co/api/external/v1/affiliation-requests

Ejemplo completo para una persona dependiente sin beneficiarios:

{
  "external_id": "VENTA-2026-000184",
  "request_type": "EPS",
  "eps_id": 2,
  "ips_id": 1365,
  "arl_id": 1,
  "pension_fund_id": 1,
  "affiliation_type": "NUEVO",
  "contributor_type": "DEPENDIENTE",
  "is_sat": false,
  "salary": 2000000,
  "position": "AUXILIAR ADMINISTRATIVO",
  "entry_date": "2026-07-25",
  "person": {
    "document_type_id": 1,
    "document_number": "10101010",
    "first_name": "ANA",
    "middle_name": "MARIA",
    "first_last_name": "PEREZ",
    "second_last_name": "GOMEZ",
    "sex": "FEMENINO",
    "gender_identity": "FEMENINO",
    "birth_date": "1990-02-10",
    "expedition_department_id": 37,
    "expedition_city_id": 477,
    "expedition_date": "2010-03-15",
    "birth_country_id": 1,
    "birth_department_id": 37,
    "birth_city_id": 477,
    "residence_department_id": 37,
    "residence_city_id": 477,
    "address": "CALLE 1 2 3",
    "neighborhood": "CENTRO",
    "residence_type": "URBANA",
    "email": "ana@example.com",
    "phone": "3000000000"
  },
  "company": {
    "document_type_id": 3,
    "document_number": "900000000",
    "business_name": "EMPRESA DEMO SAS",
    "department_id": 37,
    "city_id": 477,
    "address": "AVENIDA 1 2 3",
    "phone": "6010000000",
    "email": "rrhh@example.com",
    "hr_contact": "TALENTO HUMANO"
  },
  "documents": {
    "principal": {
      "file_name": "afiliacion.pdf",
      "content_base64": "JVBERi0xLjQKJ..."
    },
    "others": []
  },
  "beneficiaries": []
}

Variantes del flujo

Afiliado independiente

Usa contributor_type: INDEPENDIENTE, omite company y envia documents.independent_contract cuando el catalogo de la EPS indique requires_independent_contract: true.

Traslado por SAT

{
  "affiliation_type": "TRASLADO",
  "is_sat": true,
  "sat_number": "SAT202607220000000000000001",
  "documents": {
    "principal": { "file_name": "afiliacion.pdf", "content_base64": "JVBERi0..." },
    "sat_proof": { "file_name": "comprobante-sat.pdf", "content_base64": "JVBERi0..." }
  }
}

Inclusion de beneficiarios

Para affiliation_type: INCLUSION debes enviar al menos un beneficiario. Los datos de residencia y contacto se heredan de la persona principal.

"beneficiaries": [
  {
    "relationship": "HIJO",
    "person": {
      "document_type_id": 5,
      "document_number": "10203040",
      "first_name": "JUAN",
      "middle_name": "",
      "first_last_name": "PEREZ",
      "second_last_name": "GOMEZ",
      "sex": "MASCULINO",
      "gender_identity": "MASCULINO",
      "birth_date": "2015-08-20",
      "birth_country_id": 1,
      "birth_department_id": 37,
      "birth_city_id": 477
    },
    "documents": {
      "DOCUMENTO_IDENTIDAD_BENEFICIARIO": {
        "file_name": "identidad.pdf",
        "content_base64": "JVBERi0..."
      },
      "REGISTRO_CIVIL_HIJO": {
        "file_name": "registro-civil.pdf",
        "content_base64": "JVBERi0..."
      }
    }
  }
]

Parentescos aceptados: CONYUGE, ESPOSO, ESPOSA, HIJO, HIJA, PADRE y MADRE. Los soportes requeridos cambian por parentesco y edad.

Respuesta al crear

Una creacion correcta responde 201 Created. Guarda tracking_id, external_id y form_number. El cuerpo viene envuelto en data y el campo de seguimiento es tracking_id, no el id numerico: esta explicado en Forma de las respuestas.

{
  "data": {
    "id": 394900,
    "tracking_id": "AFI-394900",
    "external_id": "VENTA-2026-000184",
    "request_type": "EPS",
    "form_number": "987654324",
    "form_number_source": "EPS_SEQUENCE",
    "status": {
      "id": 8,
      "code": "REVISION_DOCUMENTAL",
      "name": "REVISION DOCUMENTAL"
    },
    "eps": { "id": 2, "code": "SANITAS", "name": "EPS SANITAS" },
    "filing_date": "2026-07-25",
    "links": {
      "detail": "https://acicolombia.com.co/api/external/v1/affiliation-requests/VENTA-2026-000184",
      "status": "https://acicolombia.com.co/api/external/v1/affiliation-requests/VENTA-2026-000184/status",
      "support_pdf": "https://acicolombia.com.co/api/external/v1/affiliation-requests/VENTA-2026-000184/support-pdf"
    }
  }
}

Consultar estado y detalle

Puedes usar tu external_id, el tracking_id devuelto por ACI o el ID interno numerico.

Respuesta resumida de estado

GEThttps://acicolombia.com.co/api/external/v1/affiliation-requests/{referencia}/status
GET https://acicolombia.com.co/api/external/v1/affiliation-requests/VENTA-2026-000184/status

Detalle completo

GEThttps://acicolombia.com.co/api/external/v1/affiliation-requests/{referencia}

Agrega ?include_documents=1 solamente cuando necesites recibir los documentos en base64. Esto aumenta considerablemente el peso de la respuesta.

El detalle devuelve la persona completa: no solo el nombre y el documento, sino tambien el lugar y la fecha de expedicion, el nacimiento y la residencia. Sirve para comprobar que quedo guardado lo que mandaste.

{
  "data": {
    "tracking_id": "AFI-394900",
    "person": {
      "document_type": "CC",
      "document_number": "1000383537",
      "name": "DANIELA OSPINA ARANGO",
      "first_name": "DANIELA",
      "middle_name": null,
      "first_last_name": "OSPINA",
      "second_last_name": "ARANGO",
      "birth_date": "2000-06-17",
      "sex": "FEMENINO",
      "gender_identity": "FEMENINO",
      "email": "OSPINARANGO6@GMAIL.COM",
      "phone": "3186231240",
      "address": "CARRERA 100 # 50B SUR 45",
      "neighborhood": "CENTRO",
      "expedition_date": "2018-06-17",
      "expedition_department": { "id": 37, "code": "11", "name": "BOGOTA" },
      "expedition_city":       { "id": 477, "code": "11001", "name": "BOGOTA" },
      "birth_country":         { "id": 1, "code": "CO", "name": "COLOMBIA" },
      "birth_department":      { "id": 37, "code": "11", "name": "BOGOTA" },
      "birth_city":            { "id": 477, "code": "11001", "name": "BOGOTA" },
      "residence_department":  { "id": 37, "code": "11", "name": "BOGOTA" },
      "residence_city":        { "id": 477, "code": "11001", "name": "BOGOTA" }
    }
  }
}

Los lugares vienen con la misma forma que eps e ips -id, code y name- o null si no hay dato; nunca a medias. El code de departamentos y ciudades es el DANE, asi que puedes cotejarlo contra tu propio catalogo sin volver a pedir nada.

Anadido el 5 de octubre de 2026. Antes el bloque person del detalle traia solo document_type, document_number y name. Se podia mandar la expedicion al crear (expedition_department_id, expedition_city_id, expedition_date) pero no volver a leerla, asi que no habia forma de comprobar que habia quedado bien. Lo reporto un integrador y tenia razon.

El resumen (/status y el listado) sigue trayendo solo el nombre y el documento, a proposito: son muchas filas y esto son siete relaciones por cada una. Para el detalle de la persona, pide la afiliacion concreta.

GET https://acicolombia.com.co/api/external/v1/affiliation-requests/AFI-394900
GET https://acicolombia.com.co/api/external/v1/affiliation-requests/VENTA-2026-000184?include_documents=1

Webhooks

Consultar el estado en bucle es caro para los dos. Con un webhook, te avisamos nosotros: cuando una afiliacion tuya queda radicada o rechazada, te llega un POST a la URL que registres, con la causal incluida.

Registrar tu URL

Entra a Configuracion > Integracion API, en el mismo sitio donde generas la API key, y pulsa Registrar webhook. Al guardar se genera un secreto que se muestra una sola vez: guardalo, es con lo que verificas que el aviso viene de nosotros.

La URL tiene que ser https y publica. No se aceptan direcciones de red interna ni localhost.

Desde ahi mismo puedes mandarte un aviso de prueba para comprobar tu endpoint sin esperar a que se mueva una afiliacion real.

De que te avisamos

Al registrar el webhook eliges el alcance:

AlcanceQue recibes
Solo radicadas y rechazadas (por defecto)El desenlace, con la causal y la observacion. Es lo que necesita la mayoria.
Todos los cambios de estadoAdemas, cada paso intermedio (revision documental, validado ADRES, cargue devolucion...). Util si quieres reflejar el avance en tu sistema.

Eventos

EventoCuando llega
affiliation.filedLa afiliacion quedo radicada en la EPS. Trae el form_number y la fecha de radicacion.
affiliation.rejectedLa afiliacion fue rechazada. Trae el objeto rejection con la causal.
affiliation.status_changedCualquier otro cambio de estado. Solo si elegiste todos los cambios.
webhook.testSolo cuando pulsas Enviar prueba desde el portal.

Todos los avisos traen data.observation: la nota del cambio de estado. Ahi va lo que respondio la EPS, lo que anoto quien lo movio a mano o el detalle del correo de rechazo (quien, que anexo y por que). Y traen data.rejection siempre que la afiliacion tenga causal, no solo en el evento de rechazo.

Que recibes

POST https://tu-sistema.com/webhooks/aci
Content-Type: application/json
X-ACI-Event: affiliation.rejected
X-ACI-Delivery: 4821
X-ACI-Timestamp: 1785443901
X-ACI-Signature: sha256=d6dda0a86b8a7b2eafc7e2b58addd2ee...

{
  "event": "affiliation.rejected",
  "sent_at": "2026-07-30T15:38:17-05:00",
  "environment": "production",
  "data": {
    "id": 420413,
    "external_id": "VENTA-2026-000184",
    "status": "RECHAZADA",
    "status_name": "RECHAZADA",
    "eps": "NUEVA_EPS",
    "document_type": "CC",
    "document_number": "1010101010",
    "form_number": "ACI-420413",
    "filing_date": "2026-07-30",
    "updated_at": "2026-07-30T15:38:17-05:00",
    "rejection": {
      "code": "FALTAN_DOCUMENTOS",
      "reason": "FALTA(N) DOCUMENTO(S)",
      "notes": "Falta el documento de identidad.",
      "refunds_credit": false
    }
  }
}

En affiliation.filed el objeto rejection no viene.

Verificar la firma

No confies en un aviso solo porque llego a tu URL: cualquiera que la conozca puede enviarte uno. La firma es lo que demuestra que es nuestro.

Se calcula asi: HMAC-SHA256( X-ACI-Timestamp + "." + cuerpo_crudo, tu_secreto ). Usa el cuerpo tal como llego, sin volver a serializarlo.

// PHP
$cuerpo = file_get_contents('php://input');
$marca = $_SERVER['HTTP_X_ACI_TIMESTAMP'];
$esperada = 'sha256=' . hash_hmac('sha256', $marca . '.' . $cuerpo, $TU_SECRETO);

if (! hash_equals($esperada, $_SERVER['HTTP_X_ACI_SIGNATURE'])) {
    http_response_code(401);
    exit;
}

// Rechaza tambien lo que venga con mas de 5 minutos, para que nadie
// pueda reenviarte manana un aviso valido de hoy.
if (abs(time() - (int) $marca) > 300) {
    http_response_code(401);
    exit;
}

http_response_code(200);
# Python
import hmac, hashlib, time

def valido(cuerpo: bytes, marca: str, firma: str, secreto: str) -> bool:
    esperada = 'sha256=' + hmac.new(
        secreto.encode(), f'{marca}.'.encode() + cuerpo, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(esperada, firma) and abs(time.time() - int(marca)) <= 300

Como respondes tu

Tu respuestaQue hacemos
2xxEntregado. No se vuelve a enviar.
4xxEntendemos que lo rechazaste a proposito. No se reintenta.
5xx o sin respuestaSe reintenta hasta 5 veces, esperando 1, 5, 15 y 60 minutos.

Contesta 200 rapido y procesa despues. Si tardas mas de 15 segundos, el envio cuenta como fallido y te llega repetido.

Un mismo evento puede llegarte mas de una vez (un reintento tras un corte de red que ya habias recibido). Usa X-ACI-Delivery para descartar los repetidos.

El historial de envios, con lo que respondio tu servidor, queda en Configuracion > Integracion API.

Causales de rechazo

Cuando una afiliacion se rechaza, el campo rejection.code trae uno de estos valores. refunds_credit indica si te devolvemos el credito.

codeQue significaDevuelve credito
YA_ACTIVO YA ACTIVO No
ACTIVO_OTRA_EPS ACTIVO EN OTRA EPS No
ACTIVO_REGIMEN_ESPECIAL ACTIVO EN REGIMEN ESPECIAL No
COTIZANTE_INACTIVO COTIZANTE INACTIVO No
COTIZANTE_NO_PERTENECE_EPS EL COTIZANTE NO PERTENECE A LA EPS SOLICITADA Si
NO_CUMPLE_TIEMPO NO CUMPLE TIEMPO No
DOCUMENTO_INCOMPLETO DOCUMENTO INCOMPLETO: FALTA UNA CARA No
FALTAN_DOCUMENTOS FALTA(N) DOCUMENTO(S) No
DOCUMENTO_ERRADO DOCUMENTO ERRADO No
DOCUMENTO_ILEGIBLE DOCUMENTO ILEGIBLE No
DOCUMENTO_PESADO DOCUMENTO DEMASIADO PESADO No
FIRMA_INVALIDA FIRMA NO VALIDA No
FIRMAS_MISMA_MANO FIRMAS DE COTIZANTE Y EMPLEADOR IGUALES No
SOLICITUD_RECIENTE_EN_LA_EPS YA HAY UNA SOLICITUD RECIENTE EN LA EPS No
EDAD_COTIZANTE_NO_PERMITIDA EDAD DEL COTIZANTE NO PERMITIDA No
PARENTESCO_INCONSISTENTE PARENTESCO DEL BENEFICIARIO INCONSISTENTE Si
FECHA_INGRESO_INCONSISTENTE FECHA DE INGRESO INCONSISTENTE Si
CORREO_INCONSISTENTE CORREO DEL AFILIADO INCONSISTENTE No
TIPO_AFILIACION_ERRADA TIPO DE AFILIACION ERRADA No
EMPRESA_NO_CREADA EMPRESA NO CREADA Si
EMPRESA_EN_MORA EMPRESA EN MORA No
DATOS_EMPRESA_INCONSISTENTES DATOS DE EMPRESA INCONSISTENTES No
EPS_NO_IDENTIFICADA EPS NO IDENTIFICADA Si
ANULADA ANULADA No
SIN_OBSERVACION SIN OBSERVACION No
RADICACION_FISICA LA EPS EXIGE RADICACION FISICA Si
SIN_COBERTURA_MUNICIPIO LA EPS NO TIENE COBERTURA EN EL MUNICIPIO Si
CELULAR_INCONSISTENTE NUMERO DE CELULAR INCONSISTENTE Si

La lista sale del catalogo, asi que siempre esta al dia. Tambien la puedes consultar con GET /catalogs/rejection-reasons.

Que hacer con las mas comunes

CausalQue la produceQue hacer
EMPRESA_NO_CREADALa EPS no tiene registrada esa empresa.La empresa debe crearse primero ante la EPS. Despues se puede reenviar.
EMPRESA_EN_MORAEl empleador esta en mora con la EPS.Se resuelve entre la empresa y la EPS; no se puede reenviar antes.
YA_ACTIVOLa persona ya esta activa en esa EPS.No hay nada que radicar. Revisa el estado en ADRES antes de enviar.
ACTIVO_OTRA_EPSEsta activo en otra EPS.Corresponde un traslado, no una afiliacion nueva.
FALTAN_DOCUMENTOS / DOCUMENTO_ILEGIBLEFalta un anexo o no se puede leer.Reenvia con el documento correcto y un external_id nuevo.
DOCUMENTO_ERRADOEl documento no corresponde a la persona.Corrige los datos y vuelve a enviar.
FECHA_INGRESO_INCONSISTENTELa fecha de ingreso no cuadra con la novedad.Revisa entry_date y reenvia.
CORREO_INCONSISTENTEEl correo del afiliado no es valido o no es suyo.Corrige el correo antes de reenviar.
NO_CUMPLE_TIEMPONo se cumplio el tiempo minimo de permanencia para el traslado.Hay que esperar. La EPS informa desde cuando.

Descargar el PDF soporte

GEThttps://acicolombia.com.co/api/external/v1/affiliation-requests/{referencia}/support-pdf

La respuesta es binaria con Content-Type: application/pdf. No intentes interpretarla como JSON. Guarda el cuerpo de la respuesta directamente como archivo.

curl "https://acicolombia.com.co/api/external/v1/affiliation-requests/VENTA-2026-000184/support-pdf" \
  --header "X-ACI-API-Key: aci_live_TU_API_KEY" \
  --header "X-ACI-Secret-Key: aci_secret_TU_SECRET_KEY" \
  --output soporte-afiliacion.pdf

Listar afiliaciones de salud

GEThttps://acicolombia.com.co/api/external/v1/affiliation-requests
ParametroDescripcion
external_idCoincidencia exacta con tu identificador.
document_numberInicio del documento de la persona.
form_numberInicio del numero de formulario.
affiliation_typeNUEVO, TRASLADO, INCLUSION o NOVEDAD.
created_from / created_toRango de fechas en formato YYYY-MM-DD.
per_pageEntre 1 y 100. Valor predeterminado: 25.
pageNumero de pagina.
GET https://acicolombia.com.co/api/external/v1/affiliation-requests?external_id=VENTA-2026-000184
GET https://acicolombia.com.co/api/external/v1/affiliation-requests?created_from=2026-07-01&created_to=2026-07-31&per_page=50

Consecutivo automatico de Sanitas

Cuando la EPS seleccionada es Sanitas, no debes enviar el numero de formulario. ACI reserva automaticamente el siguiente consecutivo y lo incrementa dentro de la misma transaccion.

  • Dos solicitudes simultaneas no reciben el mismo formulario.
  • Si la radicacion falla y la transaccion se revierte, el consecutivo no se consume.
  • El numero asignado aparece en form_number y el origen en form_number_source: EPS_SEQUENCE.
  • El PDF soporte se genera con ese mismo numero.

Ejemplos de codigo

JavaScript en servidor

const response = await fetch('https://acicolombia.com.co/api/external/v1/affiliation-requests/VENTA-2026-000184/status', {
  headers: {
    'X-ACI-API-Key': process.env.ACI_API_KEY,
    'X-ACI-Secret-Key': process.env.ACI_SECRET_KEY,
    'Accept': 'application/json'
  }
});

if (!response.ok) {
  throw new Error(`ACI respondio ${response.status}: ${await response.text()}`);
}

const result = await response.json();
console.log(result.data.status);

PHP con cURL

$curl = curl_init('https://acicolombia.com.co/api/external/v1/affiliation-requests/VENTA-2026-000184/status');
curl_setopt_array($curl, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'X-ACI-API-Key: '.getenv('ACI_API_KEY'),
        'X-ACI-Secret-Key: '.getenv('ACI_SECRET_KEY'),
        'Accept: application/json',
    ],
]);

$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);

if ($status !== 200) {
    throw new RuntimeException($body);
}

$data = json_decode($body, true, flags: JSON_THROW_ON_ERROR);

Errores y reintentos

HTTPSignificadoQue hacer
400Solicitud mal formada.Revisa JSON, cabeceras y parametros.
401Falta una credencial o alguna es invalida.Envia API key y secret key vigentes.
403Usuario inactivo o sin permiso.Valida el estado y permisos del usuario en ACI.
404La referencia no existe o no es visible.Comprueba external_id o tracking_id.
409external_id ya utilizado.Usa los enlaces de la afiliacion existente incluidos en la respuesta.
422Validacion de negocio.Lee el objeto errors; cada clave identifica el campo.
429Limite temporal excedido.Espera y reintenta con backoff exponencial.
500Error inesperado.No cambies external_id al reintentar; asi evitas duplicados.

Ejemplo 422

{
  "message": "The given data was invalid.",
  "errors": {
    "person.expedition_city_id": [
      "La ciudad de expedicion no pertenece al departamento seleccionado."
    ],
    "salary": [
      "El salario no puede ser menor al salario minimo vigente."
    ]
  }
}

Reintenta automaticamente solo errores temporales como 429 o 5xx. Para 4xx, corrige los datos antes de volver a enviar.