---
name: hoppscotch_login
kind: function
lang: py
domain: infra
version: "1.0.0"
purity: impure
signature: "def hoppscotch_login(email: str, *, backend_url: str = \"http://localhost:3170\", mailpit_url: str = \"http://localhost:8025\", timeout_s: float = 15.0) -> dict"
description: "Login headless contra un Hoppscotch self-hosted via magic link, leyendo el correo de verificacion desde una instancia Mailpit de pruebas. Reproduce el flujo sin navegador: POST /v1/auth/signin (deviceIdentifier) -> lee el correo 'Sign in' del email en Mailpit -> extrae el token (?token=...) del cuerpo -> POST /v1/auth/verify (Set-Cookie access_token + refresh_token). Devuelve los JWT de sesion que las mutations GraphQL protegidas esperan en la cookie access_token."
tags: [hoppscotch, flow-replay, http, infra, auth]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [re, requests]
params:
  - name: email
    desc: "correo del usuario que inicia sesion. Debe poder recibir el correo de verificacion en la instancia Mailpit indicada (en el self-host de pruebas, admin@example.com)."
  - name: backend_url
    desc: "base del backend Hoppscotch sin barra final. Los endpoints REST de auth cuelgan de {backend_url}/v1/auth/signin y /v1/auth/verify. Default http://localhost:3170."
  - name: mailpit_url
    desc: "base de la API de Mailpit donde aterriza el correo de verificacion, sin barra final. Default http://localhost:8025."
  - name: timeout_s
    desc: "timeout por request HTTP en segundos. Default 15.0."
output: "dict. En exito: {status: 'ok', access_token: str, refresh_token: str, email: str}. En error (signin != 201, no llega correo 'Sign in', token no encontrado en el correo, verify != 200, o fallo de transporte): {status: 'error', error: str}. Nunca lanza por errores de red esperables."
tested: true
tests:
  - "test_golden_login_devuelve_tokens"
  - "test_verify_recibe_token_extraido_y_device_identifier"
  - "test_error_signin_no_201"
  - "test_error_correo_no_encontrado"
  - "test_error_token_no_en_correo"
test_file_path: "python/functions/infra/hoppscotch_login_test.py"
file_path: "python/functions/infra/hoppscotch_login.py"
---

## Ejemplo

```python
import sys
sys.path.insert(0, "python/functions")
from infra.hoppscotch_login import hoppscotch_login
from infra.hoppscotch_create_request import hoppscotch_create_request

# 1) Obtener un JWT de sesion via magic link (headless, lee el correo de Mailpit).
login = hoppscotch_login("admin@example.com")
assert login["status"] == "ok", login["error"]
token = login["access_token"]

# 2) Usar el token para crear una request en una team collection.
#    El self-host de referencia exige team_id dentro del input.
created = hoppscotch_create_request(
    collection_id="cmq8lt8ta000t0xls4ddy6sdz",
    method="GET",
    url="https://api.example.com/ping",
    title="Ping",
    team_id="cmq8kn0v500030xls1nvminjy",
    access_token=token,
)
print(created)  # {"status": "ok", "id": "...", "title": "Ping"}
```

## Cuando usarla

Cuando necesites un JWT de sesion de un Hoppscotch self-hosted para operar su API
GraphQL protegida (crear/editar/borrar requests, gestionar collections) sin abrir
el navegador. Es el primer paso de cualquier flujo CRUD del grupo `hoppscotch`:
llama esto, captura `access_token`, y paselo a `hoppscotch_create_request` /
`hoppscotch_update_request` / `hoppscotch_delete_request` / `hoppscotch_list_requests`.
Requiere que el backend mande el correo de verificacion a una instancia Mailpit
accesible (entorno de pruebas).

## Gotchas

- **El access_token va como cookie, no como header Authorization.** Las mutations
  GraphQL leen el JWT de la cookie `access_token`. Cada funcion del grupo lo manda
  con `cookies={"access_token": ...}`.
- **El token expira (~24h).** Cuando una llamada GraphQL devuelva un error de auth,
  re-loguea con `hoppscotch_login` para obtener un access_token fresco.
- **Depende de Mailpit.** El flujo lee el correo de verificacion de una instancia
  Mailpit de pruebas. No funciona contra un backend que mande el correo a un buzon
  real al que esta funcion no pueda consultar por API.
- **Secreto — nunca logear el token en crudo.** `access_token`/`refresh_token` son
  credenciales de sesion. No los imprimas ni los persistas en claro; trataelos como
  un secreto (vault/pass) si los guardas entre ejecuciones.
- **Coincidencia del correo por subject + destinatario.** Se elige el mensaje mas
  reciente cuyo destinatario sea `email` y cuyo subject contenga "Sign in". Si hay
  varios magic links pendientes para el mismo email, se usa el ultimo de la lista.

## Capability growth log

v1.0.0 — version inicial. Flujo magic link headless validado contra el self-host
vivo (login + CRUD completo) el 10/06/2026.
