# Cadastro programático: login com carteira e criação de API key para Agents e CI

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

Para AI Agents autônomos, pipelines de CI e scripts automatizados executados sem um navegador, a BlockVectra oferece um fluxo programático de login e abertura de conta baseado em assinaturas de carteira Ethereum (EIP-4361 / EIP-191).

> **Key security**
>
> Nunca cole chaves privadas, tokens de sessão ou API keys em conversas com IA nem as passe como argumentos de ferramentas MCP.


Antes de se cadastrar, você pode experimentar o endpoint público sem chave `https://api.blockvectra.com/v1/robinhood_mainnet/public` primeiro (apenas métodos JSON-RPC de carteira; a Data API exige uma chave; os métodos e limites estão sujeitos a `/v1/chains`); cadastre uma conta se a cota não for suficiente.

## Visão geral do fluxo

O fluxo de registro programático e provisionamento de chaves é composto por quatro etapas:

1. **Solicitar challenge**: envie uma requisição para `POST /auth/siwe/challenge` para obter uma mensagem de login gerada pelo servidor.
2. **Assinar mensagem**: assine o texto exato da mensagem com uma carteira EOA Ethereum usando EIP-191 (`personal_sign`).
3. **Fazer login / abrir conta**: envie a mensagem original e a assinatura para `POST /auth/siwe/login`. No primeiro login de uma carteira, uma conta é criada automaticamente (`account_created: true`). Novas contas recebem 30,000,000 CU no cadastro — sem cartão de crédito.
4. **Criar API key**: use o token de sessão para chamar `POST /keys` e criar uma API key.

## Exemplos executáveis completos

Comece aqui: use um assinador EOA Ethereum local, crie uma chave e verifique-a com eth\_blockNumber.
Para o exemplo em Bash, você precisa de curl, jq e cast do Foundry. Mantenha as credenciais da carteira em seu ambiente de assinatura local.

Modelo inicial completo: [blockvectra/agent-quickstart](https://github.com/blockvectra/agent-quickstart)

Os scripts a seguir leem as credenciais da carteira, concluem a sequência de challenge e login, provisionam uma API key, exportam ou exibem `export BLOCKVECTRA_API_KEY=...` para configuração de ambiente e enviam uma requisição de verificação `eth_blockNumber`:

Novas chaves levam alguns segundos para se tornarem ativas; estes exemplos tentam novamente de forma automática.

**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 e modo programático

Todos os endpoints de autenticação e gerenciamento de chaves usam a URL base oficial:

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

### Omissão do cabeçalho Origin

As requisições programáticas operam no **modo programático**:

* Tanto a requisição de challenge (`POST /auth/siwe/challenge`) quanto a de login (`POST /auth/siwe/login`) **não devem incluir o cabeçalho `Origin`** (`curl` e clientes HTTP padrão omitem esse cabeçalho por padrão; não o adicione manualmente).
* Se um cabeçalho `Origin` for enviado mas não for um domínio configurado do console web (incluindo string vazia ou `null`), a requisição de challenge retornará HTTP 400 `invalid_request`.
* Se o modo no login não coincidir com o modo do challenge (por exemplo, solicitar um challenge programático sem `Origin` e depois enviar o login com um cabeçalho `Origin`, ou vice-versa), a requisição de login retornará HTTP 400 `siwe_invalid` com `reason: domain_mismatch`.

### Integridade da mensagem e requisitos da carteira

* **Assinatura e envio idênticos**: os clientes devem assinar e enviar o texto da mensagem exatamente como retornado pelo endpoint de challenge. Não altere espaços em branco, domínio, Chain ID ou qualquer outro campo. Qualquer modificação resulta em HTTP 400 `siwe_invalid` com `reason: signature`.
* **Carteiras suportadas**: Contas Externamente Proprietárias (EOA) da rede Ethereum mainnet (Chain ID 1). A assinatura deve ser uma assinatura ECDSA de 65 bytes (`personal_sign`). Carteiras de contrato (EIP-1271) e contas inteligentes (smart accounts) não são suportadas.
* **Validade do challenge**: cada nonce de challenge é de uso único e expira após 5 minutos.

### Corpo da requisição e atribuição de cadastro (opcional)

O corpo da requisição `POST /auth/siwe/login` aceita parâmetros obrigatórios de autenticação e campos opcionais de atribuição de cadastro:

* **Campos obrigatórios**:
  * `message`: a string completa da mensagem SIWE obtida do endpoint de challenge.
  * `signature`: a assinatura hexadecimal de 65 bytes (com prefixo `0x`) produzida ao assinar `message` via EIP-191 com uma carteira Ethereum.
* **Campos opcionais de atribuição** (salvos apenas uma vez quando uma nova conta é criada; ignorados em logins subsequentes):
  * `ref`: um token de canal em minúsculas correspondente a `^[a-z0-9._-]{1,64}$` (letras ASCII minúsculas, dígitos, `.`, `_`, `-`, de 1 a 64 caracteres). Por exemplo, agents autônomos podem definir isso como seu identificador de framework ou runtime (ex.: `my-agent.v1`). Valores fora do padrão (incluindo letras maiúsculas, strings vazias, comprimento excessivo ou caracteres não suportados) retornam HTTP 400 `invalid_request` sem conversão automática de maiúsculas/minúsculas e impedem a criação da conta; omita ou passe `null` quando não aplicável.
  * `referrer`: uma URL de origem ou string de hostname; apenas tipos diferentes de string retornam HTTP 400.

Enviar campos indefinidos como `signup_method` retorna HTTP 400 `invalid_request`.

## Tokens de sessão e API keys

### Ciclo de vida do token de sessão

* **Formato**: `rgs_` seguido por 64 caracteres hexadecimais minúsculos.
* **Validade**: tempo de vida absoluto de 7 dias; expira automaticamente após 24 horas de inatividade.
* **Sem token de atualização**: quando um token de sessão expirar, inicie um novo fluxo de challenge e login.
* **Cabeçalho**: passe o token de sessão no cabeçalho de requisição `Authorization: Bearer rgs_...`.

### Criação de API key

* Chame `POST /keys` com o token de sessão para criar uma API key (`rgw_` seguido por 64 caracteres hexadecimais).
* Por conta, no máximo 20 chaves não revogadas e não expiradas (`active` + `disabled`); chaves expiradas não contam. Exceder esse limite retorna HTTP 409 `key_limit_reached` com `reason: active_keys` e `limit: 20`; revogue uma chave primeiro. O limite se aplica a todas as identidades, sessões e redes da conta. A criação e rotação de chaves também são limitadas a 20 por período de 24 horas; exceder isso retorna HTTP 429 `rate_limited` com `Retry-After: 3600`.
* Limite e expiração opcionais: você pode fornecer `cu_cap` (limite vitalício de CU para a chave, que é um limite flexível) e expiração (`expires_in_secs` ou `expires_at`, até o máximo de dias permitido pela política de chaves); uma vez expirada ou com o limite esgotado, o servidor retorna 403 (JSON-RPC `-32025`, motivo `key_expired` ou `key_cap_exhausted`).
* O segredo `api_key` é **retornado apenas uma vez na criação**. Armazene-o com segurança imediatamente em seu gerenciador de segredos ou variáveis de ambiente.
* Uma API key funciona em todas as redes suportadas no JSON-RPC e na Data API.

## Perdeu sua sessão ou API key?

Na BlockVectra, **a identidade da conta de um agent é vinculada ao endereço da carteira Ethereum usado no cadastro**. Se o token de sessão expirar ou uma API key for perdida ou vazada, você pode recuperar o controle total usando apenas essa carteira:

1. **Re-autenticar com a mesma carteira**: solicite um challenge, assine-o com a mesma carteira e envie a requisição de login (`POST /auth/siwe/login`). O servidor verifica a assinatura, faz login na conta existente com `account_created: false` e emite um novo token de sessão.
2. **Criar uma nova API key**: com o novo token de sessão, chame `POST /keys` com `{"label": "..."}` e o cabeçalho `Authorization: Bearer <token>`. O endpoint retorna HTTP 201 com os detalhes da chave criada em `key` e o segredo único em `api_key`. Salve essa chave imediatamente em suas variáveis de ambiente ou gerenciador de segredos.
3. **Listar todas as chaves da conta**:
   * Endpoint: `GET /keys`
   * Cabeçalho: `Authorization: Bearer <token>`
   * Parâmetro de consulta: `include_revoked=true` opcional (quando `true`, inclui chaves revogadas; por padrão, retorna apenas chaves ativas/desativadas).
   * Resposta: HTTP 200 com JSON `{"items": [...]}`. Cada elemento no array `items` inclui:
     * `key_id`: identificador exclusivo da chave (string)
     * `label`: rótulo da chave (string ou `null`)
     * `status`: status (`"active"`, `"disabled"` ou `"revoked"`)
     * `created_at`: carimbo de data/hora de criação (string ISO 8601)
     * `revoked_at`: carimbo de data/hora de revogação (string, ou `null` se não revogada)
4. **Revogar chaves não utilizadas ou comprometidas**:
   * Endpoint: `POST /keys/{key_id}/revoke` (observação: usa `POST` com o `key_id` de destino no caminho; corpo de requisição vazio)
   * Cabeçalho: `Authorization: Bearer <token>`
   * Comportamento: idempotente; chaves com status `active` ou `disabled` podem ser revogadas. Se já revogada, retorna HTTP 200 inalterada. Após a revogação, requisições usando essa chave são rejeitadas.
   * Resposta: HTTP 200 retornando o objeto da chave revogada (os campos coincidem com o objeto da chave acima, com `status: "revoked"` e um carimbo de data/hora em `revoked_at`).

> **Key and secret security**
>
> Armazene chaves privadas de carteiras e API keys em variáveis de ambiente ou em um gerenciador de segredos. Nunca as confirme em repositórios de código, grave-as em logs ou cole-as em conversas de chat com IA.


## Recomendações de segurança

* **Use chaves de curta duração e revogue ao concluir**: para tarefas automatizadas ou efêmeras, crie chaves de curta duração com `expires_in_secs` e revogue-as imediatamente via `POST /keys/{key_id}/revoke` assim que o trabalho for concluído.

## Limites de taxa de cadastro (signup\_rate\_limited)

A criação de contas está sujeita a limites de taxa de cadastro. O bucket de tokens por IP tem capacidade para 100 contas e se reabastece a 100 contas/hora por endereço IPv4 ou prefixo IPv6 /64, compartilhado entre cadastros via SIWE e OAuth:

* Ao exceder os limites de cadastro, `POST /auth/siwe/login` retorna HTTP 429 `signup_rate_limited` com um cabeçalho `Retry-After` indicando o número de segundos a aguardar.
* O campo `reason` diferencia o escopo do limite:
  * `per_ip`: o orçamento de registro para o prefixo IP solicitante foi esgotado.
  * `global`: o limite agregado de cadastros da plataforma foi esgotado.
* Os limites de taxa de cadastro avaliam apenas novos registros de contas. Contas existentes fazendo login não são bloqueadas por limites de taxa de cadastro.

## Recursos relacionados

* Leia o [guia de integração de AI Agents](https://docs.blockvectra.com/en/guides/ai-agents/) para conhecer o servidor MCP sem chave e arquivos de contexto legíveis por máquina.
* Consulte o [Início rápido](https://docs.blockvectra.com/en/quickstart/) para ver exemplos de clientes em várias linguagens.
* Inspecione a [referência de erros](https://docs.blockvectra.com/en/errors/) para ver códigos de erro completos, motivos e ações automáticas de recuperação.

## Próximos passos

* Envie sua primeira chamada JSON-RPC ou Data API com `x-api-key: $BLOCKVECTRA_API_KEY`.
* [Consulte o saldo da conta e os limites](https://docs.blockvectra.com/en/guides/ai-agents/#query-balance-get-v1account) usando `GET /v1/account`.
* Siga o [guia de recarga programática para Agents](https://docs.blockvectra.com/en/guides/agent-topup/) para manter o saldo.
