DEX
Open app

Hopivia API

Hopivia gives AI agents, trading bots and scripts the same thing the web app gives people: the best price across every major exchange of a network, a Tron ↔ EVM USDT bridge and token safety checks. You get plain JSON and unsigned transactions. Three ways to use it:

  1. Quick start — quote, build, sign from the agent's own wallet.
  2. Delegated access — a user gives your agent a limit on their wallet instead of a key: which tokens, how much per day or in total, until when. A smart contract enforces it.
  3. MCP server — one command, and Hopivia becomes a tool in Claude, Cursor and other assistants.
Base URLhttps://hopivia.com/api/v1
FormatJSON, UTF-8
Authoptional X-API-Key header — get a free key
Limit instead of a keyhttps://hopivia.com/agents — the user sets it there, see Delegated access
This guide as Markdownhttps://hopivia.com/docs.md — give it to your model
OpenAPI 3.1https://hopivia.com/api/openapi.json · interactive reference
Discovery/llms.txt · /.well-known/agent-card.json

Same-chain swaps: BNB Chain, Ethereum, Arbitrum, Base, Polygon, Optimism, Avalanche. Cross-chain: any pair of the networks above, plus opBNB and Tron (USDT only on Tron).

#Quick start

#1. Ask for a price

No key is needed to try it.

bash
curl "https://hopivia.com/api/v1/quote?chain=bsc&tokenIn=BNB&tokenOut=USDT&amount=0.5"

The answer names the exchange with the best output, the route, the minimum you will receive at the chosen slippage and the price impact:

json
{
  "best": {
    "venue": "PancakeSwap V3",
    "label": "BNB → 0.01% → USDT",
    "amountOut": { "wei": "355113773306503027910", "formatted": "355.11377330650302791" },
    "minOut":    { "wei": "353338204439970512770", "formatted": "353.33820443997051277" },
    "feeBps": 0
  },
  "alternatives": [
    { "venue": "Uniswap V3", "amountOut": { "formatted": "355.025274019855417697" } },
    { "venue": "PancakeSwap", "amountOut": { "formatted": "353.425882806009630914" } }
  ],
  "venuesCompared": 9,
  "priceImpactPct": 0.01
}

#2. Build the transactions

Pass the wallet that will sign as from. The server quotes again, checks the balance and the token, and returns the steps in order.

bash
curl -X POST "https://hopivia.com/api/v1/swap/tx" \
  -H "content-type: application/json" \
  -H "x-api-key: $HOPIVIA_KEY" \
  -d '{"chain":"bsc","tokenIn":"USDT","tokenOut":"BNB","amount":"25","from":"0xYourAgentWallet"}'
json
{
  "steps": [
    { "kind": "approve", "description": "Approve USDT to PancakeSwap V3 router", "chainId": 56,
      "to": "0x55d398326f99059fF775485246999027B3197955", "data": "0x095ea7b3…", "value": "0" },
    { "kind": "swap", "description": "Swap USDT → BNB via PancakeSwap V3", "chainId": 56,
      "to": "0x13f4EA83D0bd40E75C8222255bc855a974568Dd4", "data": "0xac9650d8…", "value": "0" }
  ],
  "minOut": { "formatted": "0.035018498007912409" },
  "simulation": { "ok": false, "skipped": "approval step must be mined first" },
  "warnings": []
}

#3. Sign and send, in order

js
import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { bsc } from 'viem/chains';

const BASE = 'https://hopivia.com/api/v1';
const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY); // stays on your machine
const wallet = createWalletClient({ account, chain: bsc, transport: http() });
const chain = createPublicClient({ chain: bsc, transport: http() });

async function buildSwap(body) {
  const res = await fetch(`${BASE}/swap/tx`, {
    method: 'POST',
    headers: { 'content-type': 'application/json', 'x-api-key': process.env.HOPIVIA_KEY },
    body: JSON.stringify({ ...body, from: account.address }),
  });
  const json = await res.json();
  if (!res.ok) throw new Error(`${json.error}: ${json.message}`); // see "Errors and refusals"
  return json;
}

async function send(step) {
  const hash = await wallet.sendTransaction({ to: step.to, data: step.data, value: BigInt(step.value) });
  const receipt = await chain.waitForTransactionReceipt({ hash });
  if (receipt.status !== 'success') throw new Error(`${step.kind} reverted: ${hash}`);
  return hash;
}

const order = { chain: 'bsc', tokenIn: 'USDT', tokenOut: 'BNB', amount: '25' };
let plan = await buildSwap(order);

// Approvals first, then ask again: the new plan has a fresh price and a real dry run
const approvals = plan.steps.filter((s) => s.kind !== 'swap');
if (approvals.length > 0) {
  for (const step of approvals) await send(step);
  plan = await buildSwap(order);
}
if (!plan.simulation.ok) throw new Error(`not simulated: ${plan.simulation.skipped ?? plan.simulation.error}`);
const txHash = await send(plan.steps.find((s) => s.kind === 'swap'));

#Delegated access

A user can give your agent a limit instead of a key. On the agents page the user allows your agent's address on the Hopivia delegation contract: which tokens (one or several), how much, until when. Your agent then calls the contract's swap() from its own wallet; the contract pulls the input token from the user's wallet, swaps it on one of the allowed V2 exchanges and sends the output only to the user's address. The user can remove a token, an agent or everything at any time. All of this is enforced on-chain: the API is not involved and cannot override it, and the contract has no owner and no admin functions. On first-version networks no Hopivia fee is charged on delegated swaps; version 2 executes through the Hopivia aggregator with its 0.15 % fee.

#Delegation v2

Networks where GET /chains returns venues.delegationV2: true use the second version of the contract; elsewhere everything below about the first version applies. The request flow is the same (GET /delegation, then POST /swap/tx with onBehalfOf); what the owner sets and what the contract checks differ:

GET /delegation returns version: 2, a grant object (active, capUsd, maxTradeUsd, spentUsd, availableUsd in dollars, expiry, resetsAt, flags: oneTime / sellAny / buyAny / transfers, slippageBps), tokens[] with mode (sell, buy, noPriceCheck), an optional per-token cap and the owner's allowance, plus recipients, hubs and the routers. POST /swap/tx returns feeBps: 15, viaAggregator: true and delegated with version: 2, tradeUsd (this trade in dollars), availableUsd, expiry and resetsAt.

Additional refusals on v2: delegation_no_price (no price source for tokenIn), delegation_per_trade_cap, delegation_owner_cap_exceeded; delegation_cap_exceeded carries details.tradeUsd, details.availableUsd and either resetsAt (24 h window) or oneTime. The contract exposes preview(owner, agent, token, amount) → (usd, reason): the dollar value of a trade and the exact refusal reason (empty when it would pass) without sending a transaction.

The rest of this section describes the first version.

Two kinds of limit. Per day: the cap renews every UTC day until the expiry; the user's ERC-20 allowance to the contract is "daily cap × days". One-time (total): the allowance is set to exactly the amount, so once the agent has sold it the limit is used up even before the expiry — GET /delegation then shows allowance below availableToday.

What to ask the user for. Your agent's address (the agents page can generate a fresh wallet for a bot that has none yet); the tokens you will sell and the tokens you will buy (both sides must be allowed; the network coin is always allowed as output); the cap per token and its kind; an expiry date. The agent pays gas for its own transactions, so its wallet needs a little of the network coin.

Interface (Solidity):

text
function swap(address owner, address router, uint256 amountIn, uint256 amountOutMin,
              address[] path, bool toNative, uint256 deadline) returns (uint256 amountOut)
function limitOf(address owner, address agent, address token)
         returns (uint256 dailyCap, uint256 expiry, uint256 spentToday, uint256 availableToday, uint256 resetsAt)
function tokensOf(address owner, address agent) returns (address[])
function routers() returns (address[])        // the only exchanges the contract will call

Trading flow through the API (the API never signs; it checks the limits and builds the one contract call):

  1. GET /delegation?chain=bsc&owner=<owner>&agent=<your wallet> — which tokens the owner allowed you, the daily cap, what is left today, the expiry and the owner's allowance to the contract. Also the contract address and the V2 routers it may call.
  2. POST /swap/tx with from = your wallet and onBehalfOf = the owner. The API quotes only on the contract's routers, verifies the limits and returns a single swap step on the delegation contract. tokenIn must be an ERC-20 the owner allowed (the contract cannot pull the network coin, so the owner allows WBNB/WETH to sell BNB/ETH); tokenOut must be an allowed token or native. No Hopivia fee on delegated swaps.
  3. Sign and send that step from your wallet. You pay the gas; the output goes to the owner. The response carries delegated.availableToday — what remains of the cap after this swap — and delegated.resetsAt.
js
const d = await get('/delegation', { chain: 'bsc', owner, agent: agent.address });
// d.tokens → [{ token: { symbol: 'USDT', … }, availableToday: { formatted: '80' }, expiry, allowance, … }]
const plan = await post('/swap/tx', { chain: 'bsc', tokenIn: 'USDT', tokenOut: 'native', amount: '25', from: agent.address, onBehalfOf: owner });
await agent.sendTransaction(plan.steps[0]);   // one call: swap(owner, router, amountIn, minOut, path, toNative, deadline)

Refusals (HTTP 422, machine-readable):

errorMeaning
delegation_not_allowedThe owner has not allowed this tokenIn for your address, or removed/revoked it.
delegation_token_out_not_allowedtokenOut is neither an allowed token nor the network coin.
delegation_cap_exceededToday's UTC budget for tokenIn does not cover the amount; details.resetsAt is when it resets.
delegation_expiredThe owner's expiry date has passed.
delegation_allowance_lowThe owner's ERC-20 allowance to the contract is below the amount; the owner raises it by allowing the token again.
insufficient_balanceThe owner does not hold the amount.
delegation_unavailable (400)No delegation contract on this chain. The networks that have one are listed in the table above and in GET /chains (delegation).

The same checks are enforced on-chain; if you call the contract directly, the revert reasons are token in not allowed, token out not allowed, daily cap exceeded, limit expired, router not allowed, out != WETH, or an ERC-20 transferFrom failure (allowance or balance).

The counter is per token and per UTC calendar day; lowering a limit mid-day keeps what was already spent. Events LimitSet, Revoked and Swapped are emitted for every change, so you can watch them instead of polling.

#MCP server

@hopivia/mcp exposes every endpoint above as tools for Claude, Cursor, Windsurf and any other MCP client. The server runs on your machine, talks to this API over HTTPS and never sees a key or a wallet: the write tools return the same unsigned transactions as POST /swap/tx and POST /bridge/tx.

Claude Code:

bash
claude mcp add hopivia -- npx -y @hopivia/mcp

Claude Desktop, Cursor, Windsurf — mcpServers in the client's config (claude_desktop_config.json, .cursor/mcp.json, …):

json
{
  "mcpServers": {
    "hopivia": {
      "command": "npx",
      "args": ["-y", "@hopivia/mcp"],
      "env": { "HOPIVIA_API_KEY": "hk_…" }
    }
  }
}

Tools: list_chains, list_tokens, get_quote, build_swap_transactions (with onBehalfOf for delegated swaps), get_bridge_quote, build_bridge_transactions, get_bridge_status, check_token, get_balances, get_delegation, api_key_info, and for local wallets list_wallets, send_tokens, send_tokens_batch, swap_tokens. The resource hopivia://docs is this guide. HOPIVIA_API_KEY is optional — without it the server runs at the anonymous limit. API refusals (422) come back as tool errors with their error and message, so the agent can explain and fix the request. Source and development notes: packages/mcp in the API repository.

#Local wallets

"Send 50 USDT from work to mom": the server can also sign with a separate wallet you fund for your AI assistant (any MCP client — Claude Desktop, Claude Code, OpenAI Codex, Cursor and others). Run npx -y @hopivia/mcp setup — it creates or imports the wallet (on macOS the key goes to the Keychain), sets the limits per transfer and per 24 hours, the default network, the contacts and when to ask you (always, only for new addresses, never within the limits), and connects Claude Desktop (on Linux, Claude Code and optionally Cursor). Other MCP clients are added by hand, e.g. Claude Code: claude mcp add hopivia -s user -- npx -y @hopivia/mcp; OpenAI Codex: [mcp_servers.hopivia] with command = "npx" and args = ["-y", "@hopivia/mcp"] in ~/.codex/config.toml; Cursor, Windsurf and others: the usual mcpServers JSON with the same command. The key never leaves the machine and is never sent to this API. Before signing, the server decodes every transaction the API prepared and checks the recipient, the amount, the fee wallet, a 2% fee ceiling and the pinned contract addresses; anything else is refused. Every transfer pays a visible $0.01 service fee (see Fees); results state the total fees in USD. send_tokens takes an optional memo (the user's comment, shown in the approval window). send_tokens_batch pays a whole list in one transaction with one signature where the transfer contract is deployed (POST /transfer/batch/tx; the server checks the signed list equals the approved one), otherwise one by one.

#Remote connector (assistant on the phone)

Mobile AI apps cannot run a local server, so there is a hosted one: https://hopivia.com/api/v1/mcp (Streamable HTTP, stateless, no sign-in). Add it in any assistant that can add a custom connector (remote MCP server) by URL, if your plan allows it — for example in Claude: claude.ai → Settings → Connectors → Add custom connector; it then appears in the Claude mobile app. It holds no keys and sends nothing: besides the read-only tools (list_chains, list_tokens, get_quote, get_bridge_quote, get_bridge_status, check_token, get_balances, get_delegation) it has transfer_link, which returns a link to https://hopivia.com/pay?chain=…&token=…&to=…&amount=…&fees=ontop|included[&memo=…] (memo: an optional comment, URL-encoded, shown on the page and signed with the transfer). The user opens it, connects their own wallet, checks the recipient and signs.

#API keys and limits

Every endpoint works without a key. A key raises the rate limit and lets you see your usage.

TierRequests per minuteHow to get it
Anonymous30 per IPnothing to do
Free120 per keyhttps://hopivia.com/agents: connect an EVM wallet or TronLink, sign a message (no gas), create a key
Pro600 per keyon request from the Hopivia team

Send the key in a header:

bash
curl "https://hopivia.com/api/v1/keys/me" -H "x-api-key: hk_…"
json
{ "tier": "free", "limitPerMinute": 120,
  "key": { "prefix": "hk_yDZR90mR", "label": "my-agent", "requestsToday": 37, "requests7d": 412 } }

#Conventions

Networks. Pass the chain id or a key: bsc (56), ethereum (1), polygon (137), arbitrum (42161), base (8453), optimism (10), avalanche (43114), opbnb (204, bridges only). tron (728126428) is accepted only by the bridge endpoints.

Tokens. Three forms are accepted:

On Tron only USDT is supported.

Amounts. Send amount in human units ("0.5") or amountWei as an integer string in the smallest unit ("500000000000000000"). Every amount in a response comes in both forms:

json
{ "wei": "500000000000000000", "formatted": "0.5" }

Slippage. slippageBps in basis points: 50 is 0.5% (default), the maximum is 1000 (10%). minOut is derived from it.

Addresses. EVM addresses are returned checksummed. Tron addresses are base58 (T…).

#Endpoints

#GET/quote

The best same-chain price across every exchange of the network. Read-only.

ParameterRequiredDescription
chainyesnetwork id or key
tokenIn, tokenOutyestoken references, see Conventions
amount or amountWeiyeshow much tokenIn to sell
slippageBpsnodefault 50

Response fields you will use:

FieldMeaning
best.venue, best.labelexchange and human-readable route
best.amountOut, best.minOutexpected and guaranteed minimum output
best.feeBps, best.viaAggregatorHopivia fee on this route, see Fees
alternatives[]other exchanges, best first
venuesComparedhow many exchanges were asked
priceImpactPcthow much this trade moves the price
savingsPctbest compared with second best
warnings[]human-readable notes, for example high price impact

#POST/swap/tx

Unsigned transactions for the best same-chain swap from a given wallet.

json
{
  "chain": "arbitrum",
  "tokenIn": "ETH",
  "tokenOut": "USDC",
  "amount": "0.05",
  "from": "0xYourAgentWallet",
  "recipient": "0xOptionalOtherAddress",
  "slippageBps": 50,
  "maxPriceImpactPct": 10,
  "simulate": true,
  "checkSafety": true,
  "allowRiskyToken": false
}
FieldDefaultDescription
fromrequiredthe wallet that signs and pays
recipientfromwho receives tokenOut
maxPriceImpactPct10refuse above this, see refusals
simulatetruedry-run the swap with eth_estimateGas
checkSafetytrueGoPlus check of tokenOut
allowRiskyTokenfalsebuild even if tokenOut is high risk; only with the user's explicit consent

The response contains steps[] with kind set to approve, permit2-approve or swap. Each step has to, data, value in wei and chainId. It also contains spender, minOut, deadline (unix seconds; V2 swaps revert after it), simulation, safety and warnings.

Send the steps one after another from from, and wait for each to be mined. Do not change to, data or value.

#GET/bridge/quote

A cross-chain quote. The provider is chosen by direction:

DirectionProviderNotes
Tron ↔ any EVM networkSymbiosis and deBridge DLN, quoted together — the one that delivers more is returned (provider in the response)USDT only on the Tron side
EVM ↔ EVMLI.FIfalls back to Symbiosis when LI.FI has no route
ParameterRequiredDescription
fromChain, toChainyesnetwork ids or keys, tron allowed
fromToken, toTokenyestoken references
amount or amountWeiyesamount of fromToken
fromrecommendedsender on fromChain (0x… or T…)
towhen formats differrecipient on toChain; required for Tron ↔ EVM
slippageBpsnodefault 50
providernoforce lifi or symbiosis
json
{
  "provider": "symbiosis",
  "amountIn":     { "formatted": "100" },
  "amountOut":    { "formatted": "99.284676421224127265" },
  "amountOutMin": { "formatted": "98.787754007547489072" },
  "estimatedSeconds": 32,
  "feesUsd": 0.5,
  "approveTo": "TPuaJ6gnYfE9gLUDFrbWbPx3tMnjG8max1",
  "warnings": ["Tron side: the sender needs ~70–220k Energy (or ~15–25 TRX burned) …"]
}

Bridging from BNB Chain to Tron costs about $4 in fixed fees. Small amounts lose a large share, and the quote warns when fees exceed 10% of the amount.

#POST/bridge/tx

The same parameters as /bridge/quote in a JSON body, with from required. It returns steps[] and a track object.

EVM steps (approve, bridge) have to, data, value like swap steps.

Tron steps (tron-approve, tron-call) carry what TronWeb needs:

json
{
  "kind": "tron-call",
  "contract": "TPuaJ6gnYfE9gLUDFrbWbPx3tMnjG8max1",
  "functionSelector": "metaRoute((bytes,bytes,address[],address,address,uint256,bool,address,bytes))",
  "rawParameter": "0000…",
  "callValueSun": 0,
  "feeLimitSun": 200000000
}
js
// TronWeb (tronweb npm package or the TronLink extension)
const { transaction } = await tronWeb.transactionBuilder.triggerSmartContract(
  step.contract,
  step.functionSelector,
  { feeLimit: step.feeLimitSun, callValue: step.callValueSun, rawParameter: step.rawParameter },
  [],
  fromAddress,
);
const signed = await tronWeb.trx.sign(transaction);
const result = await tronWeb.trx.sendRawTransaction(signed);

track.statusUrl is a ready-made link to /bridge/status; append &txHash= with the hash of the bridge or tron-call step.

#GET/bridge/status

ParameterDescription
providerlifi or symbiosis, from track.provider
txHashhash of the source transaction
fromChain, toChainas in the quote
bridgeLI.FI only: track.bridge

status is one of:

StatusMeaningWhat to do
NOT_FOUNDnot indexed yetnormal for the first minute; poll every 15–30 s
PENDINGon the waykeep polling
DONEdelivered; receivingTxHash is setfinish
FAILEDthe bridge failedreport to the user with explorer
STUCKSymbiosis could not completethe user can revert it at app.symbiosis.finance

#GET/token/check

A safety verdict for a token, from GoPlus Security.

bash
curl "https://hopivia.com/api/v1/token/check?chain=bsc&address=0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82"
json
{
  "token": { "symbol": "CAKE", "decimals": 18 },
  "safety": { "checked": true, "risk": "medium", "flags": ["owner can mint"],
              "isHoneypot": false, "isOpenSource": true, "holderCount": 1912641 }
}
riskMeaning
lowno red flags found
mediumowner privileges or unverified code; common in legitimate tokens, mention it to the user
highhoneypot, sell tax of 50% or more, or the owner can change balances; do not buy
unknownGoPlus has no data; treat new tokens with care

#GET/balances

The native coin and every known token with a non-zero balance. Add more tokens with tokens=0x…,0x….

bash
curl "https://hopivia.com/api/v1/balances?chain=bsc&address=0xYourWallet"

#POST/transfer/tx

A plain token transfer from from to to, with the Hopivia transfer fee ($0.01). Body: chain, token, amount (or amountWei), from, to, optional feesIncluded (the wallet spends amount in total and the recipient gets it minus the fees; default: the recipient gets exactly amount and fees come on top), optional memo — a comment in the user's own words, at most 100 bytes of UTF-8 (about 50 Cyrillic or 100 Latin characters), one line, no control or text-direction characters. The memo is signed with the transfer and recorded on-chain (the Transferred event), so only contract mode carries it; for the chain coin and on networks without the contract the request is refused with memo_unsupported rather than sent without it.

bash
curl -X POST https://hopivia.com/api/v1/transfer/tx -H 'content-type: application/json' \
  -d '{"chain":"bsc","token":"USDT","amount":"50","from":"0xYourWallet","to":"0xRecipient"}'

mode says how it is sent. contract: on networks with the Hopivia transfer contract, an ERC-20 transfer is one transaction — send the steps (a one-time approve to Permit2 at most), then sign permit.typedData with from and call transferWithPermit(owner, token, to, amount, maxFee, relayerFee, memo, nonce, deadline, signature) on permit.contract; the signature (witness Transfer(address to,uint256 amount,uint256 maxFee,uint256 relayerFee,string memo)) names the recipient, the amount, a fee ceiling and the comment, so nothing else can be taken. two-step: a transfer step, then a separate fee step. cost.totalUsd is the expected network gas plus the service fee. Refusals: insufficient_balance, insufficient_gas (the message says how much of the chain coin to add), amount_too_small, same_address, recipient_is_token, invalid_memo, memo_unsupported, and 503 transfers_paused while the transfer contract is paused by its guardian (the API does not silently switch to the two-step mode then).

#POST/transfer/batch/tx

A list of recipients paid in one transaction through the transfer contract: everyone is paid or the transaction reverts and nobody is. Body: chain, token, from, recipients — 1 to 200 items {to, amount} (or amountWei), no duplicates, not from, not the token contract, not the zero address — and optional memo (one comment for the whole list, same rules as above).

bash
curl -X POST https://hopivia.com/api/v1/transfer/batch/tx -H 'content-type: application/json' \
  -d '{"chain":"bsc","token":"USDT","from":"0xYourWallet","recipients":[{"to":"0xAlice","amount":"50"},{"to":"0xBob","amount":"20"}],"memo":"salary"}'

ERC-20: send the steps (a one-time approve to Permit2 at most), sign permit.typedData (witness BatchTransfer(Recipient[] recipients,uint256 maxFee,uint256 relayerFee,string memo), Recipient(address to,uint256 amount)) with from and call transferBatchWithPermit(owner, token, recipients, maxFee, relayerFee, memo, nonce, deadline, signature) on permit.contract with the values from permit.call. One signature covers the whole list in its order. The chain coin: one payable transferNativeBatch(recipients, maxFee, memo) step whose value is the total plus the fee ceiling; the contract refunds what it does not charge. The fee is $0.01 per recipient (fee.amount), fee.maxFee is the signed ceiling of the total (+20% for price moves); cost has totalUsd, networkUsd, serviceUsd. Refusals: insufficient_balance, insufficient_gas, duplicate_recipient, same_address, recipient_is_token, invalid_memo; 409 batch_unavailable when the network has no transfer contract or the contract cannot price the token — send the transfers one by one with POST /transfer/tx then; 503 transfers_paused while the contract is paused.

#GET/transfer/estimate

The fees of a transfer before the sender is known: chain, token, amount, optional feesIncluded → recipientGets, walletSpends, feeUsd, networkUsd, totalUsd, mode. Balances are not checked.

#GET/price

USD price of a token: /price?chain=bsc&token=CAKE. Stablecoins are 1.

#GET/delegation

What a wallet owner allowed your agent on the delegation contract. Version 1: per token the daily cap, what is spent and available today, the expiry and the owner's allowance. Version 2: one dollar limit (grant), the sell and buy lists and the address book. Plus the contract address and its routers. Read-only; see Delegated access for the swap itself (POST /swap/tx with onBehalfOf).

bash
curl "https://hopivia.com/api/v1/delegation?chain=bsc&owner=0xOwnerWallet&agent=0xYourAgentWallet"

#GET/chains

Networks with swap and bridge flags, the number of exchanges per network, whether the Hopivia aggregator is deployed there and which delegation version is live (venues.delegation, venues.delegationV2).

#GET/tokens

Known tokens of a network: /tokens?chain=bsc&q=usd. Any other token can still be used by its address.

#GET/health

{ "ok": true, "version": "0.1.0" } — for uptime checks. Not rate limited.

#Errors and refusals

Every error has the same shape. Branch on error, which is a stable code; message is for people.

json
{
  "error": "price_impact_too_high",
  "message": "price impact 0.09% exceeds maxPriceImpactPct=0.01; reduce the amount or raise the ceiling explicitly",
  "details": { "priceImpactPct": 0.09, "maxPriceImpactPct": 0.01 }
}

POST /swap/tx refuses to return transactions that should not be sent. That is the difference from APIs that only warn.

HTTPerrorWhat the agent should do
422simulation_revertedthe swap would fail from this wallet; read details.simulation.error, do not send
422price_impact_too_highsplit the order or ask the user before raising maxPriceImpactPct
422token_risk_hightell the user why (details.flags); retry with allowRiskyToken only after an explicit yes
422insufficient_balancethe wallet holds less than amount; details has both values
422insufficient_gasPOST /transfer/tx: the wallet lacks the chain coin for gas; the message says how much to add
400validation, invalid_amount, invalid_address, unknown_token, unsupported_chain, same_token, amount_too_low, amount_too_small, same_address, recipient_is_token, duplicate_recipient, invalid_memofix the request
400memo_unsupportedthis transfer cannot carry a comment (the chain coin, or no transfer contract on the network); ask the user whether to send without it
409batch_unavailablePOST /transfer/batch/tx: no transfer contract here, or it cannot price the token; send one by one with POST /transfer/tx
503transfers_pausedthe transfer contract is paused by its guardian; tell the user, retry later; nothing was built
401invalid_api_keyremove or replace the key
404no_routeno liquidity for this pair or size
429rate_limitedwait Retry-After seconds
502upstream_error, provider_error, rpc_errora node or bridge provider failed; retry with backoff

#Fees

#Rules for AI agents

Put this in your agent's instructions:

text
You can trade through the Hopivia API (docs: https://hopivia.com/docs.md).
1. Before any trade, call GET /quote or GET /bridge/quote and tell the user the expected output,
   the minimum output, the price impact and the fees. Get their confirmation.
2. Build transactions only with POST /swap/tx or POST /bridge/tx, passing your own wallet as `from`.
   Never change `to`, `data` or `value` of a returned step.
3. Send steps strictly in order and wait for each receipt. After an approve is mined, request /swap/tx again.
4. If the API refuses (HTTP 422), do not try to work around it. Explain the `error` to the user.
   Use `allowRiskyToken` only after the user explicitly agrees to buy a high-risk token.
5. Never send a private key or seed phrase to any API, including this one.
6. For Tron ↔ EVM always pass `to` in the destination network's format and confirm it with the user.
7. After a bridge transaction, poll GET /bridge/status every 15–30 s until DONE or FAILED.
8. With delegated access (see "Delegated access"), trade only within limitOf() of the user's wallet and report
   every trade: what you sold, what arrived at the user's address and at which price.

#Managing keys from code

The agents page uses these endpoints. You can call them yourself, for example to rotate keys automatically. They accept EVM wallets (personal_sign) and Tron wallets (tronWeb.trx.signMessageV2, as TronLink does); smart-contract wallets are not supported yet.

  1. POST /keys/challenge with {"address": "0x…"} or {"address": "T…"} returns a message and nonce. The message is valid for 10 minutes.
  2. Sign message: personal_sign for EVM, signMessageV2 for Tron. This is an off-chain signature with no gas.
  3. POST /keys/session with {"address", "nonce", "signature"} returns a session valid for 12 hours.
  4. With Authorization: Bearer <session>:
    • GET /keys lists keys with usage;
    • POST /keys with {"label": "my-agent"} creates a free key and returns the raw key once;
    • DELETE /keys/{id} revokes a key.
js
const { message, nonce } = await post('/keys/challenge', { address: account.address });
const signature = await account.signMessage({ message });
const { session } = await post('/keys/session', { address: account.address, nonce, signature });
const { key } = await post('/keys', { label: 'trading-agent' }, { authorization: `Bearer ${session}` });

#Versioning

Version 1 of the API is stable: fields may be added, but existing fields and error codes will not change meaning. Breaking changes will go to /api/v2.