# Registro mediante código: inicio de sesión con billetera y creación de API keys para agentes y CI

> Source: https://docs.blockvectra.com/es/guides/programmatic-signup/

Para agentes de IA autónomos, pipelines de CI y scripts automatizados que se ejecutan sin navegador, BlockVectra ofrece un flujo de inicio de sesión y apertura de cuenta mediante código basado en firmas de billeteras Ethereum (EIP-4361 / EIP-191).

> **Seguridad de las API keys**
>
> Nunca pegues claves privadas, tokens de sesión ni API keys en conversaciones con IA, ni los envíes como argumentos de herramientas MCP.


Antes de registrarte, puedes probar el endpoint público sin API key `https://api.blockvectra.com/v1/robinhood_mainnet/public` (solo métodos JSON-RPC de billetera; la Data API requiere una API key; los métodos y límites dependen de `/v1/chains`); regístrate si la cuota no es suficiente.

## Resumen del flujo

El flujo de registro y aprovisionamiento de API keys mediante código consta de cuatro pasos:

1. **Solicita el desafío**: Envía una solicitud a `POST /auth/siwe/challenge` para obtener un mensaje de inicio de sesión generado por el servidor.
2. **Firma el mensaje**: Firma el mensaje exacto con una billetera EOA de Ethereum mediante EIP-191 (`personal_sign`).
3. **Inicia sesión / abre una cuenta**: Envía el mensaje sin cambios y la firma a `POST /auth/siwe/login`. En el primer inicio de sesión de una billetera, se crea una cuenta automáticamente (`account_created: true`). Las cuentas nuevas reciben 30,000,000 CU al registrarse — sin tarjeta de crédito.
4. **Crea una API key**: Usa el token de sesión para llamar a `POST /keys` y crear una API key.

## Ejemplos completos ejecutables

Empieza aquí: usa un firmante EOA de Ethereum local, crea una API key y compruébala con eth\_blockNumber.
Para el ejemplo en Bash, necesitas curl, jq y Foundry cast. Mantén las credenciales de la billetera en tu entorno local de firma.

Plantilla inicial completa: [blockvectra/agent-quickstart](https://github.com/blockvectra/agent-quickstart)

Los siguientes scripts leen las credenciales de la billetera, completan la secuencia de desafío e inicio de sesión, crean una API key, exportan o imprimen `export BLOCKVECTRA_API_KEY=...` para configurar el entorno y envían una solicitud de verificación `eth_blockNumber`:

Las nuevas API keys tardan unos segundos en activarse; estos ejemplos reintentan automáticamente.

**Bash**

```bash
BASE=https://console-api.blockvectra.com/v1
# $ADDR: Ethereum wallet address (0x...)
# $PK: wallet private key, loaded from a secrets manager (never hardcode in scripts)

# 1. Fetch server-generated SIWE message (omit Origin header)
curl -s "$BASE/auth/siwe/challenge" -H 'Content-Type: application/json' \
  -d "{\"address\":\"$ADDR\",\"purpose\":\"login\"}" > challenge.json
jq -r .message challenge.json > msg.txt

# 2. Sign the exact message with EIP-191 personal_sign
SIG=$(cast wallet sign --private-key "$PK" "$(cat msg.txt)")

# 3. Submit verbatim message and signature (omit Origin header) to log in (ref is optional)
jq -n --rawfile m msg.txt --arg s "$SIG" '{message: ($m | rtrimstr("\n")), signature: $s, ref: "docs-signup"}' |
  curl -s "$BASE/auth/siwe/login" -H 'Content-Type: application/json' -d @- > session.json
TOKEN=$(jq -r .session.token session.json)

# 4. Create an API key (the secret is returned only once)
KEY_RESP=$(curl -s "$BASE/keys" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"label":"agent-key"}')
export BLOCKVECTRA_API_KEY=$(echo "$KEY_RESP" | jq -r .api_key)

# 5. Call JSON-RPC with the key in the x-api-key request header
RPC_DEADLINE=$((SECONDS + 10))
while true; do
  RPC_TIMEOUT=$((RPC_DEADLINE - SECONDS))
  if ((RPC_TIMEOUT <= 0)); then
    printf '%s' "${RPC_BODY:-}"
    break
  fi
  RPC_RESP=$(curl -s --max-time "$RPC_TIMEOUT" -w '\n%{http_code}' "https://api.blockvectra.com/v1/robinhood_mainnet" \
    -H "x-api-key: $BLOCKVECTRA_API_KEY" \
    -H 'Content-Type: application/json' \
    -d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}') || { rc=$?; echo "request failed (curl exit $rc)" >&2; exit $rc; }
  RPC_STATUS=${RPC_RESP##*$'\n'}
  RPC_BODY=${RPC_RESP%$'\n'*}
  if ((SECONDS + 2 < RPC_DEADLINE)) &&
    printf '%s' "$RPC_BODY" | jq -e --arg status "$RPC_STATUS" '
      ($status == "401" and .error.data.reason == "invalid_api_key") or
      ($status == "503" and .error.code == -32021)
    ' >/dev/null 2>&1; then
    sleep 2
  else
    printf '%s' "$RPC_BODY"
    break
  fi
done
```


  **TypeScript**

```bash
npm i viem
```

```ts
// Requires ESM (top-level await; run with node --input-type=module or tsx)
import { privateKeyToAccount } from "viem/accounts";

const BASE = "https://console-api.blockvectra.com/v1";
const privateKey = process.env.PRIVATE_KEY as `0x${string}`;
const account = privateKeyToAccount(privateKey);

// 1. Fetch server-generated SIWE message (omit Origin header)
const challengeRes = await fetch(`${BASE}/auth/siwe/challenge`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ address: account.address, purpose: "login" }),
});
if (!challengeRes.ok) throw new Error(`Challenge failed: ${challengeRes.status}`);
const { message } = (await challengeRes.json()) as { message: string };

// 2. Sign the exact message with EIP-191 personal_sign
const signature = await account.signMessage({ message });

// 3. Submit verbatim message and signature (omit Origin header) to log in (ref is optional)
const loginRes = await fetch(`${BASE}/auth/siwe/login`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ message, signature, ref: "docs-signup" }),
});
if (!loginRes.ok) throw new Error(`Login failed: ${loginRes.status}`);
const { session } = (await loginRes.json()) as { session: { token: string } };

// 4. Create an API key (the secret is returned only once)
const keyRes = await fetch(`${BASE}/keys`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${session.token}`,
  },
  body: JSON.stringify({ label: "agent-key" }),
});
if (!keyRes.ok) throw new Error(`Create key failed: ${keyRes.status}`);
const { api_key } = (await keyRes.json()) as { api_key: string };
console.log("Created API key:", api_key);
console.log(`export BLOCKVECTRA_API_KEY=${api_key}`);

// 5. Call JSON-RPC with the key in the x-api-key request header
const rpcDeadline = performance.now() + 10_000;
while (true) {
  const rpcRes = await fetch("https://api.blockvectra.com/v1/robinhood_mainnet", {
    method: "POST",
    signal: AbortSignal.timeout(Math.max(1, Math.ceil(rpcDeadline - performance.now()))),
    headers: {
      "Content-Type": "application/json",
      "x-api-key": api_key,
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "eth_blockNumber",
      params: [],
    }),
  });
  const rpcBody = await rpcRes.json();
  const retryable =
    (rpcRes.status === 401 && rpcBody.error?.data?.reason === "invalid_api_key") ||
    (rpcRes.status === 503 && rpcBody.error?.code === -32021);
  if (retryable && performance.now() + 2_000 < rpcDeadline) {
    await new Promise((resolve) => setTimeout(resolve, 2_000));
    continue;
  }
  if (!rpcRes.ok) throw new Error(`RPC call failed: ${rpcRes.status}`);
  console.log("Block number response:", rpcBody);
  break;
}
```


  **Python**

```bash
pip install eth-account requests
```

```python
import os
import time
import requests
from eth_account import Account
from eth_account.messages import encode_defunct

BASE = "https://console-api.blockvectra.com/v1"
private_key = os.environ["PRIVATE_KEY"]
account = Account.from_key(private_key)
address = account.address

# 1. Fetch server-generated SIWE message (omit Origin header)
challenge_resp = requests.post(
    f"{BASE}/auth/siwe/challenge",
    json={"address": address, "purpose": "login"},
)
challenge_resp.raise_for_status()
message = challenge_resp.json()["message"]

# 2. Sign the exact message with EIP-191 personal_sign
signable = encode_defunct(text=message)
signed = Account.sign_message(signable, private_key=private_key)
signature = "0x" + bytes(signed.signature).hex()

# 3. Submit verbatim message and signature (omit Origin header) to log in (ref is optional)
login_resp = requests.post(
    f"{BASE}/auth/siwe/login",
    json={"message": message, "signature": signature, "ref": "docs-signup"},
)
login_resp.raise_for_status()
token = login_resp.json()["session"]["token"]

# 4. Create an API key (the secret is returned only once)
key_resp = requests.post(
    f"{BASE}/keys",
    headers={"Authorization": f"Bearer {token}"},
    json={"label": "agent-key"},
)
key_resp.raise_for_status()
api_key = key_resp.json()["api_key"]
print("Created API key:", api_key)
print(f"export BLOCKVECTRA_API_KEY={api_key}")

# 5. Call JSON-RPC with the key in the x-api-key request header
rpc_deadline = time.monotonic() + 10
while True:
    rpc_resp = requests.post(
        "https://api.blockvectra.com/v1/robinhood_mainnet",
        headers={"x-api-key": api_key, "Content-Type": "application/json"},
        json={"jsonrpc": "2.0", "id": 1, "method": "eth_blockNumber", "params": []},
        timeout=max(0.001, rpc_deadline - time.monotonic()),
    )
    rpc_data = rpc_resp.json()
    error = rpc_data.get("error") or {}
    retryable = (
        rpc_resp.status_code == 401
        and (error.get("data") or {}).get("reason") == "invalid_api_key"
    ) or (rpc_resp.status_code == 503 and error.get("code") == -32021)
    if retryable and time.monotonic() + 2 < rpc_deadline:
        time.sleep(2)
        continue
    rpc_resp.raise_for_status()
    print("Block number response:", rpc_data)
    break
```


## URL base y modo programático

Todos los endpoints de autenticación y gestión de API keys usan la URL base oficial:

```
https://console-api.blockvectra.com/v1
```

### Omisión de la cabecera Origin

Las solicitudes mediante código funcionan en **modo programático**:

* Tanto las solicitudes de desafío (`POST /auth/siwe/challenge`) como las de inicio de sesión (`POST /auth/siwe/login`) **deben omitir la cabecera `Origin`** (`curl` y los clientes HTTP estándar la omiten de forma predeterminada; no la añadas manualmente).
* Si se envía una cabecera `Origin` que no corresponde a un dominio configurado de la consola web (incluidas una cadena vacía o `null`), la solicitud de desafío devuelve HTTP 400 `invalid_request`.
* Si el modo de inicio de sesión no coincide con el modo del desafío (por ejemplo, se solicita un desafío programático sin `Origin` y luego se envía el inicio de sesión con una cabecera `Origin`, o viceversa), la solicitud de inicio de sesión devuelve HTTP 400 `siwe_invalid` con `reason: domain_mismatch`.

### Integridad del mensaje y requisitos de la billetera

* **Firma y envío sin cambios**: Los clientes deben firmar y enviar el texto del mensaje exactamente como lo devuelve el endpoint de desafío. No cambies los espacios en blanco, el dominio, el Chain ID ni ningún campo. Cualquier modificación produce HTTP 400 `siwe_invalid` con `reason: signature`.
* **Billeteras compatibles**: Cuentas de propiedad externa (EOA) de Ethereum mainnet (Chain ID 1). La firma debe ser una firma ECDSA de 65 bytes (`personal_sign`). No se admiten billeteras de contrato (EIP-1271) ni cuentas inteligentes.
* **Validez del desafío**: El nonce de cada desafío es de un solo uso y caduca a los 5 minutos.

### Cuerpo de la solicitud y atribución del registro (opcional)

El cuerpo de la solicitud `POST /auth/siwe/login` acepta parámetros obligatorios de autenticación y campos opcionales de atribución del registro:

* **Campos obligatorios**:
  * `message`: La cadena completa del mensaje SIWE obtenida del endpoint de desafío.
  * `signature`: La firma hexadecimal de 65 bytes (con prefijo `0x`) producida al firmar `message` mediante EIP-191 con una billetera Ethereum.
* **Campos opcionales de atribución** (se guardan solo una vez al crear una cuenta; se ignoran en los siguientes inicios de sesión):
  * `ref`: Un token de canal en minúsculas que coincida con `^[a-z0-9._-]{1,64}$` (letras ASCII minúsculas, dígitos, `.`, `_`, `-`, de 1–64 caracteres). Por ejemplo, los agentes autónomos pueden establecerlo como el identificador de su framework o entorno de ejecución (p. ej., `my-agent.v1`). Los valores no válidos (incluidas mayúsculas, cadenas vacías, longitud excesiva o caracteres no admitidos) devuelven HTTP 400 `invalid_request` sin convertir mayúsculas a minúsculas e impiden crear la cuenta; omítelo o envía `null` cuando no corresponda.
  * `referrer`: Una cadena con la URL o el nombre de host de origen; solo los tipos que no sean cadenas devuelven HTTP 400.

Enviar campos no definidos, como `signup_method`, devuelve HTTP 400 `invalid_request`.

## Tokens de sesión y API keys

### Ciclo de vida del token de sesión

* **Formato**: `rgs_` seguido de 64 caracteres hexadecimales en minúsculas.
* **Validez**: Duración máxima de 7 días; caduca automáticamente tras 24 horas de inactividad.
* **Sin token de renovación**: Cuando caduque un token de sesión, inicia un nuevo flujo de desafío e inicio de sesión.
* **Cabecera**: Envía el token de sesión en la cabecera de solicitud `Authorization: Bearer rgs_...`.

### Creación de API keys

* Llama a `POST /keys` con el token de sesión para crear una API key (`rgw_` seguido de 64 caracteres hexadecimales).
* Cada cuenta puede tener hasta 20 API keys sin revocar ni caducar (`active` + `disabled`); las API keys caducadas no cuentan. Superar este límite devuelve HTTP 409 `key_limit_reached` con `reason: active_keys` y `limit: 20`; revoca primero una API key. El límite se aplica a todas las identidades, sesiones y cadenas de la cuenta. La creación y rotación de API keys también están limitadas a 20 por cada 24 horas; superar este límite devuelve HTTP 429 `rate_limited` con `Retry-After: 3600`.
* Límite y caducidad opcionales: puedes proporcionar `cu_cap` (límite de CU durante toda la vida de la API key, de aplicación flexible) y una caducidad (`expires_in_secs` o `expires_at`, hasta el máximo de días permitido por la política de API keys); cuando caduque o se agote el límite, el servidor devuelve 403 (JSON-RPC `-32025`, motivo `key_expired` o `key_cap_exhausted`).
* El secreto `api_key` **se devuelve solo una vez, al crearlo**. Guárdalo inmediatamente de forma segura en tu gestor de secretos o variables de entorno.
* Una API key funciona en todas las cadenas compatibles con JSON-RPC y la Data API.

## ¿Perdiste tu sesión o API key?

En BlockVectra, **la identidad de la cuenta de un agente está vinculada a la dirección de la billetera Ethereum usada durante el registro**. Si tu token de sesión caduca o una API key se pierde o se filtra, puedes recuperar el control completo usando solo esa billetera:

1. **Vuelve a autenticarte con la misma billetera**: Solicita un desafío, fírmalo con la misma billetera y envía la solicitud de inicio de sesión (`POST /auth/siwe/login`). El servidor verifica la firma, inicia sesión en la cuenta existente con `account_created: false` y emite un nuevo token de sesión.
2. **Crea una nueva API key**: Con el nuevo token de sesión, llama a `POST /keys` con `{"label": "..."}` y la cabecera `Authorization: Bearer <token>`. El endpoint devuelve HTTP 201 con los detalles de la API key creada en `key` y el secreto de un solo uso en `api_key`. Guarda esta API key inmediatamente en tus variables de entorno o gestor de secretos.
3. **Lista todas las API keys de la cuenta**:
   * Endpoint: `GET /keys`
   * Cabecera: `Authorization: Bearer <token>`
   * Parámetro de consulta: `include_revoked=true` opcional (cuando es `true`, incluye las API keys revocadas; de forma predeterminada, solo las activas/deshabilitadas).
   * Respuesta: HTTP 200 con JSON `{"items": [...]}`. Cada elemento de la matriz `items` incluye:
     * `key_id`: identificador único de la API key (cadena)
     * `label`: etiqueta de la API key (cadena o `null`)
     * `status`: estado (`"active"`, `"disabled"` o `"revoked"`)
     * `created_at`: fecha y hora de creación (cadena ISO 8601)
     * `revoked_at`: fecha y hora de revocación (cadena o `null` si no se ha revocado)
4. **Revoca las API keys sin uso o comprometidas**:
   * Endpoint: `POST /keys/{key_id}/revoke` (nota: usa `POST` con el `key_id` de destino en la ruta; cuerpo de solicitud vacío)
   * Cabecera: `Authorization: Bearer <token>`
   * Comportamiento: idempotente; se pueden revocar API keys con estado `active` o `disabled`. Si ya está revocada, devuelve HTTP 200 sin cambios. Tras la revocación, se rechazan las solicitudes que usen esa API key.
   * Respuesta: HTTP 200 con el objeto de la API key revocada (los campos coinciden con el objeto anterior, con `status: "revoked"` y una fecha y hora en `revoked_at`).

> **Seguridad de las API keys y los secretos**
>
> Guarda las claves privadas de las billeteras y las API keys en variables de entorno o en un gestor de secretos. Nunca las incluyas en repositorios de código, las escribas en logs ni las pegues en conversaciones de chat con IA.


## Recomendaciones de seguridad

* **Usa API keys de corta duración y revócalas al terminar**: Para tareas automatizadas o efímeras, crea API keys de corta duración con `expires_in_secs` y revócalas inmediatamente mediante `POST /keys/{key_id}/revoke` cuando termines el trabajo.

## Límites de frecuencia de registro (`signup_rate_limited`)

La creación de cuentas está sujeta a límites de frecuencia de registro. El depósito de tokens por IP tiene capacidad para 100 cuentas y se repone a 100 cuentas/hora por dirección IPv4 o prefijo IPv6 /64, compartido entre los registros SIWE y OAuth:

* Al superar los límites de registro, `POST /auth/siwe/login` devuelve HTTP 429 `signup_rate_limited` con una cabecera `Retry-After` que indica los segundos de espera.
* El campo `reason` distingue el ámbito del límite:
  * `per_ip`: se ha agotado el cupo de registros del prefijo IP de la solicitud.
  * `global`: se ha agotado el límite total de registros de la plataforma.
* Los límites de frecuencia de registro solo evalúan el registro de nuevas cuentas. Los inicios de sesión de cuentas existentes no quedan bloqueados por estos límites.

## Recursos relacionados

* Lee la [guía de integración de agentes de IA](https://docs.blockvectra.com/es/guides/ai-agents/) para conocer el servidor MCP sin API key y los archivos de contexto legibles por máquinas.
* Revisa el [inicio rápido](https://docs.blockvectra.com/es/quickstart/) para ver ejemplos de clientes en varios lenguajes.
* Consulta la [referencia de errores](https://docs.blockvectra.com/es/errors/) para ver todos los códigos de error, motivos y acciones de recuperación automatizada.

## Próximos pasos

* Envía tu primera llamada JSON-RPC o Data API con `x-api-key: $BLOCKVECTRA_API_KEY`.
* [Consulta el saldo y los límites de la cuenta](https://docs.blockvectra.com/es/guides/ai-agents/#consultar-el-saldo-get-v1account) mediante `GET /v1/account`.
* Sigue la [guía de recarga mediante código para agentes](https://docs.blockvectra.com/es/guides/agent-topup/) para mantener el saldo.
