Buy credits with USDC
Credits are bought with USDC on Base (chain id 8453). There is no card processor, no account form and no ID check. You send USDC from any wallet (Coinbase, MetaMask, Rabby, Rainbow, a hardware wallet, an exchange withdrawal to Base) and the balance appears after 3 confirmations.
The easy way is the buy page: paste your key, pick an amount, then pay with a browser wallet, a phone wallet, or a manual send. Wallets lists what each button does. This page explains the API behind that page, so you (or your agent) can do it without the browser.
How it works
- Ask for an intent. You say how many dollars you want to spend. The API answers with an exact USDC amount (your dollars plus a few random micro-cents), the address to pay and a 24-hour expiry.
- Send exactly that amount of USDC on Base to the
payToaddress. - Claim it. Send the transaction hash. The API reads the receipt from the chain, matches the Transfer to your open intent by its exact amount, and credits your balance once.
The unique amount is what ties a payment to your account, so nobody can claim your transfer and the same transaction can never be credited twice.
| Limit | Value |
|---|---|
| Minimum top-up | $1.00 USD |
| Maximum top-up | $1,000.00 USD |
| Intent lifetime | 24 hours |
| Open (unpaid) intents per account | 10 |
| Confirmations before credit | 3 |
| Exchange rate | X402_USD_PER_CAD = 0.75 USD per CAD (so $10 USD buys 1,333 credits) |
1. Create an intent
curl -s https://api.highlnd-intel.group/v1/topup/crypto/intent \
-H "authorization: Bearer $HIGHLAND_KEY" \
-H "content-type: application/json" \
-d '{"usd": 10}'{
"intentId": "int_3f0c9a...",
"network": "base",
"chainId": 8453,
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xfdb6ea04e7e75d39d3bdb4ba21e6b6c8fec36048",
"amount": "10.004217",
"amountMicro": "10004217",
"creditCents": 1333,
"expiresAt": "2026-10-24T23:10:00.000Z",
"paymentUri": "ethereum:0x8335...2913@8453/transfer?address=0xfdb6...6048&uint256=10004217",
"next": "Send exactly `amount` USDC on this network to `payTo`, then POST /v1/topup/crypto {\"txHash\":\"0x...\"} with the same API key."
}2. Send the USDC
Any wallet that can send USDC on Base works. Check three things before you confirm:
| Check | Must be |
|---|---|
| Network | Base (not Ethereum mainnet, not Arbitrum, not Polygon) |
| Token | USDC, contract 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| Amount | exactly amount from the intent |
3. Claim the payment
curl -s https://api.highlnd-intel.group/v1/topup/crypto \
-H "authorization: Bearer $HIGHLAND_KEY" \
-H "content-type: application/json" \
-d '{"txHash": "0x5c1e...9ab2"}'{ "intentId": "int_3f0c9a...", "txHash": "0x5c1e...9ab2", "credited": true, "creditCents": 1333 }Calling claim again with the same hash is safe: it answers "credited": false, "creditCents": 0, "note": "already_credited" and adds nothing. Check the balance with GET /v1/account.
Automate it (Python, with web3.py)
import os, time, requests
from web3 import Web3
API = "https://api.highlnd-intel.group"
H = {"authorization": f"Bearer {os.environ['HIGHLAND_KEY']}"}
USDC_ABI = [{"name": "transfer", "type": "function", "inputs": [
{"name": "to", "type": "address"}, {"name": "value", "type": "uint256"}],
"outputs": [{"type": "bool"}], "stateMutability": "nonpayable"}]
intent = requests.post(f"{API}/v1/topup/crypto/intent", headers=H, json={"usd": 10}, timeout=30).json()
w3 = Web3(Web3.HTTPProvider("https://mainnet.base.org"))
acct = w3.eth.account.from_key(os.environ["WALLET_PRIVATE_KEY"]) # your own wallet, never shared
usdc = w3.eth.contract(address=Web3.to_checksum_address(intent["asset"]), abi=USDC_ABI)
tx = usdc.functions.transfer(Web3.to_checksum_address(intent["payTo"]), int(intent["amountMicro"])).build_transaction(
{"from": acct.address, "nonce": w3.eth.get_transaction_count(acct.address)})
tx_hash = w3.eth.send_raw_transaction(acct.sign_transaction(tx).raw_transaction).hex()
for _ in range(20): # about 3 confirmations on Base takes ~10 seconds
r = requests.post(f"{API}/v1/topup/crypto", headers=H, json={"txHash": tx_hash}, timeout=30)
if r.status_code == 200: # 409 pending_confirmations until 3 blocks
print(r.json()); break
time.sleep(5)Claim errors
| Status | Error | What to do |
|---|---|---|
| 400 | invalid_tx_hash | Send the full 0x + 64 hex characters. |
| 404 | tx_not_found | Not mined yet or sent on another network. Retry in a minute. |
| 404 | intent_not_found | No open intent with this exact amount on this key. Use the key that made the intent. |
| 409 | pending_confirmations | Fewer than 3 confirmations so far (confirmations, required in the body). Wait a few seconds and retry. |
| 409 | intent_already_claimed / tx_already_used | That payment was already credited. |
| 422 | tx_failed | The transaction reverted on-chain. Nothing was sent; try again from your wallet. |
| 422 | no_matching_transfer | The transaction did not send USDC to payTo. |
| 422 | outside_intent_window | The payment landed after the intent expired. Contact us with the hash. |
| 429 | too_many_open_intents | You have 10 unpaid intents. Pay one or wait for them to expire. |
Prefer a monthly pass?
The same flow buys a monthly plan with bonus credits: use POST /v1/plans/intent {"plan":"solo"} instead of step 1.