PulseSwap for bots & AI agents

Updated August 20267 min readView as Markdown
Hand this page to your agent

One click copies the whole page as agent instructions — endpoints, addresses and code the agent can trust verbatim, plus a pointer to the rest of the docs.

then paste it into Claude, ChatGPT, Cursor or your bot and say “integrate this”

Who this is for

A program with no browser: a trading bot, a backend service, or a coding agent wiring a swap into someone else's app. The quote API takes JSON, needs no API key and no account, and hands back the transaction data your own signer sends. Nothing here custodies funds or keys.

This API is PulseChain only. Chain 369, and any other chainId is rejected with a 400. The 17-chain reach you see on the site is the web app's own adapters, which are not part of this API or of the SDK — do not plan a multi-chain agent around either.

That narrowness is the reason the page exists. PulseChain is not covered by LI.FI, 1inch or 0x, so an agent that reaches chain 369 through one of those simply fails; this is the way to route on it programmatically.

One call, best route

POST https://quotes.pulseswap.io/api/v2/quotes/advanced with "platform": "mixed". The service prices the route across the PulseChain DEXes it indexes, splits it where splitting pays, and returns the one that pays out the most — you do not pick a DEX, compare answers, or run the search yourself.

Shell
curl -X POST https://quotes.pulseswap.io/api/v2/quotes/advanced \
  -H "Content-Type: application/json" \
  -H "User-Agent: my-bot/1.0 (+https://example.com)" \
  -d '{
    "chainId": 369,
    "platform": "mixed",
    "fromToken": "0xA1077a294dDE1B09bB078844df40758a5D0f9a27",
    "toToken": "0x95B303987A60C71504D99Aa1b13B4DA07b0790ab",
    "amountIn": "1000000000000000000",
    "slippage": 0.5,
    "userAddress": "0x742d35Cc6634C0532925a3b8D221691B5c1b6b29",
    "extra": { "gasPrice": "12345667890" }
  }'
Response
{
  "success": true,
  "data": {
    "success": true,
    "quoteId": "b7c4ee4a-1dd6-4a4f-9003-fcaae0593fb2",
    "amountIn": "1000000000000000000",
    "amountOut": "1401565066428738108",
    "amountOutUSD": "0",
    "gasEstimate": 391968,
    "tx": {
      "from": "0x742D35Cc6634C0532925A3B8D221691b5C1b6b29",
      "to": "0xC994375187988C751C8fCb96A68A0f242947f0E6",
      "data": "0x2d09aed500000000000000…",
      "value": "0"
    }
  },
  "message": "OK",
  "timestamp": "2026-08-19T06:47:54.152610116Z"
}
Always send extra. Leave the object out and two things go wrong at once: userAddress stops reaching tx.from, which leaves calldata you cannot send, and the search runs with no gas price. One field — extra.gasPrice in wei, as a string — is enough. Add extra.tokenInPrice and extra.tokenOutPrice in USD when you have them: they are what fills amountOutUSD in and let the service rank routes by net value.
  • Native PLS is 0x0000000000000000000000000000000000000000 in fromToken or toToken. Sending it in puts the amount in tx.value instead of needing an approval.
  • amountIn is a wei string, never a number — a large value does not survive a JavaScript number.
  • slippage is a percent between 0.0 and 100.0: 0.5 means 0.5%.
  • tx.to is always the PulseSwap router, 0xC994375187988C751C8fCb96A68A0f242947f0E6, whichever DEXes the route ends up crossing — so one approval covers every route.

The bot flow

Three steps, and the last one is yours alone:

  1. Quote without userAddress — to price a pair, watch a spread, or decide whether to act at all. You get amountOut and gasEstimate; the tx that comes back carries the zero address and is not meant to be sent.
  2. Quote again with userAddress once you have decided. Same call, and now tx is built for your account.
  3. Approve if needed, then sign and send. An ERC-20 going in needs one approval with the router as spender; after that you sign tx with your own key and broadcast it.
TypeScript
import { createPublicClient, createWalletClient, erc20Abi, http, maxUint256 } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { pulsechain } from 'viem/chains';

const WPLS = '0xA1077a294dDE1B09bB078844df40758a5D0f9a27' as const;
const PLSX = '0x95B303987A60C71504D99Aa1b13B4DA07b0790ab' as const;

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const pub = createPublicClient({ chain: pulsechain, transport: http() });
const wallet = createWalletClient({ account, chain: pulsechain, transport: http() });

// 1. Ask for the best route, with calldata built for this account.
const res = await fetch('https://quotes.pulseswap.io/api/v2/quotes/advanced', {
  method: 'POST',
  headers: { 'content-type': 'application/json', 'user-agent': 'my-bot/1.0' },
  body: JSON.stringify({
    chainId: 369,
    platform: 'mixed',
    fromToken: WPLS,
    toToken: PLSX,
    amountIn: '1000000000000000000',
    slippage: 0.5,
    userAddress: account.address,
    extra: { gasPrice: (await pub.getGasPrice()).toString() },
  }),
});
const { data: quote } = await res.json();
// No route (a platform can't price this pair): HTTP 200, data.success=false, tx=null. Check it.
if (!quote?.success || !quote.tx) throw new Error('no route for this pair');

// 2. ERC-20 going in: approve the router once and WAIT for it. Native PLS needs no approval.
const approveHash = await wallet.writeContract({
  address: WPLS, abi: erc20Abi, functionName: 'approve', args: [quote.tx.to, maxUint256],
});
await pub.waitForTransactionReceipt({ hash: approveHash });

// 3. Sign and send it yourself. The keys never leave your process.
const hash = await wallet.sendTransaction({
  to: quote.tx.to, data: quote.tx.data, value: BigInt(quote.tx.value),
});
Quotes are short-lived. Fetch, then send promptly — a route priced a block ago is a different route. Treat gasEstimate as a planning figure rather than a limit to send as-is, and keep quoteId in your logs: it is what lets anyone trace a specific quote later.

Common token addresses

The ones a bot asks for first. PLS is the zero address and travels in tx.value; every other row is an ERC-20 that needs a one-time approve to the router. Mind the decimals — HEX and WBTC are 8, USDC and USDT are 6. The full list (8,800+ tokens, with logos) is tokensList-369.json .

SymbolAddressDecimalsNote
PLS0x000000000000000000000000000000000000000018native — sent in tx.value
WPLS0xA1077a294dDE1B09bB078844df40758a5D0f9a2718Wrapped PLS
PLSX0x95B303987A60C71504D99Aa1b13B4DA07b0790ab18PulseX
HEX0x2b591e99afE9f32eAA6214f7B7629768c40Eeb398HEX
INC0x2fa878Ab3F87CC1C9737Fc071108F904c0B0C95d18Incentive
DAI0xefD766cCb38EaF1dfd701853BFCe31359239F30518DAI from Ethereum
USDC0x15D38573d2feeb82e7ad5187aB8c1D52810B1f076USDC from Ethereum
USDT0x0Cb6F5a34ad42ec934882A05265A7d5F59b51A2f6USDT from Ethereum
WETH0x02DcdD04e3F455D838cd1249292C58f3B79e3C3C18WETH from Ethereum
WBTC0xb17D901469B9208B17d916112988A3FeD19b5cA18WBTC from Ethereum

Single-platform quotes

Naming one DEX instead of mixed asks that DEX alone. It is the call for a bot that already knows where it wants to trade — comparing a single pool against the routed answer, or executing on a DEX it has its own reasons to prefer. The identifiers, from the SDK's own list:

platformTypeRoutes through
pulsex_v1uni-v2PulseX V1
pulsex_v2uni-v2PulseX V2
pulsex_stablestablePulseX Stable
9inch_v2uni-v29inch v2
9inch_v3uni-v39inch v3
9mm_v2uni-v29mm v2
9mm_v3uni-v39mm v3
phux_v2bal-v2Phux.io
tide_v3bal-v30xTide
mixedMixedEvery row above, priced together

POST /quotes is the same request against the standard algorithm rather than the deeper search. It answers faster; on a mixed request it is also the weaker of the two, so reach for it only when you have named a single DEX. Every schema, validation rule and error shape is written out on the API reference.

Piteas is a separate service, not a platform value. Its own API whitelists by Referer, so calling api.piteas.io from a script does not answer — which is why the SDK snippet below points piteasUrl at our proxy, api.pulseswap.io/prod/piteasQuote, and that one does. You only need it if you want Piteas as a second opinion: a mixed request already competes every routed DEX in the one call.

The SDK

If you are writing TypeScript, pulseswap-sdk is the same service behind a typed client, and it is what this site's own swap uses. It is PulseChain only, like the API under it.

TypeScript
import { PulseSwapSDK, Platform, QuoteMode } from 'pulseswap-sdk';

const sdk = new PulseSwapSDK({
  quoteUrl: 'https://quotes.pulseswap.io/api/v2',
  piteasUrl: 'https://api.pulseswap.io/prod/piteasQuote',
});

// Platform.PULSESWAP goes over the wire as "mixed", and QuoteMode.OPTIMAL is
// what selects /quotes/advanced. Any other mode returns null without a request.
const quote = await sdk.getQuote(
  { chainId: 369, fromToken, toToken, userAddress, amountIn, slippage: 0.5, mode: QuoteMode.OPTIMAL },
  Platform.PULSESWAP,
);

getQuote resolves to the quote or to null — it does not throw — so a null check is not optional. Every type, enum value and behaviour is written out on the SDK reference.

Limits & etiquette

  • Stay under 60 requests per minute per IP. Responses carry no RateLimit-* headers today, so budget your own calls rather than waiting to be told; back off if you are refused, and honour Retry-After if one arrives.
  • Identify yourself with a real User-Agent — a name and a URL we could reach you at. An unlabelled flood from a datacentre IP looks like the scrapers we block.
  • Do not scrape the HTML. Every page of this documentation is served as Markdown at the same URL plus .md, which is cheaper for you to read and cannot break under a redesign.
  • CORS is openAccess-Control-Allow-Origin: *, and preflight is answered — so a browser extension or a front end can call the API directly.

Machine-readable docs

This documentation is built to be read by a program as well as a person, so an agent can find the rest of it without being handed each link:

  • /llms.txt — an index of every page on the site, each entry pointing at its Markdown.
  • /docs/<page>.md — any article as clean Markdown. This page is /docs/agents.md. Each page also declares its mirror as rel="alternate" in the HTML.
  • "Copy for your AI agent" — the chip under the title of every page you build from puts that page on the clipboard with a short framing preamble. The one on the docs home copies the whole integration guide at once.
Every endpoint, field and response on this page was probed against the live service rather than copied from a changelog, and the response above is a real capture with tx.data truncated.
About PulseSwap

A keyless PulseChain swap API for programs

One POST to the quote service prices a swap across the PulseChain DEXes it indexes and returns the route that pays out the most, together with the transaction data your own signer sends. No API key, no account, and no custody of keys or funds at any point.