Collar API

Passports and pay-per-call data for AI agents on Robinhood Chain.

What Collar is

Collar gives AI agents two things on Robinhood Chain: a passport, an on-chain record of who owns the agent, which key it signs with, what it may spend and everything it has paid for; and a pay-per-call API of live Stock Token data at 0.001 USDG a request.

Payment follows the x402 idea: a request without payment gets HTTP 402 Payment Required with the price and how to pay. Instead of one on-chain transfer per request, which would cost more in gas than the call itself, agents prepay a USDG balance and the calls are settled in batches.

The passport

Registering stores, in the CollarRegistry contract: the owner (the wallet that registered), the signer (the address of the key the agent signs requests with, unique per agent), a name of up to 32 characters and a daily cap in USDG. The agent gets the next number, starting from #1.

The owner can top up, withdraw any part of the balance at any time, change the cap, rotate the signer key, pause the agent, rename it or hand it to another owner. Anyone can top up any agent. Every registration, top-up, withdrawal and settled batch is an event, which is what the passport page shows as the agent's record.

Signing a request

For every paid request the agent signs this exact text with its signer key (EIP-191, a normal personal_sign):

collar:<agentId>:<METHOD>:<path>:<unixSeconds>

e.g.  collar:7:GET:/v1/stocks/NVDA:1791460000

and sends three headers: x-collar-agent (the id), x-collar-ts (the same unix seconds) and x-collar-sig. The timestamp must be within 60 seconds of the server's clock, and a signature works once. The path is without the query string.

A paid answer carries x-collar-charged (0.001) and x-collar-spendable, what the agent can still spend today after this call.

The 402 answer

Any paid endpoint answers 402 when the request is unsigned, stale, replayed, signed by the wrong key, or when the agent is paused or cannot afford the call. The error field says which: payment_required, stale_signature, replayed_signature, bad_signature, wrong_signer, unknown_agent, agent_paused or insufficient_balance.

HTTP/1.1 402 Payment Required
x-payment-required: collar-prepaid; price=0.001; asset=USDG; chain=4663

{
  "x402Version": 1,
  "error": "payment_required",
  "accepts": [{
    "scheme": "collar-prepaid", "network": "robinhood", "chainId": 4663,
    "asset": "0x5fc5…d168", "assetSymbol": "USDG",
    "maxAmountRequired": "0.001", "payTo": "<registry>", "resource": "/v1/stocks/NVDA"
  }],
  "how": "Register an agent at collar402.xyz/app and prepay USDG. Sign ..."
}

Endpoints

Every paid endpoint costs 0.001 USDG. Prices come from the Chainlink feeds of each Stock Token (8 decimals, with the round's update time); 24h change, liquidity, volume and trades from the deepest pool on DexScreener. Data is cached for up to 20 seconds.

GET /v1/stockspaidAll 27 Stock Tokens: symbol, token address, price, updatedAt, change24h.
GET /v1/stocks/{symbol}paidOne Stock Token.
GET /v1/pools/{symbol}paidIts deepest pool: pair, dex, liquidityUsd, volume24hUsd, trades24h.
GET /v1/moverspaidTop 5 gainers and losers of the last 24h.
GET /v1/agents/{id}freeA passport as JSON.
GET /v1freeThe index: price, registry address, endpoints.

Code

TypeScript with viem:

import { privateKeyToAccount } from "viem/accounts";

const agent = privateKeyToAccount(process.env.AGENT_KEY);
const id = 7; // your passport number

async function collar(path) {
  const ts = Math.floor(Date.now() / 1000);
  const sig = await agent.signMessage({ message: `collar:${id}:GET:${path}:${ts}` });
  const r = await fetch("https://collar402.xyz" + path, {
    headers: { "x-collar-agent": String(id), "x-collar-ts": String(ts), "x-collar-sig": sig },
  });
  if (r.status === 402) throw new Error((await r.json()).error); // top up or raise the cap
  return r.json();
}

const nvda = await collar("/v1/stocks/NVDA");

Python with eth-account:

import time, requests
from eth_account import Account
from eth_account.messages import encode_defunct

agent, agent_id = Account.from_key(AGENT_KEY), 7

def collar(path):
    ts = int(time.time())
    msg = encode_defunct(text=f"collar:{agent_id}:GET:{path}:{ts}")
    sig = agent.sign_message(msg).signature.hex()
    r = requests.get("https://collar402.xyz" + path, headers={
        "x-collar-agent": str(agent_id), "x-collar-ts": str(ts), "x-collar-sig": sig})
    return r.json()

Settlement and limits

Served calls are counted per agent. At most every 10 minutes the meter wallet charges them on chain with one chargeMany transaction. The contract charges an agent only if it is not paused, the amount fits its balance and today's spending stays within its daily cap (days start at 00:00 UTC); otherwise that entry is skipped, not charged. Before serving, the API checks the same limits against the calls not yet settled, so an agent is never served beyond what it can pay.

The price is fixed in the contract at 0.001 USDG and can never be set above 0.01. The owner of the contract can change the price within that limit, the meter and the treasury that collects the fees. It has no function that moves an agent's balance.

$CLLR holders

Every 1M $CLLR in the wallet that owns an agent gives that owner 1,000 free calls a day, up to 10,000 at 10M. The quota is per owner, shared by all of the owner's agents, and resets at 00:00 UTC. The balance is read on chain and cached for a minute.

Free calls are used before the prepaid balance and are not charged on chain; their answers carry x-collar-charged: 0 and x-collar-free-left. The agent still needs a passport, a valid signature and must not be paused.

Risks

The contracts are new and unaudited. Fork tests on Robinhood Chain mainnet register an agent, prepay real USDG, meter 3,000 calls and withdraw the rest.

The meter is trusted to count calls honestly; the contract limits what it can take to the agent's balance and daily cap, so keep the cap at what you are willing to spend in a day. Anyone holding the signer key can spend the agent's budget: keep it secret, and rotate it if it leaks. Data comes from Chainlink and DexScreener as is.

Contracts

CollarRegistryat launch
Meterat launch
Owner and treasuryat launch

Register an agent