Skip to content
Hookline

Build on Hookline

One HTTP API for the best price on Robinhood Chain. Quote any pair, get a simulated transaction your own wallet signs, and track it to settlement.

Overview

The same API that runs the Hookline swap page is open to everyone: list tokens, read balances, get the best price across Uniswap v3, Uniswap v4 and the outside pools that Aggregator Hooks plug into, then build a ready-to-sign Universal Router transaction. Trades are non-custodial: the API never holds funds or keys, your wallet signs and sends every transaction itself.

Base URL

https://hookline.trade/api

Conventions

FormatJSON over HTTPS
POST bodies are JSON with Content-Type: application/json. Every answer is JSON, never cached when it depends on the caller.
Tokenssymbol or address
Pass a listed symbol (ETH, usdg, case does not matter) or a 0x address. Native ETH is 0x0000000000000000000000000000000000000000. Ambiguous symbols need the address.
Amountsdecimal strings
Requests take human decimals ("0.5"). Responses give both: amount is human, fields ending in Atomic and balances are integer strings in the token's smallest unit. Keep them as bigints.
USDnumber
USD values are plain numbers rounded to cents, for display only. null means the price is unknown, never zero.
ChainRobinhood Chain
Chain id 4663. Explorer: robinhoodchain.blockscout.com.

Rate limits and keys

Limits count requests per fixed one-minute window. Without a key they apply per IP address.

Default limits

Quote and build60 per minute
/api/quote and /api/swap, the calls that route and simulate.
Reads240 per minute
Tokens, balances, hook pools, stats, tracking and transaction status.
X-Api-Keyheader
A key lifts the quote and build limit to its own rate (reads get four times that rate). Keys start with hkl_ and are issued on request: ask us on X. An invalid or revoked key gets 401, it does not fall back to the IP limit.
429Retry-After
Over the limit, the answer is 429 rate_limited with a Retry-After header in seconds. Wait that long before the next call.
CORSopen
Every public endpoint answers any origin, accepts the Content-Type and X-Api-Key headers and exposes Retry-After, so a browser app can call it directly. Keep keys on your server.
Calling with a key
curl -s "https://hookline.trade/api/quote?sell=ETH&buy=USDG&amount=0.5" \
  -H "X-Api-Key: hkl_your_key"
429 Too Many Requests
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Retry in 17s."
  }
}

Endpoints

Ten public endpoints. Reads are cached for a few seconds at the edge; anything that depends on the caller is never cached.

Tokens

GET/api/tokens

Every token you can trade, with its USD price. kind is base (ETH, WETH, USDG), pons (a Pons launchpad token) or listed (listed automatically from Aggregator Hook pools: check its contract before buying). viaHooks is true when some Aggregator Hook pool trades it. Cached 60 seconds.

Request

curl -s "https://hookline.trade/api/tokens"

Response

200 OK
{
  "tokens": [
    {
      "address": "0x0000000000000000000000000000000000000000",
      "symbol": "ETH",
      "name": "Ether",
      "decimals": 18,
      "kind": "base",
      "priceUsd": 2405.05,
      "viaHooks": true
    },
    {
      "address": "0x5fc5360d0400a0fd4f2af552add042d716f1d168",
      "symbol": "USDG",
      "name": "Global Dollar",
      "decimals": 6,
      "kind": "base",
      "priceUsd": 1,
      "viaHooks": true
    },
    {
      "address": "0x07ebb29a38fbcb41563817e5e19f2cec619c90d2",
      "symbol": "BUN",
      "name": "Bundle Cat",
      "decimals": 18,
      "kind": "pons",
      "priceUsd": 0.0456633451,
      "viaHooks": true
    }
  ],
  "updatedAt": 1792681673
}

Balances

GET/api/balances?address=0x…

A wallet's balance of every listed token, read in one call. Keys are lowercase token addresses, ETH sits under the zero address, values are atomic integer strings.

Parameters

addressquery, 0x address, required
The wallet to read.

Request

curl -s "https://hookline.trade/api/balances?address=0x1111111111111111111111111111111111111111"

Response

200 OK
{
  "address": "0x1111111111111111111111111111111111111111",
  "balances": {
    "0x0000000000000000000000000000000000000000": "60679951590011889",
    "0x5fc5360d0400a0fd4f2af552add042d716f1d168": "500000",
    "0x39dbed3a2bd333467115de45665cc57f813c4571": "43789961722725797152"
  },
  "updatedAt": 1792681673
}

Quote

POST / GET/api/quote

The best exact-input price for selling amount of sell. The order can be split across up to 4 paths (one or two pools each), Aggregator Hook pools included, with gas priced in. GET takes the same fields as query parameters. The quote is stored for about 60 seconds so it can be built.

Parameters

sellstring, required
Token sold: symbol or 0x address.
buystring, required
Token bought: symbol or 0x address.
amountdecimal string, required
Human amount of the sold token, above 0, no more decimals than the token has.
taker0x address, optional
The wallet that will swap. Needed to get steps, the balance warning and to build the transaction.
slippageBpsinteger, optional
Tolerance in basis points, 1 to 500. Default 50 (0.5%).

Response fields

idstring
Quote id for /api/swap and tracking.
sell, buyobject
Token, symbol, decimals, amount (human), amountAtomic and USD value. buy is what you receive after the fee.
minReceivedobject
Least you receive after slippage and fee. The transaction reverts below it.
priceImpactBpsinteger
Price impact against the mid price. Above 3% the quote carries a warning; at 20% it is refused.
feeobject
Hookline fee in basis points and in the bought token.
partsarray
The split: shareBps of the amount down each path, with its legs (venue, pool, fee label).
sourcesstring[]
Every venue the order touches, in route order.
vsUniswapOnlyobject or null
Best result with Uniswap pools alone and the gain in bps. null when no Aggregator Hook pool is used.
vsSinglePathobject or null
Best single path and what splitting adds. null when the order is not split.
gasobject
Estimated gas units and their USD cost.
stepsarray
Approvals the taker still needs: approve (a transaction) then permit (a signature). Empty when ready to build, and always empty without taker.
warningsstring[]
Readable warnings: high price impact, automatically listed token, balance below the amount.
expiresAtunix seconds
After this the quote cannot be built.

Request

curl -s -X POST "https://hookline.trade/api/quote" \
  -H "Content-Type: application/json" \
  -d '{"sell":"USDG","buy":"PONS","amount":"20000","taker":"0x1111111111111111111111111111111111111111","slippageBps":50}'

Response

200 OK
{
  "id": "q_cjrwryzgblvfyihvhbahfqcp",
  "sell": {
    "token": "0x5fc5360d0400a0fd4f2af552add042d716f1d168",
    "symbol": "USDG",
    "decimals": 6,
    "amount": "20000",
    "amountAtomic": "20000000000",
    "usd": 20000
  },
  "buy": {
    "token": "0x39dbed3a2bd333467115de45665cc57f813c4571",
    "symbol": "PONS",
    "decimals": 18,
    "amount": "77880.86043419",
    "amountAtomic": "77880860434195477994280",
    "usd": 19959.42
  },
  "minReceived": {
    "amountAtomic": "77491456132024500604308",
    "amount": "77491.45613202"
  },
  "slippageBps": 50,
  "priceImpactBps": 30,
  "fee": {
    "bps": 0,
    "amountAtomic": "0",
    "amount": "0"
  },
  "parts": [
    {
      "shareBps": 8000,
      "amountIn": "16000",
      "amountOut": "62323.22364486",
      "legs": [
        {
          "from": "USDG",
          "to": "PONS",
          "venue": "Slipstream",
          "pool": "0xf7e11343da17c02cf40edb94ae634c349ecf6d15559048ca1a8f6222a0209052",
          "feeLabel": "outside pool fee"
        }
      ]
    },
    {
      "shareBps": 1000,
      "amountIn": "2000",
      "amountOut": "7785.72818795",
      "legs": [
        {
          "from": "USDG",
          "to": "WETH",
          "venue": "Uniswap v3",
          "pool": "0x52e65b17fb6e5ba00ed806f37afcd2daa50271ca",
          "feeLabel": "0.01%"
        },
        {
          "from": "WETH",
          "to": "PONS",
          "venue": "Uniswap v3",
          "pool": "0xed50bdeea8adc232f159486192a4157281d722ff",
          "feeLabel": "0.3%"
        }
      ]
    },
    {
      "shareBps": 1000,
      "amountIn": "2000",
      "amountOut": "7771.90860137",
      "legs": [
        {
          "from": "USDG",
          "to": "PONS",
          "venue": "Uniswap v4",
          "pool": "0x4be9657ec9002e528f4f17a5c43edc525a07f888f7b180c2afbf75e096c4f38a",
          "feeLabel": "0.3%"
        }
      ]
    }
  ],
  "sources": [
    "Slipstream",
    "Uniswap v3",
    "Uniswap v4"
  ],
  "vsUniswapOnly": {
    "amountAtomic": "77375900250980508271801",
    "amount": "77375.90025098",
    "gainBps": 65
  },
  "vsSinglePath": {
    "amountAtomic": "77851683723637453929419",
    "amount": "77851.68372363",
    "gainBps": 3
  },
  "gas": {
    "units": "503488",
    "usd": 0.02459
  },
  "steps": [
    {
      "kind": "approve",
      "token": "0x5fc5360d0400a0fd4f2af552add042d716f1d168",
      "spender": "0x000000000022d473030f116ddee9f6b43ac78ba3",
      "tx": {
        "to": "0x5fc5360d0400a0fd4f2af552add042d716f1d168",
        "data": "0x095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
        "value": "0"
      }
    },
    {
      "kind": "permit",
      "token": "0x5fc5360d0400a0fd4f2af552add042d716f1d168",
      "typedData": {
        "domain": {
          "name": "Permit2",
          "chainId": 4663,
          "verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3"
        },
        "types": {
          "PermitDetails": [
            {
              "name": "token",
              "type": "address"
            },
            {
              "name": "amount",
              "type": "uint160"
            },
            {
              "name": "expiration",
              "type": "uint48"
            },
            {
              "name": "nonce",
              "type": "uint48"
            }
          ],
          "PermitSingle": [
            {
              "name": "details",
              "type": "PermitDetails"
            },
            {
              "name": "spender",
              "type": "address"
            },
            {
              "name": "sigDeadline",
              "type": "uint256"
            }
          ]
        },
        "primaryType": "PermitSingle",
        "message": {
          "details": {
            "token": "0x5fc5360d0400a0fd4f2af552add042d716f1d168",
            "amount": "20000000000",
            "expiration": 1792683472,
            "nonce": 0
          },
          "spender": "0x204faca1764b154221e35c0d20abb3c525710498",
          "sigDeadline": "1792683472"
        }
      }
    }
  ],
  "warnings": [],
  "expiresAt": 1792681732
}

Build a swap

POST/api/swap

Turns a quote made with taker into one Universal Router transaction, simulated from the taker against the latest block before it is returned. gas is the estimate plus 25% (or null: let the wallet estimate). The router deadline is 5 minutes (expiresAt).

Parameters

quoteIdstring, required
Id of an unexpired quote made with taker.
permitSignature0x hex, 65 bytes, optional
Required when the quote had a permit step: the taker's EIP-712 signature of it.

Request

curl -s -X POST "https://hookline.trade/api/swap" \
  -H "Content-Type: application/json" \
  -d '{"quoteId":"q_cjrwryzgblvfyihvhbahfqcp","permitSignature":"0x…"}'

Response

200 OK
{
  "quoteId": "q_qzqlh4wuo3apkyktbn6y3hks",
  "tx": {
    "to": "0x204faca1764b154221e35c0d20abb3c525710498",
    "data": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000…",
    "value": "1000000000000000",
    "chainId": 4663,
    "gas": "176412"
  },
  "simulated": true,
  "expiresAt": 1792682267
}

Track a swap

POST/api/swaps/track

Registers the hash your wallet returned for a built quote (one hash per quote) and reports its status. It counts only when the transaction comes from the taker, goes to the Universal Router and carries exactly the built calldata; anything else ends as unknown.

Parameters

quoteIdstring, required
The built quote.
txHash0x hash, required
Hash returned by the wallet.

Request

curl -s -X POST "https://hookline.trade/api/swaps/track" \
  -H "Content-Type: application/json" \
  -d '{"quoteId":"q_cjrwryzgblvfyihvhbahfqcp","txHash":"0x5e1f…1e0f"}'

Response

200 OK
{
  "hash": "0x5e1f0c3b9a7d4e2f8c6b1a0d9e8f7c6b5a4d3e2f1c0b9a8d7e6f5c4b3a2d1e0f",
  "status": "pending",
  "amountOut": null,
  "blockNumber": null
}

Swap status

GET/api/swaps/:hash

Status of a tracked swap: pending, confirmed, failed (reverted) or unknown (not the built transaction, or never seen on chain). Pending swaps are re-checked on chain at most every 3 seconds, so poll every 2. amountOut is the atomic amount the taker received once confirmed.

Parameters

:hashpath, 0x hash, required
A hash registered with /api/swaps/track.

Request

curl -s "https://hookline.trade/api/swaps/0x5e1f0c3b9a7d4e2f8c6b1a0d9e8f7c6b5a4d3e2f1c0b9a8d7e6f5c4b3a2d1e0f"

Response

200 OK
{
  "hash": "0x5e1f0c3b9a7d4e2f8c6b1a0d9e8f7c6b5a4d3e2f1c0b9a8d7e6f5c4b3a2d1e0f",
  "status": "confirmed",
  "amountOut": "2390894",
  "blockNumber": 95206412
}

Transaction status

GET/api/tx/:hash

Receipt status of any Robinhood Chain transaction: pending (no receipt yet), success or reverted. Handy to wait for an approval.

Parameters

:hashpath, 0x hash, required
Any transaction hash.

Request

curl -s "https://hookline.trade/api/tx/0x8a2b6c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b"

Response

200 OK
{
  "hash": "0x8a2b6c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b",
  "status": "success",
  "blockNumber": 95206390
}

Hook pools

GET/api/hooks

Every Aggregator Hook pool on Robinhood Chain: venue, tokens, outside reserves, USD liquidity, 24 hour volume and swaps, the protocol fee the hook takes, and routable (true when Hookline routes through it). Also per-venue totals and the hooks themselves. Cached 30 seconds.

Request

curl -s "https://hookline.trade/api/hooks"

Response

200 OK
{
  "pools": [
    {
      "id": "0xf7e11343da17c02cf40edb94ae634c349ecf6d15559048ca1a8f6222a0209052",
      "hook": "0xa117c4f25474b1283345e4df8d88b9aec4f62888",
      "venue": "Slipstream",
      "token0": {
        "address": "0x39dbed3a2bd333467115de45665cc57f813c4571",
        "symbol": "PONS",
        "decimals": 18,
        "listed": true
      },
      "token1": {
        "address": "0x5fc5360d0400a0fd4f2af552add042d716f1d168",
        "symbol": "USDG",
        "decimals": 6,
        "listed": true
      },
      "tickSpacing": 50,
      "reserve0": "2746122.8435",
      "reserve1": "249549.6741",
      "liquidityUsd": 953330.07,
      "volume24hUsd": 7088.94,
      "swaps24h": 29,
      "protocolFeeBps": 10,
      "routable": true
    }
  ],
  "venues": [
    {
      "venue": "Slipstream",
      "pools": 102,
      "wrappedLiquidityUsd": 7415896.11,
      "volume24hUsd": 20936.24
    }
  ],
  "hooks": [
    {
      "address": "0xa1ad09c98710d2c709e3bea03708823255312888",
      "venue": "Slipstream",
      "pools": 56,
      "version": "Slipstream aggregator"
    }
  ],
  "updatedAt": 1792681967
}

Stats

GET/api/stats

Network numbers: hook pools, venues, tokens, wrapped outside liquidity, 24 hour hook volume and swaps (all routers), protocol fees, and swaps settled through Hookline. A field is null while it cannot be established. Cached 30 seconds.

Request

curl -s "https://hookline.trade/api/stats"

Response

200 OK
{
  "hookPools": 102,
  "venues": [
    "Slipstream"
  ],
  "tokens": 36,
  "wrappedLiquidityUsd": 7437687.58,
  "hookVolume24hUsd": 20382.59,
  "hookSwaps24h": 111,
  "protocolFeesUsd": 18.36,
  "hooklineSwaps": 0,
  "hooklineVolumeUsd": 0,
  "updatedAt": 1792681673
}

Health

GET/api/health

A cheap liveness probe for uptime monitors: latest block, age of the market registry in seconds and whether swaps are open. Answers 503 when the chain or the registry is unavailable. Not rate limited.

Request

curl -s "https://hookline.trade/api/health"

Response

200 OK
{
  "ok": true,
  "block": 95206308,
  "registryAge": 96,
  "swapsOpen": true
}

Swap flow for bots

Six calls take an order from a price to a settled trade. Your code holds the key and signs; the API only prepares and checks.

  1. Quote with your address

    POST /api/quote with taker set to the wallet that will sign. The answer holds the route, minReceived and the steps that wallet still needs. A quote lives for about 60 seconds (expiresAt).

  2. Send the approve step, once per token

    When steps has kind: "approve", send step.tx as is: an ERC-20 approve of Permit2 on the token you sell. Wait for the receipt (GET /api/tx/:hash or your own RPC), then quote again: the steps change once it lands. Selling ETH never needs it.

  3. Sign the permit step

    A kind: "permit" step carries a Permit2 PermitSingle as EIP-712 typed data: a short-lived allowance (30 minutes) for exactly this amount, spendable only by the Universal Router. Sign it with eth_signTypedData_v4. The types object leaves out EIP712Domain: add it before calling the wallet (viem's signTypedData does this for you).

  4. Build the transaction

    POST /api/swap with quoteId and permitSignature when there was a permit step. The API checks the signature, encodes the Universal Router call and simulates it against the latest block. A 422 means it would fail right now; the message says why.

  5. Verify, then send

    Check tx.to is the Universal Router, tx.chainId is 4663 and tx.value equals the ETH you sell (else "0"). Send it from the taker within 5 minutes (expiresAt), without changing data.

  6. Track it

    POST /api/swaps/track with the quote id and the hash your wallet returned, then poll GET /api/swaps/:hash every 2 seconds until the status is confirmed, failed or unknown.

Signing the permit with a browser wallet

eth_signTypedData_v4
// step = quote.steps.find((s) => s.kind === "permit")
const typedData = {
  ...step.typedData,
  types: {
    // Required by eth_signTypedData_v4: wallets hash an empty domain without it.
    EIP712Domain: [
      { name: "name", type: "string" },
      { name: "chainId", type: "uint256" },
      { name: "verifyingContract", type: "address" },
    ],
    ...step.typedData.types,
  },
};
const permitSignature = await provider.request({
  method: "eth_signTypedData_v4",
  params: [account, JSON.stringify(typedData)],
});

A complete bot with viem

bot.ts
import { createPublicClient, createWalletClient, defineChain, getAddress, http, type Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";

const BASE = "https://hookline.trade/api";
const API_KEY = process.env.API_KEY; // optional, raises your rate limit
const UNIVERSAL_ROUTER = getAddress("0x204faca1764b154221e35c0d20abb3c525710498");
const PERMIT2 = getAddress("0x000000000022d473030f116ddee9f6b43ac78ba3");
const NATIVE_ETH = "0x0000000000000000000000000000000000000000";

const robinhood = defineChain({
  id: 4663,
  name: "Robinhood Chain",
  nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
  rpcUrls: { default: { http: ["https://rpc.mainnet.chain.robinhood.com"] } },
});
const account = privateKeyToAccount(process.env.PRIVATE_KEY as Hex);
const wallet = createWalletClient({ account, chain: robinhood, transport: http() });
const client = createPublicClient({ chain: robinhood, transport: http() });

type Step =
  | { kind: "approve"; token: string; spender: string; tx: { to: Hex; data: Hex; value: "0" } }
  | { kind: "permit"; token: string; typedData: PermitTypedData };
type PermitTypedData = {
  domain: { name: string; chainId: number; verifyingContract: Hex };
  types: Record<string, { name: string; type: string }[]>;
  primaryType: "PermitSingle";
  message: { details: { token: Hex; amount: string; expiration: number; nonce: number }; spender: Hex; sigDeadline: string };
};
type Quote = { id: string; sell: { token: string; amountAtomic: string }; steps: Step[]; warnings: string[] };
type Built = { tx: { to: Hex; data: Hex; value: string; chainId: number; gas: string | null } };
type Tracked = { hash: Hex; status: "pending" | "confirmed" | "failed" | "unknown"; amountOut: string | null };

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function call<T>(path: string, body?: unknown): Promise<T> {
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fetch(`${BASE}${path}`, {
      method: body === undefined ? "GET" : "POST",
      headers: { "Content-Type": "application/json", ...(API_KEY ? { "X-Api-Key": API_KEY } : {}) },
      body: body === undefined ? undefined : JSON.stringify(body),
    });
    if (res.status === 429) {
      await sleep(Number(res.headers.get("Retry-After") ?? "5") * 1000);
      continue;
    }
    const data = await res.json();
    if (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);
    return data as T;
  }
  throw new Error("Still rate limited after 5 attempts.");
}

export async function swap(sell: string, buy: string, amount: string): Promise<Tracked> {
  const ask = { sell, buy, amount, taker: account.address, slippageBps: 50 };
  let quote = await call<Quote>("/quote", ask);

  // 1. ERC-20 approval to Permit2 (once per token).
  const approve = quote.steps.find((s) => s.kind === "approve");
  if (approve) {
    if (getAddress(approve.spender) !== PERMIT2 || getAddress(approve.tx.to) !== getAddress(approve.token)) throw new Error("Unexpected approval");
    const hash = await wallet.sendTransaction({ to: approve.tx.to, data: approve.tx.data });
    const receipt = await client.waitForTransactionReceipt({ hash });
    if (receipt.status !== "success") throw new Error("Approval reverted");
    quote = await call<Quote>("/quote", ask); // steps change once the approval lands
  }

  // 2. Permit2 signature (viem adds the EIP712Domain type itself).
  let permitSignature: Hex | undefined;
  const permit = quote.steps.find((s) => s.kind === "permit");
  if (permit) {
    const { domain, types, message } = permit.typedData;
    if (getAddress(message.spender) !== UNIVERSAL_ROUTER || getAddress(domain.verifyingContract) !== PERMIT2 || domain.chainId !== 4663) {
      throw new Error("Unexpected Permit2 request");
    }
    permitSignature = await wallet.signTypedData({
      domain,
      types,
      primaryType: "PermitSingle",
      message: {
        details: { ...message.details, amount: BigInt(message.details.amount) },
        spender: message.spender,
        sigDeadline: BigInt(message.sigDeadline),
      },
    });
  }

  // 3. Build: the API simulates the transaction before returning it.
  const { tx } = await call<Built>("/swap", { quoteId: quote.id, permitSignature });

  // 4. Verify before signing anything.
  const expectedValue = quote.sell.token === NATIVE_ETH ? quote.sell.amountAtomic : "0";
  if (getAddress(tx.to) !== UNIVERSAL_ROUTER) throw new Error("Refusing: tx.to is not the Universal Router");
  if (tx.chainId !== 4663) throw new Error("Refusing: wrong chain");
  if (tx.value !== expectedValue) throw new Error("Refusing: unexpected ETH value");

  // 5. Send.
  const hash = await wallet.sendTransaction({ to: tx.to, data: tx.data, value: BigInt(tx.value), gas: tx.gas ? BigInt(tx.gas) : undefined });

  // 6. Track until settled.
  let status = await call<Tracked>("/swaps/track", { quoteId: quote.id, txHash: hash });
  while (status.status === "pending") {
    await sleep(2_000);
    status = await call<Tracked>(`/swaps/${hash}`);
  }
  return status;
}

Errors

Every error has a real HTTP status and the same body: a stable code for your program and a message you can show to a person.

422 Unprocessable Content
{
  "error": {
    "code": "no_route",
    "message": "No pool connects USDG and XYZ right now."
  }
}
400invalid_requestMalformed body or query: missing field, wrong type, body not JSON, wrong Content-Type.
400invalid_tokensell or buy is neither a symbol nor a 0x address.
400token_not_listedThe token is not listed, or its symbol is ambiguous (use the address).
400same_tokenBoth sides are the same asset (ETH and WETH count as one).
400invalid_amountamount is not a plain decimal above 0, or has more decimals than the token.
400invalid_slippageslippageBps is not a whole number between 1 and 500.
400invalid_takertaker is not a 0x address, or is the zero address or a precompile.
400invalid_addressaddress (balances) is not a 0x address.
400invalid_hashThe transaction hash is not 0x plus 64 hex characters.
400invalid_quote_idquoteId does not look like a quote id.
400no_takerThe quote was made without taker, so it cannot be built. Quote again with taker.
400permit_requiredThe quote has a permit step and no permitSignature was sent.
400bad_signaturepermitSignature was not signed by the taker over this quote's Permit2 request.
401unauthorizedThe X-Api-Key header holds an invalid or revoked key.
404not_foundUnknown quote, or a hash that was never tracked.
409not_builtTracking a quote that was never built with /api/swap.
409already_trackedThis hash is already tracked for another quote.
410quote_expiredThe quote is older than its expiresAt. Get a fresh quote.
410permit_expiredThe Permit2 request of the quote has expired. Get a fresh quote.
413payload_too_largeThe body is larger than the endpoint accepts (4 KB quote, 2 KB swap, 1 KB track).
422no_routeNo pool connects the pair, or no liquidity at this size.
422too_smallThe amount is too small to trade after slippage and fee.
422too_largeThe order is above the per-order USD cap.
422impact_too_highThe order would move the price by 20% or more. Trade a smaller amount.
422simulation_failedThe built transaction would revert right now; the message says why (price moved, allowance, balance).
422route_invalidThe stored route can no longer be encoded. Get a fresh quote.
422quote_corruptThe stored quote cannot be read. Get a fresh quote.
429rate_limitedToo many requests in this minute. Wait for the Retry-After header (seconds).
500internal_errorUnexpected failure. Retry in a moment.
503rpc_unavailableThe chain could not be read (allowances). Retry in a few seconds.
503swaps_pausedSwaps are paused while the pinned contracts are re-verified.
503swaps_not_configuredThis deployment cannot settle swaps yet.

Security

Hookline never takes custody: there is no deposit, no Hookline contract holding funds, and the API never sees a private key. Still, treat every answer as untrusted input and check it before your wallet signs.

Always verify the destination

The swap transaction must go to the Uniswap Universal Router, nothing else. Refuse to send it if tx.to differs from this address.
0x204faca1764b154221e35c0d20abb3c525710498

Checklist before signing

tx.toswap
Equals the Universal Router above.
tx.chainIdswap
Equals 4663 (Robinhood Chain).
tx.valueswap
Equals the ETH you sell, in wei, when selling ETH; "0" for any other token.
approve steptransaction
Goes to the token you sell and approves Permit2 (0x000000000022d473030f116ddee9f6b43ac78ba3), never another spender.
permit stepsignature
Domain Permit2, chain 4663, verifying contract Permit2; spender is the Universal Router; token and amount match what you sell; expiration and deadline are minutes away, not months.
minReceivedquote
Matches your own floor. The router enforces it on chain: the swap reverts rather than fill below it.