# Agent programmatic top-up: on-chain balance funding with x-api-key

> Original page: https://docs.blockvectra.com/en/guides/agent-topup/

For autonomous AI agents, automated scripts, and backend server programs, BlockVectra provides a programmatic on-chain top-up workflow over standard HTTP endpoints. Without requiring browser interaction, programs can use an existing API key to retrieve an account-dedicated EVM deposit address, inspect open networks and tokens, and poll deposit status after broadcasting an on-chain transfer.

> **API key security and server-side requirement**
>
> The `x-api-key` header **can only be called from server-side environments**. Browser cross-origin preflight requests intentionally do not permit this header on top-up endpoints. Never call top-up endpoints from client-side browser code, and never expose your API key in frontend bundles, public repositories, or AI chat conversations.


## Applicable scenarios

* **Autonomous AI agents**: When Compute Units (CU) or account balance run low, agents can inspect top-up availability and replenish funds on-chain independently.
* **CI/CD and automation pipelines**: Automated test suites and recurring server jobs can maintain an active account balance programmatically.
* **Backend services without browsers**: Headless server services can manage balance top-ups directly via standard HTTP clients.

## Prerequisites

* **Existing API key**: Calling authenticated top-up endpoints requires an active BlockVectra RPC API key. If you do not have an API key yet, follow the [Programmatic sign-up guide](https://docs.blockvectra.com/en/guides/programmatic-signup/) to sign up and create a key using an Ethereum wallet signature, or create one in the [Console](https://console.blockvectra.com/en/login/?next=%2Fen%2Fkeys%2F).
* **On-chain assets**: Your agent environment or funding wallet must hold USDT or USDC on a supported network, along with sufficient native gas tokens to broadcast transactions.
* **Environment variable**: Store your key in the `BLOCKVECTRA_API_KEY` environment variable.

The authenticated top-up endpoints accept the `x-api-key` header directly using the same API key used for RPC calls. No browser session is required.

## Four-step top-up workflow

All endpoints use the official production host:

```
https://api.blockvectra.com
```

### 1. Check availability (GET /v1/topup/status)

Before initiating a transfer, verify global top-up status and check which networks and tokens are currently open. This endpoint is public and requires no credentials.

```bash
curl -s https://api.blockvectra.com/v1/topup/status
```

Example response:

```json
{
  "enabled": true,
  "networks": [
    {
      "network": "base_mainnet",
      "chain_id": 8453,
      "token": "USDC",
      "enabled": true
    },
    {
      "network": "bsc_mainnet",
      "chain_id": 56,
      "token": "USDT",
      "enabled": true
    },
    {
      "network": "bsc_mainnet",
      "chain_id": 56,
      "token": "USDC",
      "enabled": true
    }
  ],
  "min_deposit_usd": "1.000000"
}
```

* `enabled`: Global switch. If `false`, top-up is closed across all networks.
* `networks`: Open status per network and token. When `enabled` is `false` for a network or token, **do not transfer funds on that network**.
* `min_deposit_usd`: Global minimum deposit amount in USD formatted to 6 decimal places.

### 2. Retrieve deposit address and parameters (GET /v1/topup/deposit-address)

Retrieve or allocate the customer EVM deposit address and inspect supported networks and token contracts. This endpoint requires `x-api-key` authentication and must be called from server-side environments only.

```bash
curl -s \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  https://api.blockvectra.com/v1/topup/deposit-address
```

Example response:

```json
{
  "address": "0x<your-dedicated-deposit-address>",
  "min_deposit_usd": "1.000000",
  "deposits_url": "https://api.blockvectra.com/v1/topup/deposits",
  "networks": [
    {
      "chain": "base_mainnet",
      "chain_id": 8453,
      "name": "Base",
      "typical_credit_seconds": 30,
      "explorer_tx_url": "https://basescan.org/tx/{tx_hash}",
      "tokens": [
        {
          "symbol": "USDC",
          "contract": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
          "decimals": 6,
          "min_amount_raw": "1000000"
        }
      ]
    },
    {
      "chain": "bsc_mainnet",
      "chain_id": 56,
      "name": "BNB Smart Chain",
      "typical_credit_seconds": 60,
      "explorer_tx_url": "https://bscscan.com/tx/{tx_hash}",
      "tokens": [
        {
          "symbol": "USDT",
          "contract": "0x55d398326f99059fF775485246999027B3197955",
          "decimals": 18,
          "min_amount_raw": "1000000000000000000"
        },
        {
          "symbol": "USDC",
          "contract": "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
          "decimals": 18,
          "min_amount_raw": "1000000000000000000"
        }
      ]
    }
  ]
}
```

* `address`: EIP-55 checksummed EVM deposit address dedicated to your account.
* `networks`: List of open EVM networks. Closed networks are omitted. Includes chain slug `chain`, EVM chain ID `chain_id`, display name `name`, typical credit latency in seconds after block inclusion `typical_credit_seconds`, and block explorer transaction URL template `explorer_tx_url`.
* `tokens`: Supported stablecoins on this network, including token symbol `symbol` (USDT or USDC), contract address `contract`, token decimals `decimals`, and minimum deposit amount in raw atomic units `min_amount_raw`.

> **Token decimals and amount conversion**
>
> The same token can have different decimals on different chains (for example, USDT and USDC on BSC are 18 decimals, whereas USDC on Base is 6 decimals). Amount calculation must use the `decimals` returned for that specific network rather than hardcoding a single token decimal value.


#### Error responses

Authenticated top-up endpoints (`/v1/topup/deposit-address` and `/v1/topup/deposits`) return standard JSON error structures:

* **HTTP 401 (Authentication failure)**: Returned when the `x-api-key` header is missing (`missing_api_key`) or the key is invalid, revoked, or disabled (`invalid_api_key`):

```json
{
  "error": {
    "code": "missing_api_key",
    "message": "missing x-api-key header"
  }
}
```

* **HTTP 409 (Top-up disabled)**: Returned when top-up is closed globally or across all networks (`topup_disabled`):

```json
{
  "error": {
    "code": "topup_disabled",
    "message": "topup is disabled"
  }
}
```

When HTTP 409 is returned, no new address is allocated.

### 3. Broadcast on-chain transfer

Using your agent's wallet or script, submit an ERC-20 `transfer` transaction to the deposit `address` retrieved in Step 2.

Transfer requirements:

* Send only tokens and contracts listed in the `tokens` array for that network.
* Ensure the transfer amount is greater than or equal to `min_amount_raw` (or `min_deposit_usd`), formatted according to the token's `decimals` on that network.
* Record the on-chain transaction hash (`tx_hash`) once submitted.

### 4. Poll deposit records and verify credit (GET /v1/topup/deposits)

After the transaction is included in a block, query deposit transfer history to track crediting status. This endpoint requires `x-api-key` and is server-side only.

#### Query parameters

* `limit`: Number of deposit records to return per page. Default is `20`, valid range is `1`–`100`.
* `before`: Cursor pagination parameter based on `deposit_id`. Pass the `next_before` value from the previous page response to fetch the next page of earlier records.
* `tx_hash`: Optional 0x-prefixed 64-character hexadecimal transaction hash to filter for a specific transfer.

Filter by transaction hash (`tx_hash`) to inspect your specific transfer:

```bash
curl -s \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  "https://api.blockvectra.com/v1/topup/deposits?tx_hash=0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
```

Example response:

```json
{
  "items": [
    {
      "deposit_id": 42,
      "chain": "base_mainnet",
      "chain_id": 8453,
      "token": "USDC",
      "contract": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "amount": "25.000000",
      "tx_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
      "tx_log_ordinal": 0,
      "block_number": 123456789,
      "external_ref": "eip155:8453:0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890:0",
      "status": "credited",
      "reason": null,
      "credited_units": 250000,
      "credited_cu": 250000000,
      "detected_at": "2026-10-02T10:00:00Z"
    }
  ],
  "next_before": null
}
```

* `items`: Array of deposit records matching the query parameters.
* `next_before`: Cursor ID for the next page when more records exist, or `null` if there are no earlier records. Combine with the `before` query parameter for cursor-based pagination.

Deposit `status` values:

* `processing`: Transfer detected on-chain, awaiting required block confirmations.
* `credited`: Transfer confirmed and credited to the account balance. `credited_units` and `credited_cu` indicate the credited amounts.
* `not_credited`: Transfer cannot be credited. The `reason` field indicates the cause:
  * `below_minimum`: Deposit amount is below the minimum threshold.
  * `large_amount`: Deposit amount exceeds threshold and requires manual review.
  * `other`: Other crediting exception.

Credit latency and polling guidance:

* **Arrival and crediting time**: Credit time is governed by the `typical_credit_seconds` returned in Step 2.
* **Polling interval**: Poll at a recommended interval of **every 20–60 seconds**, not more frequently, to avoid triggering rate limits.
* **Status progression**: Once sufficient block confirmations are reached, status transitions automatically from `processing` to `credited`.

## Code examples

The following examples demonstrate how to read `BLOCKVECTRA_API_KEY` from the environment and query top-up endpoints in Node.js and Python.

### Node.js (fetch)

```javascript
import process from "node:process";

const apiKey = process.env.BLOCKVECTRA_API_KEY;
if (!apiKey) {
  throw new Error("Missing BLOCKVECTRA_API_KEY environment variable");
}

const BASE_URL = "https://api.blockvectra.com";

// 1. Check availability
const statusRes = await fetch(`${BASE_URL}/v1/topup/status`);
const status = await statusRes.json();
if (!status.enabled) {
  throw new Error("Top-up is currently disabled");
}

// 2. Retrieve deposit address and open networks
const addressRes = await fetch(`${BASE_URL}/v1/topup/deposit-address`, {
  headers: { "x-api-key": apiKey },
});

if (addressRes.status === 401) {
  throw new Error("Missing or invalid API key (HTTP 401)");
}
if (addressRes.status === 409) {
  throw new Error("Top-up is disabled (topup_disabled)");
}
if (!addressRes.ok) {
  throw new Error(`Failed to retrieve deposit address: ${addressRes.status}`);
}

const depositData = await addressRes.json();
console.log("Deposit address:", depositData.address);
console.log("Open networks count:", depositData.networks.length);

// 3. Poll deposit status
async function checkDepositStatus(txHash) {
  const url = new URL(`${BASE_URL}/v1/topup/deposits`);
  url.searchParams.set("tx_hash", txHash);

  const res = await fetch(url, {
    headers: { "x-api-key": apiKey },
  });
  if (res.status === 401) {
    throw new Error("Missing or invalid API key (HTTP 401)");
  }
  if (!res.ok) {
    throw new Error(`Failed to query deposits: ${res.status}`);
  }
  return res.json();
}
```

### Python (requests)

```python
# pip install requests
import os
import requests

api_key = os.environ.get("BLOCKVECTRA_API_KEY")
if not api_key:
    raise ValueError("Missing BLOCKVECTRA_API_KEY environment variable")

base_url = "https://api.blockvectra.com"

# 1. Check availability
resp = requests.get(f"{base_url}/v1/topup/status", timeout=10)
resp.raise_for_status()
status_data = resp.json()
if not status_data.get("enabled"):
    raise RuntimeError("Top-up is currently disabled")

# 2. Retrieve deposit address
resp = requests.get(
    f"{base_url}/v1/topup/deposit-address",
    headers={"x-api-key": api_key},
    timeout=10,
)
if resp.status_code == 401:
    raise RuntimeError("Missing or invalid API key (HTTP 401)")
if resp.status_code == 409:
    raise RuntimeError("Top-up is disabled (topup_disabled)")
resp.raise_for_status()
deposit_data = resp.json()
print("Deposit address:", deposit_data["address"])

# 3. Poll deposit status
def check_deposit_status(tx_hash: str):
    resp = requests.get(
        f"{base_url}/v1/topup/deposits",
        headers={"x-api-key": api_key},
        params={"tx_hash": tx_hash},
        timeout=10,
    )
    if resp.status_code == 401:
        raise RuntimeError("Missing or invalid API key (HTTP 401)")
    resp.raise_for_status()
    return resp.json()
```

## Important notes

* **Supported networks and tokens**: Only transfer to networks and token contracts explicitly listed in `GET /v1/topup/status` and `GET /v1/topup/deposit-address`. If `enabled` is `false` for a network in `status`, do not transfer funds to it.
* **Token decimals**: Token decimals differ across chains (e.g. 18 on BSC vs 6 on Base). Always calculate amounts using the `decimals` returned for that network.
* **Minimum deposit amount**: Minimum amounts are determined by `min_deposit_usd` and `min_amount_raw` returned by the API. Transfers below this threshold are not credited automatically.
* **Irreversible transfers**: Transfers sent to unsupported chains or with incorrect tokens cannot be credited automatically. Always verify chain ID and contract address prior to broadcasting.
* **Plan and rate limit updates**: Once your first paid top-up is credited, the account transitions from the Free Plan to a paid account, removing the Free Plan's per-second call limit. Each key remains subject to Compute Unit (CU) rate and burst limits. For complete details, see [Billing rules](https://docs.blockvectra.com/en/guides/billing-rules/) and [Free plan](https://docs.blockvectra.com/en/guides/free-plan/).
* **Polling frequency**: Poll `GET /v1/topup/deposits` at a recommended interval of every 20–60 seconds, not more frequently, to avoid triggering rate limits.
* **Server-side only**: Never expose `x-api-key` in frontend applications or client-side browser code.

## Next steps

* [Query balance (`GET /v1/account`)](https://docs.blockvectra.com/en/guides/billing-rules/#query-balance-get-v1account) to verify your account balance and remaining Compute Units (CU).
* [Billing rules](https://docs.blockvectra.com/en/guides/billing-rules/) to review Compute Unit (CU) metering, rate limits, and non-billed errors.
* [Programmatic sign-up guide](https://docs.blockvectra.com/en/guides/programmatic-signup/) to create accounts and provision API keys using wallet signatures.
