# Programmatische Registrierung: Wallet-Login und API-Key-Erstellung für Agenten und CI

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

Für autonome KI-Agenten, CI-Pipelines und automatisierte Skripte, die ohne Browser ausgeführt werden, bietet BlockVectra einen programmatischen Login- und Kontoerstellungsworkflow basierend auf Ethereum-Wallet-Signaturen (EIP-4361 / EIP-191).

> **Key-Sicherheit**
>
> Fügen Sie niemals private Schlüssel, Session-Tokens oder API keys in Konversationen mit KI ein und übergeben Sie diese nicht als Argumente für MCP-Tools.


Vor der Registrierung können Sie zunächst den schlüssellosen öffentlichen Endpunkt `https://api.blockvectra.com/v1/robinhood_mainnet/public` ausprobieren (nur Wallet-JSON-RPC-Methoden, die Data API erfordert einen Key; Methoden und Limits unterliegen `/v1/chains`); registrieren Sie ein Konto, falls das Kontingent nicht ausreicht.

## Workflow-Übersicht

Der Ablauf für die programmatische Registrierung und Key-Bereitstellung besteht aus vier Schritten:

1. **Challenge anfordern**: Senden Sie eine Anfrage an `POST /auth/siwe/challenge`, um eine vom Server generierte Anmelde-Nachricht zu erhalten.
2. **Nachricht signieren**: Signieren Sie die exakte Nachricht mit einem Ethereum EOA-Wallet mittels EIP-191 (`personal_sign`).
3. **Anmelden / Konto eröffnen**: Übermitteln Sie die wortgetreue Nachricht und Signatur an `POST /auth/siwe/login`. Bei der ersten Anmeldung für ein Wallet wird automatisch ein Konto erstellt (`account_created: true`). Neue Konten erhalten 30,000,000 CU bei der Registrierung — keine Kreditkarte erforderlich.
4. **API key erstellen**: Verwenden Sie das Session-Token, um `POST /keys` aufzurufen und einen API key zu erstellen.

## Vollständige ausführbare Beispiele

Beginnen Sie hier: Verwenden Sie einen lokalen Ethereum EOA-Signer, erstellen Sie einen Key und verifizieren Sie ihn mit eth\_blockNumber.
Für das Bash-Beispiel benötigen Sie curl, jq und Foundry cast. Bewahren Sie Wallet-Zugangsdaten in Ihrer lokalen Signaturumgebung auf.

Vollständige Starter-Vorlage: [blockvectra/agent-quickstart](https://github.com/blockvectra/agent-quickstart)

Die folgenden Skripte lesen die Wallet-Zugangsdaten aus, schließen die Challenge- und Login-Sequenz ab, stellen einen API key bereit, exportieren oder geben `export BLOCKVECTRA_API_KEY=...` für die Umgebungskonfiguration aus und senden eine `eth_blockNumber`-Verifizierungsanfrage:

Neue Keys benötigen einige Sekunden, um aktiv zu werden; diese Beispiele wiederholen die Anfrage automatisch.

**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
```


## Basis-URL und programmatischer Modus

Alle Authentifizierungs- und Key-Management-Endpunkte verwenden die offizielle Basis-URL:

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

### Auslassen des Origin-Headers

Programmatische Anfragen laufen im **programmatischen Modus**:

* Sowohl die Challenge- (`POST /auth/siwe/challenge`) als auch die Login-Anfrage (`POST /auth/siwe/login`) **dürfen den Header `Origin` nicht enthalten** (`curl` und Standard-HTTP-Clients lassen diesen Header standardmäßig weg; fügen Sie ihn nicht manuell hinzu).
* Wenn ein `Origin`-Header gesendet wird, dieser jedoch keine konfigurierte Web-Konsolen-Domain ist (einschließlich leerer Zeichenkette oder `null`), gibt die Challenge-Anfrage HTTP 400 `invalid_request` zurück.
* Wenn der Modus beim Login nicht mit dem Challenge-Modus übereinstimmt (beispielsweise das Anfordern einer programmatischen Challenge ohne `Origin` und anschließendes Absenden des Logins mit einem `Origin`-Header oder umgekehrt), gibt die Login-Anfrage HTTP 400 `siwe_invalid` mit `reason: domain_mismatch` zurück.

### Nachrichtenintegrität und Wallet-Anforderungen

* **Wortgetreue Signatur und Übermittlung**: Clients müssen den Nachrichtentext exakt so signieren und übermitteln, wie er vom Challenge-Endpunkt zurückgegeben wurde. Ändern Sie weder Leerzeichen, Domain, Chain ID noch andere Felder. Jede Änderung führt zu HTTP 400 `siwe_invalid` mit `reason: signature`.
* **Unterstützte Wallets**: Externally Owned Accounts (EOA) im Ethereum Mainnet (Chain ID 1). Die Signatur muss eine 65-Byte-ECDSA-Signatur sein (`personal_sign`). Contract-Wallets (EIP-1271) und Smart Accounts werden nicht unterstützt.
* **Challenge-Gültigkeit**: Jede Challenge-Nonce ist für den einmaligen Gebrauch bestimmt und läuft nach 5 Minuten ab.

### Request-Body und Registrierungs-Attribution (optional)

Der Request-Body von `POST /auth/siwe/login` akzeptiert erforderliche Authentifizierungsparameter und optionale Felder zur Registrierungs-Attribution:

* **Erforderliche Felder**:
  * `message`: Der vollständige, vom Challenge-Endpunkt erhaltene SIWE-Nachrichten-String.
  * `signature`: Die 65-Byte-Hexadezimalsignatur (mit `0x`-Präfix), die durch das Signieren von `message` mittels EIP-191 mit einem Ethereum-Wallet erzeugt wurde.
* **Optionale Attributionsfelder** (werden nur einmal gespeichert, wenn ein neues Konto erstellt wird; bei nachfolgenden Logins ignoriert):
  * `ref`: Ein Token für den Kanal in Kleinbuchstaben, das `^[a-z0-9._-]{1,64}$` entspricht (ASCII-Kleinbuchstaben, Ziffern, `.`, `_`, `-`, 1–64 Zeichen). Autonome Agenten können dies beispielsweise auf ihre Framework- oder Laufzeitkennung setzen (z. B. `my-agent.v1`). Nicht konforme Werte (einschließlich Großbuchstaben, leerer Zeichenketten, übermäßiger Länge oder nicht unterstützter Zeichen) geben HTTP 400 `invalid_request` ohne Fallumwandlung zurück und verhindern die Kontoerstellung; weglassen oder `null` übergeben, wenn nicht zutreffend.
  * `referrer`: Eine Quell-URL oder ein Hostname-String; nur Nicht-String-Typen geben HTTP 400 zurück.

Das Senden undefinierter Felder wie `signup_method` gibt HTTP 400 `invalid_request` zurück.

## Session-Tokens und API keys

### Lebenszyklus von Session-Tokens

* **Format**: `rgs_`, gefolgt von 64 hexadezimalen Kleinbuchstaben.
* **Gültigkeit**: Absolute Lebensdauer von 7 Tagen; läuft nach 24 Stunden Inaktivität automatisch ab.
* **Kein Refresh-Token**: Wenn ein Session-Token abläuft, initiieren Sie einen neuen Challenge- und Login-Ablauf.
* **Header**: Übergeben Sie das Session-Token im Request-Header `Authorization: Bearer rgs_...`.

### API-Key-Erstellung

* Rufen Sie `POST /keys` mit dem Session-Token auf, um einen API key zu erstellen (`rgw_`, gefolgt von 64 Hexadezimalzeichen).
* Pro Konto maximal 20 nicht widerrufene, nicht abgelaufene (`active` + `disabled`) Keys; abgelaufene Keys zählen nicht mit. Ein Überschreiten gibt HTTP 409 `key_limit_reached` mit `reason: active_keys` und `limit: 20` zurück; widerrufen Sie zuerst einen Key. Die Obergrenze gilt über alle Identitäten, Sessions und Chains des Kontos hinweg. Key-Erstellung und -Rotation sind zudem auf 20 pro 24 Stunden begrenzt; ein Überschreiten gibt HTTP 429 `rate_limited` mit `Retry-After: 3600` zurück.
* Optionale Obergrenze und Ablaufzeit: Sie können `cu_cap` (lebenslange CU-Obergrenze für den Key, die als Soft-Cap fungiert) und eine Ablaufzeit (`expires_in_secs` oder `expires_at`, bis zu den maximal durch die Key-Richtlinie zulässigen Tagen) angeben; nach Ablauf oder Erschöpfung der Obergrenze gibt der Server 403 zurück (JSON-RPC `-32025`, Grund `key_expired` oder `key_cap_exhausted`).
* Das Secret `api_key` wird **bei der Erstellung nur ein einziges Mal zurückgegeben**. Speichern Sie es unverzüglich sicher in Ihrem Secrets-Manager oder in Umgebungsvariablen.
* Ein API key funktioniert über alle unterstützten Chains hinweg für JSON-RPC und die Data API.

## Session oder API key verloren?

Bei BlockVectra ist **die Kontoidentität eines Agenten an die bei der Registrierung verwendete Ethereum-Wallet-Adresse gebunden**. Wenn Ihr Session-Token abläuft oder ein API key verloren geht bzw. kompromittiert wird, können Sie die vollständige Kontrolle ausschließlich über dieses Wallet wiedererlangen:

1. **Erneut mit demselben Wallet authentifizieren**: Fordern Sie eine Challenge an, signieren Sie diese mit demselben Wallet und senden Sie die Login-Anfrage ab (`POST /auth/siwe/login`). Der Server überprüft die Signatur, meldet sich am bestehenden Konto mit `account_created: false` an und stellt ein neues Session-Token aus.
2. **Neuen API key erstellen**: Rufen Sie mit dem neuen Session-Token `POST /keys` mit `{"label": "..."}` und dem Header `Authorization: Bearer <token>` auf. Der Endpunkt gibt HTTP 201 mit den Details des erstellten Keys in `key` und dem einmaligen Secret in `api_key` zurück. Speichern Sie diesen Key sofort in Ihren Umgebungsvariablen oder Ihrem Secrets-Manager.
3. **Alle Keys des Kontos auflisten**:
   * Endpunkt: `GET /keys`
   * Header: `Authorization: Bearer <token>`
   * Query-Parameter: optional `include_revoked=true` (wenn `true`, schließt widerrufene Keys ein; standardmäßig nur aktive/deaktivierte Keys).
   * Antwort: HTTP 200 mit JSON `{"items": [...]}`. Jedes Element im Array `items` enthält:
     * `key_id`: eindeutige Key-Kennung (String)
     * `label`: Key-Label (String oder `null`)
     * `status`: Status (`"active"`, `"disabled"` oder `"revoked"`)
     * `created_at`: Erstellungszeitpunkt (ISO-8601-String)
     * `revoked_at`: Widerrufszeitpunkt (String oder `null`, falls nicht widerrufen)
4. **Ungenutzte oder kompromittierte Keys widerrufen**:
   * Endpunkt: `POST /keys/{key_id}/revoke` (Hinweis: verwendet `POST` mit der Ziel-`key_id` im Pfad; leerer Request-Body)
   * Header: `Authorization: Bearer <token>`
   * Verhalten: idempotent; Keys mit dem Status `active` oder `disabled` können beide widerrufen werden. Falls bereits widerrufen, wird unverändert HTTP 200 zurückgegeben. Nach dem Widerruf werden Anfragen mit diesem Key abgelehnt.
   * Antwort: HTTP 200 mit dem widerrufenen Key-Objekt (Felder entsprechen dem obigen Key-Objekt, mit `status: "revoked"` und einem Zeitstempel in `revoked_at`).

> **Sicherheit von Keys und Secrets**
>
> Speichern Sie private Wallet-Schlüssel und API keys in Umgebungsvariablen oder einem Secrets-Manager. Übertragen Sie diese niemals in Code-Repositories, schreiben Sie sie nicht in Logs und fügen Sie sie keinesfalls in KI-Chat-Konversationen ein.


## Sicherheitsempfehlungen

* **Kurzlebige Keys verwenden und nach Abschluss widerrufen**: Erstellen Sie für automatisierte oder kurzlebige Aufgaben Keys mit geringer Lebensdauer über `expires_in_secs` und widerrufen Sie diese unmittelbar nach Abschluss der Arbeit über `POST /keys/{key_id}/revoke`.

## Registrierungs-Ratenlimits (`signup_rate_limited`)

Die Kontoerstellung unterliegt Registrierungs-Ratenlimits. Der Token-Bucket pro IP verfügt über eine Kapazität für 100 Konten und füllt sich mit 100 Konten/Stunde pro IPv4-Adresse oder IPv6-/64-Präfix auf, geteilt zwischen SIWE- und OAuth-Registrierung:

* Bei Überschreiten der Registrierungslimits gibt `POST /auth/siwe/login` HTTP 429 `signup_rate_limited` mit einem `Retry-After`-Header zurück, der die Wartezeit in Sekunden angibt.
* Das Feld `reason` unterscheidet den Geltungsbereich des Limits:
  * `per_ip`: Das Registrierungsbudget für das anfragende IP-Präfix ist aufgebraucht.
  * `global`: Das aggregierte Plattform-Registrierungslimit ist aufgebraucht.
* Registrierungs-Ratenlimits bewerten nur Neuregistrierungen. Bereits bestehende Konten, die sich anmelden, werden durch Registrierungs-Ratenlimits nicht blockiert.

## Zugehörige Ressourcen

* Lesen Sie den [Integrationsleitfaden für KI-Agenten](https://docs.blockvectra.com/de/guides/ai-agents/), um mehr über den schlüssellosen MCP-Server und maschinenlesbare Kontextdateien zu erfahren.
* Konsultieren Sie den [Schnellstart](https://docs.blockvectra.com/de/quickstart/) für mehrsprachige Client-Beispiele.
* Überprüfen Sie die [Fehlerreferenz](https://docs.blockvectra.com/de/errors/) für vollständige Fehlercodes, Gründe und automatisierte Wiederherstellungsmaßnahmen.

## Nächste Schritte

* Senden Sie Ihren ersten JSON-RPC- oder Data-API-Aufruf mit `x-api-key: $BLOCKVECTRA_API_KEY`.
* [Kontoguthaben und Limits prüfen](https://docs.blockvectra.com/de/guides/ai-agents/#query-balance-get-v1account) mittels `GET /v1/account`.
* Folgen Sie dem [Leitfaden für programmatische Agent-Aufladungen](https://docs.blockvectra.com/de/guides/agent-topup/), um Ihr Guthaben aufrechtzuerhalten.
