@apifycr/connect
v0.1.0
Published
SDK oficial de Node.js para la API TSE v2 de ApifyConnect. Consulta de cédulas, personas y empresas en Costa Rica.
Maintainers
Readme
@apifycr/connect
SDK oficial de Node.js para la API TSE v2 de ApifyConnect.
- URL base:
https://tse.apifycr.com/api/v2 - Autenticación:
Authorization: Bearer <api_key> - Requiere una cuenta activa en tse.apifycr.com.
Tabla de contenidos
- Instalación
- Inicio rápido
- Configuración del cliente
- Endpoints
- Clase
Persona - Clase
Juridica - Manejo de errores
- Rate limiting
- Hooks de observabilidad
- Seguridad y buenas prácticas
- Uso en frameworks
Instalación
npm install @apifycr/connectRequiere Node.js 18 o superior.
Inicio rápido
import { createApifyConnectClient } from "@apifycr/connect";
const client = createApifyConnectClient({
apiKey: process.env.APIFYCR_API_KEY!,
});
const persona = await client.getByCedula({ cedula: "123456789" });
console.log(persona.fullName); // "PABLO VERA QUESADA"
console.log(persona.nombreFormateado); // "Pablo Vera Quesada"
console.log(persona.cedulaFormateada); // "1-2345-6789"
console.log(persona.edad()); // "30 años 5 meses"
console.log(persona.direccionElectoral); // "Brasil, Santa Ana, San José"Configuración del cliente
const client = createApifyConnectClient({
apiKey: "...", // Requerido. Su API key de ApifyConnect.
timeoutMs: 20_000, // Opcional. Timeout en ms (defecto: 20 000).
baseUrl: "...", // Opcional. Sobreescribe la URL base (para pruebas).
userAgent: "mi-app/1", // Opcional. Se añade al encabezado User-Agent.
allowBrowser: false, // Opcional. Ver sección de seguridad.
onRequest: (e) => {}, // Opcional. Hook de observabilidad (ver más abajo).
onResponse: (e) => {},
onError: (e) => {},
});Protección de la API key en el navegador
Por defecto el SDK bloquea su uso en el navegador para evitar exponer la clave accidentalmente. Si hace llamadas desde su propio backend, no necesita ningún ajuste.
Si por alguna razón necesita llamar desde el cliente (SPA, extensión de Chrome, etc.), debe optar explícitamente:
const client = createApifyConnectClient({
apiKey: process.env.APIFYCR_API_KEY!,
allowBrowser: true, // ⚠️ Expone la API key al usuario final
});Endpoints
getByCedula — consulta por cédula
Retorna una instancia de Persona con todos los campos del API y propiedades computadas adicionales.
const persona = await client.getByCedula({ cedula: "123456789" });| Parámetro | Tipo | Descripción |
|-----------|----------|-----------------------------------|
| cedula | string | Cédula de 9 dígitos (solo números). |
searchPersona — búsqueda por nombre
Busca personas por nombre y apellidos. El nombre acepta coincidencia parcial. Retorna un resultado paginado con instancias de Persona.
const result = await client.searchPersona({
nombre: "pablo",
apellido: "vera",
apellido2: "quesada", // opcional
page: 1, // opcional, defecto: 1
});
console.log(result.total); // 16
console.log(result.last_page); // 2
console.log(result.data); // Persona[]
for (const persona of result.data) {
console.log(persona.nombreFormateado, persona.edad());
}
// Siguiente página
if (result.next_page_url) {
const page2 = await client.searchPersona({ nombre: "pablo", apellido: "vera", page: 2 });
}| Parámetro | Tipo | Requerido | Descripción |
|-------------|----------|-----------|-------------------------------------|
| nombre | string | Sí | Nombre o parte del nombre. |
| apellido | string | Sí | Primer apellido (exacto). |
| apellido2 | string | No | Segundo apellido (exacto). |
| page | number | No | Número de página. Defecto: 1. |
Paginación — la respuesta incluye current_page, last_page, total, per_page (fijo en 15), next_page_url y prev_page_url.
iteratePersona — recorrer todas las páginas
Genera automáticamente todas las páginas y las recorre una a una. Ideal para exportaciones o búsquedas que devuelven muchos resultados.
for await (const persona of client.iteratePersona({ nombre: "pablo", apellido: "vera" })) {
console.log(persona.cedulaFormateada, persona.fullName);
}Cada iteración hace una llamada al API. Tenga en cuenta los límites de uso.
getJuridica — persona jurídica
Retorna una instancia de Juridica.
const empresa = await client.getJuridica({ cedula: "1234567890" });
console.log(empresa.nombre); // "APIFY LATAM SOCIEDAD ANONIMA"
console.log(empresa.nombreFormateado); // "Apify Latam Sociedad Anonima"
console.log(empresa.tipoNombre); // "Cédula Jurídica"| Parámetro | Tipo | Descripción |
|-----------|----------|--------------------------------------|
| cedula | string | Cédula jurídica de 10 dígitos. |
Clase Persona
Todos los campos crudos del API siguen disponibles directamente (persona.nombre, persona.cedula, etc.). La clase añade las siguientes propiedades y métodos computados.
Identidad
| Propiedad | Tipo | Ejemplo | Descripción |
|--------------------|----------|----------------------------|--------------------------------------------------|
| fullName | string | "PABLO VERA QUESADA" | Nombre completo en mayúsculas. |
| nombreFormateado | string | "Pablo Vera Quesada" | Nombre completo en formato título. |
| cedulaFormateada | string | "1-2345-6789" | Cédula en formato estándar costarricense. |
persona.fullName // "PABLO VERA QUESADA"
persona.nombreFormateado // "Pablo Vera Quesada"
persona.cedulaFormateada // "1-2345-6789"Fechas
El API devuelve fecha_nacimiento en formato DD/MM/YYYY y fecha_caduc en YYYYMMDD. El SDK los parsea correctamente a instancias de Date listas para usar.
No use
new Date(persona.fecha_nacimiento)directamente — el formatoDD/MM/YYYYno es reconocido de forma consistente por todos los motores de JavaScript y puede producir fechas incorrectas oInvalid Date.
| Propiedad | Tipo | Descripción |
|------------------|----------------|-----------------------------------------------------|
| fechaNacimiento| Date \| null | Fecha de nacimiento. Parseada desde DD/MM/YYYY. |
| fechaCaducidad | Date \| null | Caducidad de la cédula. Parseada desde YYYYMMDD. |
const fn = persona.fechaNacimiento;
if (fn) {
console.log(fn.getFullYear()); // 1995
console.log(fn.toLocaleDateString("es-CR")); // "11/4/1995"
}
const fc = persona.fechaCaducidad;
if (fc && fc < new Date()) {
console.log("Cédula vencida");
}Edad
persona.edad() // "30 años 5 meses" (defecto: 2 partes)
persona.edad(1) // "30 años"
persona.edad(2) // "30 años 5 meses"
persona.edad(3) // "30 años 5 meses 12 días"Retorna "Fecha de nacimiento desconocida" si fecha_nacimiento es null.
Estado
| Propiedad | Tipo | Descripción |
|-----------------|-----------|--------------------------------------------------------------------------|
| isEmpadronado | boolean | true si la persona tiene codelec (está inscrita en el padrón). |
| isMenorDeEdad | boolean | true si es menor de 18 años. false si la fecha de nacimiento es null.|
if (!persona.isEmpadronado) {
console.log("Persona no empadronada (posible menor de edad u otro)");
}
if (persona.isMenorDeEdad) {
console.log("Acceso restringido: menor de edad");
}Ubicación electoral
Los campos provincia, canton y distrito son null cuando codelec es null (persona no empadronada).
| Propiedad | Tipo | Ejemplo |
|-----------------------|---------------------------------------------------|-----------------------|
| provincia | string \| null | "SAN JOSE" |
| canton | string \| null | "SANTA ANA" |
| distrito | string \| null | "BRASIL" |
| provinciaFormateada | string \| null | "San Jose" |
| cantonFormateado | string \| null | "Santa Ana" |
| distritoFormateado | string \| null | "Brasil" |
| location | { provincia, canton, distrito } \| null | Objeto en mayúsculas |
| locationFormateada | { provincia, canton, distrito } \| null | Objeto en título |
| direccionElectoral | string \| null | "Brasil, Santa Ana, San Jose" |
// Acceso individual
console.log(persona.provincia); // "SAN JOSE"
console.log(persona.provinciaFormateada); // "San Jose"
console.log(persona.canton); // "SANTA ANA"
console.log(persona.cantonFormateado); // "Santa Ana"
console.log(persona.distrito); // "BRASIL"
console.log(persona.distritoFormateado); // "Brasil"
// Objeto completo
console.log(persona.location);
// { provincia: "SAN JOSE", canton: "SANTA ANA", distrito: "BRASIL" }
console.log(persona.locationFormateada);
// { provincia: "San Jose", canton: "Santa Ana", distrito: "Brasil" }
// Una sola línea
console.log(persona.direccionElectoral);
// "Brasil, Santa Ana, San Jose"
// Guarda con null check
if (persona.location) {
mostrarMapa(persona.location.provincia, persona.location.canton);
}Padres
| Propiedad | Tipo | Ejemplo |
|-----------------|------------------|-------------------|
| padre | string \| null | "JUAN VERA" |
| madre | string \| null | "MARIA QUESADA" |
| padreFormateado| string \| null| "Juan Vera" |
| madreFormateado| string \| null| "Maria Quesada" |
console.log(persona.padreFormateado); // "Juan Vera"
console.log(persona.madreFormateado); // "Maria Quesada"Selección de campos — .only()
Retorna un objeto plano con únicamente los campos solicitados. Útil para construir respuestas de API propias, logs o formularios sin exponer datos innecesarios.
Acepta tanto campos crudos del API como cualquiera de los campos computados listados arriba.
// Solo nombre y cédula
persona.only(['nombre', 'apellido1', 'cedula'])
// → { nombre: 'PABLO', apellido1: 'VERA', cedula: '123456789' }
persona.only(['nombreFormateado', 'cedulaFormateada', 'fechaNacimiento'])
// Respuesta ligera para una API propia
const payload = persona.only([
'cedulaFormateada',
'nombreFormateado',
'direccionElectoral',
'isEmpadronado',
'isMenorDeEdad',
]);
res.json(payload);Campos disponibles en .only():
Todos los campos crudos del API (cedula, codelec, fecha_caduc, nombre, apellido1, apellido2, fecha_nacimiento, padre, cedula_padre, madre, cedula_madre, provincia, canton, distrito) más los computados:
fullName · nombreFormateado · cedulaFormateada · fechaNacimiento · fechaCaducidad · isEmpadronado · isMenorDeEdad · provinciaFormateada · cantonFormateado · distritoFormateado · location · locationFormateada · direccionElectoral · padreFormateado · madreFormateado
Registro crudo — .toPlain()
Retorna el objeto original tal como lo devolvió el API, sin propiedades computadas. Útil para serializar, almacenar en base de datos o pasar a librerías que no aceptan instancias de clase.
const raw = persona.toPlain();
await db.insert("personas", raw);Clase Juridica
| Propiedad | Tipo | Ejemplo | Descripción |
|--------------------|----------|----------------------------------|------------------------------------|
| nombre | string | "APIFY LATAM SOCIEDAD ANONIMA" | Razón social en mayúsculas. |
| tipoIdentificacion| string| "02" | Código de tipo del API. |
| nombreFormateado | string | "Apify Latam Sociedad Anonima" | Razón social en formato título. |
| tipoNombre | string | "Cédula Jurídica" | Descripción legible del tipo. |
const empresa = await client.getJuridica({ cedula: "1234567890" });
empresa.nombre // "APIFY LATAM SOCIEDAD ANONIMA"
empresa.nombreFormateado // "Apify Latam Sociedad Anonima"
empresa.tipoIdentificacion // "02"
empresa.tipoNombre // "Cédula Jurídica"
const raw = empresa.toPlain();
// { nombre: "APIFY LATAM SOCIEDAD ANONIMA", tipoIdentificacion: "02" }Tipos soportados en tipoNombre:
| Código | Nombre |
|--------|-------------------------|
| 01 | Cédula de Identidad |
| 02 | Cédula Jurídica |
| 03 | DIMEX |
| 04 | NITE |
Manejo de errores
El SDK lanza ApifyConnectError para cualquier respuesta que no sea 2xx.
import { ApifyConnectError } from "@apifycr/connect";
try {
const persona = await client.getByCedula({ cedula: "000000000" });
} catch (error) {
if (error instanceof ApifyConnectError) {
console.error(error.status); // 404
console.error(error.data?.error); // "Persona no encontrada."
console.error(error.rateLimit.retryAfterSeconds); // undefined (o segundos si fue 429)
}
}| Propiedad | Tipo | Descripción |
|----------------------------------|-------------------|-----------------------------------------------------|
| error.status | number | Código HTTP (404, 401, 429, etc.). |
| error.statusText | string | Texto del status HTTP. |
| error.data | object \| null | Cuerpo JSON de la respuesta de error. |
| error.data?.error | string \| undefined | Mensaje de error del API. |
| error.rateLimit.limit | number \| undefined | Límite de solicitudes de la ventana. |
| error.rateLimit.remaining | number \| undefined | Solicitudes restantes. |
| error.rateLimit.retryAfterSeconds | number \| undefined | Segundos a esperar antes de reintentar (429). |
Tabla de códigos de error del API:
| Código | Significado | Facturable | Descripción |
|--------|-------------------------|------------|--------------------------------------------------|
| 401 | No autorizado | No | API key inválida o ausente. |
| 403 | Suscripción / CORS | Sí | Sin suscripción activa o dominio no permitido. |
| 404 | No encontrado | Sí | Cédula sin resultados. |
| 422 | Parámetros inválidos | Sí | cedula ausente o con formato incorrecto. |
| 429 | Límite superado | Sí | Demasiadas solicitudes. Ver Retry-After. |
| 500 | Error interno | No | Error del servidor de ApifyConnect. |
| 502 | Bad Gateway | No | Error temporal en servicio upstream. |
| 503 | Service Unavailable | No | Servicio no disponible temporalmente. |
Rate limiting
El API v2 aplica los siguientes límites por cuenta:
- 1 solicitud cada 5 segundos
- 12 solicitudes por minuto
- 1 000 solicitudes por día
Cuando se supera el límite el API responde 429 Too Many Requests con el encabezado Retry-After indicando cuántos segundos esperar.
try {
await client.getByCedula({ cedula: "123456789" });
} catch (error) {
if (error instanceof ApifyConnectError && error.status === 429) {
const wait = error.rateLimit.retryAfterSeconds ?? 5;
console.log(`Límite alcanzado. Reintente en ${wait} segundos.`);
}
}Las respuestas exitosas incluyen los encabezados X-RateLimit-Limit y X-RateLimit-Remaining, accesibles a través del hook onResponse (ver abajo).
Hooks de observabilidad
Los hooks permiten integrar el SDK con su sistema de logging, métricas o trazabilidad sin modificar la lógica de negocio.
const client = createApifyConnectClient({
apiKey: process.env.APIFYCR_API_KEY!,
onRequest: ({ method, url, redactedHeaders }) => {
// Usar redactedHeaders para no loguear la API key
console.log(`→ ${method} ${url}`, redactedHeaders);
},
onResponse: ({ status, url, rateLimit }) => {
console.log(`← ${status} ${url}`, {
remaining: rateLimit.remaining,
limit: rateLimit.limit,
});
},
onError: ({ url, error }) => {
console.error(`✗ ${url}`, error);
},
});Importante: use siempre
redactedHeaders(noheaders) enonRequestpara evitar que la API key aparezca en logs.
Seguridad y buenas prácticas
- Guarde la API key en variables de entorno (
process.env.APIFYCR_API_KEY) o en un gestor de secretos (AWS Secrets Manager, Doppler, etc.). - Nunca incluya la clave en código fuente, repositorios públicos ni respuestas de su API.
- Llame al API desde su backend, no desde el navegador. Así la clave nunca llega al usuario final.
- Si usa el hook
onRequest, registreredactedHeaders, noheaders. - Rote y revoque claves comprometidas desde el dashboard de su cuenta.
Uso en frameworks
Express / Node.js
import express from "express";
import { createApifyConnectClient, ApifyConnectError } from "@apifycr/connect";
const client = createApifyConnectClient({ apiKey: process.env.APIFYCR_API_KEY! });
const app = express();
app.get("/api/persona/:cedula", async (req, res) => {
try {
const persona = await client.getByCedula({ cedula: req.params.cedula });
res.json(persona.only([
"cedulaFormateada",
"nombreFormateado",
"fechaNacimiento",
"direccionElectoral",
]));
} catch (error) {
if (error instanceof ApifyConnectError) {
res.status(error.status).json({ error: error.data?.error ?? error.message });
} else {
res.status(500).json({ error: "Error interno" });
}
}
});Next.js (App Router)
// app/api/cedula/route.ts
import { NextRequest, NextResponse } from "next/server";
import { createApifyConnectClient, ApifyConnectError } from "@apifycr/connect";
const client = createApifyConnectClient({ apiKey: process.env.APIFYCR_API_KEY! });
export async function GET(req: NextRequest) {
const cedula = req.nextUrl.searchParams.get("cedula") ?? "";
try {
const persona = await client.getByCedula({ cedula });
return NextResponse.json({
nombre: persona.nombreFormateado,
cedula: persona.cedulaFormateada,
edad: persona.edad(),
ubicacion: persona.direccionElectoral,
});
} catch (error) {
if (error instanceof ApifyConnectError) {
return NextResponse.json(
{ error: error.data?.error ?? error.message },
{ status: error.status },
);
}
return NextResponse.json({ error: "Error interno" }, { status: 500 });
}
}Verificación de mayoría de edad
const persona = await client.getByCedula({ cedula: req.body.cedula });
if (persona.isMenorDeEdad) {
return res.status(403).json({ error: "Acceso restringido a mayores de edad." });
}
// Continuar con el flujo...Exportar todos los resultados de una búsqueda
const resultados: object[] = [];
for await (const persona of client.iteratePersona({ nombre: "maria", apellido: "rodriguez" })) {
resultados.push(persona.only([
"cedulaFormateada",
"nombreFormateado",
"provinciaFormateada",
"cantonFormateado",
"fechaNacimiento",
]));
}
await fs.writeFile("resultados.json", JSON.stringify(resultados, null, 2));