Ir al contenido

Acceso, activación y rate limit

EMA Emi no autentica usuarios finales. Asume que el producto cliente ya autenticó al usuario y que la conversación viene firmada con la API key del producto. El control de acceso vive en cuatro capas:

1. API key (¿qué producto está hablando?)
2. App scope (¿qué agente puede invocar este producto?)
3. Activación (¿este tenant tiene acceso a esta app?)
4. Rate limit (¿pasó el cupo diario de invocaciones?)

Capas 3 y 4 son nuevas en Wave 3 y se evalúan en checkAccess.

Capa 1 — API key (autenticación de la app)

Sección titulada «Capa 1 — API key (autenticación de la app)»

Cada producto cliente tiene su propia API key. El header Authorization: Bearer <key> la identifica:

SecretProductoAgente
EMAHEALTH_API_KEYemahealth.iosales
EMACLINIC_API_KEYemaclinicsupport
EMALAB_API_KEYemalablab_assistant (alpha)
EMAVAULT_API_KEYemavaultanalytics
EMAPATH_API_KEYemapathpathology_assistant (planned)
EMAWELL_API_KEYemawellwellness_coach (501)

Sin API key válida → 401.

permissions.ts → APP_SCOPES define qué puede invocar cada producto. Mapeo 1:1 entre app y agente. Tools por agente:

AppAgenteTools
emahealthsaleslead_management
emaclinicsupportaidbox per-tenant (11 tools)
emavaultanalyticssupabase-vault (6 tools)
emalablab_assistant(Wave 5+)
emapathpathology_assistant(Wave 6)
emawellwellness_coach— (501)

Si un cliente intenta invocar algo fuera de su whitelist, el middleware rechaza la request.

Para apps que requieren tenant (emaclinic, emalab, emapath, emawell), el header X-Tenant-Id: <uuid> es obligatorio:

POST /api/chat
Authorization: Bearer EMACLINIC_API_KEY
X-Tenant-Id: 8c3a2f10-3e5a-4f7e-bc1d-...

Si falta → 400 X-Tenant-Id header required.

Para emahealth (sitio público) y emavault (dashboard interno), X-Tenant-Id es opcional (F002).

Una vez resuelto el tenant, checkAccess consulta emashared.tenant_emi_access:

CasoResultado
No existe row para el (tenant_id, app)403 inactive
status != 'active'403 inactive
Supabase query falla403 (fail-closed, F003)

tenant_emi_access.daily_invocation_limit define cuántas invocaciones exitosas puede hacer el tenant en un día UTC.

Comportamiento:

  • Cuenta emi_invocations con status='success' y invoked_at desde las 00:00 UTC del día actual.
  • Si la query del count falla → fail-open (F003). No queremos derribar tenants legítimos por una sola query lenta.
  • Si supera el límite → 403 rate limited.

Decisión F004: solo invocaciones exitosas cuentan. Deniegos no se penalizan dos veces.

Diferencia entre usuario autenticado vs anónimo

Sección titulada «Diferencia entre usuario autenticado vs anónimo»
  • SalesAgent (emahealth): el chatbot del website lo invocan visitantes anónimos. El widget firma con la API key del producto, no con la sesión del visitante. El ChatContext puede traer sessionId, visitorEmail, referrer para tracking.
  • SupportAgent / AnalyticsAgent / LabAssistant / PathologyAgent: los productos solo exponen Emi a usuarios logueados. El frontend usa apiFetch para incluir el token de Supabase en la request al worker proxy del producto, y ese worker es el que llama a emaemi con la API key correcta + X-Tenant-Id.

Cada request — exitosa, denegada o con error — genera una fila en emi_invocations. Es fail-silent: un error en audit nunca bloquea la respuesta al usuario (F006).

Lo que se guarda (sin PHI, F005):

  • timestamp, app, agent, tenant_id, status
  • turn_count, input_tokens, output_tokens, cost_usd
  • context_summary con presencia (has_patient_id, etc.) y metadata no-PHI (action, locale, route)

Lo que nunca se guarda: prompts completos, responses completos, ni ningún valor PHI.