Prueba LUMA gratis 14 días, sin tarjeta.Empieza ahora

Endpoints de la API OAuth

Referencia técnica de los endpoints OAuth 2.0 de LUMA.

Referencia técnica completa de la implementación OAuth 2.0 de LUMA. Estos endpoints siguen las especificaciones OAuth 2.0 RFC 6749 y PKCE RFC 7636.

#URLs base

EntornoURL
Autorizaciónhttps://app.midday.ai/oauth
Token y APIhttps://api.midday.ai/v1

#Endpoint de autorización

Inicia el flujo OAuth redirigiendo a los usuarios para que inicien sesión y autoricen tu aplicación.

GET https://app.midday.ai/oauth/authorize

#Parámetros de la solicitud

ParámetroTipoObligatorioDescripción
response_typestringDebe ser code
client_idstringEl ID de cliente de tu aplicación
redirect_uristringURI a la que redirigir tras la autorización (debe estar registrada)
scopestringLista de scopes separados por espacios
statestringRecomendadoValor opaco para protección CSRF
code_challengestringPKCEHash SHA-256 del verificador de código, codificado en Base64-URL
code_challenge_methodstringPKCEDebe ser S256

#Ejemplo de solicitud

https://app.midday.ai/oauth/authorize?
  response_type=code&
  client_id=mid_client_abc123&
  redirect_uri=https://yourapp.com/callback&
  scope=transactions.read%20invoices.read&
  state=xyz789&
  code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
  code_challenge_method=S256

#Respuesta correcta

Redirige a tu redirect_uri con:

ParámetroDescripción
codeCódigo de autorización (válido durante 10 minutos)
stateEl mismo valor que enviaste (¡verifícalo!)
https://yourapp.com/callback?code=AUTH_CODE_HERE&state=xyz789

#Respuesta de error

Redirige a tu redirect_uri con:

ParámetroDescripción
errorCódigo de error
error_descriptionDescripción legible del error
stateEl mismo valor que enviaste
https://yourapp.com/callback?error=access_denied&error_description=User%20denied%20access&state=xyz789

#Códigos de error

CódigoDescripción
invalid_requestParámetro ausente o inválido
unauthorized_clientEl cliente no está autorizado para este tipo de concesión
access_deniedEl usuario denegó la autorización
invalid_scopeScope inválido o desconocido
server_errorError interno del servidor

#Endpoint de token

Intercambia códigos de autorización por tokens de acceso, o renueva tokens existentes.

POST https://api.midday.ai/v1/oauth/token

#Tipos de contenido

Acepta ambos:

  • application/json
  • application/x-www-form-urlencoded

#Concesión de código de autorización

Intercambia un código de autorización por tokens.

#Cuerpo de la solicitud

ParámetroTipoObligatorioDescripción
grant_typestringDebe ser authorization_code
codestringCódigo de autorización recibido en el callback
redirect_uristringMisma URI usada en la autorización
client_idstringEl ID de cliente de tu aplicación
client_secretstringClientes confidencialesEl secreto de tu cliente
code_verifierstringPKCEVerificador de código original

#Ejemplo de solicitud (cliente confidencial)

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": "https://yourapp.com/callback",
    "client_id": "mid_client_abc123",
    "client_secret": "mid_secret_xyz789"
  }'

#Ejemplo de solicitud (cliente público con PKCE)

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": "https://yourapp.com/callback",
    "client_id": "mid_client_abc123",
    "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
  }'

#Respuesta correcta

{
  "access_token": "mid_at_xxxxxxxxxxxxx",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "mid_rt_xxxxxxxxxxxxx",
  "scope": "transactions.read invoices.read"
}
CampoDescripción
access_tokenToken para las solicitudes a la API
token_typeSiempre Bearer
expires_inSegundos hasta la expiración (3600 = 1 hora)
refresh_tokenToken para obtener nuevos tokens de acceso
scopeScopes concedidos (separados por espacios)

#Concesión de refresh token

Obtén un nuevo token de acceso usando un refresh token.

#Cuerpo de la solicitud

ParámetroTipoObligatorioDescripción
grant_typestringDebe ser refresh_token
refresh_tokenstringRefresh token actual
client_idstringEl ID de cliente de tu aplicación
client_secretstringClientes confidencialesEl secreto de tu cliente
scopestringNoSolicita un subconjunto de los scopes originales

#Ejemplo de solicitud

curl -X POST https://api.midday.ai/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "refresh_token",
    "refresh_token": "mid_rt_xxxxxxxxxxxxx",
    "client_id": "mid_client_abc123",
    "client_secret": "mid_secret_xyz789"
  }'

#Respuesta

Mismo formato que la concesión de código de autorización. El refresh token puede rotar (se devuelve un token nuevo).

#Errores del endpoint de token

{
  "error": "invalid_grant",
  "error_description": "The authorization code has expired"
}
ErrorDescripción
invalid_requestFalta un parámetro obligatorio
invalid_clientCredenciales de cliente inválidas
invalid_grantCódigo o token inválido, expirado o ya usado
unauthorized_clientEl cliente no está autorizado para este tipo de concesión
unsupported_grant_typeTipo de concesión no compatible

#Endpoint de revocación

Revoca un token de acceso o un refresh token.

POST https://api.midday.ai/v1/oauth/revoke

#Cuerpo de la solicitud

ParámetroTipoObligatorioDescripción
tokenstringToken a revocar
client_idstringEl ID de cliente de tu aplicación
client_secretstringClientes confidencialesEl secreto de tu cliente

#Ejemplo de solicitud

curl -X POST https://api.midday.ai/v1/oauth/revoke \
  -H "Content-Type: application/json" \
  -d '{
    "token": "mid_at_xxxxxxxxxxxxx",
    "client_id": "mid_client_abc123",
    "client_secret": "mid_secret_xyz789"
  }'

#Respuesta

Siempre devuelve éxito, incluso si el token ya era inválido:

{
  "success": true
}

#Límites de peticiones

Los endpoints OAuth tienen límites de peticiones específicos para evitar abusos:

EndpointLímite
/oauth/authorize20 peticiones cada 15 minutos por IP
/oauth/token20 peticiones cada 15 minutos por IP
/oauth/revoke20 peticiones cada 15 minutos por IP

Superar los límites devuelve 429 Too Many Requests.


#Vida útil de los tokens

Tipo de tokenVida útilNotas
Código de autorización10 minutosUso único
Token de acceso1 horaUsa el refresh token para renovarlo
Refresh token30 díasRota con cada uso

#Implementación de PKCE

PKCE añade seguridad para clientes públicos (apps móviles, SPA).

#1. Genera el verificador de código

Crea una cadena aleatoria (43-128 caracteres, segura para URL):

function base64UrlEncode(buffer: Uint8Array): string {
  return btoa(String.fromCharCode(...buffer))
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
}

function generateCodeVerifier(): string {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return base64UrlEncode(array);
}

#2. Crea el code challenge

Hash SHA-256 del verificador, codificado en base64-URL:

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));
}

#3. Úsalo en el flujo

  1. Guarda el code_verifier de forma segura (almacenamiento de sesión)
  2. Envía el code_challenge en la solicitud de autorización
  3. Envía el code_verifier en el intercambio del token

#Consideraciones de seguridad

#Parámetro state

Usa y valida siempre el parámetro state:

// Generar
const state = crypto.randomUUID();
sessionStorage.setItem("oauth_state", state);

// Validar en el callback
const storedState = sessionStorage.getItem("oauth_state");
if (callbackState !== storedState) {
  throw new Error("State mismatch - possible CSRF attack");
}

#Validación de la redirect URI

  • Registra todas las redirect URIs en los ajustes de tu aplicación
  • Usa validación de coincidencia exacta (sin comodines)
  • Usa siempre HTTPS en producción

#Almacenamiento de tokens

  • Guarda los tokens de forma segura (cifrados, preferiblemente en el servidor)
  • No expongas nunca los tokens en URLs o logs
  • Elimina los tokens al cerrar sesión

#Protección del client secret

  • No incluyas nunca los client secrets en código de cliente
  • Usa variables de entorno en los servidores
  • Rota los secretos si se ven comprometidos

#Ejemplos de manejo de errores

#Gestionar errores de autorización

app.get("/callback", (req, res) => {
  const { error, error_description, code, state } = req.query;
  
  if (error) {
    console.error(`OAuth error: ${error} - ${error_description}`);
    return res.redirect("/connect?error=" + encodeURIComponent(error as string));
  }
  
  // Verificar el state
  if (state !== req.session.oauthState) {
    return res.status(400).send("Invalid state");
  }
  
  // Intercambiar el código por tokens
  // ...
});

#Gestionar errores de token

async function exchangeCode(code: string) {
  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,
    }),
  });
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Token error: ${error.error} - ${error.error_description}`);
  }
  
  return response.json();
}

#Gestionar fallos de refresco

async function refreshTokens(refreshToken: string) {
  try {
    const response = await fetch("https://api.midday.ai/v1/oauth/token", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        grant_type: "refresh_token",
        refresh_token: refreshToken,
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET,
      }),
    });
    
    const tokens = await response.json();
    
    if (tokens.error) {
      // El refresh token expiró o fue revocado
      // Redirigir al usuario para que vuelva a autorizar
      return null;
    }
    
    return tokens;
  } catch (error) {
    console.error("Refresh failed:", error);
    return null;
  }
}

#Usar el SDK con tokens OAuth

Una vez que tengas un token de acceso, usa el SDK de Midday para las solicitudes a la API:

import { Midday } from "@midday-ai/sdk";

const midday = new Midday({
  token: accessToken, // Token de acceso OAuth
});

// Listar transacciones
const transactions = await midday.transactions.list({
  pageSize: 50,
});

// Obtener facturas
const invoices = await midday.invoices.list({
  statuses: ["unpaid", "overdue"],
});

// Obtener métricas financieras
const profit = await midday.metrics.profit({
  from: "2024-01-01",
  to: "2024-12-31",
});

Consulta la documentación del SDK para ver todos los métodos disponibles.


#Relacionado