Gemini Developer API: configurar un proxy HTTP y diagnosticar la conexión
Enruta google-genai 2.30.1 por HTTPS_PROXY y CONNECT, conserva TLS con SSL_CERT_FILE y separa un fallo de transporte de un 403 o un timeout.
Esta guía es para quien programa contra la Gemini Developer API con google-genai 2.30.1 y necesita pasar el cliente por un proxy HTTP, luego leer un error de conexión. Los ejemplos usan datos dummy. Top Proxy no vende IPs ni reparte claves. Una IP vista en el navegador con Mi IP no demuestra la ruta de este SDK.
01Qué cliente Gemini usar en este recorrido
Gemini no es un único socket. La web de consumidor y la app móvil siguen el proxy del sistema o el de la propia app. Eso no se configura con HTTPS_PROXY en Python. La Gemini CLI es otro binario, con su propio arranque. Vertex AI, ahora documentado también como Gemini Enterprise Agent Platform, pide proyecto, región y credenciales de Cloud. Mezclar esas cuatro superficies en un solo procedimiento inventa interfaces.
Este texto toma un cliente versionado y documentado: el paquete Python google-genai 2.30.1 contra la Gemini Developer API, con una clave de API. La README del 15 de octubre de 2026 dice que el cliente síncrono y el asíncrono usan httpx por defecto. aiohttp entra solo si instalas el extra google-genai[aiohttp]. En este entorno aiohttp no estaba instalado. El transporte visible se llamó SyncHttpxClient. El extra aiohttp queda sin probar. Instalarlo no cambia esta receta síncrona de HTTPX.
La documentación de la API habla de modelos, claves y llamadas. No habla de proxy. La guía de troubleshooting, con last-modified 13 de octubre de 2026, cubre 429, 403, timeouts y errores de red. No menciona HTTPS_PROXY. El proxy vive en la README del SDK y en httpx, no en una suposición sobre tu cuenta de Google.
Un proxy cambia la ruta TCP. No cambia si la clave es válida, si hay facturación ni si tu país aparece en regiones disponibles. Esa página, last-modified 28 de abril de 2026, lista Spain y Mexico entre los países de Google AI Studio y la Gemini API. Es una línea de documentación. No es una promesa para tu proyecto ni para un datacenter concreto.
Fija la versión antes de tocar variables de entorno. google-genai 2.30.1 tiró de httpx 0.28.1 en este intérprete Python 3.14.6, con OpenSSL 3.6.3 dentro de Python.
python3.14 -m venv .venv
.venv/bin/pip install 'google-genai==2.30.1' 'httpx==0.28.1'
.venv/bin/python -c "import google.genai, httpx; print(google.genai.__version__, httpx.__version__)"
Si la salida no es 2.30.1 y 0.28.1, los campos de más abajo no te valen. Paquetes viejos usaban otros nombres de cliente. Paquetes nuevos pueden mover HttpOptions otra vez.

02Cómo declarar el proxy que el SDK sí lee
La README de python-genai, sección Proxy, líneas 269 a 291 del main leído el 15 de octubre de 2026, pide las variables antes de crear el cliente:
export HTTPS_PROXY='http://usuario:***@proxy_uri:puerto'
export SSL_CERT_FILE='client.pem'
Guarda este cliente, instala el pin de arriba y ejecútalo en el mismo shell. HTTPS_PROXY y SSL_CERT_FILE salen del entorno. Sin GEMINI_BASE_URL el SDK usa el host por defecto de Google. Con GEMINI_BASE_URL apuntas a un origen tuyo, por ejemplo un mock local. Este texto no llamó a Google.
#!/usr/bin/env python3
"""google-genai 2.30.1: clave del entorno, HTTPS_PROXY y cierre del cliente."""
from __future__ import annotations
import os
import sys
from google import genai
from google.genai import types
def main() -> int:
api_key = os.environ.get("GEMINI_API_KEY") or os.environ.get("GOOGLE_API_KEY")
if not api_key:
print("Falta GEMINI_API_KEY o GOOGLE_API_KEY", file=sys.stderr)
return 2
http_options = types.HttpOptions()
base_url = os.environ.get("GEMINI_BASE_URL")
if base_url:
http_options.base_url = base_url
client = genai.Client(api_key=api_key, http_options=http_options)
try:
response = client.models.generate_content(
model="gemini-flash-latest",
contents="ping",
)
print(response.text)
finally:
client.close()
return 0
if __name__ == "__main__":
raise SystemExit(main())
export GEMINI_API_KEY='***'
# opcional, origen propio; si omites esta variable vas a la API de Google
# export GEMINI_BASE_URL='https://127.0.0.1:PUERTO'
.venv/bin/python ejemplo_cliente.py
Este script pasa api_key explícito: primero GEMINI_API_KEY y, si falta, GOOGLE_API_KEY. Ese argumento gana. Si creas Client() sin api_key, el SDK lee el entorno y, si ambas variables existen, gana GOOGLE_API_KEY.
httpx y aiohttp leen, según esa misma README, urllib.request.getproxies. Es la lista del entorno, no un argumento mágico proxy= del SDK. google-genai 2.30.1 no pasa proxy= a httpx.Client si dejas client_args vacío. El trust_env por defecto de httpx sigue activo. Si exportas HTTPS_PROXY después de genai.Client(...), llegas tarde.
El esquema del proxy y el esquema del destino son dos botones distintos. El destino de la Developer API es HTTPS. Un proxy HTTP que hable CONNECT basta para ese hop. Un https:// delante de la URL del proxy no es el caso documentado en esa README.
SOCKS5 aparece aparte. httpx lo admite si instalas httpx[socks] y pasas client_args={'proxy': 'socks5://usuario:pass@host:puerto'} en types.HttpOptions. Eso no se ejecutó aquí. HTTPS_PROXY=socks5://... sin ese extra no es un camino demostrado.
En este laboratorio, con el extra de SOCKS sin instalar, sí se observó otra vía de httpx: HttpOptions(client_args={'proxy': 'http://127.0.0.1:57863'}) produjo CONNECT al origen local. La README ilustra client_args con socks5://, no con http://. El hallazgo vale para httpx 0.28.1 en este build. No sustituye el camino documentado HTTPS_PROXY y no prueba SOCKS.
El comprobador de proxies prueba en el servidor los endpoints que tú le pasas. Mi IP muestra la salida que ve el navegador. Ninguno de los dos demuestra la ruta de google-genai. Filtrar proxies HTTP o HTTPS en el catálogo tampoco enruta el SDK. Un proxy móvil cambia el tipo de salida. No concede cupo de Gemini.
- Fija
google-genai==2.30.1yhttpx==0.28.1. - Exporta
HTTPS_PROXYcon host, puerto y usuario si hace falta, antes de arrancar Python. - Guarda
ejemplo_cliente.py, exportaGEMINI_API_KEYy ejecuta el script. - Mira el log del proxy buscando CONNECT, no solo el texto que imprime Python.

El orden GEMINI-then-GOOGLE de este script no es el default automático del SDK. Sin api_key en el constructor, si ambas variables existen, gana GOOGLE_API_KEY. No mezcles una clave real en el mismo shell donde depuras el proxy.
03Cómo comprobar el CONNECT en local
Un curl al proxy o un ping desde el navegador no equivalen a client.models.generate_content. El SDK arma una URL /v1beta/models/{modelo}:generateContent sobre la base del cliente. En este laboratorio esa ruta exacta llegó al origen loopback: /v1beta/models/gemini-flash-latest:generateContent.
Para no tocar Google se usó HttpOptions.base_url apuntando a https://127.0.0.1:57862. La README documenta base_url sobre todo para un API gateway en el camino enterprise. Aquí sirvió para clavar el destino en un origen propio. El JSON de respuesta iba marcado como SIMULADO. Un 200 local no es cupo, región ni facturación.
El proxy del laboratorio solo implementó CONNECT. El origen era HTTPS con una CA emitida para IP:127.0.0.1. Python 3.14.6, con OpenSSL 3.6.3, rechazó un CA hecho con LibreSSL 3.3.6 porque faltaba Authority Key Identifier. El CA que sí pasó se emitió con /opt/homebrew/bin/openssl OpenSSL 3.6.3. Si tu laboratorio usa el openssl de macOS por defecto, puedes reproducir un falso fallo de TLS que no es el proxy.
Caso https_proxy_env: HTTPS_PROXY=http://127.0.0.1:57863, SSL_CERT_FILE con esa CA, clave dummy. urllib.request.getproxies() devolvió solo https -> http://127.0.0.1:57863. El proxy registró CONNECT a 127.0.0.1:57862. El SDK devolvió el texto simulado en 0.003 s. Transporte: SyncHttpxClient.
Caso directo_sin_proxy: mismas CA y base_url, sin variables de proxy. getproxies() vacío. Cero eventos CONNECT. El origen recibió el POST igual. Eso prueba que el laboratorio responde sin túnel. No prueba un proxy de proveedor.
No hace falta un fixture enorme. Te bastan tu proxy, un destino que controles y el SDK instalado. Si apuntas base_url al host real de Google, sales de este texto: hay clave, hay cuota y hay regiones. Este artículo no hizo esa llamada.
Los puertos 57862 y 57863 se cerraron al terminar el proceso. Un connect posterior a esos puertos devolvió connection refused. No se tocó el proxy del sistema ni el almacén de confianza de macOS.
04Cómo dejar TLS encendido y ocultar secretos
La README pone SSL_CERT_FILE al lado del proxy, opcional. HTTPX 0.28.1 sí lee ese archivo: con verify=True y trust_env=True, ambos por defecto, carga la CA desde SSL_CERT_FILE. Lo documenta Environment Variables de HTTPX. google-genai 2.30.1 no deja esa rama. _ensure_httpx_ssl_ctx construye un ssl.SSLContext con cafile=os.environ.get('SSL_CERT_FILE', certifi.where()) y se lo pasa a httpx como verify=. Si verify ya es un contexto, HTTPX no vuelve a abrir el archivo. En este cliente cuenta el contexto del SDK. El comentario histórico del SDK no describe HTTPX en general. Exporta el archivo antes de Client. Un export posterior no reabre el socket.
verify=False no entra en este procedimiento. Apagar TLS no demuestra que el proxy tunelice. Demuestra que nadie mira el certificado.
Un CONNECT ordinario no inserta una CA. Solo si hay interceptación TLS, el proxy termina HTTPS y reemite el certificado, hace falta la CA de esa interceptación en SSL_CERT_FILE. Sin interceptación, el certificado es el del destino. Un PEM de otro laboratorio no sirve si el origen se reemitió. localhost, 127.0.0.1 y generativelanguage.googleapis.com son nombres distintos. El certificado tiene que coincidir con el host que CONNECT anuncia.
Caso proxy_sin_ca_lab: HTTPS_PROXY igual, sin SSL_CERT_FILE. CONNECT a 127.0.0.1:57862 quedó autorizado. El origen no registró POST. httpx levantó ConnectError con CERTIFICATE_VERIFY_FAILED y el texto unable to get local issuer certificate. El túnel existía. certifi no confiaba en la CA del laboratorio. Eso no es un proxy caído.

No dejes usuario, contraseña ni clave de API en git ni en capturas. Un traceback de httpx puede arrastrar la URL del proxy si está en el entorno. Redacta antes de pegar el error en un ticket. Este laboratorio usó la etiqueta DUMMY-LAB-KEY-NOT-GOOGLE y la sustituyó por *** en la salida guardada.
- Si hay interceptación TLS, coloca la CA de esa interceptación. Si no, la CA del destino.
- Exporta
SSL_CERT_FILEantes de crear el cliente. - Para separar TLS del túnel, el mismo CONNECT puede autorizarse y fallar después el certificado. Eso no es un proxy apagado.
- No dejes
verify=Falsecomo apaño.
05Cómo separar un fallo de transporte de un 403 o un timeout
El troubleshooting oficial pide reintentos con backoff en 429 y 5xx, y no reintentar 400, 402 o 403. También afirma que el SDK de Python reintenta timeouts y 429 por defecto. En google-genai 2.30.1, HttpOptions.retry_options vale None. retry_args(None) corta en un intento. Los cuatro reintentos de _RETRY_ATTEMPTS = 5 (llamada inicial más cuatro) solo existen si configuras retry_options. Este build no reintentó solo.
Tres fallos distintos, todos locales:
| Caso | Qué ocurrió | Qué no es |
|---|---|---|
https_proxy_env | CONNECT a 127.0.0.1:57862 y JSON SIMULADO | Éxito de producción Gemini |
proxy_sin_ca_lab | CONNECT sí, luego CERTIFICATE_VERIFY_FAILED | Proxy apagado |
proxy_puerto_muerto | ConnectError: [Errno 61] Connection refused, cero CONNECT | 403 de API |
simulado_403 | CONNECT sí, luego ClientError: 403 PERMISSION_DENIED con mensaje SIMULADO | Fallo de túnel |
timeout_corto | CONNECT sí, HttpOptions.timeout=200 ms, ReadTimeout a 0.208 s | Cuota de Google |
El 403 se fabricó en el origen loopback cuando el cuerpo llevaba FORCE_403. No es una denegación de cuenta real. Un 403 ya recibido es respuesta de aplicación. Puede llegar con CONNECT o por ruta directa, sin proxy y con getproxies() vacío. La falta de CONNECT en un log no convierte ese 403 en un fallo de TCP. Si no hay log, o el cliente no usó proxy, no infieras transporte solo por la ausencia del evento. CONNECT en el proxy demuestra el hop. Un Connection refused sin cuerpo HTTP es otro asunto.
proxy_puerto_muerto usó HTTPS_PROXY=http://127.0.0.1:1. Nadie escuchaba. El error salió en 0.002 s, sin evento CONNECT y sin 403. Confundir un refused con un 403 te manda a revisar claves cuando no hubo respuesta de aplicación.
El timeout corto no prueba streaming ni generate_content_stream. No se llamó a la API asíncrona. HttpOptions.timeout en este SDK va en milisegundos. 200 ms y un origen que dormía 1.5 s bastaron para ReadTimeout. Con retry_options=None no hubo segunda espera larga.

Async con aiohttp y SOCKS quedan sin probar. El extra aiohttp no invalida la receta síncrona de HTTPX de arriba. No copies estos tiempos.
06Qué protocolo de proxy encaja con este cliente
google-genai 2.30.1, con httpx 0.28.1 y sin extra SOCKS, envió HTTP CONNECT en los casos con proxy de este laboratorio. El destino era HTTPS. Un proxy HTTP con CONNECT es el encaje observado. Un proxy que solo haga GET adelante, sin CONNECT, no se midió y no se da por bueno.
La autenticación del proxy en la README va en la propia URL: http://usuario:***@host:puerto. Este laboratorio no exigió usuario. Un 407 no se provocó. Si tu proveedor pide usuario y contraseña, mételos en HTTPS_PROXY y redacta la URL en logs. No asumas que un header Proxy-Authorization suelto llega al SDK.
TLS hacia el proxy (https:// en la URL del proxy) no se ejecutó. SOCKS5 no se ejecutó. Residencial frente a datacenter no cambia el verbo CONNECT. Cambia reputación, rotación y precio. Para un login ruidoso a menudo se mira salida móvil. Eso no otorga región Gemini.
El comprobador sigue siendo útil antes de cablear el SDK: le das host y puerto y el servidor los sondea. Mi IP solo habla de la salida del navegador. Ninguno comparte el httpx.Client de Python. Después, el log de CONNECT del propio proxy es la prueba de ruta de este proceso.
El encaje de protocolo es documental, no una medida contra el gateway de pago. La ficha de Oxylabs está publicada en español. Su README de residential proxies lista HTTP, HTTPS y SOCKS5, y autenticación con usuario y contraseña o IP allowlist, con ejemplo curl -x http://pr.oxylabs.io:7777 -U customer-USERNAME:PASSWORD. La ficha de Decodo (Smartproxy) también está publicada. El quick start HTTP usa http://usuario:***@gate.decodo.com:7000 y pide usuario o IP en allowlist. No se llamó a esos gateways ni se midió google-genai contra ellos. La ficha no sustituye tu prueba con HTTPS_PROXY.

Oxylabs vende proxies residenciales, de datacenter y móviles. El residencial de autoservicio empieza en $6/GB para 5 GB/mes. Pagos del comprador: tarjetas, transferencia, AliPay, PayPal.

Decodo (antes Smartproxy) vende proxies residenciales y de datacenter. Residencial: $4/GB sin plan mensual, o paquetes mensuales desde $3.75/GB por 3 GB. Pagos: tarjetas, PayPal, Alipay, Google/Apple Pay, cripto (con límites).
Si el proveedor solo documenta SOCKS5, no lo fuerces en este cliente hasta instalar httpx[socks] y repetir la medida. Datacenter aparece en proxies datacenter. Puede valer para un hop de desarrollo. No promete pasar un filtro de cuenta.

Cierra el cliente con client.close() o un with Client() as client. La README lo pide para no dejar conexiones httpx abiertas. En el laboratorio cada caso cerró el cliente antes de limpiar el entorno.
¿HTTPS_PROXY configura google-genai para la Gemini API?
¿Un proxy me da acceso a Gemini en España o en México?
¿Por qué veo CERTIFICATE_VERIFY_FAILED si el proxy ya hizo CONNECT?
¿El comprobador del navegador demuestra que el SDK usa el proxy?
¿Un 403 significa que el proxy está mal?
¿google-genai reintenta solo los timeouts?
¿Puedo usar la misma receta en Vertex o en la CLI?
Artículos relacionados
Proxy con curl: configuración, autenticación y errores
Configura curl con un proxy HTTP o SOCKS5, separa -U de -u, lee CONNECT, http_proxy y los códigos 407, 7, 28 y 60.
Configurar un proxy en HTTPX: autenticación, entorno y tiempos de espera
Configura proxy= en HTTPX 0.28.1, separa las credenciales del proxy y del sitio, deja HTTPS en CONNECT y distingue entorno, 407 y ReadTimeout.
Cómo configurar un proxy en Playwright
Configura un proxy HTTP o SOCKS5 en Playwright Python, en el browser o en el context, comprueba CONNECT y 407, y deja TLS encendido.



