Crea aplicaciones que permitan a los usuarios conectar sus cuentas de LUMA. Las apps OAuth pueden acceder a datos financieros en nombre de los usuarios, lo que permite integraciones, automatizaciones y herramientas personalizadas.
#Qué puedes construir
Con OAuth, puedes construir:
- Extensiones de navegador que muestran datos de LUMA
- Apps móviles con integración con LUMA
- Herramientas de automatización que sincronizan datos entre servicios
- Paneles personalizados para flujos de trabajo específicos
- Herramientas para desarrolladores, como extensiones de IDE
- Apps públicas listadas en el directorio de apps de LUMA
#Cómo funciona OAuth
OAuth 2.0 permite que tu app solicite acceso a los datos de LUMA de un usuario sin gestionar su contraseña:
- Tu app redirige a los usuarios a la página de autorización de LUMA
- Los usuarios inician sesión y aprueban los permisos solicitados
- LUMA redirige de vuelta con un código de autorización
- Tu app intercambia el código por tokens de acceso
- Usa los tokens de acceso para llamar a la API de LUMA
#Primeros pasos
#Paso 1: Crea una aplicación OAuth
- Ve a Ajustes → Desarrollador
- Haz clic en Crear app OAuth
- Rellena los datos de tu aplicación:
- Nombre: el nombre de tu app (se muestra a los usuarios)
- Descripción: una breve descripción
- Sitio web: la página principal de tu app
- URIs de redirección: URLs a las que redirigir tras la autorización
- Selecciona los scopes que necesita tu app
- Haz clic en Crear
Recibirás:
- Client ID: identificador público de tu app
- Client Secret: mantenlo en secreto (se muestra una sola vez)
#Paso 2: Implementa el flujo de autorización
Redirige a los usuarios para iniciar la autorización:
https://app.midday.ai/oauth/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_REDIRECT_URI&
scope=transactions.read%20invoices.read&
state=RANDOM_STATE_VALUE
Parámetros:
| Parámetro | Obligatorio | Descripción |
|---|---|---|
response_type | Sí | Debe ser code |
client_id | Sí | El client ID de tu aplicación |
redirect_uri | Sí | Debe coincidir con una URI de redirección registrada |
scope | Sí | Lista de scopes separados por espacios |
state | Recomendado | Cadena aleatoria para prevenir ataques CSRF |
code_challenge | Para clientes públicos | Code challenge de PKCE |
code_challenge_method | Para clientes públicos | Debe ser S256 |
#Paso 3: Gestiona el callback
Tras autorizar, LUMA redirige a tu redirect_uri:
https://yourapp.com/callback?code=AUTH_CODE&state=YOUR_STATE
Si el usuario deniega el acceso:
https://yourapp.com/callback?error=access_denied&error_description=User%20denied%20access
#Paso 4: Intercambia el código por tokens
Haz una petición POST para intercambiar el código de autorización:
curl -X POST https://api.midday.ai/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "AUTH_CODE",
"redirect_uri": "YOUR_REDIRECT_URI",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}'
Respuesta:
{
"access_token": "mid_at_xxxxx",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "mid_rt_xxxxx",
"scope": "transactions.read invoices.read"
}
#Paso 5: Realiza peticiones a la API
Instala el SDK de LUMA:
npm install @midday-ai/sdk
Usa el token de acceso con el SDK:
import { Midday } from "@midday-ai/sdk";
const midday = new Midday({
token: "mid_at_xxxxx", // Token de acceso del flujo de OAuth
});
// Lista las transacciones
const transactions = await midday.transactions.list({
pageSize: 50,
});
// Obtiene las facturas
const invoices = await midday.invoices.list({
pageSize: 20,
});
// Obtiene métricas financieras
const revenue = await midday.metrics.revenue({
from: "2024-01-01",
to: "2024-12-31",
});
#PKCE para clientes públicos
Para apps móviles, SPAs o cualquier cliente que no pueda almacenar secretos de forma segura, usa PKCE (Proof Key for Code Exchange).
#Genera el code verifier y el code challenge
function base64UrlEncode(buffer: Uint8Array): string {
return btoa(String.fromCharCode(...buffer))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
// Genera un code verifier aleatorio
function generateCodeVerifier(): string {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64UrlEncode(array);
}
// Crea un hash SHA-256 para el code challenge
async function generateCodeChallenge(verifier: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest("SHA-256", data);
return base64UrlEncode(new Uint8Array(hash));
}
#Inclúyelo en la petición de autorización
https://app.midday.ai/oauth/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_REDIRECT_URI&
scope=transactions.read&
code_challenge=CHALLENGE&
code_challenge_method=S256&
state=STATE
#Incluye el verifier en el intercambio de tokens
curl -X POST https://api.midday.ai/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "AUTH_CODE",
"redirect_uri": "YOUR_REDIRECT_URI",
"client_id": "YOUR_CLIENT_ID",
"code_verifier": "YOUR_CODE_VERIFIER"
}'
Nota: los clientes públicos no deben enviar client_secret.
#Renovación de tokens
Los tokens de acceso caducan al cabo de 1 hora. Usa el refresh token para obtener tokens nuevos:
curl -X POST https://api.midday.ai/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "refresh_token",
"refresh_token": "mid_rt_xxxxx",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}'
Los refresh tokens son válidos durante 30 días y rotan en cada uso.
#Revocación de tokens
Permite que los usuarios desconecten tu app revocando los tokens:
curl -X POST https://api.midday.ai/v1/oauth/revoke \
-H "Content-Type: application/json" \
-d '{
"token": "mid_at_xxxxx",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}'
#Ejemplo: implementación en TypeScript
import express from "express";
import crypto from "crypto";
import { Midday } from "@midday-ai/sdk";
const app = express();
const CLIENT_ID = process.env.MIDDAY_CLIENT_ID!;
const CLIENT_SECRET = process.env.MIDDAY_CLIENT_SECRET!;
const REDIRECT_URI = "http://localhost:3000/callback";
// En producción, usa un almacén de sesiones adecuado
const sessions = new Map<string, { state: string; accessToken?: string }>();
// Paso 1: Redirige a la autorización
app.get("/connect", (req, res) => {
const sessionId = crypto.randomUUID();
const state = crypto.randomUUID();
sessions.set(sessionId, { state });
res.cookie("session_id", sessionId);
const authUrl = new URL("https://app.midday.ai/oauth/authorize");
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("client_id", CLIENT_ID);
authUrl.searchParams.set("redirect_uri", REDIRECT_URI);
authUrl.searchParams.set("scope", "transactions.read invoices.read");
authUrl.searchParams.set("state", state);
res.redirect(authUrl.toString());
});
// Paso 2: Gestiona el callback e intercambia el código por tokens
app.get("/callback", async (req, res) => {
const { code, state, error } = req.query;
const sessionId = req.cookies.session_id;
const session = sessions.get(sessionId);
if (error) {
return res.send("Authorization denied");
}
// Verifica que el state coincide
if (state !== session?.state) {
return res.status(400).send("Invalid state");
}
// Intercambia el código por tokens
const response = await fetch("https://api.midday.ai/v1/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "authorization_code",
code,
redirect_uri: REDIRECT_URI,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
}),
});
const tokens = await response.json();
session.accessToken = tokens.access_token;
res.send("Connected successfully!");
});
// Paso 3: Usa el SDK con el token de acceso
app.get("/transactions", async (req, res) => {
const sessionId = req.cookies.session_id;
const session = sessions.get(sessionId);
if (!session?.accessToken) {
return res.status(401).send("Not connected");
}
const midday = new Midday({
token: session.accessToken,
});
const result = await midday.transactions.list({
pageSize: 50,
});
res.json(result);
});
app.listen(3000, () => {
console.log("Server running on http://localhost:3000");
});
#Gestiona tu app
#Edita los ajustes de la aplicación
- Ve a Ajustes → Desarrollador
- Haz clic en tu aplicación OAuth
- Actualiza los ajustes que necesites
- Haz clic en Guardar
#Regenera el client secret
Si tu client secret se ve comprometido:
- Ve a los ajustes de tu aplicación OAuth
- Haz clic en Regenerar secreto
- Actualiza tu app con el nuevo secreto de inmediato
El secreto anterior deja de funcionar de inmediato.
#Supervisa el uso
Haz seguimiento del uso de tu aplicación:
- Número de usuarios autorizados
- Llamadas a la API por día
- Tasa de errores
#Buenas prácticas de seguridad
#Almacena los secretos de forma segura
- Nunca subas
client_secretal control de versiones - Usa variables de entorno o un gestor de secretos
- Rota los secretos si es posible que se hayan expuesto
#Valida el parámetro state
Comprueba siempre que el parámetro state coincide con el que enviaste:
if (req.query.state !== storedState) {
throw new Error("Invalid state parameter");
}
#Usa HTTPS en todas partes
- Todas las URIs de redirección deben usar HTTPS (salvo localhost para desarrollo)
- Nunca envíes tokens por conexiones sin cifrar
#Solicita los scopes mínimos
Solicita solo los scopes que tu app realmente necesita. Los usuarios tienen más probabilidades de autorizar apps que piden un acceso limitado.
#Gestiona la caducidad de los tokens
- Detecta las respuestas 401 y renueva los tokens automáticamente
- Implementa una lógica adecuada de renovación de tokens
- Gestiona con elegancia la caducidad del refresh token
#Próximos pasos
- Referencia de scopes de OAuth: consulta todos los scopes disponibles
- Endpoints de la API OAuth: referencia técnica de endpoints
- Proceso de revisión de apps: consigue que verifiquen tu app
- Referencia de la API: documentación completa de la API