TunjaAPI

API pública · v1

Usuarios, tarjetas
y billetera. Ya hechos.

Tu proyecto del semestre necesita gente que se registre, algo que la identifique y un saldo que suba y baje. Eso ya está resuelto y en pie: pide tu clave, apunta tus fetch aquí y dedica el tiempo a lo tuyo.

Base
/api/v1
Peticiones/min
Usuarios por clave
Perfiles

Lo primero

Una sola base, y la comparten todos

Alguien que se registró desde el proyecto de tu compañera puede iniciar sesión en el tuyo, y es su misma cuenta: el mismo saldo, el mismo historial. No es un efecto secundario, es la idea — así el semestre entero construye sobre la misma gente en vez de sobre dieciocho bases vacías.

Leer es público

GET /users funciona sin credenciales. Puedes listar, buscar y paginar desde el primer minuto, antes de tener clave. Público no es entero: el email, el documento, el saldo y el uid de las tarjetas solo salen con clave.

Escribir va firmado

Cada usuario, tarjeta y movimiento guarda cuál clave lo creó, y el usuario que registró la tuya solo lo edita o lo borra la tuya. El saldo sí se cruza: para eso hay una sola base — tu torniquete le cobra a quien sea.

Las dos credenciales

La clave es del proyecto; el token, de la persona

No se parecen y conviene no mezclarlas. La clave dice desde qué proyecto se escribe. El token dice quién está usándolo. Mover saldo pide las dos.

Cabecera

X-API-Key: tk_…

La clave

Tuya, del proyecto, y te la da el profesor. Hace falta para crear usuarios, vincular tarjetas y mover dinero. No se enseña en el navegador de nadie.

Cabecera

Authorization: Bearer eyJ…

El token

Del usuario que inició sesión. Sirve para hacer cosas como él: /users/me, /wallet. Sale del registro o del login.

Por qué te devuelven dos tokens

El acceso es un JWT y caduca a los 15 minutos: el servidor no lo guarda, lo comprueba mirando su firma. El refresco dura 30 días, sí está en la base y sirve para pedir un acceso nuevo sin volver a escribir la contraseña.

Cada refresco se usa una sola vez: el que te devuelve /auth/refresh es el nuevo y el anterior ya no vale. Si mandas uno gastado, el servidor asume que alguien tiene una copia y cierra todas tus sesiones. Es molesto a propósito.

El mapa

Todo lo que hay

Base /api/v1. Todo va y viene en JSON, y los errores tienen siempre la misma forma: { "error": "codigo_en_snake" }, a veces con algún dato más para saber qué pasó.

Esto es el índice, para ver de un vistazo qué hay y qué pide cada cosa. Cada endpoint por dentro —parámetros, qué cambia al mandar la clave, ejemplos de petición y respuesta, y sus errores— está en la referencia.

El uid no sale sin clave

Un uid no es un secreto —lo lee cualquier móvil acercándose a una tarjeta— pero una lista de uids sí es un problema: sin esto, un curl sin cabeceras equivalía a pasar un lector por todos los bolsillos del campus a la vez. Así que el uid viaja con el email y el saldo, en la parte que pide clave. Consultar /cards/:uid sigue abierto: si ya tienes el uid es porque tienes la tarjeta.

Lo que esto no arregla: una tarjeta se autentica por su uid y nada más, así que quien consiga uno y tenga una etiqueta regrabable tiene una copia funcionando. Cerrar la lista sube el coste, no lo elimina. La defensa de verdad va en el chip —DESFire, NTAG con firma— y aquí no está hecha. Tenlo presente antes de mover con esto dinero que importe.

La API no sabe lo que valen las cosas

Le pasas un número de pesos y lo mueve. Cuánto cuesta un pasaje, un café o una entrada es cosa de tu proyecto, y concepto es texto libre para que apuntes qué era. Lo mismo con perfil y descuento: son información sobre la persona, no un precio.

Casos de uso

Qué se puede montar encima

Las cuatro piezas —cuenta, identidad, saldo y movimientos— se combinan de muchas maneras. Estas son las que ya se han hecho o se están haciendo.

Un torniquete

Lees el UID de la tarjeta, buscas de quién es y le cobras el pasaje que decida tu tarifa.

GET /cards/:uid · POST /users/:id/wallet/charge

La cafetería del campus

El mismo saldo, otro concepto. Sin tarjeta: el usuario entra con su contraseña y paga desde su teléfono.

POST /auth/login · POST /wallet/charge

Control de asistencia

El carné estudiantil ya suelta su UID por NFC. Vinculado a un usuario, sirve de identificación sin cobrar nada.

POST /cards · GET /cards/:uid

Solo el login

A veces no hace falta ni dinero ni tarjetas: registro, sesión y perfil resueltos para no escribir otra tabla de usuarios más.

POST /auth/register · GET /users/me

Recargas en ventanilla

Con tu clave le subes saldo a cualquiera de la base, sin pedirle la contraseña a nadie. Queda firmado con tu clave: úsalo con cabeza.

POST /users/:id/wallet/topup

Un tablero

Leer es público, así que un panel de estadísticas sale sin credenciales: cuánta gente hay, con qué perfiles.

GET /users?limite=&perfil=

Empezar

De cero a un saldo que se mueve

Pide tu clave al profesor —es una cadena que empieza por tk_ y se enseña una sola vez— y sigue estos tres pasos contra https://nfc.multimediaudeb.com/api/v1. Para mirar no hace falta nada: el botón de abajo llama a la API de verdad, ahora mismo, sin credenciales.

¿Node y Express? El cliente, las rutas y dónde guardar la clave están resueltos en la referencia.

GET /api/v1/users?limite=3
Sin llamar todavía.
  1. Registra a alguien

    Con tu clave. Devuelve 201 con el usuario y el par de tokens ya listos: no hace falta iniciar sesión después.

    POST /api/v1/auth/register
    X-API-Key: tk_…
    
    { "email": "ana@ejemplo.com", "contrasena": "tunja2026",
      "nombre": "Ana Ruiz", "perfil": "estudiante" }
  2. Muévele el saldo

    Las dos credenciales juntas: el token dice quién es, la clave desde qué proyecto. charge es lo mismo pero al revés.

    POST /api/v1/wallet/topup
    X-API-Key: tk_…
    Authorization: Bearer eyJ…
    
    { "monto": 50000, "concepto": "Recarga" }
  3. Refresca cuando caduque

    A los 15 minutos el acceso deja de valer y responde 401 token_expirado. Se refresca y se reintenta, una vez.

    if (respuesta.status === 401 && refresco) {
      const nueva = await fetch(`${API}/auth/refresh`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ refresco }),
      });
      guardar(await nueva.json());   // el refresco viejo ya no vale
    }

Los límites

  • 120 peticiones por minuto por clave → 429
  • 200 usuarios por clave → 409 cuota_agotada
  • Cuerpos de más de 4 kB → 400 cuerpo_muy_grande

Son barandillas para que nadie llene la base de basura, no política de precios.

CORS abierto

Puedes llamar desde donde sirvas tu proyecto: localhost:5173, un Netlify o un index.html abierto a pelo. Las credenciales van en cabeceras y no hay cookies, que es justo lo que hace seguro el comodín.

Hecho con esto

MetroTunja

El metro que Tunja todavía no tiene: dos líneas, quince estaciones, una tarjeta sin contacto y trenes que se mueven de verdad. Los pasajeros, sus tarjetas y su saldo son los de esta API — el metro solo pone las tarifas, que es exactamente lo que la API no hace.