{"openapi":"3.1.0","info":{"title":"ads-engine API","version":"1.0.0","description":"API pública v1 que consume la app kekunda: descuentos de bancos/tarjetas/retail de Chile, tarjetas, telemetría y registro de dispositivos."},"servers":[{"url":"https://ads-engine.cl","description":"current host"}],"tags":[{"name":"discounts","description":"Descuentos y tarjetas"},{"name":"telemetry","description":"Eventos de uso"},{"name":"user","description":"Estado del usuario (tarjetas, dispositivo)"},{"name":"ops","description":"Salud y estado de fuentes"}],"paths":{"/api/v1/discounts":{"get":{"tags":["discounts"],"summary":"Lista descuentos activos","description":"Filtros opcionales por tarjetas, categoría, región y ubicación. Con lat+lng usa orden por distancia.","parameters":[{"name":"cards","in":"query","schema":{"type":"string"},"description":"Slugs separados por coma, ej. santander-black,santander-gold"},{"name":"category","in":"query","schema":{"type":"string","enum":["gastronomia","salud","retail","entretenimiento","viajes","supermercado","combustible","belleza","tecnologia","otros"]}},{"name":"region","in":"query","schema":{"type":"string"},"example":"Metropolitana"},{"name":"lat","in":"query","schema":{"type":"number"},"example":-33.4489},{"name":"lng","in":"query","schema":{"type":"number"},"example":-70.6693},{"name":"radius_km","in":"query","schema":{"type":"number","default":5}},{"name":"day","in":"query","schema":{"type":"integer","minimum":0,"maximum":6},"description":"Día de la semana (0=dom .. 6=sáb, JS). Un solo día (compat). Preferir `days`."},{"name":"days","in":"query","schema":{"type":"string"},"example":"1,4","description":"Días separados por coma (0=dom .. 6=sáb). Combinaciones, ej. \"1,4\" = lunes o jueves. Incluye los que aplican todos los días."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"discounts":{"type":"array","items":{"$ref":"#/components/schemas/Discount"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/api/v1/cards":{"get":{"tags":["discounts"],"summary":"Lista todas las tarjetas","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"cards":{"type":"array","items":{"$ref":"#/components/schemas/Card"}}}}}}}}}},"/api/v1/events":{"post":{"tags":["telemetry"],"summary":"Ingesta batch de eventos (máx 100)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["events"],"properties":{"events":{"type":"array","minItems":1,"maxItems":100,"items":{"$ref":"#/components/schemas/Event"}}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"accepted":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/api/v1/user/cards":{"post":{"tags":["user"],"summary":"Sincroniza tarjetas del usuario (replace-all)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user_hash","card_slugs"],"properties":{"user_hash":{"type":"string","minLength":8},"card_slugs":{"type":"array","maxItems":30,"items":{"type":"string"}}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"count":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/api/v1/user/device":{"post":{"tags":["user"],"summary":"Registra/actualiza token push del dispositivo","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user_hash","expo_push_token","platform"],"properties":{"user_hash":{"type":"string","minLength":8},"expo_push_token":{"type":"string","minLength":8},"platform":{"type":"string","enum":["ios","android","web"]},"app_version":{"type":"string"},"region":{"type":"string"},"language":{"type":"string","default":"es-CL"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/api/v1/waitlist":{"post":{"tags":["user"],"summary":"Registro a la waitlist (pre-lanzamiento)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"cards":{"type":"array","maxItems":20,"items":{"type":"string"}},"region":{"type":"string"},"source":{"type":"string"}}}}}},"responses":{"200":{"description":"OK (idempotente ante email duplicado)","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/api/v1/health":{"get":{"tags":["ops"],"summary":"Health check","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"active_discounts":{"type":"integer"},"db_latency_ms":{"type":"integer"},"version":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}}}}}}}},"/api/v1/sources/status":{"get":{"tags":["ops"],"summary":"Estado de las fuentes de scraping","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"sources":{"type":"array","items":{"$ref":"#/components/schemas/Source"}}}}}}}}}}},"components":{"responses":{"BadRequest":{"description":"Validación fallida","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"issues":{"type":"array","items":{"type":"object"}}}}}}}},"schemas":{"Discount":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["card","merchant"]},"status":{"type":"string","enum":["pending","active","spam","expired"]},"percent":{"type":"integer","nullable":true,"example":40},"amount_clp":{"type":"integer","nullable":true},"description":{"type":"string","nullable":true},"valid_from":{"type":"string","nullable":true},"valid_to":{"type":"string","nullable":true},"days_of_week":{"type":"array","items":{"type":"integer","minimum":0,"maximum":6},"description":"0=domingo … 6=sábado; vacío = todos los días"},"region":{"type":"string","nullable":true},"lat":{"type":"number","nullable":true},"lng":{"type":"number","nullable":true},"source_url":{"type":"string","nullable":true},"merchants":{"$ref":"#/components/schemas/Merchant"},"discount_cards":{"type":"array","items":{"type":"object","properties":{"card_id":{"type":"string"},"cards":{"$ref":"#/components/schemas/Card"}}}}}},"Merchant":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"canonical_name":{"type":"string","example":"Café Milan"},"category":{"type":"string","enum":["gastronomia","salud","retail","entretenimiento","viajes","supermercado","combustible","belleza","tecnologia","otros"],"nullable":true},"region":{"type":"string","nullable":true},"comuna":{"type":"string","nullable":true}}},"Card":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string","example":"santander-black"},"bank":{"type":"string","example":"Santander"},"name":{"type":"string"},"brand":{"type":"string","enum":["visa","mastercard","amex","cmr","other"]}}},"Event":{"type":"object","required":["user_hash","type"],"properties":{"user_hash":{"type":"string","minLength":8},"session_id":{"type":"string"},"app_version":{"type":"string"},"region":{"type":"string"},"type":{"type":"string","enum":["view","tap","used","search","filter","card_added"]},"discount_id":{"type":"string","format":"uuid"},"merchant_id":{"type":"string","format":"uuid"},"card_id":{"type":"string","format":"uuid"},"category":{"type":"string"},"query":{"type":"string"},"amount_clp":{"type":"integer"},"lat":{"type":"number"},"lng":{"type":"number"},"metadata":{"type":"object","additionalProperties":true}}},"Source":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["healthy","degraded","paused"]},"last_run_at":{"type":"string","format":"date-time","nullable":true},"last_run_count":{"type":"integer"},"consecutive_failures":{"type":"integer"}}}}}}