Autenticación y Claves API

La Clave API es lo único que identifica a quien llama. No hay usuario, no hay contraseña y no hay identificador de cuenta: el prefijo de la clave dice a qué cuenta pertenece, y el resto de la clave lo demuestra. Por eso ninguna petición de esta API lleva un campo con el id de la cuenta — mandarlo no serviría de nada, y no mandarlo no falta.

De ahí se sigue lo demás: quien tenga la clave puede hacer lo que la clave permita, en la cuenta de la clave. Una clave nunca va en código que se ejecute en el navegador del pagador.

Se manda en una de dos cabeceras, y nunca en la URL

Se manda una de las dos, indistintamente:

curl https://api.sandbox.aloha.co/api/external/v1/wallets \
  -H "X-API-KEY: pay_aBcD1234_test_CLAVE_DE_EJEMPLO"
curl https://api.sandbox.aloha.co/api/external/v1/wallets \
  -H "Authorization: Bearer pay_aBcD1234_test_CLAVE_DE_EJEMPLO"

Si llegan las dos, se lee la de Authorization y X-API-KEY se ignora sin avisar. Mandar las dos con valores distintos no da un error: da el resultado de una sola de ellas, y no la que se espera cuando el bug está justamente en la otra.

Una credencial no viaja nunca en la query string. Una URL se escribe entera en los registros del servidor, en los del proxy que haya en medio y en el historial del navegador, y se pega en tickets de soporte sin pensarlo; una cabecera no. Esa es la razón, y vale igual para Sandbox que para Producción.

El prefijo dice de qué cuenta es la clave y para qué Ambiente se creó

Una clave tiene la forma pay_<cuenta>_<ambiente>_<secreto>: el prefijo pay_, el identificador corto de la cuenta, el Ambiente —dev, test o live— y 48 caracteres de secreto. Esos tres primeros segmentos son los que viajan visibles: son los que el panel enseña como prefijo de la clave, y son lo único que queda de ella una vez cerrado el diálogo que la creó.

El segmento del Ambiente se fija al crear la clave y no se puede cambiar después, pero no lo elige quien la crea: el panel lo pone siempre en live, también en Sandbox. Está explicado en Ambientes y Sandbox, junto con lo que decide de verdad contra qué host responde una clave.

Cada operación exige un Permiso, salvo cuatro que no exigen ninguno

El panel los llama Permisos y la API los llama abilities; son la misma lista. Se marcan al crear la clave y se pueden ajustar después, sin sustituirla. Una operación compara el Permiso que exige contra los que lleva la clave, y la comparación es exacta: payment_links:read no abre payment_links:cancel. La única excepción es *, que los satisface todos.

OperaciónPermiso
POST /v1/payment-linkspayment_links:create
GET /v1/payment-links/{id}payment_links:read
DELETE /v1/payment-links/{id}payment_links:cancel
POST /v1/cards/payment-linkscard_payment_links:create
GET /v1/cards/payment-links/{id}card_payment_links:read
DELETE /v1/cards/payment-links/{id}card_payment_links:cancel
GET /v1/webhooks y GET /v1/webhooks/{id}webhooks:read
POST /v1/webhooks, PUT /v1/webhooks/{id}, DELETE /v1/webhooks/{id} y POST /v1/webhooks/{id}/rotate-secretwebhooks:write
GET /v1/wallets, GET /v1/payment-methods, POST /v1/conversions/quote y POST /v1/conversions/rateninguno

Las cuatro últimas no declaran Permiso alguno, así que cualquier clave activa de la cuenta llega a ellas lleve los Permisos que lleve — y 403 insufficient_scope no puede ocurrir ahí. Lo que sí exigen es una clave: una petición anónima se rechaza con 401 missing_api_key.

Tener el Permiso no siempre alcanza. Crear un cobro, en cualquiera de los dos carriles, exige además la cuenta verificada; leer y cancelar no lo exigen nunca. Esa distinción es del producto y está en Verificación de la cuenta.

Una clave lleva además cuatro cosas que no son permisos

Las cuatro se pueden cambiar en cualquier momento sin sustituir la clave. Tres se eligen al crearla; el Estado no, porque una clave nace activa. El Ambiente, que no está en esta lista, es lo único que no se puede cambiar nunca.

  • Un Límite de Peticiones propio, en peticiones por minuto. Una clave recién creada nace con 60 y admite cualquier valor entre 1 y 10000.
  • Unos Orígenes Permitidos, opcionales, y la opción con la trampa más cara de esta página. Con la lista vacía la clave no se comprueba por origen. Con la lista llena se rechaza toda petición cuyo Origin no esté en ella — y una petición sin cabecera Origin también se rechaza, que es el caso de cualquier llamada de servidor a servidor, curl incluido. Es decir: poner Orígenes Permitidos en una clave de backend la deja fuera de servicio entera, con un 403 en cada llamada. Esa lista es para claves que se usan desde un navegador. El valor * dentro de la lista vuelve a abrirla a todo, cabecera ausente incluida.
  • Una fecha de caducidad, opcional y por omisión ausente. Una clave caducada no se rechaza por caducada: la primera petición que llega después de la fecha la desactiva y contesta 401 invalid_api_key, igual que si el secreto estuviera mal. Una integración que deja de funcionar de golpe y sin haber cambiado nada es el síntoma.
  • Un Estado. Una clave desactivada deja de poder hacer peticiones, y borrarla rompe toda integración que la estuviera usando. Lo que no se lleva por delante son los endpoints de webhook que esa clave registró: siguen vivos, y se atienden aparte.

Los fallos no tienen una envoltura: tienen dos

Esta es la parte que más código rompe, y no se nota hasta que falla algo en Producción.

La autenticación ocurre en una capa que corre antes de los controladores. Esa capa contesta con error y code, sin success y sin message:

{ "error": "Invalid API key", "code": "invalid_api_key" }

Todo lo que hay más allá de esa capa contesta con la envoltura ordinaria, success, code y message, más un errors o un meta cuando hay algo que poner en ellos:

{ "success": false, "code": "PAYMENT_LINK_NOT_FOUND", "message": "Payment link not found" }

El código de estado no dice cuál de las dos llegó. Un cliente que lea message a ciegas recibe undefined en un 401 de la primera capa, que es exactamente el caso en el que más falta le hace saber qué pasó. La prueba fiable es qué clave trae el cuerpo: la envoltura de la capa de autenticación trae error, la ordinaria trae message. Y la decisión se toma sobre code, nunca sobre el estado a secas.

Hay un rechazo que no encaja limpiamente en ninguna de las dos y conviene tenerlo presente: el de origen bloqueado trae success: false —como la envoltura ordinaria— y sin embargo pone el texto bajo error —como la otra—. Comprobar solo si viene success no basta; hay que mirar error frente a message.

Los cinco 401 de la capa de autenticación

Todos llevan la envoltura de dos claves y el mensaje es fijo:

codeerrorCuándo
missing_api_keyAPI key requiredLa petición no trajo credencial en ninguna de las cabeceras aceptadas
invalid_key_formatInvalid API key formatEl prefijo de la clave no se pudo interpretar
invalid_tenantInvalid tenantEl prefijo se interpreta pero no corresponde a ninguna cuenta
tenant_init_failedTenant initialization failedLa cuenta se encontró y no se pudo cargar
invalid_api_keyInvalid API keyLa clave no verificó contra ninguna clave activa de la cuenta

Esa capa corre también antes que la validación del cuerpo, así que una petición sin clave y con un cuerpo inválido vuelve como 401 y no como 422.

El sexto 401, que viene en la envoltura ordinaria

Seis de las dieciséis operaciones —crear, leer y cancelar Solicitudes de Pago, y las tres equivalentes de los cobros con tarjeta— pueden contestar además así:

{ "success": false, "code": "INVALID_API_KEY", "message": "Invalid API key configuration" }

Significa otra cosa: la clave verificó, pero su registro está incompleto — le falta el usuario que la creó, que es por donde estas seis operaciones llegan a las Solicitudes de Pago. El fallo es de la clave, no de la cuenta, y la clave hay que reemplazarla. Las otras diez operaciones no pueden producirlo. Nótese que el code es el mismo nombre en mayúsculas que uno de los cinco de arriba, en otra envoltura y por otro motivo: distinguirlos por el texto no funciona.

Los 403

Dos rechazos comparten ese estado y ninguno de los dos usa la envoltura ordinaria:

{ "error": "Insufficient permissions", "code": "insufficient_scope" }
{ "success": false, "error": "Origin not allowed", "code": "ORIGIN_NOT_ALLOWED" }

El primero es la clave sin el Permiso que la operación exige, y solo alcanza a las doce operaciones que declaran uno. El segundo es el origen bloqueado, alcanza a las dieciséis, y solo puede ocurrir cuando la clave tiene Orígenes Permitidos configurados.

Hay un tercer grupo de 403 que sí usa la envoltura ordinaria y no es sobre la clave sino sobre la cuenta —una cuenta en pausa, por ejemplo—. Cada operación declara los suyos.

El 404 que no es sobre lo que se pidió

{ "success": false, "code": "USER_NOT_FOUND", "message": "User associated with API key not found" }

La clave verificó, pero el usuario que la creó ya no existe. Nada de la petición lo provoca y reintentar no lo arregla: la clave hay que reemplazarla. Lo pueden devolver las mismas seis operaciones que devuelven el 401 de la envoltura ordinaria; las otras diez, no.

Los dos 429

Hay dos límites y son independientes. Uno cuenta por IP —100 peticiones cada 60 segundos— y corre antes de la autenticación, así que las peticiones sin clave también cuentan y todos los que salgan por la misma IP comparten presupuesto. El otro es el Límite de Peticiones de la clave.

Contestan distinto, y los dos code se distinguen solo por las mayúsculas:

{ "success": false, "message": "Too many requests. Please try again later.", "code": "RATE_LIMIT_EXCEEDED", "retry_after": 37 }
{ "error": "Too many requests", "code": "rate_limit_exceeded", "retry_after": 37 }

El primero es el de IP, el segundo el de la clave. Solo el de IP pone la cabecera Retry-After, así que la espera se lee de retry_after en el cuerpo, que está en los dos. Las dos ventanas son fijas y no deslizantes: se abren con la primera petición y el contador se reinicia entero al expirar.

Crear una clave en el panel

Ninguna operación de esta API emite una Clave API: las claves se crean en el panel del Ambiente donde se van a usar.

La sección vive en el menú lateral, bajo Administrativo → Claves API.

La entrada Claves API en el menú lateral del panel

La lista muestra las claves de la cuenta con su nombre, su prefijo —nunca el secreto—, su Estado, cuándo se creó y cuándo se usó por última vez.

La lista de Claves API de la cuenta

Al crearla se decide todo lo que hay que decidir: un nombre, los Permisos que va a llevar y, si se quiere restringir su uso a ciertos dominios, los Orígenes Permitidos. El Ambiente no aparece en el diálogo, porque no se elige.

El secreto completo se muestra una sola vez, al terminar. Al cerrar ese diálogo ya no hay forma de recuperarlo: se copia en ese momento, o se sustituye la clave.

El secreto de la clave, visible una sola vez

Perder, rotar y revocar

El secreto se devuelve una sola vez, en la respuesta que crea la clave. Ninguna operación lo vuelve a entregar, así que una clave perdida se sustituye: se crea otra, se despliega, y solo entonces se borra la anterior.

Lo que ese reemplazo no toca son los endpoints de webhook, y conviene saberlo en las dos direcciones. Un endpoint no muere con la clave que lo registró: pierde la asociación con ella y sigue igual de vivo, con el mismo secret, en la misma lista y recibiendo las mismas entregas. Así que no hay que volver a registrarlo con la clave nueva — intentarlo se rechaza con 422 y code WEBHOOK_ALREADY_EXISTS, porque esa URL ya está registrada en la cuenta.

La otra dirección es la que importa cuando algo se ha filtrado: revocar una Clave API comprometida no cierra el endpoint que se registró con ella. El endpoint sigue entregando, y su secret —que nunca fue el de la clave— sigue siendo el mismo. Si lo filtrado alcanza a un endpoint, ese endpoint se rota o se borra por su cuenta, con su propia operación. Borrar la clave no lo hace y no avisa de que no lo ha hecho.

Seguir por aquí

  • Inicio rápido — las cuatro llamadas que van de la clave al cobro.
  • Ambientes y Sandbox — qué Ambiente lleva una clave y qué no se puede comprobar en Sandbox.
  • Glosario — qué significa cada término que aparece en esta página.

Did this page help you?