npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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.

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

  1. Instalación
  2. Inicio rápido
  3. Configuración del cliente
  4. Endpoints
  5. Clase Persona
  6. Clase Juridica
  7. Manejo de errores
  8. Rate limiting
  9. Hooks de observabilidad
  10. Seguridad y buenas prácticas
  11. Uso en frameworks

Instalación

npm install @apifycr/connect

Requiere 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 formato DD/MM/YYYY no es reconocido de forma consistente por todos los motores de JavaScript y puede producir fechas incorrectas o Invalid 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 (no headers) en onRequest para 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, registre redactedHeaders, no headers.
  • 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));

Sitio web

https://tse.apifycr.com