{"openapi":"3.1.0","info":{"title":"3Route","version":"6.1.0","description":"3Route is a multichain DEX aggregator built by Baking Bad. It searches available liquidity, can split an order across multiple pools, and returns either a read-only quote or a ready-to-sign swap transaction. The swap surface follows the conventional Swap API v6.1 request model so existing integrations can use familiar chain, token, quote, and swap flows.","contact":{"name":"Baking Bad","url":"https://bakingbad.dev","email":"hello@bakingbad.dev"},"x-agent-skill":{"name":"use-3route","description":"Read this operational guide after the OpenAPI contract and before planning API calls. It covers the recommended workflow, payment selection, safety checks, failure handling, and the supplied Etherlink payment clients.","url":"/SKILL.md"},"x-guidance":"Agent workflow: (1) read GET /openapi.json for the current machine-readable contract and payment metadata; (2) select a supported chain ID from components.parameters.chainId.schema.enum; (3) resolve token addresses and decimals with GET /api/v6.1/{chainId}/tokens; (4) call quote before swap to inspect the expected amounts; (5) call swap only when a user has authorized transaction creation, then validate chain ID, from, receiver, tokens, amounts, slippage, tx.to, and tx.value before requesting a signature. OpenAPI and token endpoints are free. Quote and swap are paid; read each operation's x-payment-info for MPP or x-x402-payment-info for x402. Amounts are unsigned decimal strings in token smallest units: human amount multiplied by 10^decimals, and must be greater than zero. By default amount is the src amount to sell; with isExactOutput=true it is the desired dst amount and srcAmount is the required input. The zero address represents the selected chain's native asset, and the sentinel address 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee is accepted for the same purpose, case-insensitively. Quote and swap accept optional flags: includeTokensInfo, includeProtocols, and includeBlockNumber only add response fields, while protocols, excludedProtocols, fee, referrer, and gasPrice change routing, payout, or gas assumptions. An ERC-20 source token must approve the router in tx.to for at least srcAmount; a native source needs no approval and is supplied through tx.value. Error bodies carry error, description, statusCode, requestId, and an optional meta array. Branch on the HTTP status, not on the body: the no-route case answers HTTP 400 while its body reports statusCode 404 and error \"Quote not found\". Never sign or broadcast automatically. The swap response is unsigned, and execution can fail if price, liquidity, allowance, balance, gas, or chain state changes."},"externalDocs":{"description":"3Route agent skill: operational workflow, payment selection, safety rules, and failure handling","url":"/SKILL.md"},"servers":[{"url":"/","description":"Same origin as this OpenAPI document"}],"tags":[{"name":"Documentation","description":"Machine-readable API discovery"},{"name":"Tokens","description":"Chain-specific token metadata"},{"name":"Swap","description":"Quote and unsigned transaction construction"}],"paths":{"/openapi.json":{"get":{"operationId":"getOpenApiDocument","summary":"Get the current machine-readable API contract","description":"Returns this OpenAPI document as served by the proxy, including runtime MPP and x402 metadata on priced operations. Agents should read this endpoint before planning calls instead of relying on a cached copy.","tags":["Documentation"],"responses":{"200":{"description":"Current OpenAPI 3.x document","content":{"application/json":{"schema":{"type":"object","required":["openapi","info","paths"],"properties":{"openapi":{"type":"string","description":"OpenAPI specification version","example":"3.1.0"},"info":{"type":"object","description":"API title, version, description, and agent guidance"},"paths":{"type":"object","description":"Available operations keyed by request path"}},"additionalProperties":true}}}},"503":{"description":"The OpenAPI document is not ready","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","example":"The OpenAPI spec is not ready yet"}}}}}}}}},"/api/v6.1/{chainId}/tokens":{"get":{"operationId":"listTokens","summary":"List supported tokens, keyed by address","description":"Returns the authoritative token catalog for a chain. Use each token's address and decimals when constructing quote or swap requests.","tags":["Tokens"],"parameters":[{"$ref":"#/components/parameters/chainId"}],"responses":{"200":{"description":"Routable tokens, keyed by lowercase address","content":{"application/json":{"schema":{"type":"object","properties":{"tokens":{"type":"object","description":"Map of lowercase token address to token metadata","additionalProperties":{"$ref":"#/components/schemas/Token"}}}}}}},"400":{"description":"Unsupported chain id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v6.1/{chainId}/tokens/{address}":{"get":{"operationId":"getToken","summary":"Get metadata for a single token by address","description":"Looks up one token on the selected chain. Use the returned decimals to convert between human-readable and smallest-unit amounts.","tags":["Tokens"],"parameters":[{"$ref":"#/components/parameters/chainId"},{"name":"address","in":"path","required":true,"description":"Token address, case-insensitive; use the zero address for the chain's native asset","schema":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","example":"0x2306c83638bECFf7F567d5b330602D0dDb5780af"}}],"responses":{"200":{"description":"Token metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Token"}}}},"400":{"description":"Unsupported chain id or malformed address","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The aggregator does not know this token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Not Found","description":"Token not found","statusCode":404,"requestId":"3f0c2a6e-6f4a-4a1a-9a6d-2b1c0a9f5e77"}}}}}}},"/api/v6.1/{chainId}/quote":{"get":{"operationId":"getQuote","summary":"Price a swap without building a transaction","description":"Returns expected input, output, and gas without constructing an executable transaction. Quotes are informational and can change with liquidity and chain state.","tags":["Swap"],"parameters":[{"$ref":"#/components/parameters/chainId"},{"name":"src","in":"query","required":true,"description":"Address of the token being sold; use the zero address, or the case-insensitive sentinel 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, for the chain's native asset","schema":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","example":"0x0000000000000000000000000000000000000000"}},{"name":"dst","in":"query","required":true,"description":"Address of the token being bought; use the zero address, or the case-insensitive sentinel 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, for the chain's native asset","schema":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","example":"0x2306c83638bECFf7F567d5b330602D0dDb5780af"}},{"name":"amount","in":"query","required":true,"description":"Amount in the token's smallest units, as a decimal string. It must be greater than zero: \"0\" is rejected with 400. By default this is the amount of src to sell; with isExactOutput=true it is the amount of dst to receive.","schema":{"type":"string","pattern":"^[0-9]+$","example":"1000000000000000000"}},{"name":"isExactOutput","in":"query","description":"Set true to treat amount as the desired output in dst units and solve for the required src input, which comes back as srcAmount","schema":{"type":"boolean","default":false}},{"name":"includeTokensInfo","in":"query","description":"Set true to add the srcToken and dstToken metadata objects to the response","schema":{"type":"boolean","default":false}},{"name":"includeProtocols","in":"query","description":"Set true to add protocols, the route breakdown across DEXes, to the response","schema":{"type":"boolean","default":false}},{"name":"includeBlockNumber","in":"query","description":"Set true to add blockNumber, the block the route was computed against, to the response","schema":{"type":"boolean","default":false}},{"name":"protocols","in":"query","description":"Comma-separated allow-list of DEX provider names to route through. Omit it to allow every provider. Unknown names are ignored when at least one name is known, but a list in which no name is known is rejected with 400. Provider names come back in protocols when includeProtocols=true.","schema":{"type":"string"}},{"name":"excludedProtocols","in":"query","description":"Comma-separated deny-list of DEX provider names to exclude from routing. Unknown names are ignored.","schema":{"type":"string"}},{"name":"fee","in":"query","description":"Referral fee in percent. It is deducted from the input and reflected in srcAmount. A value outside the range is rejected with 400.","schema":{"type":"number","minimum":0,"maximum":3,"default":0}},{"name":"gasPrice","in":"query","description":"Gas price in wei, as a decimal string, used for gas-aware route selection. Defaults to the chain's current base fee.","schema":{"type":"string","pattern":"^[0-9]+$","example":"1000000000"}}],"responses":{"200":{"description":"Best route found for the requested pair and amount","content":{"application/json":{"schema":{"type":"object","properties":{"dstAmount":{"type":"string","description":"Expected output in dst smallest units"},"srcAmount":{"type":"string","description":"Required input in src smallest units"},"gas":{"type":"integer","description":"Estimated gas for the equivalent swap transaction"},"srcToken":{"allOf":[{"$ref":"#/components/schemas/Token"}],"description":"Metadata of the sold token; present only when includeTokensInfo=true"},"dstToken":{"allOf":[{"$ref":"#/components/schemas/Token"}],"description":"Metadata of the bought token; present only when includeTokensInfo=true"},"protocols":{"allOf":[{"$ref":"#/components/schemas/Protocols"}],"description":"Route breakdown across DEXes; present only when includeProtocols=true"},"blockNumber":{"type":"integer","description":"Block the route was computed against; present only when includeBlockNumber=true"}}}}}},"400":{"description":"Unknown token, unsupported chain id, missing parameter, zero amount, an allow-list in which no protocol name is known, a fee outside 0 to 3, or no route for the requested pair and amount. The no-route case is a legacy compatibility quirk: the HTTP status is 400 while the body reports error \"Quote not found\" and statusCode 404. Branch on the HTTP status; a body statusCode that disagrees with it is expected here and is not a transport error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalidRequest":{"summary":"Rejected parameters","description":"The body statusCode matches the HTTP status.","value":{"error":"Bad Request","description":"Amount must be greater than zero","statusCode":400,"requestId":"3f0c2a6e-6f4a-4a1a-9a6d-2b1c0a9f5e77"}},"quoteNotFound":{"summary":"No route for the pair, returned as HTTP 400 with body statusCode 404","description":"Retrying the same request does not help. Change the pair, the amount, or the protocol filters, and do not pay again for an unchanged request.","value":{"error":"Quote not found","description":"Quote not found","statusCode":404,"requestId":"3f0c2a6e-6f4a-4a1a-9a6d-2b1c0a9f5e77"}}}}}},"402":{"description":"Payment Required"}},"x-agentcash-auth":{"mode":"paid"},"x-payment-info":{"method":"tempo","intent":"charge","currency":"0x20C000000000000000000000b9537d11c60E8b50","recipient":"0x4AD353fA712ad83A44C92Db9e96492d3D366C255","price":{"mode":"fixed","currency":"USD","amount":"0.002000"},"protocols":[{"mpp":{"method":"tempo","intent":"charge","currency":"0x20C000000000000000000000b9537d11c60E8b50"}}],"amount":"2000"},"x-x402-payment-info":{"price":{"mode":"fixed","currency":"USD","amount":"0.002000"},"accepts":[{"scheme":"exact","network":"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp","payTo":"BB8kcj27e5EiMzaW2rwU7CePCeUdu2q3x6mu9UbP3o6i"},{"scheme":"exact","network":"eip155:8453","payTo":"0x4AD353fA712ad83A44C92Db9e96492d3D366C255"},{"scheme":"exact","network":"eip155:42793","payTo":"0x4AD353fA712ad83A44C92Db9e96492d3D366C255","asset":{"address":"0x796Ea11Fa2dD751eD01b53C372fFDB4AAa8f00F9","decimals":6,"assetTransferMethod":"permit2","name":"USD Coin","version":"2","supportsEip2612":false}}]}}},"/api/v6.1/{chainId}/swap":{"get":{"operationId":"getSwap","summary":"Build an unsigned swap transaction for the best route","description":"Builds an unsigned transaction for the selected route. Compare it with a fresh quote, validate every returned transaction field, obtain explicit signing approval, and broadcast it through the selected chain's RPC.","tags":["Swap"],"parameters":[{"$ref":"#/components/parameters/chainId"},{"name":"src","in":"query","required":true,"description":"Address of the token being sold; use the zero address, or the case-insensitive sentinel 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, for the chain's native asset. An ERC-20 src must approve the router returned in tx.to for at least srcAmount before the swap transaction executes.","schema":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","example":"0x0000000000000000000000000000000000000000"}},{"name":"dst","in":"query","required":true,"description":"Address of the token being bought; use the zero address, or the case-insensitive sentinel 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, for the chain's native asset","schema":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","example":"0x2306c83638bECFf7F567d5b330602D0dDb5780af"}},{"name":"amount","in":"query","required":true,"description":"Amount in the token's smallest units, as a decimal string. It must be greater than zero: \"0\" is rejected with 400. By default this is the amount of src to sell; with isExactOutput=true it is the amount of dst to receive.","schema":{"type":"string","pattern":"^[0-9]+$","example":"1000000000000000000"}},{"name":"from","in":"query","required":true,"description":"Address that signs and sends the transaction, and that the src tokens are taken from","schema":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","example":"0x4AD353fA712ad83A44C92Db9e96492d3D366C255"}},{"name":"isExactOutput","in":"query","description":"Set true to treat amount as the desired output in dst units and solve for the required src input, which comes back as srcAmount","schema":{"type":"boolean","default":false}},{"name":"receiver","in":"query","description":"Address that receives the dst tokens; defaults to from","schema":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","example":"0x4AD353fA712ad83A44C92Db9e96492d3D366C255"}},{"name":"slippage","in":"query","description":"Maximum acceptable slippage in percent. It sets dstAmountMin, below which the transaction reverts.","schema":{"type":"number","minimum":0,"maximum":50,"default":1,"example":1}},{"name":"includeTokensInfo","in":"query","description":"Set true to add the srcToken and dstToken metadata objects to the response","schema":{"type":"boolean","default":false}},{"name":"includeProtocols","in":"query","description":"Set true to add protocols, the route breakdown across DEXes, to the response","schema":{"type":"boolean","default":false}},{"name":"includeBlockNumber","in":"query","description":"Set true to add blockNumber, the block the route was computed against, to the response","schema":{"type":"boolean","default":false}},{"name":"protocols","in":"query","description":"Comma-separated allow-list of DEX provider names to route through. Omit it to allow every provider. Unknown names are ignored when at least one name is known, but a list in which no name is known is rejected with 400. Provider names come back in protocols when includeProtocols=true.","schema":{"type":"string"}},{"name":"excludedProtocols","in":"query","description":"Comma-separated deny-list of DEX provider names to exclude from routing. Unknown names are ignored.","schema":{"type":"string"}},{"name":"fee","in":"query","description":"Referral fee in percent. It is deducted from the input, reflected in srcAmount, and encoded into the router call data. A value outside the range is rejected with 400. Set referrer as well, because a fee without a referrer is paid to the zero address.","schema":{"type":"number","minimum":0,"maximum":3,"default":0}},{"name":"referrer","in":"query","description":"Address that receives the fee, encoded into the router call data. It defaults to the zero address, which discards the fee. It has no effect when fee is 0.","schema":{"type":"string","pattern":"^0x[0-9a-fA-F]{40}$","example":"0x4AD353fA712ad83A44C92Db9e96492d3D366C255"}},{"name":"gasPrice","in":"query","description":"Gas price in wei, as a decimal string, used for gas-aware route selection and returned in tx.gasPrice. Defaults to the chain's current base fee.","schema":{"type":"string","pattern":"^[0-9]+$","example":"1000000000"}}],"responses":{"200":{"description":"Best route found, with an unsigned transaction to sign and broadcast","content":{"application/json":{"schema":{"type":"object","properties":{"dstAmount":{"type":"string","description":"Expected output in dst smallest units"},"dstAmountMin":{"type":"string","description":"Minimum output after slippage, in dst smallest units; the transaction reverts below it"},"srcAmount":{"type":"string","description":"Required input in src smallest units"},"gas":{"type":"integer","description":"Estimated gas for the swap transaction"},"tx":{"type":"object","description":"Unsigned transaction; sign it with the from account and broadcast it yourself","properties":{"from":{"type":"string"},"to":{"type":"string","description":"3Route router address for the selected chain"},"data":{"type":"string","description":"Hex-encoded call data for the router"},"value":{"type":"string","description":"Native asset amount to attach, in the chain's smallest unit; \"0\" when src is an ERC-20 token"},"gas":{"type":"string","description":"Gas limit as a decimal string"},"gasPrice":{"type":"string","description":"Gas price in wei, as a decimal string"}}},"srcToken":{"allOf":[{"$ref":"#/components/schemas/Token"}],"description":"Metadata of the sold token; present only when includeTokensInfo=true"},"dstToken":{"allOf":[{"$ref":"#/components/schemas/Token"}],"description":"Metadata of the bought token; present only when includeTokensInfo=true"},"protocols":{"allOf":[{"$ref":"#/components/schemas/Protocols"}],"description":"Route breakdown across DEXes; present only when includeProtocols=true"},"blockNumber":{"type":"integer","description":"Block the route was computed against; present only when includeBlockNumber=true"}}}}}},"400":{"description":"Unknown token, unsupported chain id, missing parameter, zero amount, malformed from or receiver, slippage outside 0 to 50, an allow-list in which no protocol name is known, a fee outside 0 to 3, or no route for the requested pair and amount. The no-route case is a legacy compatibility quirk: the HTTP status is 400 while the body reports error \"Quote not found\" and statusCode 404. Branch on the HTTP status; a body statusCode that disagrees with it is expected here and is not a transport error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalidRequest":{"summary":"Rejected parameters","description":"The body statusCode matches the HTTP status.","value":{"error":"Bad Request","description":"Amount must be greater than zero","statusCode":400,"requestId":"3f0c2a6e-6f4a-4a1a-9a6d-2b1c0a9f5e77"}},"quoteNotFound":{"summary":"No route for the pair, returned as HTTP 400 with body statusCode 404","description":"Retrying the same request does not help. Change the pair, the amount, or the protocol filters, and do not pay again for an unchanged request.","value":{"error":"Quote not found","description":"Quote not found","statusCode":404,"requestId":"3f0c2a6e-6f4a-4a1a-9a6d-2b1c0a9f5e77"}}}}}},"402":{"description":"Payment Required"}},"x-agentcash-auth":{"mode":"paid"},"x-payment-info":{"method":"tempo","intent":"charge","currency":"0x20C000000000000000000000b9537d11c60E8b50","recipient":"0x4AD353fA712ad83A44C92Db9e96492d3D366C255","price":{"mode":"fixed","currency":"USD","amount":"0.005000"},"protocols":[{"mpp":{"method":"tempo","intent":"charge","currency":"0x20C000000000000000000000b9537d11c60E8b50"}}],"amount":"5000"},"x-x402-payment-info":{"price":{"mode":"fixed","currency":"USD","amount":"0.005000"},"accepts":[{"scheme":"exact","network":"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp","payTo":"BB8kcj27e5EiMzaW2rwU7CePCeUdu2q3x6mu9UbP3o6i"},{"scheme":"exact","network":"eip155:8453","payTo":"0x4AD353fA712ad83A44C92Db9e96492d3D366C255"},{"scheme":"exact","network":"eip155:42793","payTo":"0x4AD353fA712ad83A44C92Db9e96492d3D366C255","asset":{"address":"0x796Ea11Fa2dD751eD01b53C372fFDB4AAa8f00F9","decimals":6,"assetTransferMethod":"permit2","name":"USD Coin","version":"2","supportsEip2612":false}}]}}}},"components":{"parameters":{"chainId":{"name":"chainId","in":"path","required":true,"description":"Supported decimal chain ID. The served OpenAPI document populates enum from the configured routes; select one of those values instead of calling a chain-discovery endpoint.","schema":{"type":"string","pattern":"^[0-9]+$","example":"42793","enum":["128064","42793"]}}},"schemas":{"Token":{"type":"object","description":"Token metadata as published by the aggregator","properties":{"address":{"type":"string","description":"Lowercase token address; the native asset uses the zero address"},"symbol":{"type":"string"},"name":{"type":"string"},"decimals":{"type":"integer","description":"Decimal places; convert a human amount to smallest units by multiplying by 10^decimals"},"logoURI":{"type":["string","null"]},"domainVersion":{"type":["string","null"],"description":"EIP-712 domain version, when the token exposes one"},"eip2612":{"type":["boolean","null"],"description":"Whether the token supports EIP-2612 permit"},"isFoT":{"type":["boolean","null"],"description":"Whether the token charges a fee on transfer"},"tags":{"type":["array","null"],"description":"Classification tags, e.g. \"native\"","items":{"type":"string"}}}},"Protocols":{"type":"array","description":"Route breakdown, returned when includeProtocols=true. It nests three levels: each entry is one source junction that carries part of the input, each junction holds the sequential hops along its path, and each hop holds the protocol splits that carry it. Treat it as informational routing detail; the entry shape may grow, so ignore unknown fields.","items":{"type":"array","description":"Sequential hops inside one source junction","items":{"type":"array","description":"Protocol splits that carry one hop","items":{"type":"object","description":"One DEX taking a share of a hop","properties":{"name":{"type":"string","description":"DEX provider name, usable in the protocols and excludedProtocols filters"},"part":{"type":"number","description":"Share of the hop routed through this protocol, in percent"},"fromTokenAddress":{"type":"string","description":"Token entering this split"},"toTokenAddress":{"type":"string","description":"Token leaving this split"}},"additionalProperties":true}}}},"Error":{"type":"object","description":"Error body returned by the aggregator. Branch on the HTTP status rather than on statusCode: quote and swap report statusCode 404 inside a 400 response when no route exists.","properties":{"error":{"type":"string","description":"Short reason, usually the HTTP reason phrase","example":"Bad Request"},"description":{"type":"string","description":"Human-readable cause"},"statusCode":{"type":"integer","description":"Status code reported in the body. It normally equals the HTTP status, except for the documented no-route case.","example":400},"requestId":{"type":"string","description":"Upstream correlation id; quote it when reporting a problem"},"meta":{"type":"array","description":"Optional structured details about the failure, such as the offending parameter","items":{"type":"object","properties":{"type":{"type":"string","description":"Detail kind"},"value":{"description":"Detail payload; its type depends on type"}}}}}}}}}