---
name: hoppscotch_run_request
kind: function
lang: py
domain: infra
version: "1.0.0"
purity: impure
signature: "def hoppscotch_run_request(method: str, url: str, *, title: str | None = None, headers: dict | None = None, body: str | None = None, body_type: str | None = None, variables: dict | None = None, access_token: str, backend_url: str = \"http://localhost:3170\", record_history: bool = True, timeout_s: float = 30.0, verify_tls: bool = True) -> dict"
description: "Ejecuta una peticion HTTP real (resolviendo placeholders <<var>>/{{var}} con un dict de variables) y la registra en el UserHistory de un Hoppscotch self-hosted via la mutation GraphQL createUserHistory, para que el humano la vea aparecer en vivo en la pestana History de su GUI (subscription userHistoryCreated). La request se ejecuta con las variables resueltas, pero en el History se guarda SIN resolver (con los literales <<var>>) igual que en el editor. resMetadata minimo: statusCode + duration. El access_token va como cookie, no como header Authorization."
tags: [hoppscotch, flow-replay, http]
uses_functions: [build_hoppscotch_collection_py_infra]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [json, re, requests]
params:
  - name: method
    desc: "metodo HTTP de la peticion (GET, POST, ...)."
  - name: url
    desc: "endpoint de la peticion. Puede contener placeholders <<var>> o {{var}} que se resuelven con `variables` antes de ejecutar."
  - name: title
    desc: "nombre visible de la request en el History. None = derivar de method + path via build_hoppscotch_collection."
  - name: headers
    desc: "dict name->value de cabeceras. Sus values tambien admiten placeholders <<var>>/{{var}}."
  - name: body
    desc: "cuerpo de la peticion como texto ya serializado. Admite placeholders. None = sin cuerpo."
  - name: body_type
    desc: "tipo de cuerpo para el HoppRESTRequest del History: 'json' | 'form' | 'raw' | None."
  - name: variables
    desc: "dict name->value para resolver los placeholders al EJECUTAR. Una variable que falte deja el literal intacto. None = no se resuelve nada."
  - name: access_token
    desc: "JWT de sesion (de hoppscotch_login). Viaja en la cookie access_token, NO en el header Authorization. Necesario para registrar en el History."
  - name: backend_url
    desc: "base del backend Hoppscotch self-host sin barra final. La mutation cuelga de {backend_url}/graphql. Default http://localhost:3170."
  - name: record_history
    desc: "si True y hay access_token, registra la request ejecutada en el UserHistory via createUserHistory. Default True."
  - name: timeout_s
    desc: "timeout en segundos de la peticion HTTP ejecutada (y del POST de History). Default 30.0."
  - name: verify_tls
    desc: "verificacion del certificado TLS de la peticion ejecutada. Default True."
output: "dict. En exito de la ejecucion HTTP: {status: 'ok', status_code: int, duration_ms: int, response_body: str (truncado a 5000 chars), response_headers: dict, recorded: bool, history_id: str|None}. Si la ejecucion fue ok pero el registro de History fallo, status sigue 'ok', recorded False y se anade history_error. Si la ejecucion HTTP falla (RequestException): {status: 'error', error: str, recorded: False}. Nunca lanza por errores de red esperables."
tested: true
tests:
  - "test_ejecuta_resolviendo_variables_angle"
  - "test_ejecuta_resolviendo_variables_brace"
  - "test_record_history_registra_request_sin_resolver"
  - "test_record_history_false_no_llama_create_user_history"
  - "test_request_exception_status_error"
  - "test_variable_faltante_conserva_literal"
test_file_path: "python/functions/infra/hoppscotch_run_request_test.py"
file_path: "python/functions/infra/hoppscotch_run_request.py"
---

## Ejemplo

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

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

# 2) Ejecutar una request con una variable y dejar rastro en el History de la GUI.
result = hoppscotch_run_request(
    "GET",
    "<<baseURL>>/api/status",
    title="Status",
    variables={"baseURL": "https://registry.organic-machine.com"},
    access_token=token,
)
print(result["status_code"], result["recorded"], result["history_id"])
# 200 True hist-...
# -> aparece en vivo en la pestana History del Hoppscotch self-host.
```

## Cuando usarla

Cuando el agente ejecuta una consulta HTTP y quiere que el humano la vea en el
History de su GUI Hoppscotch self-hosted, en vivo. La entry aparece via la
subscription `userHistoryCreated` sin que el humano refresque. Util para hacer
auditable/observable lo que el agente prueba: cada `hoppscotch_run_request` deja
en la pestana History la request (con sus variables sin resolver) y su statusCode
+ duracion. Encadena con `hoppscotch_login` para obtener el `access_token`.

## Gotchas

- **El access_token va como cookie, no como header Authorization.** La mutation
  `createUserHistory` lee el JWT de la cookie `access_token`. Se manda con
  `cookies={"access_token": ...}`. Si expira (~24h), re-loguea con
  `hoppscotch_login`.
- **reqData lleva la request SIN resolver.** Lo que se guarda en el History es el
  HoppRESTRequest con los placeholders `<<var>>`/`{{var}}` literales, igual que en
  el editor de la GUI, para que el humano vea la plantilla con sus variables y no
  los valores expandidos. La peticion SI se ejecuta con las variables resueltas.
- **Soporta `<<>>` y `{{}}`.** Hoppscotch usa `<<var>>`; muchas plantillas traen
  `{{var}}`. Ambas sintaxis se resuelven al ejecutar. Una variable que falte en
  `variables` deja el literal intacto (no rompe).
- **resMetadata minimo: statusCode + duration.** Se envia
  `{"statusCode": ..., "duration": ...}`. Si una version del backend exigiera mas
  campos, el registro fallaria con `history_error` (la ejecucion HTTP sigue siendo
  ok). Ajustar el shape si el self-host lo pide.
- **El body de respuesta se trunca a 5000 chars** en `response_body` del output,
  para no devolver payloads enormes. Los `response_headers` van completos.
- **duration_ms viene de `resp.elapsed`,** no de `time.time()`: es la latencia que
  midio `requests` para la peticion ejecutada.
- **Degradacion suave del History:** si la ejecucion HTTP fue ok pero el POST de la
  mutation falla (transporte, no-JSON, errores GraphQL, sin id), `status` sigue
  "ok", `recorded` es False y se anade `history_error` con el detalle.

## Capability growth log

v1.0.0 — version inicial. Ejecucion + registro en UserHistory del self-host;
resolucion de placeholders `<<>>`/`{{}}`.
