# การลงทะเบียนแบบเป็นโปรแกรม: การเข้าสู่ระบบด้วยกระเป๋าเงินและการสร้าง API Key สำหรับ Agent และ CI

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

สำหรับ autonomous AI agent, ไปป์ไลน์ CI และสคริปต์อัตโนมัติที่ทำงานโดยไม่มีเบราว์เซอร์ BlockVectra มีเวิร์กโฟลว์การเข้าสู่ระบบและเปิดบัญชีแบบเป็นโปรแกรมโดยอิงจากลายเซ็นกระเป๋าเงิน Ethereum (EIP-4361 / EIP-191)

> **ความปลอดภัยของคีย์**
>
> ห้ามวาง private key, session token หรือ API key ลงในบทสนทนากับ AI หรือส่งเป็นอาร์กิวเมนต์ของเครื่องมือ MCP โดยเด็ดขาด


ก่อนที่จะลงทะเบียน คุณสามารถทดลองใช้ public endpoint แบบไม่ต้องใช้คีย์ `https://api.blockvectra.com/v1/robinhood_mainnet/public` ดูก่อนได้ (เฉพาะเมธอดกระเป๋าเงิน JSON-RPC เท่านั้น ส่วน Data API จำเป็นต้องใช้คีย์ ทั้งนี้ เมธอดและขีดจำกัดเป็นไปตาม `/v1/chains`) และสามารถลงทะเบียนบัญชีได้หากโควตาไม่เพียงพอ

## ภาพรวมขั้นตอนการทำงาน

ขั้นตอนการลงทะเบียนและจัดสรรคีย์แบบเป็นโปรแกรมประกอบด้วย 4 ขั้นตอน:

1. **ขอ Challenge**: ส่งคำขอไปยัง `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 key**: ใช้ session token เพื่อเรียก `POST /keys` และสร้าง API key

## ตัวอย่างที่สามารถรันได้ฉบับสมบูรณ์

เริ่มต้นที่นี่: ใช้ตัวลงนาม Ethereum EOA ภายในเครื่อง, สร้างคีย์ และตรวจสอบความถูกต้องด้วย eth\_blockNumber
สำหรับตัวอย่าง Bash คุณจำเป็นต้องมี curl, jq และ Foundry cast เก็บรักษาข้อมูลประจำตัวของกระเป๋าเงินไว้ในสภาพแวดล้อมการลงนามภายในเครื่องของคุณ

เทมเพลตเริ่มต้นฉบับสมบูรณ์: [blockvectra/agent-quickstart](https://github.com/blockvectra/agent-quickstart)

สคริปต์ต่อไปนี้จะอ่านข้อมูลประจำตัวของกระเป๋าเงิน, ดำเนินการตามลำดับขั้นตอน challenge และการเข้าสู่ระบบ, จัดสรร API key, export หรือพิมพ์ `export BLOCKVECTRA_API_KEY=...` สำหรับการกำหนดค่าสภาพแวดล้อม และส่งคำขอ `eth_blockNumber` เพื่อตรวจสอบความถูกต้อง:

คีย์ใหม่จะใช้เวลา 2-3 วินาทีกว่าจะเริ่มใช้งานได้ ตัวอย่างเหล่านี้จะลองใหม่อัตโนมัติ

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


## Base URL และโหมดการทำงานแบบเป็นโปรแกรม

endpoint การยืนยันตัวตนและการจัดการคีย์ทั้งหมดใช้ base URL อย่างเป็นทางการดังนี้:

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

### การละเว้นส่วนหัว Origin

คำขอแบบเป็นโปรแกรมจะทำงานใน **โหมดโปรแกรม (programmatic mode)**:

* ทั้งคำขอ challenge (`POST /auth/siwe/challenge`) และการเข้าสู่ระบบ (`POST /auth/siwe/login`) **ต้องไม่รวมส่วนหัว `Origin`** (`curl` และไคลเอนต์ HTTP มาตรฐานจะละเว้นส่วนหัวนี้เป็นค่าเริ่มต้นอยู่แล้ว อย่าเพิ่มส่วนหัวนี้ด้วยตนเอง)
* หากมีการส่งส่วนหัว `Origin` แต่ไม่ใช่โดเมนของเว็บคอนโซลที่กำหนดค่าไว้ (รวมถึงสตริงว่างหรือ `null`) คำขอ challenge จะส่งคืน HTTP 400 `invalid_request`
* หากโหมดขณะเข้าสู่ระบบไม่ตรงกับโหมดของ challenge (เช่น ขอ challenge แบบโปรแกรมโดยไม่มี `Origin` แต่ส่งการเข้าสู่ระบบพร้อมส่วนหัว `Origin` หรือในทางกลับกัน) คำขอเข้าสู่ระบบจะส่งคืน HTTP 400 `siwe_invalid` พร้อม `reason: domain_mismatch`

### ความสมบูรณ์ของข้อความและข้อกำหนดของกระเป๋าเงิน

* **การลงนามและการส่งข้อความตามตัวอักษร**: ไคลเอนต์ต้องลงนามและส่งข้อความตรงตามที่ได้รับจาก endpoint ของ challenge ทุกประการ อย่าแก้ไขช่องว่าง, โดเมน, chain ID หรือฟิลด์ใดๆ การแก้ไขใดๆ จะส่งผลให้ได้รับ HTTP 400 `siwe_invalid` พร้อม `reason: signature`
* **กระเป๋าเงินที่รองรับ**: Externally Owned Accounts (EOA) บน Ethereum mainnet (Chain ID 1) ลายเซ็นต้องเป็นลายเซ็น ECDSA ขนาด 65 ไบต์ (`personal_sign`) ไม่รองรับกระเป๋าเงินแบบสัญญาอัจฉริยะ (EIP-1271) และ smart account
* **อายุการใช้งานของ Challenge**: แต่ละ challenge nonce จะใช้งานได้เพียงครั้งเดียวและหมดอายุหลังจาก 5 นาที

### เนื้อหาคำขอและการระบุที่มาของการลงทะเบียน (ไม่บังคับ)

เนื้อหาคำขอ `POST /auth/siwe/login` รับพารามิเตอร์การยืนยันตัวตนที่จำเป็นและฟิลด์ระบุที่มาของการลงทะเบียนที่ไม่บังคับ:

* **ฟิลด์ที่จำเป็น**:
  * `message`: สตริงข้อความ SIWE ฉบับสมบูรณ์ที่ได้รับจาก challenge endpoint
  * `signature`: ลายเซ็นเลขฐานสิบหกขนาด 65 ไบต์ (ขึ้นต้นด้วย `0x`) ที่สร้างขึ้นจากการลงนาม `message` ผ่าน EIP-191 ด้วยกระเป๋าเงิน Ethereum
* **ฟิลด์ระบุที่มาที่ไม่บังคับ** (บันทึกเพียงครั้งเดียวเมื่อสร้างบัญชีใหม่ และจะถูกละเว้นในการเข้าสู่ระบบครั้งถัดไป):
  * `ref`: โทเค็นช่องทางที่เป็นตัวพิมพ์เล็กตรงตาม `^[a-z0-9._-]{1,64}$` (ตัวอักษร ASCII ตัวพิมพ์เล็ก, ตัวเลข, `.`, `_`, `-`, ความยาว 1–64 ตัวอักษร) ตัวอย่างเช่น autonomous agent สามารถตั้งค่านี้เป็นตัวระบุเฟรมเวิร์กหรือรันไทม์ของตนเองได้ (เช่น `my-agent.v1`) ค่าที่ไม่เป็นไปตามข้อกำหนด (รวมถึงตัวพิมพ์ใหญ่, สตริงว่าง, ความยาวเกิน หรืออักขระที่ไม่รองรับ) จะส่งคืน HTTP 400 `invalid_request` โดยไม่มีการแปลงตัวพิมพ์ และทำให้ไม่สามารถสร้างบัญชีได้ ให้ละเว้นหรือส่ง `null` เมื่อไม่ต้องการใช้งาน
  * `referrer`: URL ต้นทางหรือสตริงชื่อโฮสต์ โดยเฉพาะประเภทที่ไม่ใช่สตริงเท่านั้นที่จะส่งคืน HTTP 400

การส่งฟิลด์ที่ไม่ได้กำหนดไว้ เช่น `signup_method` จะส่งคืน HTTP 400 `invalid_request`

## Session token และ API key

### วงจรชีวิตของ Session token

* **รูปแบบ**: `rgs_` ตามด้วยอักขระเลขฐานสิบหกตัวพิมพ์เล็ก 64 ตัว
* **อายุการใช้งาน**: อายุการใช้งานสูงสุด 7 วัน และจะหมดอายุโดยอัตโนมัติหลังจากไม่มีการใช้งานเป็นเวลา 24 ชั่วโมง
* **ไม่มี Refresh token**: เมื่อ session token หมดอายุ ให้เริ่มขั้นตอน challenge และการเข้าสู่ระบบใหม่
* **ส่วนหัว**: ส่ง session token ในส่วนหัวของคำขอ `Authorization: Bearer rgs_...`

### การสร้าง API key

* เรียก `POST /keys` พร้อม session token เพื่อสร้าง API key (`rgw_` ตามด้วยอักขระเลขฐานสิบหก 64 ตัว)
* แต่ละบัญชีสามารถมีคีย์ที่ยังไม่ถูกเพิกถอนและยังไม่หมดอายุ (`active` + `disabled`) ได้สูงสุด 20 คีย์ โดยคีย์ที่หมดอายุแล้วจะไม่ถูกนับ หากเกินจำนวนนี้จะส่งคืน HTTP 409 `key_limit_reached` พร้อม `reason: active_keys` และ `limit: 20` โปรดเพิกถอนคีย์ก่อน ขีดจำกัดนี้มีผลครอบคลุมทุกข้อมูลระบุตัวตน, เซสชัน และเชนทั้งหมดของบัญชี นอกจากนี้ การสร้างและการสลับคีย์ยังจำกัดอยู่ที่ 20 ครั้งต่อ 24 ชั่วโมง หากเกินจะส่งคืน HTTP 429 `rate_limited` พร้อม `Retry-After: 3600`
* เพดานและวันหมดอายุที่ไม่บังคับ: คุณสามารถระบุ `cu_cap` (เพดาน CU ตลอดอายุการใช้งานสำหรับคีย์ ซึ่งเป็น soft cap) และวันหมดอายุ (`expires_in_secs` หรือ `expires_at` สูงสุดไม่เกินจำนวนวันที่นโยบายคีย์อนุญาต) เมื่อหมดอายุหรือเพดานหมดลง เซิร์ฟเวอร์จะส่งคืน 403 (JSON-RPC `-32025`, reason `key_expired` หรือ `key_cap_exhausted`)
* ค่าความลับ `api_key` จะ **แสดงเพียงครั้งเดียวเมื่อสร้าง** โปรดจัดเก็บอย่างปลอดภัยทันทีใน secrets manager หรือตัวแปรสภาพแวดล้อมของคุณ
* API key เดียวสามารถใช้งานได้กับทุกเชนที่รองรับทั้งบน JSON-RPC และ Data API

## ทำ Session หรือ API key หายใช่ไหม?

ใน BlockVectra **ข้อมูลระบุตัวตนของบัญชี agent จะผูกกับที่อยู่กระเป๋าเงิน Ethereum ที่ใช้ระหว่างการลงทะเบียน** หาก session token ของคุณหมดอายุ หรือ API key สูญหายหรือรั่วไหล คุณสามารถกู้คืนการควบคุมทั้งหมดได้โดยใช้กระเป๋าเงินนั้นเพียงอย่างเดียว:

1. **ยืนยันตัวตนใหม่ด้วยกระเป๋าเงินเดิม**: ขอ challenge, ลงนามด้วยกระเป๋าเงินเดิม และส่งคำขอเข้าสู่ระบบ (`POST /auth/siwe/login`) เซิร์ฟเวอร์จะตรวจสอบลายเซ็น, เข้าสู่ระบบบัญชีเดิมที่มีอยู่โดยส่ง `account_created: false` และออก session token ใหม่ให้
2. **สร้าง API key ใหม่**: เมื่อได้ session token ใหม่แล้ว ให้เรียก `POST /keys` พร้อม `{"label": "..."}` และส่วนหัว `Authorization: Bearer <token>` endpoint จะส่งคืน HTTP 201 พร้อมรายละเอียดคีย์ที่สร้างขึ้นใน `key` และค่าความลับที่แสดงครั้งเดียวใน `api_key` บันทึกคีย์นี้ในตัวแปรสภาพแวดล้อมหรือ secrets manager ของคุณทันที
3. **แสดงรายการคีย์ทั้งหมดของบัญชี**:
   * Endpoint: `GET /keys`
   * ส่วนหัว: `Authorization: Bearer <token>`
   * พารามิเตอร์ Query: ไม่บังคับ `include_revoked=true` (เมื่อเป็น `true` จะรวมคีย์ที่ถูกเพิกถอนด้วย ค่าเริ่มต้นคือเฉพาะคีย์ที่ active/disabled เท่านั้น)
   * การตอบกลับ: HTTP 200 พร้อม JSON `{"items": [...]}` แต่ละองค์ประกอบในอาร์เรย์ `items` ประกอบด้วย:
     * `key_id`: ตัวระบุคีย์ที่ไม่ซ้ำกัน (สตริง)
     * `label`: ป้ายกำกับของคีย์ (สตริงหรือ `null`)
     * `status`: สถานะ (`"active"`, `"disabled"` หรือ `"revoked"`)
     * `created_at`: เวลาที่สร้าง (สตริง ISO 8601)
     * `revoked_at`: เวลาที่เพิกถอน (สตริง หรือ `null` หากยังไม่ถูกเพิกถอน)
4. **เพิกถอนคีย์ที่ไม่ได้ใช้หรือคีย์ที่ถูกบุกรุก**:
   * Endpoint: `POST /keys/{key_id}/revoke` (หมายเหตุ: ใช้ `POST` พร้อมระบุ `key_id` เป้าหมายในพาธ โดยเนื้อหาคำขอว่างเปล่า)
   * ส่วนหัว: `Authorization: Bearer <token>`
   * พฤติกรรม: idempotent คีย์ที่มีสถานะ `active` หรือ `disabled` สามารถเพิกถอนได้ทั้งคู่ หากถูกเพิกถอนอยู่แล้วจะส่งคืน HTTP 200 โดยไม่เปลี่ยนแปลง หลังจากการเพิกถอน คำขอที่ใช้คีย์นั้นจะถูกปฏิเสธ
   * การตอบกลับ: HTTP 200 ส่งคืนออบเจกต์ของคีย์ที่ถูกเพิกถอน (ฟิลด์ตรงกับออบเจกต์คีย์ด้านบน โดยมี `status: "revoked"` และการประทับเวลาใน `revoked_at`)

> **ความปลอดภัยของคีย์และข้อมูลลับ**
>
> จัดเก็บ private key ของกระเป๋าเงินและ API key ไว้ในตัวแปรสภาพแวดล้อมหรือ secrets manager ห้ามคอมมิตลงในที่เก็บโค้ด, เขียนลงใน log หรือวางลงในบทสนทนากับ 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 agent](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`
* ทำตาม [คู่มือการเติมเงินแบบเป็นโปรแกรมสำหรับ Agent](https://docs.blockvectra.com/en/guides/agent-topup/) เพื่อรักษายอดคงเหลือของคุณ
