# Програмна реєстрація: вхід за допомогою гаманця та створення API ключа для агентів і CI

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

Для автономних AI-агентів, конвеєрів CI та автоматизованих скриптів, що працюють без браузера, BlockVectra надає робочий процес програмного входу та відкриття акаунта на основі підписів Ethereum-гаманця (EIP-4361 / EIP-191).

> **Безпека ключів**
>
> Ніколи не вставляйте приватні ключі, токени сесії або API ключі в розмови з AI і не передавайте їх як аргументи інструментів MCP.


Перед реєстрацією ви можете спочатку спробувати публічний ендпоінт без ключа `https://api.blockvectra.com/v1/robinhood_mainnet/public` (тільки гаманцеві методи JSON-RPC, для Data API потрібен ключ; методи та ліміти визначаються `/v1/chains`); зареєструйте акаунт, якщо квоти недостатньо.

## Огляд робочого процесу

Процес програмної реєстрації та створення ключів складається з чотирьох кроків:

1. **Запит челенджу**: надішліть запит до `POST /auth/siwe/challenge`, щоб отримати згенероване сервером повідомлення для входу.
2. **Підпис повідомлення**: підпишіть точний текст повідомлення за допомогою Ethereum EOA-гаманця з використанням EIP-191 (`personal_sign`).
3. **Вхід / відкриття акаунта**: надішліть точне повідомлення та підпис до `POST /auth/siwe/login`. Під час першого входу для гаманця акаунт створюється автоматично (`account_created: true`). Нові акаунти отримують 30,000,000 CU під час реєстрації — без кредитної картки.
4. **Створення API ключа**: використайте токен сесії для виклику `POST /keys` і створіть API ключ.

## Повні готові до запуску приклади

Почніть звідси: використайте локальний інструмент підпису Ethereum EOA, створіть ключ і перевірте його за допомогою eth\_blockNumber.
Для прикладу з Bash вам знадобляться curl, jq та Foundry cast. Зберігайте облікові дані гаманця у вашому локальному середовищі підпису.

Повний стартовий шаблон: [blockvectra/agent-quickstart](https://github.com/blockvectra/agent-quickstart)

Наведені нижче скрипти читають облікові дані гаманця, виконують послідовність челенджу та входу, створюють API ключ, експортують або виводять `export BLOCKVECTRA_API_KEY=...` для налаштування середовища та надсилають перевірочний запит `eth_blockNumber`:

Новим ключам потрібно кілька секунд, щоб стати активними; ці приклади повторюють спроби автоматично.

**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 та програмний режим

Усі ендпоінти автентифікації та керування ключами використовують офіційний базовий URL:

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

### Опущення заголовка Origin

Програмні запити працюють у **програмному режимі**:

* Обидва запити: челендж (`POST /auth/siwe/challenge`) та вхід (`POST /auth/siwe/login`) **не повинні містити заголовок `Origin`** (`curl` та стандартні HTTP-клієнти за замовчуванням опускають цей заголовок; не додавайте його вручну).
* Якщо заголовок `Origin` надіслано, але він не є налаштованим доменом вебконсолі (включаючи порожній рядок або `null`), запит челенджу повертає HTTP 400 `invalid_request`.
* Якщо режим під час входу не збігається з режимом челенджу (наприклад, запит програмного челенджу без `Origin`, а потім відправка входу із заголовком `Origin`, або навпаки), запит входу повертає HTTP 400 `siwe_invalid` з `reason: domain_mismatch`.

### Цілісність повідомлення та вимоги до гаманця

* **Точний підпис та відправка**: клієнти повинні підписати та надіслати текст повідомлення точно в такому вигляді, як його повернув ендпоінт челенджу. Не змінюйте пробіли, домен, chain ID чи будь-які інші поля. Будь-яка зміна призводить до HTTP 400 `siwe_invalid` з `reason: signature`.
* **Підтримувані гаманці**: Externally Owned Accounts (EOA) в Ethereum mainnet (Chain ID 1). Підпис має бути 65-байтним підписом ECDSA (`personal_sign`). Контрактні гаманці (EIP-1271) та смарт-акаунти не підтримуються.
* **Дійсність челенджу**: кожен nonce челенджу є одноразовим і втрачає силу через 5 хвилин.

### Тіло запиту та атрибуція реєстрації (необов'язково)

Тіло запиту `POST /auth/siwe/login` приймає обов'язкові параметри автентифікації та необов'язкові поля атрибуції реєстрації:

* **Обов'язкові поля**:
  * `message`: повний рядок повідомлення SIWE, отриманий від ендпоінта челенджу.
  * `signature`: 65-байтний шістнадцятковий підпис (із префіксом `0x`), створений шляхом підписання `message` за допомогою EIP-191 в Ethereum-гаманці.
* **Необов'язкові поля атрибуції** (зберігаються лише один раз під час створення нового акаунта; ігноруються під час наступних входів):
  * `ref`: токен каналу в нижньому регістрі, що відповідає `^[a-z0-9._-]{1,64}$` (рядкові літери ASCII, цифри, `.`, `_`, `-`, від 1 до 64 символів). Наприклад, автономні агенти можуть встановити значення свого фреймворку або ідентифікатора середовища виконання (наприклад, `my-agent.v1`). Некоректні значення (включаючи великі літери, порожні рядки, перевищення довжини або непідтримувані символи) повертають HTTP 400 `invalid_request` без переведення регістру і блокують створення акаунта; опускайте або передавайте `null`, якщо не застосовується.
  * `referrer`: рядок вихідного URL або імені хоста; лише нерядкові типи повертають HTTP 400.

Надсилання невизначених полів, таких як `signup_method`, повертає HTTP 400 `invalid_request`.

## Токени сесії та API ключі

### Життєвий цикл токена сесії

* **Формат**: `rgs_`, за яким слідують 64 шістнадцяткові символи в нижньому регістрі.
* **Дійсність**: абсолютний термін дії 7 днів; автоматично закінчується після 24 годин бездіяльності.
* **Без refresh token**: коли термін дії токена сесії закінчується, почніть новий процес челенджу та входу.
* **Заголовок**: передавайте токен сесії в заголовку запиту `Authorization: Bearer rgs_...`.

### Створення API ключа

* Викличте `POST /keys` із токеном сесії, щоб створити API ключ (`rgw_`, за яким слідують 64 шістнадцяткові символи).
* На один акаунт дозволено не більше 20 невідкликаних і нетермінованих ключів (`active` + `disabled`); прострочені ключі не враховуються. Перевищення повертає HTTP 409 `key_limit_reached` з `reason: active_keys` та `limit: 20`; спочатку відкличте ключ. Ліміт діє для всіх ідентифікаторів, сесій та мереж акаунта. Створення та ротація ключів також обмежені 20 операціями за 24 години; перевищення повертає HTTP 429 `rate_limited` з `Retry-After: 3600`.
* Необов'язковий ліміт і термін дії: ви можете вказати `cu_cap` (загальний ліміт CU для ключа на весь час його дії, що є м'яким лімітом) та термін дії (`expires_in_secs` або `expires_at`, аж до максимальної кількості днів, дозволеної політикою ключів); після закінчення терміну дії або вичерпання ліміту сервер повертає 403 (JSON-RPC `-32025`, reason `key_expired` або `key_cap_exhausted`).
* Секретний `api_key` **повертається лише один раз під час створення**. Негайно збережіть його в надійному менеджері секретів або змінних середовища.
* Один API ключ працює в усіх підтримуваних мережах для JSON-RPC та Data API.

## Втратили сесію або API ключ?

У BlockVectra **ідентичність акаунта агента прив'язана до адреси Ethereum-гаманця, використаної під час реєстрації**. Якщо термін дії вашого токена сесії закінчився або API ключ втрачено чи скомпрометовано, ви можете відновити повний контроль, використовуючи лише цей гаманець:

1. **Повторна автентифікація з тим самим гаманцем**: запитайте челендж, підпишіть його тим самим гаманцем і надішліть запит на вхід (`POST /auth/siwe/login`). Сервер перевіряє підпис, входить до наявного акаунта з `account_created: false` та видає новий токен сесії.
2. **Створення нового API ключа**: із новим токеном сесії викличте `POST /keys` з `{"label": "..."}` та заголовком `Authorization: Bearer <token>`. Ендпоінт повертає HTTP 201 з даними створеного ключа в `key` та одноразовим секретом в `api_key`. Негайно збережіть цей ключ у змінних середовища або менеджері секретів.
3. **Список усіх ключів акаунта**:
   * Ендпоінт: `GET /keys`
   * Заголовок: `Authorization: Bearer <token>`
   * Параметр запиту: необов'язковий `include_revoked=true` (якщо `true`, включає відкликані ключі; за замовчуванням повертаються лише активні/відключені ключі).
   * Відповідь: HTTP 200 з JSON `{"items": [...]}`. Кожен елемент масиву `items` містить:
     * `key_id`: унікальний ідентифікатор ключа (string)
     * `label`: мітка ключа (string або `null`)
     * `status`: статус (`"active"`, `"disabled"` або `"revoked"`)
     * `created_at`: позначка часу створення (рядок ISO 8601)
     * `revoked_at`: позначка часу відкликання (string або `null`, якщо не відкликано)
4. **Відкликання невикористаних або скомпрометованих ключів**:
   * Ендпоінт: `POST /keys/{key_id}/revoke` (зверніть увагу: використовується `POST` із цільовим `key_id` у шляху; порожнє тіло запиту)
   * Заголовок: `Authorization: Bearer <token>`
   * Поведінка: ідемпотентна; ключі зі статусом `active` або `disabled` можуть бути відкликані. Якщо ключ уже відкликано, повертається HTTP 200 без змін. Після відкликання запити з цим ключем відхиляються.
   * Відповідь: HTTP 200, що повертає об'єкт відкликаного ключа (поля відповідають об'єкту ключа вище, зі `status: "revoked"` та позначкою часу в `revoked_at`).

> **Безпека ключів та секретів**
>
> Зберігайте приватні ключі гаманця та API ключі в змінних середовища або в менеджері секретів. Ніколи не фіксуйте їх у репозиторіях коду, не записуйте в журнали та не вставляйте в розмови в чатах AI.


## Рекомендації з безпеки

* **Використовуйте короткострокові ключі та відкликайте їх після завершення**: для автоматизованих або ефемерних завдань створюйте короткоживучі ключі з `expires_in_secs` та негайно відкликайте їх через `POST /keys/{key_id}/revoke` після завершення роботи.

## Ліміти запитів на реєстрацію (`signup_rate_limited`)

Створення акаунтів підлягає обмеженням частоти реєстрацій. Кошик токенів (token bucket) для кожної IP-адреси має місткість 100 акаунтів і поповнюється зі швидкістю 100 акаунтів/годину на IPv4-адресу або префікс IPv6 /64, спільно для реєстрацій через SIWE та OAuth:

* У разі перевищення лімітів реєстрації `POST /auth/siwe/login` повертає HTTP 429 `signup_rate_limited` із заголовком `Retry-After`, що вказує кількість секунд очікування.
* Поле `reason` визначає область дії ліміту:
  * `per_ip`: вичерпано бюджет реєстрацій для префікса IP, що запитує.
  * `global`: вичерпано сукупний ліміт реєстрацій на платформі.
* Ліміти частоти реєстрацій оцінюють лише створення нових акаунтів. Вхід у наявні акаунти не блокується лімітами реєстрації.

## Пов'язані ресурси

* Прочитайте [посібник з інтеграції AI-агентів](https://docs.blockvectra.com/en/guides/ai-agents/), щоб дізнатися про MCP-сервер без ключа та машиночитні файли контексту.
* Ознайомтеся зі [швидким стартом](https://docs.blockvectra.com/en/quickstart/) для прикладів клієнтів різними мовами.
* Перегляньте [довідник помилок](https://docs.blockvectra.com/en/errors/) для отримання повного списку кодів помилок, причин та автоматизованих дій з відновлення.

## Наступні кроки

* Надішліть свій перший виклик JSON-RPC або Data API з `x-api-key: $BLOCKVECTRA_API_KEY`.
* [Перевірте баланс і ліміти акаунта](https://docs.blockvectra.com/en/guides/ai-agents/#query-balance-get-v1account) за допомогою `GET /v1/account`.
* Дотримуйтесь [посібника з програмного поповнення для агентів](https://docs.blockvectra.com/en/guides/agent-topup/), щоб підтримувати баланс.
