API de Chupilot
Versión 1 · la usa la app Android; no es una API pública.
Los reportes solo se aceptan si vienen de la app original y como máximo uno por hora por instalación y por IP. Para eso cada reporte pasa cuatro filtros, del más barato al más caro:
- Reto de un solo uso. La app pide un
nonceque vence en 5 minutos y solo sirve para su propia instalación. Un request copiado no se puede repetir. - Firma HMAC. La app firma el método, la ruta, la hora, el nonce, su ID y el hash del cuerpo con una llave compartida. Frena scripts caseros, pero la llave vive dentro del APK: no basta sola.
- Límite por hora. Se revisa la última vez que reportaron esa instalación y esa IP (IPv6 agrupada por /64). Se hace antes de llamar a Google para no gastar verificaciones en requests que se van a rechazar.
- Play Integrity. La app pide a Google Play un token amarrado a este request (
requestHash= SHA-256 de la cadena firmada). El servidor lo descifra con Google y exige app reconocida por Play y dispositivo íntegro.
Al final, una transacción de Firestore quema el nonce, vuelve a revisar el límite y guarda el reporte. Así el límite vale aunque Cloud Run tenga varias instancias o lleguen dos requests al mismo tiempo.
GET /api/v1/challenge
Encabezado X-Chupilot-Install: <uuid v4>. Máximo 30 por IP por hora.
{ "nonce": "…", "expires_in": 300, "integrity": "required" }POST /api/v1/margarita/report
{
"tequila": "g4",
"citric": "alma",
"limon": "fresco",
"presentacion": "cristal",
"lat": 19.4326,
"lng": -99.1332,
"place_id": "ChIJ...", // opcional
"place_name": "La Pasita" // opcional
}Los ingredientes son los IDs de la app (por ejemplo tres_tonos, cointreau_marnier, botella_plastico). El servidor recalcula el puntaje con su propia tabla; no acepta uno del cliente. Respuesta 201: { "ok": true, "score": 100, "verdict": "Gurmee" }.
POST /api/v1/ai/report
{ "text": "respuesta ofensiva…", "screen": "games" }Mismo protocolo y mismo límite de uno por hora, contado aparte del de margaritas.
POST /api/v1/favorito/report
{ "type": "tequila", "brand": "fortaleza", "aroma": 5, "sabor": 5, "suavidad": 4, "final": 5, "valor": 4 }Solo se aceptan marcas del catálogo de la app (favoritos.json) y del tipo elegido, y cada cualidad tiene que ser un paso de su slider: un entero del 1 al 5. El servidor calcula el score de 0 a 100. Mismo protocolo y límite de uno por hora, contado aparte.
GET /api/v1/favorito/ranking?type=tequila|mezcal
Público. Marcas ordenadas por promedio bayesiano, con el promedio y la etiqueta de cada cualidad.
GET /api/v1/margarita/bars
Público, sin firma. Parámetros opcionales min_reports y limit. Devuelve los bares ordenados por promedio bayesiano (rank_score): con pocos reportes el promedio se acerca al promedio general, así que un solo 100 no gana. Se cachea 60 segundos.
Encabezados de los reportes
| Encabezado | Valor |
|---|---|
X-Chupilot-Install | UUID v4 generado por la app al instalarse. |
X-Chupilot-Timestamp | Segundos Unix. Se acepta ±5 minutos. |
X-Chupilot-Nonce | El de /challenge. |
X-Chupilot-Signature | HMAC-SHA256 en hex de la cadena canónica. |
X-Chupilot-Integrity | Token estándar de Play Integrity. |
Cadena canónica, una línea por campo:
POST
/api/v1/margarita/report
1790000000
<nonce>
<install-id>
<sha256 hex del cuerpo>Errores
Todos responden { "error": "<código>", "message": "…", "retry_after": null }.
| HTTP | Código | Cuándo |
|---|---|---|
| 400 | bad_install_id | Falta X-Chupilot-Install o no es un UUID v4. |
| 401 | unsigned · stale · bad_signature · bad_nonce | Sin firma, hora desfasada más de 5 min, firma inválida o reto usado o vencido. |
| 403 | integrity | Play Integrity rechazó el request (solo con INTEGRITY_MODE=required). |
| 413 | too_large | Cuerpo de más de 8 KB. |
| 415 | json_only | El cuerpo no es application/json. |
| 422 | invalid | Campo desconocido, ingrediente o marca que no existe, valor fuera de los sliders o coordenadas fuera de rango. |
| 429 | rate_limited | Ya hubo un reporte en la última hora desde esa instalación o esa IP. Trae Retry-After. |