¿Dónde va la API key de CaptchaAI? En una variable de entorno que el proceso lee al arrancar, nunca en un archivo que Git pueda seguir. Una clave escrita en scraper.py deja de ser tuya en cuanto alguien clona el repositorio, y borrar la línea no basta: el commit anterior sigue ahí. Esta es la ruta completa, del .env local a los secretos de CI/CD.
Por dónde se escapan las claves en la práctica
Tres descuidos concentran casi todas las filtraciones. Los cinco pasos los cierran.
| Descuido | Qué lo hace grave | Cómo se evita |
|---|---|---|
Hacer commit de .env |
La clave queda en el historial, no solo en el archivo | .gitignore antes del primer commit |
| Imprimir la API key en los logs | Queda copiada en cada agregador de logs | Nunca registres la clave completa |
| Escribir la clave en el Dockerfile | Queda incrustada en las capas de la imagen | ENV en runtime, no en el build |
Paso 1: el archivo .env en tu máquina de desarrollo
Crea un archivo .env en la raíz del proyecto:
CAPTCHAAI_API_KEY=your_actual_api_key_here
Añádelo a .gitignore antes del primer commit:
# .gitignore
.env
.env.local
.env.production
Ese "antes" importa: ignorarlo ya versionado obliga a reescribir commits y, aun así, a rotar la clave.
Cargar la clave en Python con python-dotenv
pip install python-dotenv
import os
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
# Use in API calls
import requests
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": "6Le-SITEKEY",
"pageurl": "https://example.com",
"json": "1",
})
print(resp.json())
Con el acceso por corchetes, si falta la variable el proceso se detiene ahí, sin encadenar solicitudes rechazadas contra in.php.
Cargar la clave en Node.js con dotenv
npm install dotenv
require('dotenv').config();
const API_KEY = process.env.CAPTCHAAI_API_KEY;
if (!API_KEY) {
console.error('CAPTCHAAI_API_KEY not set');
process.exit(1);
}
// Use in API calls
const axios = require('axios');
const resp = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: API_KEY,
method: 'userrecaptcha',
googlekey: '6Le-SITEKEY',
pageurl: 'https://example.com',
json: 1,
},
});
console.log(resp.data);
En Node.js ese corte temprano lo da el process.exit(1).
Paso 2: variables del sistema, cuando no quieres el archivo en disco
Si no quieres la clave en ningún archivo del proyecto, defínela en el sistema operativo: el código sigue leyendo os.environ. Solo cambia dónde vive el valor y cuánto dura:
| Sistema | Alcance de la primera línea | Qué la hace permanente |
|---|---|---|
| Linux y macOS | La sesión de shell actual | Añadirla a ~/.bashrc o ~/.zshrc |
| Windows | La sesión de PowerShell actual | SetEnvironmentVariable con alcance User |
En Linux y macOS:
export CAPTCHAAI_API_KEY="your_actual_api_key_here"
# Persist across sessions — add to ~/.bashrc or ~/.zshrc
echo 'export CAPTCHAAI_API_KEY="your_actual_api_key_here"' >> ~/.bashrc
En Windows, con PowerShell:
$env:CAPTCHAAI_API_KEY = "your_actual_api_key_here"
# Persist permanently
[System.Environment]::SetEnvironmentVariable("CAPTCHAAI_API_KEY", "your_actual_api_key_here", "User")
Documenta ambas en el README si mezclas sistemas: ahí suele romperse el onboarding.
Paso 3: contenedores, entre la variable directa y el secreto
En un contenedor hay dos niveles, según quién pueda inspeccionar la máquina anfitriona. El directo va en el arranque:
docker run -e CAPTCHAAI_API_KEY="your_key" my-scraper
Con Docker Compose, la referencia con llaves:
# docker-compose.yml
services:
scraper:
image: my-scraper
environment:
- CAPTCHAAI_API_KEY=${CAPTCHAAI_API_KEY}
Con llaves, el valor se toma del host en runtime: la clave nunca entra en el archivo de Compose, y ese sí puede versionarse.
Secretos de Docker en modo Swarm
echo "your_actual_api_key_here" | docker secret create captchaai_key -
# docker-compose.yml (Swarm mode)
services:
scraper:
image: my-scraper
secrets:
- captchaai_key
secrets:
captchaai_key:
external: true
El secreto se monta como archivo dentro del contenedor:
with open("/run/secrets/captchaai_key") as f:
API_KEY = f.read().strip()
La diferencia es de visibilidad: la variable aparece en docker inspect; el secreto vive bajo /run/secrets/ con permisos restringidos. En producción, secreto; en local, variable.
Paso 4: CI/CD, donde la clave la inyecta el almacén
En CI el valor vive en el almacén de secretos y el job lo recibe como variable. Dos reglas valen para cualquier proveedor: el secreto se da de alta en su interfaz, nunca en el repositorio, y se activa el enmascarado para que un echo accidental no lo imprima en el log.
En GitHub Actions:
# .github/workflows/scrape.yml
jobs:
scrape:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: python scraper.py
env:
CAPTCHAAI_API_KEY: ${{ secrets.CAPTCHAAI_API_KEY }}
El secreto se da de alta en Settings → Secrets and variables → Actions.
En GitLab CI:
# .gitlab-ci.yml
scrape:
script:
- python scraper.py
variables:
CAPTCHAAI_API_KEY: $CAPTCHAAI_API_KEY
Y la variable, en Settings → CI/CD → Variables con "Masked" activado.
Paso 5: valida al arrancar, no a mitad de la ejecución
Antes de lanzar el pipeline, confirma que la variable existe y que la clave sigue válida. Lo segundo, con getbalance contra res.php:
import os
import sys
import requests
API_KEY = os.environ.get("CAPTCHAAI_API_KEY")
if not API_KEY:
print("ERROR: CAPTCHAAI_API_KEY environment variable not set")
sys.exit(1)
# Verify key works
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "getbalance", "json": "1"
}).json()
if resp["status"] != 1:
print(f"ERROR: Invalid API key — {resp['request']}")
sys.exit(1)
print(f"API key valid — balance: ${float(resp['request']):.2f}")
Separa dos fallos que en soporte llegan mezclados: la variable sin definir (entorno) y la clave rechazada (cuenta). CaptchaAI factura por threads concurrentes con resoluciones ilimitadas — BASIC $15/mes, STANDARD $30/mes, ADVANCE $90/mes —, y el saldo avisa de cuándo renovar.
Cómo se ve esto en una agencia con varios clientes
Una agencia en Ciudad de México mantiene en un monorepo el QA de los formularios de alta de tres clientes. Con la clave en el código, los tres comparten credencial: nadie sabe qué consumo es de quién ni puede rotarla sin romper las otras. Tres decisiones lo resuelven:
- Una API key por entorno y por cliente, nunca la misma entre QA y producción.
- Un
.env.exampleversionado, con los valores vacíos. - Los valores reales solo en el almacén de secretos del CI, enmascarados.
Todo ello respetando los términos de servicio y la normativa de protección de datos aplicable.
Diagnóstico rápido cuando algo no encaja
| Síntoma | Acción recomendada |
|---|---|
| La clave llega vacía al proceso | load_dotenv() debe ejecutarse antes de leer os.environ |
CaptchaAI devuelve ERROR_NO_SLOT_AVAILABLE |
Reintenta con backoff en tu pipeline |
| El backend rechaza el token | Compara action y sitekey con tu configuración real |
| Funciona en local y falla en CI | Iguala viewport, idioma y user-agent |
Preguntas frecuentes
¿Qué hago si ya subí la API key a un repositorio público?
Rótala desde tu panel de control y limpia el repositorio después: borrar el archivo no lo saca del historial, pero rotar deja esa copia inservible.
¿Cómo comparto la configuración con quien se incorpora al equipo?
Versiona un .env.example con los nombres vacíos y entrega el valor real por el gestor de secretos: así nunca viaja por chat ni correo.
¿Puedo separar QA y producción con varias claves en el mismo .env?
Sí, y conviene. Usa valores separados por comas o variables numeradas:
CAPTCHAAI_KEYS=key1,key2,key3
keys = os.environ["CAPTCHAAI_KEYS"].split(",")
Aislar entornos así también te deja atribuir el consumo de threads por proyecto.
¿Cómo evito que la clave termine en los logs?
Registra como mucho los últimos cuatro caracteres y activa el enmascarado en el CI. Vigila las librerías HTTP: muchas vuelcan el payload entero.
¿Cambia el procedimiento si resuelvo Turnstile o GeeTest v3 en lugar de reCAPTCHA v2?
No. Guardar y leer la credencial es idéntico para todos los tipos que resuelve CaptchaAI — reCAPTCHA v2 y v3, Turnstile, Cloudflare Challenge, GeeTest v3, imagen/OCR y grid, más CaptchaFox, Friendly Captcha y Lemin (beta). Solo cambia el method.
La credencial dentro del flujo de pruebas
El mismo driver en todos los entornos
from selenium import webdriver
def make_driver(headless: bool = True) -> webdriver.Chrome:
options = webdriver.ChromeOptions()
if headless:
options.add_argument('--headless=new')
options.add_argument('--window-size=1280,800')
options.add_argument('--lang=es-ES')
return webdriver.Chrome(options=options)
Viewport, idioma y user-agent iguales en local y en CI eliminan buena parte de los fallos irreproducibles.
El recorrido del token
- Tu test detecta el widget de CAPTCHA en tu aplicación.
- Envía a CaptchaAI los datos públicos del widget (
sitekey, URL, tipo) con la clave del entorno. - CaptchaAI devuelve el token; tu test lo inyecta y envía el formulario.
- Tu backend lo verifica con el proveedor.
Protege tu integración de CaptchaAI desde el primer día
Obtén tu API key en captchaai.com, cárgala desde una variable de entorno y valida el saldo antes de la primera ejecución.
Guías relacionadas
- seguridad de la clave API y listas de IP autorizadas
- rotar la clave API sin cortar el servicio
- primeros pasos con la API
Valida tus integraciones CAPTCHA en entornos propios con CaptchaAI.